Skip to content

andygeiss/mcp

Repository files navigation

mcp

Release Go Reference OpenSSF Scorecard CodeQL Coverage Nightly Fuzz License

A minimal, zero-dependency Go implementation of the Model Context Protocol (MCP).

Single binary. Stdin/stdout transport. JSON-RPC 2.0. Nothing else.

Why

MCP servers don't need HTTP frameworks, routers, or dependency trees. This project is a production-ready MCP server in pure Go covering tools, resources, prompts, logging, and progress, with auto-derived schemas and a three-state initialization handshake -- all backed by the standard library alone.

Use it directly, or scaffold your own with make init.

Features

  • MCP 2025-11-25 -- tools, resources (list/read), prompts, logging, progress
  • JSON-RPC 2.0 over stdin/stdout -- newline-delimited, no LSP framing
  • Auto-derived schemas -- struct tags drive tool and prompt input schemas via reflection
  • Bidirectional transport -- generic server-to-client request primitive (no built-in handlers for sampling/elicitation/roots)
  • Safe by default -- per-message cap (4 MB), handler timeout (30s), panic recovery
  • Structured diagnostics -- slog.JSONHandler to stderr; stdout stays protocol-only
  • Zero external dependencies -- standard library only
  • Supply-chain ready -- cosign-signed releases, SBOMs, SLSA L3 provenance, OSS-Fuzz

Requirements

  • Go 1.26+
  • Tested in CI on macOS, Linux, Windows. Release binaries are published for macOS and Linux (amd64, arm64).

Install

go install github.com/andygeiss/mcp/cmd/mcp@latest

Or download a signed release archive from Releases and verify it (see Verify a release).

Use with an MCP client

Point any MCP client (Claude Desktop, VS Code, etc.) at the installed binary:

{
  "mcpServers": {
    "mcp": {
      "command": "/absolute/path/to/mcp"
    }
  }
}

Set MCP_TRACE=1 in the client's environment to log every request and response to stderr. Trace output includes full tool arguments — do not enable in production if handlers may receive credentials or PII.

Scaffold your own

Fork or clone this repo, then rewrite the module path:

make init MODULE=github.com/yourorg/yourproject

Open internal/tools/echo.go — your first tool, wired up and ready to rename.

This rewrites all imports, repoints badge URLs (shields.io, codecov, Actions) at your repo, runs go mod tidy, and removes cmd/scaffold/. The binary directory stays at cmd/mcp/, so every scaffolded project produces a binary named mcp -- install it with go install github.com/yourorg/yourproject/cmd/mcp@latest. If two MCP servers share $GOBIN, disambiguate with go build -o <name> or rename cmd/mcp/ after init.

The rewriter refuses to run if the working tree is dirty — resetGitHistory is destructive and would wipe uncommitted edits. Commit/stash first, or pass --force to override: go run ./cmd/scaffold --force github.com/yourorg/yourproject.

Your first tool

After make init succeeds, the welcome banner names three steps. Here's what each one looks like:

Edit — internal/tools/echo.go

Open the starter tool. The first line is your anchor:

// START HERE — your first tool. Edit, copy, rename. It's yours.

// Package tools holds the registered MCP tools and the registry primitives
// that wire them into the server.
package tools

import "context"

type EchoInput struct {
    Message string `json:"message" description:"The message to echo back"`
}

func Echo(_ context.Context, input EchoInput) Result {
    return TextResult(input.Message)
}

Replace the Echo body, rename the input struct, and edit the description tag — that's the text the agent reads when deciding whether to call your tool.

Wire — cmd/mcp/main.go

Register it with one line:

if err := tools.Register(registry, "echo", "Echoes the input message", tools.Echo); err != nil {
    return fmt.Errorf("register echo: %w", err)
}

Need a clean copy-target for tool number two? Open internal/tools/_TOOL_TEMPLATE.go — same shape, no working logic, ready to rename.

Verify — make smoke

make smoke

On success you see exactly:

Your server works. It exposes N tool(s).

