Skip to content

Fool around with a crane MCP server for exploring registries with Claude code #2090

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ dist/
cmd/crane/crane
cmd/gcrane/gcrane
cmd/krane/krane
cmd/crane/mcp/crane-mcp

.DS_Store
22 changes: 22 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build Commands
- Run all tests: `go test ./...`
- Run specific test: `go test ./path/to/package -run TestName`
- Run linter: `staticcheck ./pkg/...`
- Verify formatting: `gofmt -d -e -l ./`
- Full presubmit checks: `./hack/presubmit.sh`
- Update generated docs: `./hack/update-codegen.sh`

## Code Style
- Use standard Go formatting (gofmt)
- Import ordering: standard library, third-party, internal packages
- Naming: follow Go conventions (CamelCase for exported, camelCase for unexported)
- Error handling: return errors with context rather than logging
- Testing: use standard Go testing patterns, include table-driven tests
- Copyright header required at top of each file
- Interface-driven design, with immutable view resources
- Implement minimal subset interfaces, use partial package for derived accessors
- Avoid binary data in logs (use redact.NewContext)
77 changes: 77 additions & 0 deletions cmd/crane/mcp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Crane MCP Server

This is an implementation of the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) for the `crane` CLI tool. It allows AI assistants and other MCP clients to interact with container registries using the functionality provided by the `crane` command line tool.

## Overview

The server exposes several tools from the `crane` CLI as MCP tools, including:

- `digest`: Get the digest of a container image
- `pull`: Pull a container image and save it as a tarball
- `push`: Push a container image from a tarball to a registry
- `copy`: Copy an image from one registry to another
- `catalog`: List repositories in a registry
- `ls`: List tags for a repository
- `config`: Get the config of an image
- `manifest`: Get the manifest of an image

## Usage

### Building

```bash
go build -o crane-mcp
```

### Running

```bash
./crane-mcp
```

The server communicates through stdin/stdout according to the MCP protocol. It can be integrated with any MCP client.

## Authentication

The server uses the same authentication mechanisms as the `crane` CLI. For private registries, you may need to:

1. Log in to the registry with `docker login` or `crane auth login`
2. Set up appropriate environment variables or credentials files

## Example Client Requests

To get the digest of an image:

```json
{
"type": "call_tool",
"id": "1",
"params": {
"name": "digest",
"arguments": {
"image": "docker.io/library/ubuntu:latest",
"full-ref": true
}
}
}
```

To copy an image between registries:

```json
{
"type": "call_tool",
"id": "2",
"params": {
"name": "copy",
"arguments": {
"source": "docker.io/library/nginx:latest",
"destination": "otherregistry.io/nginx:latest"
}
}
}
```

## License

Licensed under the Apache License, Version 2.0. See LICENSE file for details.
34 changes: 34 additions & 0 deletions cmd/crane/mcp/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2025 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Binary crane-mcp provides an MCP server for crane commands.
//
// This binary implements the Model Context Protocol, allowing AI assistants
// to interact with OCI registries using crane functionality.
package main

import (
"log"

"github.com/google/go-containerregistry/pkg/crane/mcp"
)

func main() {
// Create a new server with default configuration
svc := mcp.New(mcp.DefaultConfig())

// Start the server
log.Println("Starting crane MCP server...")
svc.Run()
}
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/docker/docker v28.0.0+incompatible
github.com/google/go-cmp v0.6.0
github.com/klauspost/compress v1.17.11
github.com/mark3labs/mcp-go v0.22.0
github.com/mitchellh/go-homedir v1.1.0
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0
Expand All @@ -31,15 +32,18 @@ require (
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/vbatts/tar-split v0.11.6 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
go.opentelemetry.io/otel v1.33.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions pkg/crane/mcp/auth/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2025 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package auth provides authentication utilities for crane MCP tools.
package auth

import (
"context"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/crane"
)

// CreateOptions returns a set of default options for all crane commands
// that include authentication from the default keychain.
func CreateOptions(ctx context.Context) []crane.Option {
return []crane.Option{
crane.WithAuthFromKeychain(authn.DefaultKeychain),
crane.WithContext(ctx),
}
}
105 changes: 105 additions & 0 deletions pkg/crane/mcp/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright 2025 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package mcp

import (
"fmt"
"os"

"github.com/google/go-containerregistry/pkg/crane/mcp/tools/catalog"
"github.com/google/go-containerregistry/pkg/crane/mcp/tools/config"
"github.com/google/go-containerregistry/pkg/crane/mcp/tools/copy"
"github.com/google/go-containerregistry/pkg/crane/mcp/tools/digest"
"github.com/google/go-containerregistry/pkg/crane/mcp/tools/list"
"github.com/google/go-containerregistry/pkg/crane/mcp/tools/manifest"
"github.com/google/go-containerregistry/pkg/crane/mcp/tools/pull"
"github.com/google/go-containerregistry/pkg/crane/mcp/tools/push"
"github.com/mark3labs/mcp-go/server"
)

// Config contains configuration options for the MCP server.
type Config struct {
// Name is the server name.
Name string
// Version is the server version.
Version string
}

// DefaultConfig returns the default configuration for the server.
func DefaultConfig() Config {
return Config{
Name: "crane-mcp",
Version: "1.0.0",
}
}

// Server is the crane MCP server.
type Server struct {
mcpServer *server.MCPServer
config Config
}

// New creates a new crane MCP server.
func New(cfg Config) *Server {
s := &Server{
mcpServer: server.NewMCPServer(cfg.Name, cfg.Version),
config: cfg,
}

// Register all tools
s.registerTools()

return s
}

// registerTools registers all crane tools with the MCP server.
func (s *Server) registerTools() {
// Register digest tool
s.mcpServer.AddTool(digest.NewTool(), digest.Handle)

// Register pull tool
s.mcpServer.AddTool(pull.NewTool(), pull.Handle)

// Register push tool
s.mcpServer.AddTool(push.NewTool(), push.Handle)

// Register copy tool
s.mcpServer.AddTool(copy.NewTool(), copy.Handle)

// Register catalog tool
s.mcpServer.AddTool(catalog.NewTool(), catalog.Handle)

// Register list tool
s.mcpServer.AddTool(list.NewTool(), list.Handle)

// Register config tool
s.mcpServer.AddTool(config.NewTool(), config.Handle)

// Register manifest tool
s.mcpServer.AddTool(manifest.NewTool(), manifest.Handle)
}

// Serve starts the server with stdio.
func (s *Server) Serve() error {
return server.ServeStdio(s.mcpServer)
}

// Run starts the server and exits the program if an error occurs.
func (s *Server) Run() {
if err := s.Serve(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
Loading
Loading