Skip to content

Commit 66a041c

Browse files
committed
Fool around with a crane MCP server for exploring registries with Claude code
Signed-off-by: Matt Moore <[email protected]>
1 parent 098045d commit 66a041c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+12805
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ dist/
88
cmd/crane/crane
99
cmd/gcrane/gcrane
1010
cmd/krane/krane
11+
cmd/crane/mcp/crane-mcp
1112

1213
.DS_Store

CLAUDE.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Build Commands
6+
- Run all tests: `go test ./...`
7+
- Run specific test: `go test ./path/to/package -run TestName`
8+
- Run linter: `staticcheck ./pkg/...`
9+
- Verify formatting: `gofmt -d -e -l ./`
10+
- Full presubmit checks: `./hack/presubmit.sh`
11+
- Update generated docs: `./hack/update-codegen.sh`
12+
13+
## Code Style
14+
- Use standard Go formatting (gofmt)
15+
- Import ordering: standard library, third-party, internal packages
16+
- Naming: follow Go conventions (CamelCase for exported, camelCase for unexported)
17+
- Error handling: return errors with context rather than logging
18+
- Testing: use standard Go testing patterns, include table-driven tests
19+
- Copyright header required at top of each file
20+
- Interface-driven design, with immutable view resources
21+
- Implement minimal subset interfaces, use partial package for derived accessors
22+
- Avoid binary data in logs (use redact.NewContext)

cmd/crane/mcp/README.md

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Crane MCP Server
2+
3+
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.
4+
5+
## Overview
6+
7+
The server exposes several tools from the `crane` CLI as MCP tools, including:
8+
9+
- `digest`: Get the digest of a container image
10+
- `pull`: Pull a container image and save it as a tarball
11+
- `push`: Push a container image from a tarball to a registry
12+
- `copy`: Copy an image from one registry to another
13+
- `catalog`: List repositories in a registry
14+
- `ls`: List tags for a repository
15+
- `config`: Get the config of an image
16+
- `manifest`: Get the manifest of an image
17+
18+
## Usage
19+
20+
### Building
21+
22+
```bash
23+
go build -o crane-mcp
24+
```
25+
26+
### Running
27+
28+
```bash
29+
./crane-mcp
30+
```
31+
32+
The server communicates through stdin/stdout according to the MCP protocol. It can be integrated with any MCP client.
33+
34+
## Authentication
35+
36+
The server uses the same authentication mechanisms as the `crane` CLI. For private registries, you may need to:
37+
38+
1. Log in to the registry with `docker login` or `crane auth login`
39+
2. Set up appropriate environment variables or credentials files
40+
41+
## Example Client Requests
42+
43+
To get the digest of an image:
44+
45+
```json
46+
{
47+
"type": "call_tool",
48+
"id": "1",
49+
"params": {
50+
"name": "digest",
51+
"arguments": {
52+
"image": "docker.io/library/ubuntu:latest",
53+
"full-ref": true
54+
}
55+
}
56+
}
57+
```
58+
59+
To copy an image between registries:
60+
61+
```json
62+
{
63+
"type": "call_tool",
64+
"id": "2",
65+
"params": {
66+
"name": "copy",
67+
"arguments": {
68+
"source": "docker.io/library/nginx:latest",
69+
"destination": "otherregistry.io/nginx:latest"
70+
}
71+
}
72+
}
73+
```
74+
75+
## License
76+
77+
Licensed under the Apache License, Version 2.0. See LICENSE file for details.

cmd/crane/mcp/main.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2025 Google LLC All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Binary crane-mcp provides an MCP server for crane commands.
16+
//
17+
// This binary implements the Model Context Protocol, allowing AI assistants
18+
// to interact with OCI registries using crane functionality.
19+
package main
20+
21+
import (
22+
"log"
23+
24+
"github.com/google/go-containerregistry/pkg/crane/mcp"
25+
)
26+
27+
func main() {
28+
// Create a new server with default configuration
29+
svc := mcp.New(mcp.DefaultConfig())
30+
31+
// Start the server
32+
log.Println("Starting crane MCP server...")
33+
svc.Run()
34+
}