On failure the target prints two diagnostic hints (forgot to register? doesn't compile?) followed by the captured stderr — usually enough to get unstuck without leaving the terminal.

Add a tool

Define an input struct, write a handler, register it.

// internal/tools/greet.go
package tools

import "context"

type GreetInput struct {
    Name string `json:"name" description:"Name to greet"`
}

func Greet(_ context.Context, input GreetInput) Result {
    return TextResult("Hello, " + input.Name + "!")
}
// cmd/mcp/main.go
if err := tools.Register(registry, "greet", "Greets someone by name", tools.Greet); err != nil {
    return fmt.Errorf("register greet: %w", err)
}

The input schema ({"type":"object","properties":{"name":{"type":"string","description":"Name to greet"}},"required":["name"]}) is derived from struct tags. No manual schema definition needed.

Verify a release

Release archives are keyless-signed with cosign and ship with SLSA L3 provenance generated by slsa-framework/slsa-github-generator. Each archive has a .sigstore.json bundle; SHA-256 digests are in checksums.txt; SBOMs are attached as *.sbom.json.

# Replace <version>, <os>, <arch> with your target, e.g. 0.1.0, Linux, x86_64
cosign verify-blob \
  --bundle mcp_<version>_<os>_<arch>.tar.gz.sigstore.json \
  --certificate-identity-regexp "^https://github.com/andygeiss/mcp/" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  mcp_<version>_<os>_<arch>.tar.gz

For a stronger guarantee — rebuild the binary from source on your own machine and confirm the SHA matches the published checksum: see docs/reproducible-build.md.

Protocol compliance

MCP version 2025-11-25. JSON-RPC 2.0 with these specifics:

Behavior Implementation
Framing Newline-delimited JSON objects
Batch requests Rejected with -32700
Missing params Normalized to {}
Request id Preserved as json.RawMessage, echoed exactly
Notifications Never responded to
Unknown notifications Silently ignored
Error messages Contextual (e.g. "unknown tool: foo")

Transport rationale and alternatives considered: docs/adr/ADR-001.

Scope

Implements tools/list, tools/call, resources/list, resources/read, prompts/list, prompts/get, logging/setLevel, plus notifications/initialized, notifications/cancelled, notifications/progress, notifications/message, and a generic server-to-client request primitive.

Not implemented -- calls are rejected with -32601: resources/subscribe, resources/unsubscribe, completion/complete, roots/list, sampling/*, elicitation/*, and the */list_changed notifications.

Architecture

cmd/mcp/           main.go -- wiring only: flags, I/O injection, os.Exit
cmd/scaffold/      template rewriter -- not part of normal builds
internal/
  assert/          test assertion helpers
  prompts/         prompt registry, argument derivation
  protocol/        JSON-RPC 2.0 codec, types, constants
  resources/       resource registry, static resources, URI templates
  schema/          shared JSON Schema derivation via reflection
  server/          lifecycle, dispatch, notifications, bidirectional transport
  tools/           tool registry, schema derivation, tool handlers

Dependency direction: cmd/mcp/ -> server/ -> protocol/; server/ imports tools/, resources/, prompts/. protocol/ and schema/ have zero internal dependencies.

Transport rules:

  • stdout is protocol-only -- every byte is a valid JSON-RPC message.
  • stderr is diagnostics-only via slog.JSONHandler.
  • Constructors accept io.Reader/io.Writer so tests inject buffers.

Development

make check      # build + test + lint
make test       # go test -race ./...
make fuzz       # fuzz the JSON decoder (FUZZTIME=5m to extend)
make coverage   # enforce 90% threshold
make bench      # benchstat against testdata/benchmarks/baseline.txt

Authoring guidelines, test conventions, and the full workflow matrix live in docs/development-guide.md.

Documentation

License

MIT -- Andreas Geiß

About

Minimal, zero-dependency Go implementation of the Model Context Protocol (MCP) 2025-11-25 — single binary, stdin/stdout, JSON-RPC 2.0, auto-derived schemas, signed releases. Use as a template to scaffold your own MCP server.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages