Protocol types 协议类型
Types needed for the protocol are generated from the JSON schema of the MCP spec.
MCP 协议所需的类型均从 MCP 规范的 JSON Schema 生成
These types will be included in the mcp package, but will be unexported unless they are needed for the user-facing API. Notably, JSON-RPC request types are elided(省略,取消), since they are handled by the jsonrpc2 package and should not be observed by the user.
这些类型会包含在 mcp 包中,但仅暴露用户需感知的类型(非导出类型仅用于内部逻辑)
值得注意的是,JSON-RPC 请求类型不会对外暴露 —— 它们由 jsonrpc2 包处理,用户无需感知
For user-provided data, we use json.RawMessage or map[string]any, depending on the use case.
对于用户提供的数据,根据场景使用 json.RawMessage(保留原始 JSON 格式)或 map[string]any(灵活的键值对)
For union types, which can’t be represented in Go, we use an interface for Content (implemented by types like TextContent).
For other union types like ResourceContents, we use a struct with optional fields.
对于 Go 语言不直接支持的 “联合类型”(Union Type):Content 类型(包含文本、图片、音频等内容)通过接口实现(如 TextContent 实现 Content 接口)
其他联合类型(如 ResourceContents)通过 “带可选字段的结构体” 实现
elide / ɪˈlaɪd /
/ ɪˈlaɪd /
省略;取消;删去;不予考虑删节
represent / ˌreprɪˈzent /
/ ˌreprɪˈzent /
代表;为……代言(辩护);代表(选区、政党、群体)任国会议员(或其他立法机构议员);代表(国家、学校、城镇等参加比赛);作为某人的代表(尤指在正式场合或仪式中);等于,相当于;(符号或象征)代表,表示;象征,体现;展示,描绘;(尤指不真实地)描述,描写;扮演;<正式>明确讲述,清楚说明;声称;正式提出(意见、抗议等)
以下为部分类型示例(完整类型需参考规范):
// ReadResourceParams 是“读取资源”请求的参数类型
type ReadResourceParams struct {
URI string `json:"uri"` // 资源 URI
}
// CallToolResult 是“调用工具”请求的返回结果类型
type CallToolResult struct {
Meta Meta `json:"_meta,omitempty"` // 元数据
Content []Content `json:"content"` // 工具返回的内容(如文本、图片)
IsError bool `json:"isError,omitempty"` // 是否为错误结果
}
// Content 是一个接口,包含 TextContent、ImageContent、AudioContent、EmbeddedResource 等实现
type Content interface {
// 【其实源码中,该接口的方法是导出的】
// 未导出方法(确保仅 SDK 定义的类型可实现该接口)
}
// TextContent 是文本类型的 Content 实现
type TextContent struct {
Text string // 文本内容
}
// (其他 Content 实现略)
// Meta 包含请求/响应的元数据
type Meta struct {
Data map[string]any // 自定义元数据(键值对)
ProgressToken any // 进度令牌(用于跟踪请求进度,支持 string 或 int 类型)
}
The Meta type includes a map[string]any for arbitrary data, and a ProgressToken field.
Differences from mcp-go: these types are largely similar, but our type generator flattens types rather than using struct embedding.
与 mcp-go 的差异:本文档中的类型与 mcp-go 大致相似,但类型生成逻辑采用 “扁平化结构”,而非 mcp-go 的 “结构体嵌入” 方式。
Clients and Servers
Generally speaking, the SDK is used by creating a Client or Server instance, adding features to it, and connecting it to a peer.
一般而言,使用该 SDK 的流程为:创建 Client(客户端)或 Server(服务端)实例,为其添加功能(如工具、提示词),然后将其与对端(peer)建立连接。
However, the SDK must make a non-obvious choice in these APIs: are clients 1:1 with their logical connections? What about servers? Both clients and servers are stateful: users may add or remove roots from clients, and tools, prompts, and resources from servers. Additionally, handlers for these features may themselves be stateful, for example if a tool handler caches state from earlier requests in the session.
然而,SDK 在设计这些 API 时,必须做出一个并不直观的(non-obvious)选择:客户端与其逻辑连接是否是 “一对一” 关系?服务端呢?客户端和服务端均为有状态的组件:用户可能为客户端添加或移除根目录(roots),为服务端添加或移除 tool、prompt及resource;此外,这些功能的 handler 本身也可能带有状态 —— 例如,某 tool handler会在会话中缓存早期请求的状态信息(caches state from earlier requests in the session)。
We believe that in the common case, any change to a client or server, such as adding a tool, is intended for all its peers. It is therefore more useful to allow multiple connections from a client, and to a server. This is similar to the net/http packages, in which an http.Client and http.Server each may handle multiple unrelated connections. When users add features to a client or server, all connected peers are notified of the change.
我们认为,在常见场景中,对客户端或服务端的任何修改(如添加 tool),其意图都是让所有对端都能感知到该变化。因此,允许客户端发起多个连接、服务端接收多个连接,会更符合实际使用需求。这与 Go 标准库 net/http 包的设计思路类似:一个 http.Client 可发起多个无关连接,一个 http.Server 也可处理多个无关连接。当用户为客户端或服务端添加功能时,所有已连接的对端都会收到变更通知。
Supporting multiple connections to servers (and from clients) still allows for stateful components, as it is up to the user to decide whether or not to create distinct servers/clients for each connection. For example, if the user wants to create a distinct server for each new connection, they can do so in the getServer factory passed to transport handlers.
支持服务端接收多连接(及客户端发起多连接),并不影响有状态组件的使用 —— 因为用户可自主决定是否为每个连接创建独立的服务端 / 客户端实例。例如,若用户希望为每个新连接创建独立的服务端,可在传递给传输层处理器(transport handlers)的 getServer 工厂函数中实现这一逻辑。
Following the terminology of the spec, we call the logical connection between a client and server a “session.” There must necessarily be a ClientSession and a ServerSession, corresponding to the APIs available from the client and server perspective, respectively.
遵循 MCP 规范的术语定义,我们将客户端与服务端之间的逻辑连接称为 “会话(session)”。会话必然包含 ClientSession(客户端会话)和 ServerSession(服务端会话),二者分别对应从客户端视角和服务端视角可调用的 API。
Sessions are created from either Client or Server using the Connect method.
使用Connect方法从Client或Server创建会话。
type Client struct { /* ... */ }
func NewClient(impl *Implementation, opts *ClientOptions) *Client
func (*Client) Connect(context.Context, Transport) (*ClientSession, error)
func (*Client) Sessions() iter.Seq[*ClientSession]
// Methods for adding/removing client features are described below.
type ClientOptions struct { /* ... */ } // described below
type ClientSession struct { /* ... */ }
func (*ClientSession) Client() *Client
func (*ClientSession) Close() error
func (*ClientSession) Wait() error
// Methods for calling through the ClientSession are described below.
// For example: ClientSession.ListTools.
type Server struct { /* ... */ }
func NewServer(impl *Implementation, opts *ServerOptions) *Server
func (*Server) Connect(context.Context, Transport) (*ServerSession, error)
func (*Server) Sessions() iter.Seq[*ServerSession]
// Methods for adding/removing server features are described below.
type ServerOptions struct { /* ... */ } // described below
type ServerSession struct { /* ... */ }
func (*ServerSession) Server() *Server
func (*ServerSession) Close() error
func (*ServerSession) Wait() error
// Methods for calling through the ServerSession are described below.
// For example: ServerSession.ListRoots.
客户端侧示例
client := mcp.NewClient(&mcp.Implementation{Name:"mcp-client", Version:"v1.0.0"}, nil)
// Connect to a server over stdin/stdout
transport := &mcp.CommandTransport{
Command: exec.Command("myserver"},
}
session, err := client.Connect(ctx, transport)
if err != nil { ... }
// Call a tool on the server.
content, err := session.CallTool(ctx, "greet", map[string]any{"name": "you"}, nil)
...
return session.Close()
服务端示例
// Create a server with a single tool.
server := mcp.NewServer(&mcp.Implementation{Name:"greeter", Version:"v1.0.0"}, nil)
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
// Run the server over stdin/stdout, until the client disconnects.
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
log.Fatal(err)
}
For convenience, we provide Server.Run to handle the common case of running a session until the client disconnects:
方便起见,服务端提供了如下API
func (*Server) Run(context.Context, Transport)
The client API here is different, since clients and client sessions are conceptually distinct. The ClientSession is closer to mcp-go’s notion of Client.
For both clients and servers, mcp-go uses variadic options to customize behavior, whereas an options struct is used here. We felt that in this case, an options struct would be more readable, and result in simpler package documentation.
Client Features
Roots - 根
Clients support the MCP Roots feature, including roots-changed notifications. Roots can be added and removed from a Client with AddRoots and RemoveRoots:
// AddRoots adds the given roots to the client,
// replacing any with the same URIs,
// and notifies any connected servers.
func (*Client) AddRoots(roots ...*Root)
// RemoveRoots removes the roots with the given URIs.
// and notifies any connected servers if the list has changed.
// It is not an error to remove a nonexistent root.
func (*Client) RemoveRoots(uris ...string)
Server sessions can call the spec method ListRoots to get the roots. If a server installs a RootsChangedHandler, it will be called when the client sends a roots-changed notification, which happens whenever the list of roots changes after a connection has been established.
服务端会话(Server Session)可调用 MCP 规范中定义的 ListRoots 方法获取客户端的根目录(roots)信息。若服务端配置了 RootsChangedHandler(根目录变更处理器),则当客户端发送 “根目录变更通知(roots-changed notification)” 时,该处理器会被触发执行 —— 而客户端仅会在 “连接建立后,根目录列表发生变更” 的场景下发送该通知
type ServerOptions {
...
// If non-nil, called when a client sends a roots-changed notification.
RootsChangedHandler func(context.Context, *ServerSession, *RootsChangedParams)
}
The Roots method provides a cached iterator of the root set, invalidated when roots change.
func (*ServerSession) Roots(context.Context) (iter.Seq[*Root, error])
Sampling - 采样
Clients that support sampling are created with a CreateMessageHandler option for handling server calls. To perform sampling, a server session calls the spec method CreateMessage.
type ClientOptions struct {
...
CreateMessageHandler func(context.Context, *ClientSession, *CreateMessageParams) (*CreateMessageResult, error)
}
Server Features
Tools
A Tool is a logical MCP tool, generated from the MCP spec.
Tool(工具)是一种符合 MCP 规范的逻辑 MCP 工具,由 MCP 规范生成
A tool handler accepts CallToolParams and returns a CallToolResult. However, since we want to bind tools to Go input types, it is convenient in associated APIs to have a generic version of CallToolParams, with a type parameter In for the tool argument type, as well as a generic version of for CallToolResult. This allows tool APIs to manage the marshalling and unmarshalling of tool inputs for their caller.
工具处理器(Tool Handler)接收 CallToolParams(调用工具参数)并返回 CallToolResult(调用工具结果)。
但由于我们希望将工具与 Go 语言的输入类型进行绑定(bind tools to Go input types),因此在相关 API 中,为 CallToolParams 设计一个泛型版本(generic version)(通过类型参数 In 指定工具的参数类型),同时为 CallToolResult 也设计一个泛型版本,会更为便捷。
这种设计能让工具 API 为调用者自动处理工具输入的序列化(Marshal)与反序列化(Unmarshal)操作
个人理解:不用每次调用前序列化,调用后反序列化
type CallToolParamsFor[In any] struct {
Meta Meta `json:"_meta,omitempty"`
Arguments In `json:"arguments,omitempty"`
Name string `json:"name"`
}
type Tool struct {
Annotations *ToolAnnotations `json:"annotations,omitempty"`
Description string `json:"description,omitempty"`
InputSchema *jsonschema.Schema `json:"inputSchema"`
Name string `json:"name"`
}
// A ToolHandlerFor handles a call to tools/call with typed arguments and results.
type ToolHandlerFor[In, Out any] func(context.Context, *ServerRequest[*CallToolParamsFor[In]]) (*CallToolResultFor[Out], error)
Add tools to a server with the AddTool method or function. The function is generic and infers schemas from the handler arguments
可以通过 AddTool 方法或函数向服务器添加工具。该函数是泛型的,能够从处理器参数中自动推导(生成)JSON 模式(schema):
func (s *Server) AddTool(t *Tool, h ToolHandler)
func AddTool[In, Out any](s *Server, t *Tool, h ToolHandlerFor[In, Out])
mcp.AddTool(server, &mcp.Tool{Name: "add", Description: "add numbers"}, addHandler)
mcp.AddTool(server, &mcp.Tool{Name: "subtract", Description: "subtract numbers"}, subHandler)
The AddTool method requires an input schema, and optionally an output one. It will not modify them.
The handler should accept a CallToolParams and return a CallToolResult.
AddTool 方法需要一个输入 schema,还可以选择一个输出 schema。它不会修改它们
handler 应该接受一个 CallToolParams 并返回一个 CallToolResult
t := &Tool{Name: ..., Description: ..., InputSchema: &jsonschema.Schema{...}}
server.AddTool(t, myHandler)
Tools can be removed by name with RemoveTools:
可以通过 RemoveTools 移除 Tools
server.RemoveTools("add", "subtract")
A tool’s input schema, expressed as a JSON Schema, provides a way to validate the tool’s input. One of the challenges in defining tools is the need to associate them with a Go function, yet support the arbitrary complexity of JSON Schema. To achieve this, we have seen two primary approaches:
tool 的输入 Schema 以 JSON Schema 格式表示,为 tool 输入提供了校验依据。定义 tool 时面临的核心挑战之一是:既要将 tool 与 Go 函数关联,又要支持 JSON Schema 所能表达的任意复杂度(arbitrary complexity )。
explicitly / ɪkˈsplɪsɪtli /
/ ɪkˈsplɪsɪtli /
清楚明确地,详述地;直截了当地,坦率地;露骨地,不隐晦地
cumbersome / ˈkʌmbəsəm /
/ ˈkʌmbərsəm /
笨重的;繁琐的,复杂的;(话语或措词)冗长的
breadth / bredθ /
/ bredθ /
宽度,幅度;广度,广泛性
natural / ˈnætʃ(ə)rəl /
/ ˈnætʃ(ə)rəl /
天然的,天生的
boilerplate / ˈbɔɪləpleɪt /
/ ˈbɔɪlərpleɪt /
样板文件;引用
infer / ɪnˈfɜː(r) /
/ ɪnˈfɜːr /
推断,推论;暗示,暗指
目前主要有两种解决方案:
Use reflection to generate the tool’s input schema from a Go type (à la metoro-io/mcp-golang)
Explicitly build the input schema (à la mark3labs/mcp-go).
利用反射(reflection)从 Go 类型生成 tool 的 input schema(类似 metoro-io/mcp-golang 的实现方式)
显式构建输入模式(类似 mark3labs/mcp-go 的实现方式)
Both of these have their advantages and disadvantages. Reflection is nice, because it allows you to bind directly to a Go API, and means that the JSON schema of your API is compatible with your Go types by construction. It also means that concerns like parsing and validation can be handled automatically. However, it can become cumbersome to express the full breadth of JSON schema using Go types or struct tags, and sometimes you want to express things that aren’t naturally modeled by Go types, like unions.
Explicit schemas are simple and readable, and give the caller full control over their tool definition, but involve significant boilerplate.
各有优劣,反射(Reflection)方案的优势在于,可直接与 Go API 绑定,且生成的 API JSON Schema 能与 Go 类型 “天生兼容”(by construction),同时解析、校验等逻辑也可自动处理。但缺点是,用 Go 类型或结构体标签(struct tag)难以完整表达 JSON Schema 的全部特性,对于联合类型(union)等 Go 语言中无原生对应的数据结构(aren’t naturally modeled by Go types),更是无法自然建模。
显式构建方案则简单易读,调用者可完全掌控工具的模式定义,但会产生 involve 大量重复模板代码(boilerplate)
We provide both ways.
The jsonschema.For[T] function will infer a schema, and it is called by the AddTool generic function.
Users can also call it themselves, or build a schema directly as a struct literal. They can still use the AddTool function to create a typed handler, since AddTool doesn’t touch schemas that are already present.
我们的 SDK 同时支持上述两种方案:
- 通过 jsonschema.For[T] 函数可自动推导(infer) schema,泛型函数 AddTool 内部会调用该函数
- 用户也可自行调用 jsonschema.For[T],或通过结构体字面量(struct literal)直接构建 schema
- 用户仍可通过 AddTool 函数创建类型化处理器(typed handler)(由于 AddTool 不会修改已存在的schemas(touch schemas),即便工具已包含自定义模式)
If the tool’s InputSchema is nil, it is inferred from the In type parameter. If the OutputSchema is nil, it is inferred from the Out type parameter (unless Out is any).
若 tool 的 InputSchema 为 nil,SDK 会从泛型参数 In(输入类型)中推导生成;若 OutputSchema 为 nil(且泛型参数 Out 不为 any 类型),则会从 Out(输出类型)中推导生成
示例 handler
type AddParams struct {
X int `json:"x"`
Y int `json:"y"`
}
func addHandler(ctx context.Context, req *mcp.ServerRequest[*mcp.CallToolParamsFor[AddParams]]) (*mcp.CallToolResultFor[int], error) {
return &mcp.CallToolResultFor[int]{StructuredContent: req.Params.Arguments.X + req.Params.Arguments.Y}, nil
}
你可以像这样添加它到 server
mcp.AddTool(server, &mcp.Tool{Name: "add", Description: "add numbers"}, addHandler)
The input schema will be inferred from AddParams, and the output schema from int.
输入 schema(Input Schema)将从 AddParams 中自动推导生成,输出 schema(Output Schema)则从 int(整数类型)中推导生成
Since all the fields of the Tool struct are exported, a Tool can also be created directly with assignment or a struct literal.
由于 Tool 结构体的所有字段均为导出字段(首字母大写),因此也可通过直接赋值或结构体字面量(struct literal)的方式创建 Tool 实例
Client sessions can call the spec method ListTools or an iterator method Tools to list the available tools, and use spec method CallTool to call tools.
客户端会话(Client Session)可调用规范定义的方法 ListTools(列出工具)或迭代器方法 Tools(工具迭代器)来获取可用 tool 列表,还可调用规范方法 CallTool(调用工具)来执行 tool
Similar to ServerTool.Handler, CallTool expects *CallToolParams[json.RawMessage]
, but we provide a generic CallTool helper to operate on typed arguments.
与 ServerTool.Handler 类似,CallTool 方法默认接收 *CallToolParams[json.RawMessage]
类型的参数;但我们额外提供了一个泛型版 CallTool 辅助函数,支持直接使用类型化参数(typed arguments)(如自定义结构体)进行调用
func (cs *ClientSession) CallTool(context.Context, *CallToolParams[json.RawMessage]) (*CallToolResult, error)
Differences from mcp-go:
We provide a full JSON Schema implementation for validating tool input schemas against incoming arguments. The jsonschema.Schema type provides exported features for all keywords in the JSON Schema draft2020-12 spec. Tool definers can use it to construct any schema they want. The jsonschema.For[T] function can infer a schema from a Go struct. These combined features eliminate the need for variadic arguments to construct tool schemas.
eliminate / ɪˈlɪmɪneɪt /
/ ɪˈlɪmɪneɪt /
剔除,根除;对……不予考虑,把……排除在外;(比赛中)淘汰;铲除,杀害;(生理)排除,排泄;消去
variadic /ˌveəriˈædɪk/
/ˌveriˈædɪk/
可变参数的
与 mcp-go 的差异:
我们提供了完整的 JSON Schema 实现,可用于校验 tool 输入 Schema 与实际传入参数的一致性。其中,jsonschema.Schema 类型支持 JSON Schema draft2020-12 规范中的所有关键字,并通过导出字段开放相关能力,工具定义者可借助它构建任意所需的模式。
此外,jsonschema.For[T] 函数能从 Go 结构体中推导生成模式 —— 这些特性结合起来,不再需要(eliminate the need for)通过可变参数(variadic arguments)来构建 tool schemas
For registering tools, we provide only a Server.AddTool method; mcp-go’s SetTools, AddTool, AddSessionTool, and AddSessionTools are deemed unnecessary. (Similarly for Delete/Remove). The AddTool generic function combines schema inference with registration, providing a easy way to register many tools.
在 tool 注册方面,我们仅提供 Server.AddTool 这一个方法;mcp-go 中的 SetTools、AddTool、AddSessionTool 和 AddSessionTools 方法在我们的设计中被认为是不必要的(工具删除相关方法同理)。而泛型函数 AddTool 则将 “模式推导” 与 “tool注册” 功能结合,为批量注册 tool 提供便捷的方式
Prompts
Use AddPrompt to add a prompt to the server, and RemovePrompts to remove them by name.
使用 AddPrompt 向服务器添加 Prompt,使用RemovePrompts按名称删除 Prompt
type codeReviewArgs struct {
Code string `json:"code"`
}
func codeReviewHandler(context.Context, *ServerSession, *mcp.GetPromptParams) (*mcp.GetPromptResult, error) {...}
server.AddPrompt(
&mcp.Prompt{Name: "code_review", Description: "review code"},
codeReviewHandler,
)
server.RemovePrompts("code_review")
Client sessions can call the spec method ListPrompts or the iterator method Prompts to list the available prompts, and the spec method GetPrompt to get one.
客户端会话可以调用规范中定义的 ListPrompts 方法或迭代器方法 Prompts 来列出可用的提示词(prompt),也可以调用规范方法 GetPrompt 来获取某个具体的提示词 Prompt
Resources and resource templates
In our design, each resource and resource template is associated with a function that reads it, with this signature:
在我们的设计中,每个资源(Resource)和资源模板(Resource Template)都关联着一个用于读取其内容的函数,函数签名如下
type ResourceHandler func(context.Context, *ServerSession, *ReadResourceParams) (*ReadResourceResult, error)
The arguments include the ServerSession so the handler can observe the client’s roots. The handler should return the resource contents in a ReadResourceResult, calling either NewTextResourceContents or NewBlobResourceContents. If the handler omits the URI or MIME type, the server will populate them from the resource.
函数参数包含 ServerSession,以便 handler 能获取(observe)客户端的根目录(roots)信息。handler 需在 ReadResourceResult 中返回资源内容,具体可通过调用 NewTextResourceContents 或 NewBlobResourceContents 实现
若处理器未指定资源的 URI(统一资源标识符)或 MIME 类型(多用途互联网邮件扩展类型),服务端会自动从资源本身的配置中补全(populate)这些信息(populate them from the resource)
To add a resource or resource template to a server, users call the AddResource and AddResourceTemplate methods. We also provide methods to remove them.
要向服务端添加资源或资源模板,用户可调用 AddResource 和 AddResourceTemplate 方法;我们同样提供了对应的移除方法:
func (*Server) AddResource(*Resource, ResourceHandler)
func (*Server) AddResourceTemplate(*ResourceTemplate, ResourceHandler)
func (s *Server) RemoveResources(uris ...string)
func (s *Server) RemoveResourceTemplates(uriTemplates ...string)
The ReadResource method finds a resource or resource template matching the argument URI and calls its associated handler.
ReadResource 方法会根据传入的 URI 匹配对应的资源或资源模板,并调用其关联的读取处理器(ResourceHandler)
Server sessions also support the spec methods ListResources and ListResourceTemplates, and the corresponding iterator methods Resources and ResourceTemplates.
服务端会话(ServerSession)还支持 MCP 规范中定义的 ListResources(列出资源)和 ListResourceTemplates(列出资源模板)方法,以及对应的迭代器方法 Resources 和 ResourceTemplates(用于逐行遍历资源 / 资源模板)
Subscriptions
Client-Side Usage
Use the Subscribe and Unsubscribe methods on a ClientSession to start or stop receiving updates for a specific resource.
启动/停止接收某个 resource 的更新
func (*ClientSession) Subscribe(context.Context, *SubscribeParams) error
func (*ClientSession) Unsubscribe(context.Context, *UnsubscribeParams) error
To process incoming update notifications, you must provide a ResourceUpdatedHandler in your ClientOptions. The SDK calls this function automatically whenever the server sends a notification for a resource you’re subscribed to.
为了处理到来的 update 通知,必须在 ClientOptions 中提供一个 ResourceUpdatedHandler
SDK 会在服务端发送一个你已订阅的 resource 的通知时,自动调用该函数
type ClientOptions struct {
...
ResourceUpdatedHandler func(context.Context, *ClientSession, *ResourceUpdatedNotificationParams)
}
Server-Side Implementation
The server does not implement resource subscriptions. It passes along subscription requests to the user, and supplies a method to notify clients of changes. It tracks which sessions have subscribed to which resources so the user doesn’t have to.
If a server author wants to support resource subscriptions, they must provide handlers to be called when clients subscribe and unsubscribe. It is an error to provide only one of these handlers.
服务端不实现资源订阅,它传递订阅请求给用户,同时提供一个方法用于把变更内容通知给客户端
它(指服务端)会自动跟踪哪些会话订阅了哪些资源,用户无需手动管理这一映射关系
如果服务端开发者想要支持资源订阅,他们必须提供handler,它会在客户端订阅/取消订阅的时候被服务端调用
type ServerOptions struct {
...
// Function called when a client session subscribes to a resource.
// 客户端会话在订阅资源时被调用
SubscribeHandler func(context.Context, *ServerRequest[*SubscribeParams]) error
// Function called when a client session unsubscribes from a resource.
// 客户端会话在取消订阅资源时被调用
UnsubscribeHandler func(context.Context, *ServerRequest[*UnsubscribeParams]) error
}
User code should call ResourceUpdated when a subscribed resource changes.
当一个已订阅的资源发生变动时,用户代码应该调用 ResourceUpdated
func (*Server) ResourceUpdated(context.Context, *ResourceUpdatedNotificationParams) error
The server routes these notifications to the server sessions that subscribed to the resource.
服务端路由这些通知到订阅了资源的服务端会话中
ListChanged notifications
When a list of tools, prompts or resources changes as the result of an AddXXX or RemoveXXX call, the server informs all its connected clients by sending the corresponding type of notification. A client will receive these notifications if it was created with the corresponding option:
当 tool 、prompt 或 resource 的列表因调用 AddXXX(添加类方法,如 AddTool/AddPrompt)或 RemoveXXX(移除类方法,如 RemoveResource/RemovePrompt)而发生变更时,服务端会通过发送对应类型的通知,告知所有已连接的客户端。若客户端在创建时配置了对应的通知处理 option,则会接收到这些变更通知
type ClientOptions struct {
...
ToolListChangedHandler func(context.Context, *ClientRequest[*ToolListChangedParams])
PromptListChangedHandler func(context.Context, *ClientRequest[*PromptListChangedParams])
// For both resources and resource templates.
ResourceListChangedHandler func(context.Context, *ClientRequest[*ResourceListChangedParams])
}
Completion
Clients call the spec method Complete to request completions. If a server installs a CompletionHandler, it will be called when the client sends a completion request.
客户端通过调用规范中定义的 Complete 方法来请求 completion 完成。若服务端配置了 CompletionHandler,则当客户端发送补全请求时,该处理器会被触发执行
type ServerOptions struct {
...
// If non-nil, called when a client sends a completion request.
CompletionHandler func(context.Context, *ServerRequest[*CompleteParams]) (*CompleteResult, error)
}
Security Considerations - 遵守的安全准则
Implementors of the CompletionHandler MUST adhere to the following security guidelines:
Validate all completion inputs: The CompleteRequest received by the handler may contain arbitrary data from the client. Before processing, thoroughly validate all fields.
校验所有 completion 请求输入:handler 接收的 CompleteRequest 可能包含来自客户端的任意数据。在处理请求前,需对所有字段进行全面校验(如校验字段类型、取值范围、是否包含恶意内容等)
Implement appropriate rate limiting: Completion requests can be high-frequency, especially in interactive user experiences. Without rate limiting, a malicious client could potentially overload the server, leading to denial-of-service (DoS) attacks. Consider applying rate limits per client session, IP address, or API key, depending on your deployment model. This can be implemented within the CompletionHandler itself or via middleware (see Middleware) that wraps the handler.
实现合理的速率限制:Completion 请求可能出现高频调用的情况(尤其在交互式 interactive 用户场景中)。若不设置速率限制,恶意客户端可能通过高频请求导致服务器过载,引发拒绝服务(denial-of-service DoS)攻击。需根据部署模式,考虑按客户端会话、IP 地址或 API key设置速率限制。速率限制可直接在 CompletionHandler 内部实现,也可通过包装 handler 的中间件(见 “Middleware” 章节)实现
involve / ɪnˈvɒlv /
/ ɪnˈvɑːlv /
牵涉,涉及;包含,需要;使陷入,使卷入;(使)参加,加入;使承担,使面对
draw from / drɔː frəm /
从……中得到;从……提取
disclosure / dɪsˈkləʊʒə(r) /
/ dɪsˈkloʊʒər /
披露,泄密;透露的秘闻,公开的事情
seemingly / ˈsiːmɪŋli /
/ ˈsiːmɪŋli /
貌似,看似(但可能并非如此);似乎,好像(是事实)
innocuous / ɪˈnɒkjuəs /
/ ɪˈnɑːkjuəs /
无害的;无伤大雅的
inadvertently / ˌɪnədˈvɜːt(ə)ntli /
/ ˌɪnədˈvɜːrt(ə)ntli /
无意地,不经意地
reveal / rɪˈviːl /
/ rɪˈviːl
揭示,透露;表明,证明;展示,显示;(通过神或超自然手段)启示
revealing / rɪˈviːlɪŋ /
/ rɪˈviːlɪŋ /
Control access to sensitive suggestions: Completion suggestions should only expose information that the requesting client is authorized to access. If your completion logic draws from sensitive data sources (e.g., internal file paths, user data, restricted API endpoints), ensure that the CompletionHandler performs proper authorization checks before generating or returning suggestions. This might involve checking the ServerSession context for authentication details or consulting an external authorization system.
控制敏感 suggestions 的访问权限:Completion suggestions 仅应向 “已获授权的客户端” 暴露其有权访问的信息。若 completion 逻辑依赖(draw from,从…提取)敏感数据源(如内部文件路径、用户隐私数据、受限的 API 端点等),需确保 CompletionHandler 在生成或返回 suggestions 前,执行严格的权限校验。例如,可通过 ServerSession 上下文获取认证信息,或调用外部授权系统(external authorization system)完成(involve)校验
Prevent completion-based information disclosure: Be mindful that even seemingly innocuous completion suggestions can inadvertently reveal sensitive information. For example, suggesting internal system paths or confidential identifiers could be an attack vector. Ensure that the generated CompleteResult contains only appropriate and non-sensitive information for the given client and context. Avoid revealing internal data structures or error messages that could aid an attacker.
防止基于completion的信息泄漏:需警惕 “看似无害的 completion 建议” 可能意外泄露敏感信息的风险。例如,建议中包含内部系统路径或机密标识,可能成为攻击者的攻击vector。需确保生成的 CompleteResult 仅包含 “与当前客户端及上下文匹配的、非敏感的合理信息”,避免泄露可能对攻击者有帮助的内部数据结构或错误信息
Logging
MCP specifies a notification for servers to log to clients. Server sessions implement this with the LoggingMessage method. It honors the minimum log level established by the client session’s SetLevel call.
MCP 规范定义了一种 “服务端向客户端发送日志” 的通知机制。服务端会话(Server Session)通过 LoggingMessage 方法实现该机制,且会遵循客户端会话通过 SetLevel 方法设置的 “最低日志级别”(仅发送级别不低于该设置的日志)
specifies / ˈspesɪfaɪz /
As a convenience, we also provide a slog.Handler that allows server authors to write logs with the log/slog package::
为方便开发,我们还提供了一个 slog.Handler 实现,允许服务端开发者通过 Go 标准库的 log/slog 包写入日志(无需手动构造 MCP 日志通知):
// A LoggingHandler is a [slog.Handler] for MCP.
type LoggingHandler struct {...}
// LoggingHandlerOptions are options for a LoggingHandler.
type LoggingHandlerOptions struct {
// The value for the "logger" field of logging notifications.
LoggerName string
// Limits the rate at which log messages are sent.
// If zero, there is no rate limiting.
MinInterval time.Duration
}
// NewLoggingHandler creates a [LoggingHandler] that logs to the given [ServerSession] using a
// [slog.JSONHandler].
func NewLoggingHandler(ss *ServerSession, opts *LoggingHandlerOptions) *LoggingHandler
Server-to-client logging is configured with ServerOptions:
S到C的logging,需要通过如下 ServerOptions 配置
type ServerOptions {
...
// The value for the "logger" field of the notification.
// log名称
LoggerName string
// Log notifications to a single ClientSession will not be sent more frequently than this duration.
// log间隔
LoggingInterval time.Duration
}
A call to a log method like Info is translated to a LoggingMessageNotification as follows:
当调用 Info 这类日志方法时,其会按以下规则转换为 LoggingMessageNotification(MCP 日志通知):
The attributes and the message populate the “data” property with the output of a slog.JSONHandler: The result is always a JSON object, with the key “msg” for the message.
日志的属性(attributes)与消息(message)会通过 slog.JSONHandler 生成输出内容,并以此填充通知的 “data” 属性 —— 生成的内容始终是一个 JSON 对象,其中 “msg” 键对应日志的消息文本。
If the LoggerName server option is set, it populates the “logger” property.
若服务端配置(ServerOptions)中设置了 LoggerName(日志器名称),则该值会填充到通知的 “logger” 属性中。
The standard slog levels Info, Debug, Warn and Error map to the corresponding levels in the MCP spec. The other spec levels map to integers between the slog levels. For example, “notice” is level 2 because it is between “warning” (slog value 4) and “info” (slog value 0). The mcp package defines consts for these levels. To log at the “notice” level, a handler would call Log(ctx, mcp.LevelNotice, “message”).
Go 标准库 slog 的标准级别(Info、Debug、Warn、Error),会直接映射到 MCP 规范中对应的日志级别;而 MCP 规范中其他额外级别(如 “notice”),则映射为介于 slog 各级别之间的整数。例如,“notice” 级别对应整数 2,因为它处于 “warning”(slog 中对应值为 4)与 “info”(slog 中对应值为 0)之间。MCP 包中已为这些额外级别定义了常量,若要记录 “notice” 级别的日志,处理器需调用 Log(ctx, mcp.LevelNotice, “message”) 方法。
A client that wishes to receive log messages must provide a handler:
若客户端希望接收日志消息,必须提供对应的处理器(LoggingMessageHandler):
type ClientOptions struct {
...
LoggingMessageHandler func(context.Context, *ClientSession, *LoggingMessageParams)
}
Pagination - 分页
Servers initiate pagination for ListTools, ListPrompts, ListResources, and ListResourceTemplates, dictating the page size and providing a NextCursor field in the Result if more pages exist. The SDK implements keyset pagination, using the unique ID of the feature as the key for a stable sort order and encoding the cursor as an opaque string.
服务端会为 ListTools(列出工具)、ListPrompts(列出提示词)、ListResources(列出资源)和 ListResourceTemplates(列出资源模板)这四个方法主动启用分页功能:服务端会指定分页大小(page size),若存在更多分页数据,则会在返回的结果(Result)中包含 NextCursor(下一页游标)字段。
该 SDK 实现的是键集分页(keyset pagination)—— 以 feature 的唯一 ID 作为排序依据(确保排序稳定),并将游标编码为一段 “不透明字符串”(opaque string,即客户端无需解析其内容,仅需在请求下一页时原样传递)。
For server implementations, the page size for the list operation may be configured via the ServerOptions.PageSize field. PageSize must be a non-negative integer. If zero, a sensible default is used.
对于服务端实现而言,列表操作的分页大小可通过 ServerOptions.PageSize 字段配置。PageSize 必须是非负整数:若设为 0,SDK 会自动使用一个合理的默认值。
Pagination / ˌpædʒɪˈneɪʃ(ə)n /
/ ˌpædʒɪˈneɪʃ(ə)n /
分页
dictate / dɪkˈteɪt /
/ ˈdɪkteɪt /
命令,规定;影响,支配;口述,使听写;命令,规定
opaque / əʊˈpeɪk /
/ oʊˈpeɪk /
(玻璃、液体等)不透明的,不透光的;难懂的,隐晦的;迟钝的
不透明体;(用于底片)不透明颜料
使不透明
type ServerOptions {
...
PageSize int
}
Client requests for List methods include an optional Cursor field for pagination. Server responses for List methods include a NextCursor field if more pages exist.
客户端调用列表类方法(List Methods)时,请求参数中可包含一个可选的 Cursor(游标)字段,用于指定请求的分页;服务端返回列表结果时,若存在更多分页,会在响应中包含 NextCursor 字段(供客户端请求下一页使用)
In addition to the List methods, the SDK provides an iterator method for each list operation. This simplifies pagination for clients by automatically handling the underlying pagination logic. See Iterator Methods above.
除了列表类方法,SDK 还为每个列表操作提供了对应的迭代器方法(Iterator Method)(如 Tools、Prompts)。该方法会自动处理底层的分页逻辑(如检测 NextCursor、自动请求下一页),大幅简化客户端的分页操作。具体可参考上文的 “迭代器方法” 章节。
Spec Methods
In our SDK, RPC methods that are defined in the specification take a context and a params pointer as arguments, and return a result pointer and error:
func (*ClientSession) ListTools(context.Context, *ListToolsParams) (*ListToolsResult, error)
Our SDK has a method for every RPC in the spec, their signatures all share this form. We do this, rather than providing more convenient shortcut signatures, to maintain backward compatibility if the spec makes backward-compatible changes such as adding a new property to the request parameters (as in this commit, for example). To avoid boilerplate, we don’t repeat this signature for RPCs defined in the spec; readers may assume it when we mention a “spec method.”
CallTool is the only exception: for convenience when binding to Go argument types, *CallToolParams[TArgs] is generic, with a type parameter providing the Go type of the tool arguments. The spec method accepts a *CallToolParams[json.RawMessage], but we provide a generic helper function. See the section on Tools below for details.
Why do we use params instead of the full JSON-RPC request? As much as possible, we endeavor to hide JSON-RPC details when they are not relevant to the business logic of your client or server. In this case, the additional information in the JSON-RPC request is just the request ID and method name; the request ID is irrelevant, and the method name is implied by the name of the Go method providing the API.
We believe that any change to the spec that would require callers to pass a new a parameter is not backward compatible. Therefore, it will always work to pass nil for any XXXParams argument that isn’t currently necessary. For example, it is okay to call Ping like so:
err := session.Ping(ctx, nil)
Iterator Methods
For convenience, iterator methods handle pagination for the List spec methods automatically, traversing all pages. If Params are supplied, iteration begins from the provided cursor (if present).
func (*ClientSession) Tools(context.Context, *ListToolsParams) iter.Seq2[Tool, error]
func (*ClientSession) Prompts(context.Context, *ListPromptsParams) iter.Seq2[Prompt, error]
func (*ClientSession) Resources(context.Context, *ListResourceParams) iter.Seq2[Resource, error]
func (*ClientSession) ResourceTemplates(context.Context, *ListResourceTemplatesParams) iter.Seq2[ResourceTemplate, error]
Middleware
We provide a mechanism to add MCP-level middleware on the both the client and server side.
我们提供了一种机制,可在客户端和服务端两侧添加 MCP 层级的中间件(middleware)
Receiving middleware runs after the request has been parsed but before any normal handling. It is analogous to traditional HTTP server middleware.
接收中间件(Receiving Middleware):在请求(request)被解析完成后、常规处理逻辑(normal handling,如 tool 调用、resource读取)执行前运行。其作用模式与传统 HTTP 服务端中间件类似(例如 HTTP 中间件中 “请求解析后、接口处理前” 的拦截逻辑)
Sending middleware runs after a call to a method but before the request is sent. It is an alternative to transport middleware that exposes MCP types instead of raw JSON-RPC 2.0 messages. It is useful for tracing and setting progress tokens, for example.
发送中间件(Sending Middleware):在调用 MCP 方法(如 Complete、ListTools)后、请求实际发送到对端前运行。它是传输层中间件(transport middleware)的替代方案 —— 与传输层中间件操作 “原始 JSON-RPC 2.0 消息” 不同,发送中间件直接暴露 MCP 原生类型(如 CallToolParams、CompleteRequest),更便于开发者操作。
例如,可利用发送中间件实现 “请求追踪”(记录 MCP 方法调用日志)或 “设置进度令牌(progress tokens)”(为请求附加跟踪标识)等场景。
analogous / əˈnæləɡəs /
/ əˈnæləɡəs /
相似的,类似的;(器官)同功的
// A MethodHandler handles MCP messages.
// For methods, exactly one of the return values must be nil.
// For notifications, both must be nil.
type MethodHandler func(ctx context.Context, method string, req Request) (result Result, err error)
// Middleware is a function from MethodHandlers to MethodHandlers.
type Middleware func(MethodHandler) MethodHandler
// AddMiddleware wraps the client/server's current method handler using the provided
// middleware. Middleware is applied from right to left, so that the first one
// is executed first.
//
// For example, AddMiddleware(m1, m2, m3) augments the server method handler as
// m1(m2(m3(handler))).
func (c *Client) AddSendingMiddleware(middleware ...Middleware)
func (c *Client) AddReceivingMiddleware(middleware ...Middleware)
func (s *Server) AddSendingMiddleware(middleware ...Middleware)
func (s *Server) AddReceivingMiddleware(middleware ...Middleware)
服务端加日志的中间件
func withLogging(h mcp.MethodHandler) mcp.MethodHandler{
return func(ctx context.Context, method string, req mcp.Request) (res mcp.Result, err error) {
log.Printf("request: %s %v", method, params)
defer func() { log.Printf("response: %v, %v", res, err) }()
return h(ctx, s , method, params)
}
}
server.AddReceivingMiddleware(withLogging)
Rate Limiting - 限流
Rate limiting can be configured using middleware. Please see examples/rate-limiting for an example on how to implement this.
Errors 错误
With the exception of tool handler errors, protocol errors are handled transparently as Go errors: errors in server-side feature handlers are propagated as errors from calls from the ClientSession, and vice-versa.
Protocol errors wrap a JSONRPCError type which exposes its underlying error code.
type JSONRPCError struct {
Code int64 `json:"code"`
Message string `json:"message"`
Data json.RawMessage `json:"data,omitempty"`
}
As described by the spec, tool execution errors are reported in tool results.
Cancellation 取消
Cancellation is implemented transparently using context cancellation. The user can cancel an operation by cancelling the associated context:
ctx, cancel := context.WithCancel(ctx)
go session.CallTool(ctx, "slow", map[string]any{}, nil)
cancel()
When this client call is cancelled, a “notifications/cancelled” notification is sent to the server. However, the client call returns immediately with ctx.Err(): it does not wait for the result from the server.
The server observes a client cancellation as a cancelled context.
Progress handling 进度通知
A caller can request progress notifications by setting the Meta.ProgressToken field on any request.
type XXXParams struct { // where XXX is each type of call
Meta Meta
...
}
type Meta struct {
Data map[string]any
ProgressToken any // string or int
}
Handlers can notify their peer about progress by calling the NotifyProgress method. The notification is only sent if the peer requested it by providing a progress token.
func (*ClientSession) NotifyProgress(context.Context, *ProgressNotification)
func (*ServerSession) NotifyProgress(context.Context, *ProgressNotification)
Ping / KeepAlive 保活
Both ClientSession and ServerSession expose a Ping method to call “ping” on their peer.
func (c *ClientSession) Ping(ctx context.Context, *PingParams) error
func (c *ServerSession) Ping(ctx context.Context, *PingParams) error
Additionally, client and server sessions can be configured with automatic keepalive behavior. If the KeepAlive option is set to a non-zero duration, it defines an interval for regular “ping” requests. If the peer fails to respond to pings originating from the keepalive check, the session is automatically closed.
自动Ping
type ClientOptions struct {
...
KeepAlive time.Duration
}
type ServerOptions struct {
...
KeepAlive time.Duration
}
附录
什么是 mcp 中的 Completion suggestions
在 MCP(Model Context Protocol,模型上下文协议)中,Completion suggestions(补全建议) 指的是服务端响应客户端 Complete 方法请求时,生成并返回的 “针对性内容补全结果”—— 本质是服务端根据客户端的输入需求(如文本片段、指令描述等),通过预设逻辑(如调用大模型、匹配模板库、查询数据源等)生成的 “补充 / 完整内容”,用于满足客户端的交互或业务需求
MCP 的核心是实现 “客户端与模型 / 服务端的标准化交互”,而补全建议是这一交互中常见的核心能力之一,例如:
(1)客户端发送 “写一段关于春天的” 文本片段,服务端返回 “写一段关于春天的散文:柳枝轻摇,桃花漫舞,微风裹着新叶的清香,漫过青石板路……”(文本补全)
(2)客户端发送 “查询用户订单状态,订单号为” 的指令片段,服务端返回 “查询用户订单状态,订单号为 [请输入 12 位订单号],支持通过以下参数筛选:status(待发货 / 已发货)、date(下单日期)”(指令补全)
(3)客户端发送 “生成一个简单的登录接口文档” 的需求,服务端返回包含 “接口路径、请求方法、参数说明、响应格式” 的完整文档框架(内容生成型补全)
简言之,补全建议是 MCP 服务端为客户端 “补全需求、生成内容” 的核心输出,是连接客户端需求与服务端能力的关键载体