mcp

package
v0.4.1 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package mcp implements a client for the Model Context Protocol (MCP): a JSON-RPC 2.0 based protocol that lets an AI application discover and invoke tools exposed by an external server process.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Tools

func Tools(ctx context.Context, c *Client) ([]ai.Tool, error)

Tools lists the server's tools and adapts each to an ai.Tool whose Execute calls CallTool; a ToolResult with IsError=true becomes a Go error (so the ai loop records it as a failed tool call). Name/Description/ schema pass through verbatim (schema NOT re-derived).

Types

type CapabilityError added in v0.2.0

type CapabilityError struct {
	Capability string
}

CapabilityError is returned when a method requires a server capability (as advertised in the "initialize" response) that the server did not declare. It is returned before any request is sent for that method.

func (*CapabilityError) Error added in v0.2.0

func (e *CapabilityError) Error() string

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a JSON-RPC 2.0 client over a Transport, specialised for MCP. A single background goroutine reads incoming messages and dispatches responses to the waiting call by id. It is safe for concurrent use.

func NewClient

func NewClient(t Transport) *Client

NewClient wraps t. Call Initialize before making any other call.

func (*Client) CallTool

func (c *Client) CallTool(ctx context.Context, name string, args json.RawMessage) (*ToolResult, error)

CallTool issues tools/call with the given arguments (a JSON object; a nil args is sent as {}). Every content part is preserved, in order, in ToolResult.Content; parts of type "text" are additionally concatenated in order into ToolResult.Text for convenience. It returns a *CapabilityError without sending any request if the server's "initialize" response didn't advertise the "tools" capability.

func (*Client) Close

func (c *Client) Close() error

Close shuts down the receive loop, abandons any pending calls with a "mcp: client closed" error, and closes the underlying transport (via closeWith, which performs the actual transport.Close call — see its doc). It also waits (up to closeDrainGrace) for any in-flight server-request dispatch goroutines (dispatchWG) to finish before returning, so that in the normal case — a well-behaved ElicitationHandler that respects the ctx it's given — no dispatch goroutine outlives Close. A handler that ignores ctx and blocks indefinitely cannot make Close hang forever: after closeDrainGrace, Close returns anyway (see that constant's doc for why leaving such a goroutine running is safe).

