Documentation
¶
Overview ¶
Package mcp provides MCP (Model Context Protocol) parsing utilities and middleware.
Index ¶
- Constants
- func ConvertToJSONRPC2ID(id interface{}) (jsonrpc2.ID, error)
- func CreateParserMiddleware(config *types.MiddlewareConfig, runner types.MiddlewareRunner) error
- func CreateToolCallFilterMiddleware(config *types.MiddlewareConfig, runner types.MiddlewareRunner) error
- func CreateToolFilterMiddleware(config *types.MiddlewareConfig, runner types.MiddlewareRunner) error
- func GetMCPArguments(ctx context.Context) map[string]interface{}
- func GetMCPClientInfo(ctx context.Context) map[string]interface{}
- func GetMCPMeta(ctx context.Context) map[string]interface{}
- func GetMCPMethod(ctx context.Context) string
- func GetMCPProtocolVersion(ctx context.Context) string
- func GetMCPResourceID(ctx context.Context) string
- func NewListToolsMappingMiddleware(opts ...ToolMiddlewareOption) (types.MiddlewareFunction, error)
- func NewToolCallMappingMiddleware(opts ...ToolMiddlewareOption) (types.MiddlewareFunction, error)
- func ParsingMiddleware(next http.Handler) http.Handler
- type CodedError
- type HeaderMismatchError
- type MissingClientCapabilityError
- type MissingModernMetadataError
- type ParsedMCPRequest
- type ParsedMCPResponse
- type ParserMiddleware
- type ParserMiddlewareParams
- type Revision
- type SimpleTool
- type ToolFilterMiddleware
- type ToolFilterMiddlewareParams
- type ToolMiddlewareOption
- type ToolOverride
- type UnsupportedVersionError
Constants ¶
const ( ParserMiddlewareType = "mcp-parser" ToolFilterMiddlewareType = "tool-filter" ToolCallFilterMiddlewareType = "tool-call-filter" )
Middleware type constants
const ( // CodeHeaderMismatch signals a mismatch between the MCP-Protocol-Version // header and the _meta protocol version (schema.ts HeaderMismatchError). CodeHeaderMismatch int64 = -32020 // CodeMissingClientCapability signals that _meta is missing a client // capability required for the request (schema.ts MissingRequiredClientCapabilityError). CodeMissingClientCapability int64 = -32021 // CodeUnsupportedProtocolVersion signals that the _meta protocol version // is not one this server supports (schema.ts UnsupportedProtocolVersionError). CodeUnsupportedProtocolVersion int64 = -32022 // CodeInvalidParams is the standard JSON-RPC Invalid Params code, used // as a fallback when the draft spec defines no dedicated code for the failure. CodeInvalidParams int64 = -32602 )
The following JSON-RPC error codes are defined by the draft MCP spec (schema/draft/schema.ts) for the stateless "Modern" revision. They are declared as local literals, matching this repo's existing convention for JSON-RPC codes (e.g. streamable_proxy.go's -32603). The only SDK with equivalents, modelcontextprotocol/go-sdk, is reachable solely as a transitive dependency (via toolhive-core's mcpcompat) and is pinned to an older draft snapshot (2026-06-30, CodeHeaderMismatch = -32001) with no equivalents at all for the other two codes below, so importing it would risk wiring in stale values. Revisit once the go-sdk dependency is bumped to a revision that matches MCPVersionModern.
These are exported so other packages (e.g. the HTTP layer) can reference the same wire values instead of hardcoding or redeclaring them.
const JSONRPCCodeDenied = 403
JSONRPCCodeDenied is the application-space JSON-RPC error code ToolHive uses when a policy denies a call, alongside HTTP 403. Both denial paths reference this constant — the single-server authorization middleware (pkg/authz.handleUnauthorized) and the vMCP Serve-path call gate — so a denial is represented by one code across `thv run` and vMCP, by reference rather than by two copies of the literal. It is deliberately outside the reserved -32768..-32000 JSON-RPC range so it never collides with an SDK-generated code.
const (
// MCPRequestContextKey is the context key for storing parsed MCP request data.
MCPRequestContextKey contextKey = "mcp_request"
)
const MCPVersionModern = "2026-07-28"
MCPVersionModern is the single Modern (stateless) protocol version this build understands.
Variables ¶
This section is empty.
Functions ¶
func ConvertToJSONRPC2ID ¶ added in v0.17.0
ConvertToJSONRPC2ID converts an interface{} ID to jsonrpc2.ID
func CreateParserMiddleware ¶ added in v0.2.8
func CreateParserMiddleware(config *types.MiddlewareConfig, runner types.MiddlewareRunner) error
CreateParserMiddleware factory function for MCP parser middleware
func CreateToolCallFilterMiddleware ¶ added in v0.2.8
func CreateToolCallFilterMiddleware(config *types.MiddlewareConfig, runner types.MiddlewareRunner) error
CreateToolCallFilterMiddleware factory function for tool call filter middleware
func CreateToolFilterMiddleware ¶ added in v0.2.8
func CreateToolFilterMiddleware(config *types.MiddlewareConfig, runner types.MiddlewareRunner) error
CreateToolFilterMiddleware factory function for tool filter middleware
func GetMCPArguments ¶
GetMCPArguments is a convenience function to get the MCP arguments from the context.
func GetMCPClientInfo ¶ added in v0.40.0
GetMCPClientInfo is a convenience function to get the Modern (stateless MCP) per-request clientInfo from the context. Returns nil if no parsed request is available or clientInfo is not present.
func GetMCPMeta ¶ added in v0.4.0
GetMCPMeta is a convenience function to get the MCP _meta field from the context. Returns nil if no parsed request is available or if _meta field is not present.
func GetMCPMethod ¶
GetMCPMethod is a convenience function to get the MCP method from the context.
func GetMCPProtocolVersion ¶ added in v0.40.0
GetMCPProtocolVersion is a convenience function to get the Modern (stateless MCP) per-request protocol version from the context. Returns "" if no parsed request is available or protocolVersion is not present.
func GetMCPResourceID ¶
GetMCPResourceID is a convenience function to get the MCP resource ID from the context.
func NewListToolsMappingMiddleware ¶ added in v0.2.12
func NewListToolsMappingMiddleware(opts ...ToolMiddlewareOption) (types.MiddlewareFunction, error)
NewListToolsMappingMiddleware creates an HTTP middleware that parses SSE responses and plain JSON objects to extract tool names from JSON-RPC messages containing tool lists or tool calls.
The middleware looks for SSE events with: - event: message - data: {"jsonrpc":"2.0","id":X,"result":{"tools":[...]}}
This middleware is designed to be used ONLY when tool filtering or override are enabled, and expects the list of tools to be "correct" (i.e. not empty and not containing nonexisting tools).
func NewToolCallMappingMiddleware ¶ added in v0.2.12
func NewToolCallMappingMiddleware(opts ...ToolMiddlewareOption) (types.MiddlewareFunction, error)
NewToolCallMappingMiddleware creates an HTTP middleware that parses tool call requests and filters out tools that are not in the filter list.
The middleware looks for JSON-RPC messages with: - method: tool/call - params: {"name": "tool_name"}
This middleware is designed to be used ONLY when tool filtering or override is enabled, and expects the list of tools to be "correct" (i.e. not empty and not containing nonexisting tools).
func ParsingMiddleware ¶
ParsingMiddleware creates an HTTP middleware that parses MCP JSON-RPC requests and stores the parsed information in the request context for use by downstream middleware (authorization, audit, etc.).
The middleware: 1. Checks if the request should be parsed (POST with JSON content to MCP endpoints) 2. Reads and parses the JSON-RPC message 3. Extracts method, parameters, and resource information 4. Stores the parsed data in request context 5. Restores the request body for downstream handlers
Example usage:
middlewares := []types.Middleware{
authMiddleware, // Authentication first
mcp.ParsingMiddleware, // MCP parsing after auth
authzMiddleware, // Authorization uses parsed data
auditMiddleware, // Audit uses parsed data
}
Types ¶
type CodedError ¶ added in v0.33.0
CodedError is implemented by domain errors that should surface a stable error code and optional structured data in an MCP tool result.
The mcp-go tool-handler seam maps returned Go errors to a generic JSON-RPC INTERNAL_ERROR, so Serve-path handlers convert these errors to IsError tool results with StructuredContent instead of returning them as handler errors.
type HeaderMismatchError ¶ added in v0.40.0
type HeaderMismatchError struct {
// Header is the MCP-Protocol-Version header value.
Header string
// Body is the _meta protocol version value.
Body string
}
HeaderMismatchError indicates the MCP-Protocol-Version HTTP header did not match the io.modelcontextprotocol/protocolVersion carried in the request's _meta field. Neither value wins: this is a hard rejection of the request, not a signal to proceed with either value.
func (*HeaderMismatchError) Code ¶ added in v0.40.0
func (*HeaderMismatchError) Code() int64
Code implements CodedError.
func (*HeaderMismatchError) Data ¶ added in v0.40.0
func (e *HeaderMismatchError) Data() map[string]any
Data implements CodedError.
func (*HeaderMismatchError) Error ¶ added in v0.40.0
func (e *HeaderMismatchError) Error() string
type MissingClientCapabilityError ¶ added in v0.40.0
type MissingClientCapabilityError struct {
// RequiredCapabilities is the ClientCapabilities object the request was
// missing, if known.
RequiredCapabilities map[string]any
}
MissingClientCapabilityError indicates a Modern request's _meta is missing clientCapabilities (clientInfo is optional per the draft schema and is not checked here).
The draft types MissingRequiredClientCapabilityError.data.requiredCapabilities as a ClientCapabilities object, not a list of names. The classifier cannot compute per-method required capabilities (that check is deferred to the caller/handler layer), so RequiredCapabilities is populated best-effort and may be an empty object.
func (*MissingClientCapabilityError) Code ¶ added in v0.40.0
func (*MissingClientCapabilityError) Code() int64
Code implements CodedError.
func (*MissingClientCapabilityError) Data ¶ added in v0.40.0
func (e *MissingClientCapabilityError) Data() map[string]any
Data implements CodedError.
func (*MissingClientCapabilityError) Error ¶ added in v0.40.0
func (e *MissingClientCapabilityError) Error() string
type MissingModernMetadataError ¶ added in v0.40.0
type MissingModernMetadataError struct{}
MissingModernMetadataError indicates the request carried a Modern signal via one of the reserved io.modelcontextprotocol/* _meta keys — with no MCP-Protocol-Version header present at all — but _meta carried no valid protocolVersion. (When a header IS present, a missing or invalid body protocolVersion is a header/body mismatch instead; see HeaderMismatchError.) The draft spec defines no dedicated error code for this reserved-key-only case, so it falls back to the standard JSON-RPC Invalid Params code.
func (*MissingModernMetadataError) Code ¶ added in v0.40.0
func (*MissingModernMetadataError) Code() int64
Code implements CodedError.
func (*MissingModernMetadataError) Data ¶ added in v0.40.0
func (*MissingModernMetadataError) Data() map[string]any
Data implements CodedError.
func (*MissingModernMetadataError) Error ¶ added in v0.40.0
func (*MissingModernMetadataError) Error() string
type ParsedMCPRequest ¶
type ParsedMCPRequest struct {
// Method is the MCP method name (e.g., "tools/call", "resources/read")
Method string
// ID is the JSON-RPC request ID
ID interface{}
// Params contains the raw JSON parameters
Params json.RawMessage
// ResourceID is the extracted resource identifier (tool name, resource URI, etc.)
ResourceID string
// Arguments contains the extracted arguments for the operation
Arguments map[string]interface{}
// Meta contains the _meta field from the request params for protocol-level metadata
// such as progress tokens, trace IDs, or custom namespaced metadata
Meta map[string]interface{}
// MCPMethodHeader is the value of the Modern (stateless MCP) "Mcp-Method"
// request header, if present. Mandatory on every Modern POST per the draft spec.
MCPMethodHeader string
// MCPNameHeader is the raw, as-received value of the Modern (stateless MCP)
// "Mcp-Name" request header, if present. Required for tools/call,
// resources/read, prompts/get. Stored undecoded: the spec allows the header
// value to be sentinel-encoded (=?base64?...?=), and a caller comparing it to
// the plain body name/uri during validation must decode the header first.
MCPNameHeader string
// ClientInfo is the client implementation info surfaced via _meta for Modern
// (stateless) requests, sourced from _meta["io.modelcontextprotocol/clientInfo"].
ClientInfo map[string]interface{}
// ProtocolVersion is the per-request protocol version surfaced via _meta for
// Modern (stateless) requests, sourced from _meta["io.modelcontextprotocol/protocolVersion"].
ProtocolVersion string
// IsRequest indicates if this is a JSON-RPC request (vs response or notification)
IsRequest bool
// IsBatch indicates if this is a batch request
IsBatch bool
}
ParsedMCPRequest contains the parsed MCP request information.
func GetParsedMCPRequest ¶
func GetParsedMCPRequest(ctx context.Context) *ParsedMCPRequest
GetParsedMCPRequest retrieves the parsed MCP request from the request context. Returns nil if no parsed request is available.
type ParsedMCPResponse ¶ added in v0.22.0
type ParsedMCPResponse struct {
// HasError is true when the response body contains a top-level "error" field.
HasError bool
// ErrorCode is the JSON-RPC error code (e.g., -32603 for internal error).
ErrorCode int
// ErrorMessage is the raw error message from the JSON-RPC response.
ErrorMessage string
}
ParsedMCPResponse contains the result of inspecting a JSON-RPC response body for application-level errors. Only the error-related fields are extracted; the full result payload is intentionally not captured to avoid duplicating the privacy-sensitive IncludeResponseData path.
func ParseMCPResponse ¶ added in v0.22.0
func ParseMCPResponse(body []byte) *ParsedMCPResponse
ParseMCPResponse inspects a response body and returns a ParsedMCPResponse indicating whether it contains a JSON-RPC error. The function is intentionally lenient: if the body is not valid JSON or does not contain an "error" field, it returns HasError=false rather than an error.
type ParserMiddleware ¶ added in v0.2.8
type ParserMiddleware struct{}
ParserMiddleware wraps MCP parser middleware functionality
func (*ParserMiddleware) Close ¶ added in v0.2.8
func (*ParserMiddleware) Close() error
Close cleans up any resources used by the middleware.
func (*ParserMiddleware) Handler ¶ added in v0.2.8
func (*ParserMiddleware) Handler() types.MiddlewareFunction
Handler returns the middleware function used by the proxy.
type ParserMiddlewareParams ¶ added in v0.2.8
type ParserMiddlewareParams struct {
}
ParserMiddlewareParams represents the parameters for MCP parser middleware
type Revision ¶ added in v0.40.0
type Revision int
Revision identifies which MCP protocol era a request belongs to.
func ClassifyRevision ¶ added in v0.40.0
ClassifyRevision determines whether a single MCP request is Legacy or Modern, and whether it is valid.
When err != nil, the caller MUST reject the request; the returned Revision is then purely informational — it records that the request claimed Modern, not that classification succeeded.
method == "initialize" always classifies Legacy immediately, unconditionally — Modern never sends initialize (it is the Legacy session-start marker by definition) — which also guards against a spoofed Modern _meta on a Legacy call.
Otherwise, a request "signals" Modern if the MCP-Protocol-Version header is exactly MCPVersionModern, OR _meta carries ANY of the reserved io.modelcontextprotocol/* keys (protocolVersion, clientInfo, clientCapabilities) — presence of the key is the signal, independent of whether its value is well-formed, since a Legacy client never sets these keys at all. A request with no signal anywhere classifies Legacy, the safe default. A request with a signal is never silently downgraded: it either classifies Modern with a nil error, or Modern with an error the caller must reject on.
A non-empty MCP-Protocol-Version header that names some OTHER version (not MCPVersionModern) and carries no reserved _meta key is, by design, not a Modern signal: the request body's _meta is authoritative for the protocol version, and an unrecognized header value alone classifies Legacy rather than erroring.
Given a Modern signal, checks run in this order:
- meta[metaKeyProtocolVersion] must be a non-empty string. If it is not, and protoHeader is non-empty, the body has nothing valid to match against the header: this is a header/body mismatch (*HeaderMismatchError). If protoHeader is empty — the signal came only from a reserved _meta key, so there is no header to mismatch against — the request is malformed (*MissingModernMetadataError).
- that string must equal MCPVersionModern, or it names an unsupported version (*UnsupportedVersionError).
- if protoHeader is non-empty it must equal the body version, or the two conflict (*HeaderMismatchError, a hard rejection — neither value wins).
- _meta must carry clientCapabilities (clientInfo is optional per the draft schema), or the client capabilities are missing (*MissingClientCapabilityError, with per-method requirements deferred to the caller).
A request that passes all four classifies Modern with a nil error.
type SimpleTool ¶ added in v0.5.2
SimpleTool represents a minimal tool with name and description. This is used by ApplyToolFiltering to work with tools in a generic way.
func ApplyToolFiltering ¶ added in v0.5.2
func ApplyToolFiltering(opts []ToolMiddlewareOption, tools []SimpleTool) ([]SimpleTool, error)
ApplyToolFiltering applies filtering and overriding to a list of tools. This is the core logic used by both the HTTP middleware and other components that need to apply the same filtering/overriding behavior.
Returns the filtered and overridden tools.
type ToolFilterMiddleware ¶ added in v0.2.8
type ToolFilterMiddleware struct {
// contains filtered or unexported fields
}
ToolFilterMiddleware wraps tool filter middleware functionality
func (*ToolFilterMiddleware) Close ¶ added in v0.2.8
func (*ToolFilterMiddleware) Close() error
Close cleans up any resources used by the middleware.
func (*ToolFilterMiddleware) Handler ¶ added in v0.2.8
func (m *ToolFilterMiddleware) Handler() types.MiddlewareFunction
Handler returns the middleware function used by the proxy.
type ToolFilterMiddlewareParams ¶ added in v0.2.8
type ToolFilterMiddlewareParams struct {
FilterTools []string `json:"filter_tools"`
ToolsOverride map[string]ToolOverride `json:"tools_override"`
}
ToolFilterMiddlewareParams represents the parameters for tool filter middleware
type ToolMiddlewareOption ¶ added in v0.2.12
type ToolMiddlewareOption func(*toolMiddlewareConfig) error
ToolMiddlewareOption is a function that can be used to configure the tool middleware.
func WithToolsFilter ¶ added in v0.2.12
func WithToolsFilter(toolsFilter ...string) ToolMiddlewareOption
WithToolsFilter is a function that can be used to configure the tool middleware to use a filter list of tools.
func WithToolsOverride ¶ added in v0.2.12
func WithToolsOverride(actualName string, overrideName string, overrideDescription string) ToolMiddlewareOption
WithToolsOverride is a function that can be used to configure the tool middleware to use a map of tools to override the actual list of tools.
If an empty string is provided for either overrideName or overrideDescription, that field will be left unchanged. An error is returned if actualName is empty.
type ToolOverride ¶ added in v0.3.0
ToolOverride represents a tool override entry.
type UnsupportedVersionError ¶ added in v0.40.0
type UnsupportedVersionError struct {
// Requested is the _meta protocol version the client asked for.
Requested string
// Supported lists the protocol versions this server supports.
Supported []string
}
UnsupportedVersionError indicates the _meta protocol version named a Modern revision this server does not support.
func (*UnsupportedVersionError) Code ¶ added in v0.40.0
func (*UnsupportedVersionError) Code() int64
Code implements CodedError.
func (*UnsupportedVersionError) Data ¶ added in v0.40.0
func (e *UnsupportedVersionError) Data() map[string]any
Data implements CodedError.
func (*UnsupportedVersionError) Error ¶ added in v0.40.0
func (e *UnsupportedVersionError) Error() string