mcp

module
v1.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 3, 2026 License: MIT

README

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ß

Directories

Path Synopsis
cmd
mcp command
Package main provides the entry point for the MCP server binary.
Package main provides the entry point for the MCP server binary.
scaffold command
Package main provides the init tool for rewriting the MCP template project.
Package main provides the init tool for rewriting the MCP template project.
spec-coverage command
Package main is the spec-coverage aggregator: it reads the per-package audit fragments committed under docs/, deduplicates rows by clause ID, sorts ascending by ID, and writes the unified table to stdout.
Package main is the spec-coverage aggregator: it reads the per-package audit fragments committed under docs/, deduplicates rows by clause ID, sorts ascending by ID, and writes the unified table to stdout.
internal
assert
Package assert provides lightweight test assertion helpers.
Package assert provides lightweight test assertion helpers.
prompts
Package prompts provides the prompt registry for the MCP server.
Package prompts provides the prompt registry for the MCP server.
protocol
Package protocol implements JSON-RPC 2.0 types and codec for the MCP server.
Package protocol implements JSON-RPC 2.0 types and codec for the MCP server.
resources
Package resources provides the resource registry for the MCP server.
Package resources provides the resource registry for the MCP server.
schema
Package schema provides JSON Schema derivation from Go struct types via reflection.
Package schema provides JSON Schema derivation from Go struct types via reflection.
server
Package server implements the MCP server lifecycle, dispatch, and resilience.
Package server implements the MCP server lifecycle, dispatch, and resilience.
tools
Package tools holds the registered MCP tools and the registry primitives that wire them into the server.
Package tools holds the registered MCP tools and the registry primitives that wire them into the server.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL