Documentation
¶
Overview ¶
Package mcpbridge is a Model Context Protocol stdio server that reads a forge project's gen/mcp/manifest.json and exposes every RPC tool the manifest declares to an MCP client (Claude Code, Claude Desktop, the official MCP Inspector, Cline, etc.).
It is the host for the otherwise-orphan gen/mcp/manifest.json: forge codegen writes one MCP tool per Connect RPC, and this package turns that static descriptor into a live, callable MCP server. Both the `forge mcp serve` CLI subcommand and the standalone cmd/forge-mcp binary are thin wrappers over Server here, so the protocol behaviour stays defined once.
What it does:
- Reads a loaded gen/mcp/manifest.json (Manifest).
- Speaks MCP over stdio (JSON-RPC 2.0, one message per line — the shape Claude Code / Claude Desktop expect).
- Implements initialize, tools/list, tools/call, ping.
- tools/call ACTUALLY dispatches to the running Connect service over HTTP+JSON (the same transport `forge api curl` builds): POST to <addr><procedure> with the arguments map as the JSON body; the Connect response becomes structuredContent + a human-readable content block. Connect error envelopes become MCP isError:true results so agents see typed failures.
Why HTTP+JSON: Connect's JSON wire format lets a generic client call any procedure without compiling against the project's proto types — the bridge stays one implementation for every forge project. Streaming RPCs (server/client/bidi) are NOT supported because MCP tool calls are unary; streaming tools are excluded from tools/list and tools/call returns an explicit error if a client calls one by name anyway.
Auth: the bridge sets a verbatim Authorization header on every dispatched RPC when one is configured (Server.AuthHeader). A forge dev server that is NOT in AUTH_DEV_MODE runs the auth interceptor, so without a token every tools/call returns a clean Connect "unauthenticated" error — which itself proves the transport wiring. With a token, real calls succeed.
forge:exclude-contract mcpbridge is an MCP stdio-server glue package (bridges gen/mcp/manifest.json to a live MCP server over stdio/HTTP), not a bootstrap-wired Connect service. Opt out of the require-contract rule.
Index ¶
Constants ¶
const ( // ProtocolVersion is the MCP revision this server targets. 2024-11-05 // is the revision Claude Desktop / Claude Code and the official // Inspector negotiate; its Tool.inputSchema is an open object, which // is why $defs/$ref schemas pass through unmodified. ProtocolVersion = "2024-11-05" // ServerName is the identity this bridge reports in the MCP // initialize handshake. ServerName = "forge-mcp" // ServerVersion is this bridge implementation's own version, distinct // from the negotiated ProtocolVersion. ServerVersion = "0.1.0" )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Manifest ¶
type Manifest struct {
Generated string `json:"_generated"`
SchemaVersion string `json:"schema_version"`
Project string `json:"project"`
Tools []Tool `json:"tools"`
}
Manifest mirrors the gen/mcp/manifest.json shape forge writes. We keep the read shape local (rather than importing the codegen emitter type) so the bridge is insulated from forge codegen churn — a project may run this against a manifest produced by a different forge version.
func LoadManifest ¶
LoadManifest reads and parses a gen/mcp/manifest.json file.
type Server ¶
type Server struct {
// Manifest is the loaded gen/mcp/manifest.json. Required.
Manifest *Manifest
// Addr is the Connect base URL (e.g. http://localhost:8080). Empty →
// tools/call returns a clear configuration error rather than silently
// failing to dial. Trailing slash is trimmed at Run so URL
// concatenation with the procedure path always produces a clean URL.
Addr string
// AuthHeader, when non-empty, is set verbatim as the Authorization
// header on every dispatched RPC. The bridge never inspects or
// refreshes the token — the caller passes a current value.
AuthHeader string
// HTTP is the client used for all Connect dispatches. Required; its
// Timeout applies per tools/call invocation.
HTTP *http.Client
// Logf receives diagnostics. nil → log.Printf (stderr).
Logf func(format string, args ...any)
}
Server is an MCP stdio bridge over one project's manifest. Construct it directly (all fields exported) and call Run. Logf, when nil, defaults to the standard logger on stderr — stdout is reserved for MCP framing.
func (*Server) CallableToolCount ¶
CallableToolCount counts the tools the MCP surface actually advertises — manifest tools minus streaming RPCs, which tools/list excludes because the bridge cannot proxy streams.
type Tool ¶
type Tool struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
InputSchema map[string]any `json:"inputSchema"`
OutputSchema map[string]any `json:"outputSchema,omitempty"`
Service string `json:"service,omitempty"`
Method string `json:"method,omitempty"`
Procedure string `json:"procedure,omitempty"`
AuthRequired bool `json:"auth_required,omitempty"`
IdempotencyKey bool `json:"idempotency_key,omitempty"`
Streaming string `json:"streaming,omitempty"`
}
Tool carries every field the MCP spec needs for tools/list plus the forge-specific routing metadata (service / method / procedure / auth_required / idempotency_key / streaming). MCP clients ignore the snake_case extras per the protocol's permissive unknown-field policy; the bridge strips them from the tools/list wire response anyway so even strict clients accept it.