go.mod

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/docker/docker v28.0.0+incompatible
1010
github.com/google/go-cmp v0.6.0
1111
github.com/klauspost/compress v1.17.11
12+
github.com/mark3labs/mcp-go v0.22.0
1213
github.com/mitchellh/go-homedir v1.1.0
1314
github.com/opencontainers/go-digest v1.0.0
1415
github.com/opencontainers/image-spec v1.1.0
@@ -31,15 +32,18 @@ require (
3132
github.com/go-logr/logr v1.4.2 // indirect
3233
github.com/go-logr/stdr v1.2.2 // indirect
3334
github.com/gogo/protobuf v1.3.2 // indirect
35+
github.com/google/uuid v1.6.0 // indirect
3436
github.com/inconshreveable/mousetrap v1.1.0 // indirect
3537
github.com/moby/docker-image-spec v1.3.1 // indirect
3638
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
3739
github.com/morikuni/aec v1.0.0 // indirect
3840
github.com/pkg/errors v0.9.1 // indirect
3941
github.com/russross/blackfriday/v2 v2.1.0 // indirect
4042
github.com/sirupsen/logrus v1.9.3 // indirect
43+
github.com/spf13/cast v1.7.1 // indirect
4144
github.com/spf13/pflag v1.0.5 // indirect
4245
github.com/vbatts/tar-split v0.11.6 // indirect
46+
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
4347
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
4448
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
4549
go.opentelemetry.io/otel v1.33.0 // indirect

go.sum

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/crane/mcp/auth/options.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2025 Google LLC All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package auth provides authentication utilities for crane MCP tools.
16+
package auth
17+
18+
import (
19+
"context"
20+
21+
"github.com/google/go-containerregistry/pkg/authn"
22+
"github.com/google/go-containerregistry/pkg/crane"
23+
)
24+
25+
// CreateOptions returns a set of default options for all crane commands
26+
// that include authentication from the default keychain.
27+
func CreateOptions(ctx context.Context) []crane.Option {
28+
return []crane.Option{
29+
crane.WithAuthFromKeychain(authn.DefaultKeychain),
30+
crane.WithContext(ctx),
31+
}
32+
}

pkg/crane/mcp/server.go

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright 2025 Google LLC All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package mcp
16+
17+
import (
18+
"fmt"
19+
"os"
20+
21+
"github.com/google/go-containerregistry/pkg/crane/mcp/tools/catalog"
22+
"github.com/google/go-containerregistry/pkg/crane/mcp/tools/config"
23+
"github.com/google/go-containerregistry/pkg/crane/mcp/tools/copy"
24+
"github.com/google/go-containerregistry/pkg/crane/mcp/tools/digest"
25+
"github.com/google/go-containerregistry/pkg/crane/mcp/tools/list"
26+
"github.com/google/go-containerregistry/pkg/crane/mcp/tools/manifest"
27+
"github.com/google/go-containerregistry/pkg/crane/mcp/tools/pull"
28+
"github.com/google/go-containerregistry/pkg/crane/mcp/tools/push"
29+
"github.com/mark3labs/mcp-go/server"
30+
)
31+
32+
// Config contains configuration options for the MCP server.
33+
type Config struct {
34+
// Name is the server name.
35+
Name string
36+
// Version is the server version.
37+
Version string
38+
}
39+
40+
// DefaultConfig returns the default configuration for the server.
41+
func DefaultConfig() Config {
42+
return Config{
43+
Name: "crane-mcp",
44+
Version: "1.0.0",
45+
}
46+
}
47+
48+
// Server is the crane MCP server.
49+
type Server struct {
50+
mcpServer *server.MCPServer
51+
config Config
52+
}
53+
54+
// New creates a new crane MCP server.
55+
func New(cfg Config) *Server {
56+
s := &Server{
57+
mcpServer: server.NewMCPServer(cfg.Name, cfg.Version),
58+
config: cfg,
59+
}
60+
61+
// Register all tools
62+
s.registerTools()
63+
64+
return s
65+
}
66+
67+
// registerTools registers all crane tools with the MCP server.
68+
func (s *Server) registerTools() {
69+
// Register digest tool
70+
s.mcpServer.AddTool(digest.NewTool(), digest.Handle)
71+
72+
// Register pull tool
73+
s.mcpServer.AddTool(pull.NewTool(), pull.Handle)
74+
75+
// Register push tool
76+
s.mcpServer.AddTool(push.NewTool(), push.Handle)
77+
78+
// Register copy tool
79+
s.mcpServer.AddTool(copy.NewTool(), copy.Handle)
80+
81+
// Register catalog tool
82+
s.mcpServer.AddTool(catalog.NewTool(), catalog.Handle)
83+
84+
// Register list tool
85+
s.mcpServer.AddTool(list.NewTool(), list.Handle)
86+
87+
// Register config tool
88+
s.mcpServer.AddTool(config.NewTool(), config.Handle)
89+
90+
// Register manifest tool
91+
s.mcpServer.AddTool(manifest.NewTool(), manifest.Handle)
92+
}
93+
94+
// Serve starts the server with stdio.
95+
func (s *Server) Serve() error {
96+
return server.ServeStdio(s.mcpServer)
97+
}
98+
99+
// Run starts the server and exits the program if an error occurs.
100+
func (s *Server) Run() {
101+
if err := s.Serve(); err != nil {
102+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
103+
os.Exit(1)
104+
}
105+
}

0 commit comments

Comments
 (0)