go-sdk 客户端特性全解:Roots、Sampling、Elicitation 与 Multi Round-Trip Requests 实战指南
【免费下载链接】go-sdkThe official Go SDK for Model Context Protocol servers and clients. Maintained in collaboration with Google.项目地址: https://gitcode.com/GitHub_Trending/gosdk23/go-sdk
本篇技术指南以官方 Go SDK(Model Context Protocol 官方 Go 实现)的客户端能力文档为核心,系统讲解 MCP 客户端侧的四大特性:文件系统根(Roots)、AI 采样(Sampling)、用户输入获取(Elicitation)以及新协议下的 Multi Round-Trip Requests(MRTR)模式,并深入剖析客户端能力(Capabilities)的推断与显式配置机制。读完本文,你将掌握如何用mcp.NewClient构建具备 roots、sampling、elicitation 能力的客户端,理解协议版本 2026-07-28 之后服务端请求如何嵌入tools/call等回复中流转,并能在ClientOptions中精确控制客户端对外声明的能力。
本文内容以 internal/docs/client.src.md 为骨架,所有可运行示例均来自仓库中的 mcp/client_example_test.go,源码级依据可参见 mcp/client.go、mcp/mrtr.go 与 mcp/protocol.go。
Roots:客户端向服务端声明文件系统根
MCP 允许客户端向服务端指定一组文件系统根(roots),用于表达"哪些目录/URI 是我希望服务端访问的上下文边界"。roots 的完整协议语义见 MCP 规范中的 client roots 章节。
注意(重要):roots 特性自协议版本
2026-07-28起被标记为弃用(参见 SEP-2577)。在至少十二个月的弃用窗口期内该特性保持完全可用,SDK 出于兼容性考虑继续支持 roots。新代码应改为通过工具参数(tool parameters)、资源 URI(resource URIs)或配置(configuration)传递路径。
客户端侧:添加与移除 roots
SDK 客户端始终声明roots.listChanged能力(这是自 v1.0.0 起的默认行为,见 mcp/client.go 中capabilities()的默认分支)。向客户端添加 roots 使用:
Client.AddRoots:添加若干*Root,相同 URI 的旧值会被替换;若传入空列表则直接返回、不触发通知(见 mcp/client.go)。Client.RemoveRoots:按 URI 移除,移除不存在的 root 不视为错误;仅当列表确实发生变化时才发送通知(见 mcp/client.go)。
如果客户端上已有已连接的服务端,调用AddRoots/RemoveRoots会向每一个已连接的服务端广播notifications/roots/list_changed通知。底层由changeAndNotify实现:先加锁执行变更并判断是否真的发生改变,再对会话快照逐个发送通知(见 mcp/client.go)。
服务端侧:查询 roots 与监听变更
- 服务端查询客户端当前 roots:调用
ServerSession.ListRoots。 - 服务端接收变更通知:在
ServerOptions.RootsListChangedHandler中设置回调。
对于协议版本2026-07-28及之后的连接,ListRoots请求不再以独立的 JSON-RPC 请求下发,而是通过下文所述的 Multi Round-Trip Requests 模式传递(即嵌入在tools/call等请求的回复中)。
可运行示例
以下示例取自 mcp/client_example_test.go:客户端预先添加两个 root,服务端注册一个名为roots的工具,该工具在首次调用时返回InputRequests请求客户端提供 roots 列表,客户端 MRTR 驱动自动完成请求并重试原调用,最终打印出 roots 的 URI:
func Example_roots() { ctx := context.Background() // Create a client with two roots. c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil) c.AddRoots(&mcp.Root{URI: "file://a"}, &mcp.Root{URI: "file://b"}) // Create a server with a tool that requests roots via the multi round-trip // pattern (SEP-2322): server-to-client requests are no longer sent as // standalone JSON-RPC calls on protocol version >= 2026-07-28. s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) mcp.AddTool(s, &mcp.Tool{Name: "roots"}, func(_ context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) { if len(req.Params.InputResponses) == 0 { return &mcp.CallToolResult{ InputRequests: mcp.InputRequestMap{"roots": &mcp.ListRootsParams{}}, }, nil, nil } rootList := req.Params.InputResponses["roots"].(*mcp.ListRootsResult) var roots []string for _, root := range rootList.Roots { roots = append(roots, root.URI) } fmt.Println(roots) return &mcp.CallToolResult{}, nil, nil }) // Connect the server and client... t1, t2 := mcp.NewInMemoryTransports() serverSession, err := s.Connect(ctx, t1, nil) if err != nil { log.Fatal(err) } defer serverSession.Close() clientSession, err := c.Connect(ctx, t2, nil) if err != nil { log.Fatal(err) } defer clientSession.Close() // ...and call the tool. The client's multi round-trip driver fulfils the // embedded roots/list request and retries the call. if _, err := clientSession.CallTool(ctx, &mcp.CallToolParams{Name: "roots"}); err != nil { log.Fatal(err) } // Output: [file://a file://b] }Roots list changed:变更通知的完整闭环
Client.AddRoots与Client.RemoveRoots会通知每个已连接服务端"列表发生了变化"。服务端通过ServerOptions.RootsListChangedHandler观察该事件。与服务端侧 list-changed 通知一致,该通知只报告"有变化发生",不携带具体内容——服务端需要调用ServerSession.ListRoots重新读取完整列表。
完整闭环示例见 mcp/client_example_test.go,要点如下:
- 服务端设置
RootsListChangedHandler,通过 channel 接收通知; - 客户端在连接前先添加一个 root,连接后再次
AddRoots,服务端随即收到通知; - 由于
ListRoots是服务端发起的请求,示例通过ClientSessionOptions{ProtocolVersion: "2025-11-25"}协商旧协议版本以便走传统请求通道; - 服务端收到通知后调用
ss.ListRoots(ctx, nil)读回当前列表(file:///project与file:///scratch); - 客户端
RemoveRoots("file:///scratch")后服务端再次收到通知。
func Example_rootsListChanged() { ctx := context.Background() changed := make(chan struct{}, 2) s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, &mcp.ServerOptions{ RootsListChangedHandler: func(context.Context, *mcp.RootsListChangedRequest) { changed <- struct{}{} }, }) c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil) c.AddRoots(&mcp.Root{URI: "file:///project"}) t1, t2 := mcp.NewInMemoryTransports() ss, err := s.Connect(ctx, t1, nil) if err != nil { log.Fatal(err) } defer ss.Close() // ListRoots is a server-initiated request, so this session negotiates a // protocol version that still allows one. cs, err := c.Connect(ctx, t2, &mcp.ClientSessionOptions{ProtocolVersion: "2025-11-25"}) if err != nil { log.Fatal(err) } defer cs.Close() // Roots added after the client connects notify every connected server. c.AddRoots(&mcp.Root{URI: "file:///scratch"}) <-changed // The notification says only that the list changed, so read it back. res, err := ss.ListRoots(ctx, nil) if err != nil { log.Fatal(err) } for _, root := range res.Roots { fmt.Println(root.URI) } c.RemoveRoots("file:///scratch") <-changed fmt.Println("roots changed again") // Output: // file:///project // file:///scratch // roots changed again }Sampling:服务端借用客户端的 LLM 能力
Sampling 允许 MCP 服务端借助客户端的 AI(LLM)能力完成补全,例如在服务端内部流程中请求客户端调用一次模型生成。SDK 的实现方式如下:
注意(重要):sampling 特性同样自协议版本
2026-07-28起被 SEP-2577 标记为弃用,在至少十二个月的弃用窗口期内保持可用。SDK 出于兼容性继续支持。需要 LLM 补全的服务端应直接调用 LLM 提供商的 API。
客户端侧与服务端侧 API
- 客户端侧:为客户端添加
sampling能力,只需在ClientOptions.CreateMessageHandler中设置处理函数。每当服务端请求采样时,该函数会被调用。 - 服务端侧:服务端发起采样,调用
ServerSession.CreateMessage。
需要特别注意的是:ClientOptions.CreateMessageHandler与ClientOptions.CreateMessageWithToolsHandler互斥,同时设置会触发 panic(见 mcp/client.go)。后者返回CreateMessageWithToolsResult,支持包含并行工具调用的数组内容,并会使客户端额外声明sampling.tools能力。此外,SDK 中 sampling 相关的能力结构SamplingCapabilities还支持Context(客户端支持非"none"的includeContext值)与Tools(支持采样请求中的工具与toolChoice)两个子能力(见 mcp/protocol.go)。
与 roots 相同:对于协议版本2026-07-28及之后的连接,sampling 请求通过 Multi Round-Trip Requests 模式传递。
可运行示例
以下示例取自 mcp/client_example_test.go:客户端注册采样 handler(这里模拟返回一条固定的文本消息),服务端的sample工具在首次调用时通过InputRequests请求一次createMessage,MRTR 驱动自动完成请求并重试,最终工具返回采样得到的文本:
func Example_sampling() { ctx := context.Background() // Create a client with a sampling handler. c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{ CreateMessageHandler: func(_ context.Context, req *mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error) { return &mcp.CreateMessageResult{ Content: &mcp.TextContent{ Text: "would have created a message", }, }, nil }, }) // Connect the server and client... ct, st := mcp.NewInMemoryTransports() // Create a server with a tool that requests sampling via the multi // round-trip pattern (SEP-2322): server-to-client requests are no longer // sent as standalone JSON-RPC calls on protocol version >= 2026-07-28. s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) mcp.AddTool(s, &mcp.Tool{Name: "sample"}, func(_ context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) { if len(req.Params.InputResponses) == 0 { return &mcp.CallToolResult{ InputRequests: mcp.InputRequestMap{"msg": &mcp.CreateMessageParams{}}, }, nil, nil } msg := req.Params.InputResponses["msg"].(*mcp.CreateMessageWithToolsResult) return &mcp.CallToolResult{Content: msg.Content}, nil, nil }) session, err := s.Connect(ctx, st, nil) if err != nil { log.Fatal(err) } defer session.Close() clientSession, err := c.Connect(ctx, ct, nil) if err != nil { log.Fatal(err) } res, err := clientSession.CallTool(ctx, &mcp.CallToolParams{Name: "sample"}) if err != nil { log.Fatal(err) } fmt.Println(res.Content[0].(*mcp.TextContent).Text) // Output: would have created a message }Elicitation:服务端向用户请求输入
Elicitation 允许服务端向客户端请求用户输入(例如在工具执行过程中让用户确认某个选项、填写某个参数)。SDK 实现方式如下:
- 客户端侧:在
ClientOptions.ElicitationHandler中设置处理函数。该 handler 返回的结果必须匹配服务端请求的 schema,否则 elicitation 返回错误。如果你的 handler 支持 URL 模式 elicitation,则必须在 Capabilities 中显式声明该能力。 - 服务端侧:服务端发起用户输入请求,调用
ServerSession.Elicit。
对于协议版本2026-07-28及之后,elicitation 请求通过 Multi Round-Trip Requests 模式传递。
可运行示例
以下示例取自 mcp/client_example_test.go:服务端的ask工具请求一个名为test的字符串字段,客户端的ElicitationHandler返回Action: "accept"及内容{"test": "value"},工具最终打印出该值:
func Example_elicitation() { ctx := context.Background() ct, st := mcp.NewInMemoryTransports() // Create a server with a tool that requests elicitation via the multi // round-trip pattern (SEP-2322): server-to-client requests are no longer // sent as standalone JSON-RPC calls on protocol version >= 2026-07-28. s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) mcp.AddTool(s, &mcp.Tool{Name: "ask"}, func(_ context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) { if len(req.Params.InputResponses) == 0 { return &mcp.CallToolResult{ InputRequests: mcp.InputRequestMap{"input": &mcp.ElicitParams{ Message: "This should fail", RequestedSchema: &jsonschema.Schema{ Type: "object", Properties: map[string]*jsonschema.Schema{ "test": {Type: "string"}, }, }, }}, }, nil, nil } res := req.Params.InputResponses["input"].(*mcp.ElicitResult) fmt.Println(res.Content["test"]) return &mcp.CallToolResult{}, nil, nil }) ss, err := s.Connect(ctx, st, nil) if err != nil { log.Fatal(err) } defer ss.Close() c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{ ElicitationHandler: func(context.Context, *mcp.ElicitRequest) (*mcp.ElicitResult, error) { return &mcp.ElicitResult{Action: "accept", Content: map[string]any{"test": "value"}}, nil }, }) clientSession, err := c.Connect(ctx, ct, nil) if err != nil { log.Fatal(err) } if _, err := clientSession.CallTool(ctx, &mcp.CallToolParams{Name: "ask"}); err != nil { log.Fatal(err) } // Output: value }Schema 默认值与枚举(Schema defaults and enums)
ElicitParams.RequestedSchema是一个扁平的、仅含原始类型字段的 schema,客户端会将其渲染为一个表单。有两个字段关键字会影响表单形态:
Default(默认值,SEP-1034):为字段预填值。当用户不填写直接接受时,SDK 会在结果到达任一方调用者之前从 schema 中补全该字段——客户端在其 elicitation handler 返回之后补全,ServerSession.Elicit收到后再次补全。该行为无条件生效,没有任何 opt-in 开关。需要注意:把带默认值的字段标记为Required会"破坏"默认值机制——因为接受的内容会先按 schema 校验、后应用默认值,所以缺失该字段的答案会被直接拒绝而不是被默认值填充。
Enum(枚举,SEP-1330):将字段限制为一组固定取值,客户端渲染为选择项。枚举仅支持"string"类型的字段,在其它类型上声明会被拒绝。若要为选项提供标签,可通过Schema.Extra设置传统的enumNames关键字,且每个枚举值必须恰好对应一个名称,数量不匹配会被拒绝。
以下示例取自 mcp/client_example_test.go,演示了默认值与枚举的配合:format字段枚举["pdf", "csv"]且默认值为"pdf"、标签为"PDF document"/"CSV spreadsheet";用户不填写任何内容直接接受,最终工具输出Exported as pdf:
func Example_elicitationSchema() { ctx := context.Background() ct, st := mcp.NewInMemoryTransports() s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) mcp.AddTool(s, &mcp.Tool{Name: "export_report"}, func(_ context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) { if len(req.Params.InputResponses) == 0 { return &mcp.CallToolResult{ InputRequests: mcp.InputRequestMap{"format": &mcp.ElicitParams{ Message: "Export quarterly-sales as which format?", RequestedSchema: &jsonschema.Schema{ Type: "object", Properties: map[string]*jsonschema.Schema{ "format": { Type: "string", Title: "Format", Enum: []any{"pdf", "csv"}, Default: json.RawMessage(`"pdf"`), Extra: map[string]any{"enumNames": []any{"PDF document", "CSV spreadsheet"}}, }, }, }, }}, }, nil, nil } res := req.Params.InputResponses["format"].(*mcp.ElicitResult) return &mcp.CallToolResult{ Content: []mcp.Content{&mcp.TextContent{Text: "Exported as " + res.Content["format"].(string)}}, }, nil, nil }) if _, err := s.Connect(ctx, st, nil); err != nil { log.Fatal(err) } // The user accepts without filling anything in. c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{ ElicitationHandler: func(context.Context, *mcp.ElicitRequest) (*mcp.ElicitResult, error) { return &mcp.ElicitResult{Action: "accept", Content: map[string]any{}}, nil }, }) cs, err := c.Connect(ctx, ct, nil) if err != nil { log.Fatal(err) } defer cs.Close() res, err := cs.CallTool(ctx, &mcp.CallToolParams{Name: "export_report"}) if err != nil { log.Fatal(err) } fmt.Println(res.Content[0].(*mcp.TextContent).Text) // Output: Exported as pdf }完成一次 URL 模式 elicitation
在 URL 模式下,用户会在浏览器中带外(out of band)完成交互,因此 elicitation 结果本身无法告诉客户端"用户已完成"。服务端需要主动发出完成信号:
- 服务端调用
ServerSession.NotifyElicitationComplete,传入与请求携带的同一个ElicitationID; - 客户端通过
ClientOptions.ElicitationCompleteHandler观察该通知。
该通知应从托管流程重定向回来的那个回调端点发出(例如 OAuth 授权回调、浏览器重定向落地页)。
这个通知最重要的场景是:当 handler 用URLElicitationRequiredError拒绝某个请求时——客户端会暂停(park)原始请求,直到收到一条携带对应ElicitationID的完成通知,然后自动重试该请求;在通知到达之前,客户端会一直等待。
可运行示例见 mcp/client_example_test.go:客户端声明 URL 模式能力并同时设置ElicitationHandler与ElicitationCompleteHandler;服务端发起带ElicitationID与 URL 的Elicit请求(示例使用2025-11-25协议版本走传统通道),随后调用NotifyElicitationComplete,客户端打印 "flow finished: connect-calendar-1" 并解除等待:
func Example_elicitationComplete() { ctx := context.Background() ct, st := mcp.NewInMemoryTransports() s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) ss, err := s.Connect(ctx, st, nil) if err != nil { log.Fatal(err) } defer ss.Close() done := make(chan struct{}) c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{ Capabilities: &mcp.ClientCapabilities{ Elicitation: &mcp.ElicitationCapabilities{URL: &mcp.URLElicitationCapabilities{}}, }, ElicitationHandler: func(_ context.Context, req *mcp.ElicitRequest) (*mcp.ElicitResult, error) { fmt.Println("opening", req.Params.URL) return &mcp.ElicitResult{Action: "accept"}, nil }, ElicitationCompleteHandler: func(_ context.Context, req *mcp.ElicitationCompleteNotificationRequest) { fmt.Println("flow finished:", req.Params.ElicitationID) close(done) }, }) cs, err := c.Connect(ctx, ct, &mcp.ClientSessionOptions{ProtocolVersion: "2025-11-25"}) if err != nil { log.Fatal(err) } defer cs.Close() const elicitationID = "connect-calendar-1" if _, err := ss.Elicit(ctx, &mcp.ElicitParams{ Message: "Grant calendar access", URL: "https://calendar.example.com/consent?state=" + elicitationID, ElicitationID: elicitationID, }); err != nil { log.Fatal(err) } // The hosted page redirects back to the server, whose callback endpoint // signals that the user is done. if err := ss.NotifyElicitationComplete(ctx, &mcp.ElicitationCompleteParams{ElicitationID: elicitationID}); err != nil { log.Fatal(err) } <-done // Output: // opening https://calendar.example.com/consent?state=connect-calendar-1 // flow finished: connect-calendar-1 }Multi Round-Trip Requests(MRTR)
SEP-2322 引入了 MRTR 模式:sampling、elicitation、roots 这三类服务端到客户端(server-to-client)请求,在2026-07-28及之后的协议版本中不再作为全新的独立 JSON-RPC 请求发出,而是携带在正在进行中的tools/call、prompts/get或resources/read的回复里。客户端必须用产生好的响应重试原始请求。
默认安装的客户端中间件
SDK 为每一个客户端默认安装clientMultiRoundTripMiddleware(安装逻辑见 mcp/client.go,实现见 mcp/mrtr.go)。该中间件的工作流程:
- 检查每个
tools/call/prompts/get/resources/read的回复; - 若结果的
NeedsInput()为真,则将InputRequestsmap并发分发(fan out),为每个请求调用已配置的 handler(elicit、createMessage/createMessageWithTools或listRoots); - 将服务端提供的不透明
RequestState原样回传; - 带着响应集重试原始请求,循环往复,直到结果不再需要输入。
从 mcp/mrtr.go 的源码还可以看到两个实现细节:
- 重试上限为
maxMultiRoundTripRetries = 10;当服务端连续返回空请求列表(负载卸载场景)时,上限为maxLoadSheddingMultiRoundTripRetries = 3,超过后返回错误; - 服务端 handler 若同时返回 content 与
inputRequests,会被validateMultiRoundTripResult判定为服务端 bug 并返回内部错误(mcp/mrtr.go)。
退出自动处理
中间件默认启用。若想退出,设置ClientOptions.MultiRoundTrip.Disabled = true(类型为MultiRoundTripOptions,见 mcp/mrtr.go)。此时客户端会把"需要输入"的结果直接暴露给调用者:返回的CallToolResult、GetPromptResult或ReadResourceResult会报告NeedsInput() == true,并暴露服务端的InputRequests与不透明RequestState。你的代码必须自行完成每个请求,然后用设置好InputResponses、回传RequestState的方式重新发起原始调用。
新旧协议版本的兼容
- 面对旧版服务端(
<= 2025-11-25):SDK 会透明地把服务端请求放到传统的服务端发起通道上发送,MRTR 机制在该方向上是 no-op; - 面对旧版客户端连接 MRTR 风格服务端:服务端 SDK 会应用反向兼容 shim(
serverMultiRoundTripMiddleware,见 mcp/mrtr.go:当客户端不支持 MRTR 时,由服务端代为完成输入请求并重新调用一次 handler)。详见服务端文档。
Capabilities:客户端能力的声明与推断
客户端能力在初始化握手(initialization handshake)期间向服务端广播。按项目说明,服务端默认广告logging能力,而客户端默认广告roots(含listChanged: true)。更多能力会在以下场景中自动添加:
- 通过
AddTool等方式添加服务端特性时; - 在
ServerOptions中设置 handler 时(例如设置CompletionHandler会添加completions能力); - 或者显式配置。
客户端侧的能力结构ClientCapabilities定义在 mcp/protocol.go,包含Experimental(实验性能力)、Extensions(扩展能力)、Roots/RootsV2(roots 支持)、Sampling(采样支持)与Elicitation(用户输入支持)等字段。
能力推断(Capability inference)
当在ClientOptions上设置 handler 时(例如CreateMessageHandler或ElicitationHandler),如果对应能力尚未存在,SDK 会自动添加该能力,并使用默认配置。Client.capabilities()的完整推断逻辑见 mcp/client.go:
- 若
opts.Capabilities为 nil,SDK 默认能力为{"roots": {"listChanged": true}}(历史默认值,v1.0.0 起不可更改); - 设置了
CreateMessageHandler/CreateMessageWithToolsHandler时,若Sampling为空则自动补&SamplingCapabilities{};若设置的是CreateMessageWithToolsHandler,还会额外补Sampling.Tools; - 设置了
ElicitationHandler时,若Elicitation为空则自动补&ElicitationCapabilities{},且对>= 2025-11-25的协议版本会补Form子能力。
对 elicitation 而言:如果设置了 handler 但未指定Capabilities.Elicitation,客户端默认只会声明表单(form)elicitation。要启用URL 模式或同时启用两种模式,必须显式配置Capabilities.Elicitation。
关于能力推断的更多细节,可参见ClientCapabilities的文档。
显式配置能力(Explicit capabilities)
要显式声明能力,或覆盖上述默认推断的能力,可以设置ClientOptions.Capabilities。它设定的是初始客户端能力,发生在任何基于 handler 的能力添加之前;如果某个能力已经存在于Capabilities中,之后再添加 handler 也不会改变它的配置。
显式配置可以让你实现三类控制:
- 禁用默认能力:传入空的
&ClientCapabilities{}可禁用所有默认能力(包括 roots)。从源码看,这也是关闭roots能力的途径:由于历史问题(issue #607),Capabilities.Roots字段会被忽略,需通过Capabilities.RootsV2来配置或整体禁用 roots 能力(见 mcp/client.go 与 mcp/protocol.go)。 - 禁用 listChanged 通知:在某个能力上设置
ListChanged: false,可阻止客户端在添加/移除 roots 时发送 list-changed 通知(对应 mcp/client.go 中shouldSendListChangedNotification的判断逻辑)。 - 配置 elicitation 模式:指定客户端支持哪些 elicitation 模式(form、URL)。
示例:配置同时支持 form 与 URL 两种 elicitation 模式,并禁用 roots 能力:
// Configure elicitation modes and disable roots. client := mcp.NewClient(impl, &mcp.ClientOptions{ Capabilities: &mcp.ClientCapabilities{ Elicitation: &mcp.ElicitationCapabilities{ Form: &mcp.FormElicitationCapabilities{}, URL: &mcp.URLElicitationCapabilities{}, }, }, ElicitationHandler: handler, })补充说明:ElicitationCapabilities中若Form与URL都未设置,则默认假定为Form模式(见 mcp/protocol.go)。
扩展能力(Extensions)
SEP-2133 在ClientCapabilities与ServerCapabilities中增加了extensionsmap,用于在线上声明核心协议之外的可选能力。键的命名空间格式为"{vendor-prefix}/{extension-name}",值是每个扩展各自的设置对象。
在 SDK 中,推荐使用ClientCapabilities.AddExtension(name, settings)方法添加扩展:当settings为 nil 时,该方法会自动规范化为空 map(规范要求是对象而非 null),以保证 JSON 序列化合法(见 mcp/protocol.go)。赋值后不应再修改该 map 或其值(capabilities()会对用户提供的能力做深拷贝以避免意外修改,见 mcp/client.go)。
总结
围绕 MCP 客户端,本文覆盖了从协议特性到 SDK 实现的完整链路:
- Roots / Sampling / Elicitation三类客户端能力各自有清晰的双侧 API:客户端通过
Client.AddRoots/RemoveRoots、ClientOptions.CreateMessageHandler、ClientOptions.ElicitationHandler提供能力;服务端通过ServerSession.ListRoots、ServerSession.CreateMessage、ServerSession.Elicit消费能力。三者均已在2026-07-28协议版本被弃用(SEP-2577),新代码应优先改用工具参数、资源 URI 或直接调用 LLM 提供商 API。 - Multi Round-Trip Requests(SEP-2322)重构了这三类请求的传输方式:嵌入
tools/call/prompts/get/resources/read的回复并由客户端中间件自动完成与重试,同时通过协议版本协商对旧版对端保持透明兼容。 - Capabilities机制决定了客户端在握手时对外声明什么:handler 驱动的能力推断 + 显式
ClientOptions.Capabilities覆盖 +extensions扩展声明,三者结合可精确控制客户端的对外画像。
所有示例均为仓库中可运行测试(go test ./mcp -run Example_roots等),可作为理解与二次开发的基础模板;完整的客户端 API 细节可继续阅读 mcp/client.go 与 mcp/protocol.go,协议层面的进一步说明见 docs/protocol.md。
【免费下载链接】go-sdkThe official Go SDK for Model Context Protocol servers and clients. Maintained in collaboration with Google.项目地址: https://gitcode.com/GitHub_Trending/gosdk23/go-sdk
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考