Ordering matters: closeWith cancels c.ctx (so a ctx-respecting handler in a dispatch goroutine exits promptly) and closes the transport (so recvLoop's blocked Receive call returns an error and recvLoop exits, closing loopDone). Only after <-c.loopDone — meaning recvLoop has definitely stopped and will spawn no further dispatch goroutines — is it safe to wait on dispatchWG: sync.WaitGroup forbids a positive-delta Add racing with a Wait call that could observe a zero counter, and waiting immediately after closeWith (while recvLoop might still be spawning new dispatch goroutines) would risk exactly that race.

func (*Client) Complete added in v0.2.0

func (c *Client) Complete(ctx context.Context, ref CompletionRef, argName, argValue string) (*Completion, error)

Complete issues "completion/complete" to request argument autocompletion suggestions for argName/argValue against ref (a prompt or resource template). It returns a *CapabilityError without sending any request if the server's "initialize" response did not advertise the "completions" capability.

func (*Client) GetPrompt added in v0.2.0

func (c *Client) GetPrompt(ctx context.Context, name string, args map[string]string) (string, []PromptMessage, error)

GetPrompt issues prompts/get for name with the given arguments (may be nil), returning the server's description and the rendered messages with content parts flattened into PromptPart slices.

func (*Client) Initialize

func (c *Client) Initialize(ctx context.Context) error

Initialize performs the MCP handshake: an "initialize" request, followed by a "notifications/initialized" notification once the server replies. It sends the latest supported protocol version (supportedProtocolVersions[0]) and accepts any version the server returns that is in supportedProtocolVersions, rejecting the handshake only if the returned version is outside that set. The negotiated version is stored on the Client (see ProtocolVersion). The server's advertised capabilities are likewise stored on the Client so later calls (e.g. ListResources, ListPrompts) can gate on them.

func (*Client) ListPrompts added in v0.2.0

func (c *Client) ListPrompts(ctx context.Context) ([]Prompt, error)

ListPrompts issues prompts/list, transparently paginating via nextCursor. It returns a *CapabilityError without sending any request if the server's "initialize" response did not advertise the "prompts" capability.

func (*Client) ListResourceTemplates added in v0.2.0

func (c *Client) ListResourceTemplates(ctx context.Context) ([]ResourceTemplate, error)

ListResourceTemplates issues resources/templates/list, transparently paginating via nextCursor. Same capability gate as ListResources.

func (*Client) ListResources added in v0.2.0

func (c *Client) ListResources(ctx context.Context) ([]Resource, error)

ListResources issues resources/list, transparently paginating via nextCursor until the server stops returning one. It returns a *CapabilityError without sending any request if the server's "initialize" response did not advertise the "resources" capability.

func (*Client) ListTools

func (c *Client) ListTools(ctx context.Context) ([]ToolDef, error)

ListTools issues tools/list, transparently paginating via nextCursor until the server stops returning one. It returns a *CapabilityError without sending any request if the server's "initialize" response didn't advertise the "tools" capability.

func (*Client) ProtocolVersion added in v0.2.1

func (c *Client) ProtocolVersion() string

ProtocolVersion returns the MCP protocol version negotiated during Initialize (one of supportedProtocolVersions). It is empty until Initialize has completed successfully.

func (*Client) ReadResource added in v0.2.0

func (c *Client) ReadResource(ctx context.Context, uri string) ([]ResourceContents, error)

ReadResource issues resources/read for uri. A resource contents entry carrying "text" decodes into ResourceContents.Text; one carrying a base64 "blob" decodes into ResourceContents.Blob.

func (*Client) SetElicitationHandler added in v0.2.0

func (c *Client) SetElicitationHandler(h ElicitationHandler)

SetElicitationHandler installs h as the handler invoked for "elicitation/create" requests from the server. Call this before Initialize: the "elicitation" capability is only declared to the server when a handler has been set.

func (*Client) SetNotificationHandler added in v0.3.0

func (c *Client) SetNotificationHandler(h NotificationHandler)

SetNotificationHandler installs h as the handler invoked for server-initiated notifications. Call this before Initialize so no notification sent early in the session is missed. With no handler installed (the default), incoming notifications are silently dropped.

Delivery is best-effort: if the bounded dispatch pool (shared with server-initiated requests) is saturated when a notification arrives, that notification is dropped rather than queued or blocking recvLoop, matching the fire-and-forget semantics of JSON-RPC notifications.

func (*Client) SetRoots added in v0.3.0

func (c *Client) SetRoots(roots []Root)

SetRoots installs the fixed set of roots reported to "roots/list" requests from the server and causes Initialize to declare the "roots" capability (listChanged: false — v1 has no dynamic root updates). Call before Initialize: the "roots" capability is only declared to the server when roots have been set.

func (*Client) SetSamplingHandler added in v0.3.0

func (c *Client) SetSamplingHandler(h SamplingHandler)

SetSamplingHandler installs h as the handler invoked for "sampling/createMessage" requests from the server. Call this before Initialize: the "sampling" capability is only declared to the server when a handler has been set.

type Completion added in v0.2.0

type Completion struct {
	Values  []string
	Total   int // server's total count when provided
	HasMore bool
}

Completion is the server's suggested completions for one argument.

type CompletionRef added in v0.2.0

type CompletionRef struct {
	Type string // "ref/prompt" or "ref/resource"
	Name string // prompt name (ref/prompt)
	URI  string // resource template URI (ref/resource)
}

CompletionRef identifies what argument completion is being requested for: either a prompt (Type "ref/prompt", Name set) or a resource template (Type "ref/resource", URI set).

type CreateMessageRequest added in v0.3.0

type CreateMessageRequest struct {
	Messages         []SamplingMessage
	SystemPrompt     string
	MaxTokens        int
	ModelPreferences json.RawMessage // passed through verbatim; nil if absent
}

CreateMessageRequest is the payload of a server-initiated "sampling/createMessage" request: the server is asking the client to run an LLM completion on its behalf.

type CreateMessageResult added in v0.3.0

type CreateMessageResult struct {
	Role       string          // typically "assistant"
	Content    json.RawMessage // wire content object
	Model      string
	StopReason string
}

CreateMessageResult is the client's reply to a CreateMessageRequest.

type ElicitationHandler added in v0.2.0

type ElicitationHandler func(ctx context.Context, req ElicitationRequest) (ElicitationResult, error)

ElicitationHandler is called when the server sends an "elicitation/create" request. A nil handler installed on the Client causes it to auto-respond with Action "decline" and not declare the "elicitation" capability during Initialize.

Implementations must respect ctx: it is cancelled when the Client is closed, and a handler that ignores cancellation and blocks indefinitely (e.g. on an unrelated channel, a UI prompt, or a stuck downstream call) will not hang Client.Close forever, but it will delay it — Close waits up to a short grace period (see closeDrainGrace) for in-flight handlers to finish before giving up and returning anyway.

type ElicitationRequest added in v0.2.0

type ElicitationRequest struct {
	Message         string
	RequestedSchema json.RawMessage // JSON schema of the requested object
}

ElicitationRequest is the payload of a server-initiated "elicitation/create" request: the server is asking the client to gather structured input from the user mid-session.

type ElicitationResult added in v0.2.0

type ElicitationResult struct {
	Action  string         // "accept" | "decline" | "cancel"
	Content map[string]any // set when Action == "accept"
}

ElicitationResult is the client's reply to an ElicitationRequest.

type HTTPOption added in v0.2.0

type HTTPOption func(*httpTransport)

HTTPOption configures the Streamable HTTP transport constructed by NewStreamableHTTPTransportWithOptions.

func WithAuthHeader added in v0.2.0

func WithAuthHeader(name string) HTTPOption

WithAuthHeader sends the TokenProvider's token under the header named name (the raw token value, no "Bearer " prefix) instead of the default Authorization header. It has no effect without a TokenProvider.

func WithHTTPClientOpt added in v0.2.0

func WithHTTPClientOpt(c *http.Client) HTTPOption

WithHTTPClientOpt overrides the *http.Client used to send requests (default http.DefaultClient).

func WithHTTPRetry added in v0.2.0

func WithHTTPRetry(maxRetries int) HTTPOption

WithHTTPRetry enables retrying Send on transient failures: HTTP 429/503 responses (always safe — the server either rejected or didn't process the request) and a conservative allowlist of pre-delivery connection errors: connection-refused and DNS resolution failures, plus any error surfaced during the dial phase (a *net.OpError with Op == "dial"). These all prove the request bytes never reached the server, so retrying cannot cause a side-effecting call (e.g. tools/call) to run twice.

A generic client.Do error that isn't on that allowlist (e.g. "connection reset by peer" while reading the response, which can happen *after* the server has already processed a POST) is deliberately NOT retried: the transport cannot tell whether the server received and acted on the request, and retrying could double-execute a non-idempotent tool call. If your deployment needs broader retry coverage, wrap the *http.Client (WithHTTPClientOpt) with your own idempotency-aware retry logic instead.

maxRetries is the number of retries after the initial attempt (0, the default, disables retrying entirely). Retries use capped exponential backoff, honoring a Retry-After response header when present (seconds or an HTTP-date), and respect ctx cancellation while backing off. 4xx responses other than 429, and any failure once response bytes (JSON or SSE) have begun being consumed, are never retried. Each retry attempt re-invokes the configured TokenProvider, if any, for a fresh token — the transport does not retry 401s itself; refreshing credentials on auth failure is the TokenProvider's job.

No head-of-line blocking: httpTransport self-serializes (see SelfSerializes), so Client does not hold its write-serialization slot across Send. A call's retry backoff here (up to ~10s per attempt, capped by defaultRetryMaxDelay) therefore does NOT block any other concurrent call, or any server-initiated request reply, on the same Client — each Send (including its retries) runs fully independently.

func WithTokenProvider added in v0.2.0

func WithTokenProvider(tp TokenProvider) HTTPOption

WithTokenProvider configures tp to supply a fresh bearer token on every request (see TokenProvider's doc for details). It overrides any static Authorization header.

type NotificationHandler added in v0.3.0

type NotificationHandler func(method string, params json.RawMessage)

NotificationHandler is called for each server-initiated notification (a JSON-RPC message with a method but no id, e.g. "notifications/message" or "notifications/resources/updated"). params is the raw, undecoded params object from the wire; the handler is responsible for unmarshaling it into whatever shape the given method implies.

The handler runs on a dispatch goroutine spawned by recvLoop, bounded by the same dispatchSem used for server-initiated requests (see maxConcurrentServerDispatch), and therefore may run concurrently with other dispatched notifications, server requests, or in-flight calls. Since a notification owes no reply, a handler that blocks does not delay any response to the server, but it does hold a dispatch slot: a handler that never returns will eventually starve delivery of further notifications and server requests once the bound is exhausted.

type Prompt added in v0.2.0

type Prompt struct {
	Name        string
	Title       string
	Description string
	Arguments   []PromptArgument
}

Prompt describes one prompt template exposed by the server.

type PromptArgument added in v0.2.0

type PromptArgument struct {
	Name        string
	Description string
	Required    bool
}

PromptArgument describes one argument a Prompt accepts.

type PromptMessage added in v0.2.0

type PromptMessage struct {
	Role    string // "user" / "assistant"
	Content []PromptPart
}

PromptMessage is one message in a prompt's rendered conversation.

type PromptPart added in v0.2.0

type PromptPart struct {
	Type     string
	Text     string
	Resource *ResourceContents // for "resource" parts
	Data     []byte            // decoded, for "image"/"audio" parts
	MimeType string
}

PromptPart is one content part of a PromptMessage. Type is always set; which of Text, Resource, or Data is populated depends on Type ("text", "resource", "image", "audio"). Content types the client doesn't recognize are preserved with Type set and no error.

type RPCError

type RPCError struct {
	Code    int
	Message string
}

RPCError is returned when the server replies with a JSON-RPC error object. It is errors.As-able.

func (*RPCError) Error

func (e *RPCError) Error() string

type Resource added in v0.2.0

type Resource struct {
	URI         string
	Name        string
	Title       string
	Description string
	MimeType    string
}

Resource describes one resource exposed by the server.

type ResourceContents added in v0.2.0

type ResourceContents struct {
	URI      string
	MimeType string
	Text     string
	Blob     []byte
}

ResourceContents is the body of one resource, as returned by ReadResource or embedded in a prompt message. Exactly one of Text or Blob is set, depending on whether the server sent "text" or a base64 "blob".

type ResourceTemplate added in v0.2.0

type ResourceTemplate struct {
	URITemplate string
	Name        string
	Title       string
	Description string
	MimeType    string
}

ResourceTemplate describes one RFC 6570 URI template exposed by the server, from which concrete resource URIs can be constructed.

type Root added in v0.3.0

type Root struct {
	URI  string `json:"uri"`
	Name string `json:"name,omitempty"`
}

Root is a filesystem root the client exposes to the server (MCP roots capability). URI must be a file:// URI per the MCP spec.

type SamplingHandler added in v0.3.0

type SamplingHandler func(ctx context.Context, req CreateMessageRequest) (CreateMessageResult, error)

SamplingHandler is called when the server sends a "sampling/createMessage" request. A nil handler installed on the Client causes "sampling/createMessage" requests to be rejected with a JSON-RPC -32601 "Method not found" error and the "sampling" capability is not declared during Initialize.

Implementations must respect ctx: it is cancelled when the Client is closed, and a handler that ignores cancellation and blocks indefinitely will not hang Client.Close forever, but it will delay it — Close waits up to a short grace period (see closeDrainGrace) for in-flight handlers to finish before giving up and returning anyway.

type SamplingMessage added in v0.3.0

type SamplingMessage struct {
	Role    string          // "user" | "assistant"
	Content json.RawMessage // wire content object: {"type":"text","text":...} etc.
}

SamplingMessage is one message in a server-initiated "sampling/createMessage" request or its reply: a role and a wire content object (e.g. {"type":"text","text":...}), preserved verbatim.

type TokenProvider added in v0.2.0

type TokenProvider interface {
	Token(ctx context.Context) (string, error)
}

TokenProvider supplies a bearer token per request, enabling refresh and rotation: Token is called fresh on every Send (and on every retry attempt), and its result is sent as "Authorization: Bearer <token>" unless a custom header has been configured via WithAuthHeader, in which case the raw token is sent under that header instead. A TokenProvider overrides any static Authorization header configured via headers/ NewStreamableHTTPTransport.

Because httpTransport self-serializes (see SelfSerializes), Client does not hold a client-wide lock across Send, so a TokenProvider backing an httpTransport used by a Client with multiple concurrent in-flight calls must support Token being called concurrently by more than one goroutine.

type TokenProviderFunc added in v0.2.0

type TokenProviderFunc func(ctx context.Context) (string, error)

TokenProviderFunc adapts a function to the TokenProvider interface.

func (TokenProviderFunc) Token added in v0.2.0

func (f TokenProviderFunc) Token(ctx context.Context) (string, error)

Token implements TokenProvider.

type ToolContent added in v0.3.0

type ToolContent struct {
	Type     string          // "text", "image", "audio", "resource", ...
	Text     string          // set for "text"
	Data     string          // base64 payload for "image"/"audio"
	MimeType string          // media type for binary parts
	Raw      json.RawMessage // the full wire part, for forward compatibility
}

ToolContent is one content part of a CallTool result, preserved verbatim from the wire response.

type ToolDef

type ToolDef struct {
	Name        string
	Description string
	InputSchema json.RawMessage
}

ToolDef describes one tool exposed by the server.

type ToolResult

type ToolResult struct {
	Text    string        // concatenated text content parts
	Content []ToolContent // every content part, in wire order, preserved verbatim
	IsError bool
}

ToolResult is the outcome of a CallTool invocation.

type Transport

type Transport interface {
	Send(ctx context.Context, msg json.RawMessage) error
	Receive(ctx context.Context) (json.RawMessage, error)
	Close() error
}

Transport moves one JSON-RPC message each way. Implementations are safe for one concurrent reader and one concurrent writer (i.e. Send may be called concurrently with Receive, but Send is expected to be called by at most one goroutine at a time, as is Receive) — UNLESS the implementation also implements selfSerializingTransport and reports SelfSerializes() == true, in which case concurrent Send calls from multiple goroutines are required to be safe (see that interface's doc).

func NewStdioTransport

func NewStdioTransport(cmd []string, env []string) (Transport, error)

NewStdioTransport launches cmd (argv form) and speaks newline-delimited JSON-RPC over its stdin/stdout (MCP stdio framing: one JSON object per line). Env entries are appended to the child's environment. The child's stderr is passed through to os.Stderr. Close closes the child's stdin, waits briefly for it to exit on its own, and kills it if it hasn't.

cmd is trusted developer configuration, executed verbatim; callers passing user-influenced input are responsible for validating it.

func NewStreamableHTTPTransport

func NewStreamableHTTPTransport(url string, headers map[string]string) Transport

NewStreamableHTTPTransport speaks the MCP Streamable HTTP transport: each Send POSTs the JSON-RPC message to url (Content-Type application/json, Accept "application/json, text/event-stream"); responses arrive either as a direct application/json body or as an SSE stream (text/event-stream) whose events carry JSON-RPC messages — both are fed to Receive in order. The Mcp-Session-Id response header, when present, is captured and echoed on subsequent requests. headers are added to every request (e.g. Authorization).

For an SSE response, Send hands the body off to a per-response drain goroutine that enqueues messages as they arrive (in arrival order) and returns as soon as the response headers are read — it does not block until the stream ends. This matters because Client serializes all Sends behind one mutex: draining synchronously would hold that mutex, and the MCP spec only says a server SHOULD close the SSE stream after sending its response (2025-03-26), not that it MUST, so a server that keeps the stream open would otherwise wedge every subsequent call. Per response, only one goroutine drains, so ordering within that response's messages is preserved; Close closes any still-open response bodies, which unblocks their drain goroutines' reads and lets them exit.

Close marks the transport closed, closes any response bodies still being drained, unblocks any blocked Receive, and — if a session id was captured — issues a best-effort DELETE carrying that session id to let the server free session state promptly (see terminateSession's doc). That DELETE is sent synchronously from Close and is bounded by its own 5-second timeout, so against a stalled or unreachable server Close (and therefore mcp.Client.Close, which calls it) can block for up to ~5 seconds; against a session-less transport (no session id ever captured) Close returns immediately.

Known deviations from the full 2025-03-26 Streamable HTTP transport spec (v1 is scoped to tools-only MCP clients, which don't need the rest):

  • There is no standalone GET request opening a server-initiated SSE channel, so server-initiated requests/notifications outside of a POST response are not supported.

url is trusted developer configuration and is not SSRF-filtered — MCP servers legitimately live on localhost/private addresses; callers exposing URL choice to untrusted input must validate it themselves.

func NewStreamableHTTPTransportWithOptions added in v0.2.0

func NewStreamableHTTPTransportWithOptions(url string, opts ...HTTPOption) Transport

NewStreamableHTTPTransportWithOptions is the options-taking form of NewStreamableHTTPTransport: it speaks the same MCP Streamable HTTP transport (see NewStreamableHTTPTransport's doc for the full protocol description), configured by opts. Use WithTokenProvider/WithAuthHeader for per-request bearer auth, WithHTTPRetry to opt into retrying transient failures, and WithHTTPClientOpt to supply a custom *http.Client.

Jump to

Keyboard shortcuts

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