MCP SDK 示例一

交互

Say Hi

服务端提供一个 SayHi 的 Tool,然后客户端请求该 Tool,得到 Tool 结果

go 复制代码
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/modelcontextprotocol/go-sdk/mcp"
)

type SayHiParams struct {
	Name string `json:"name"`
}

func SayHi(ctx context.Context, req *mcp.CallToolRequest, args SayHiParams) (*mcp.CallToolResult, any, error) {
	return &mcp.CallToolResult{
		Content: []mcp.Content{
			&mcp.TextContent{Text: "Hi " + args.Name},
		},
	}, nil, nil
}

func main() {
	ctx := context.Background()
	clientTransport, serverTransport := mcp.NewInMemoryTransports()

	server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v0.0.1"}, nil)
	mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)

	serverSession, err := server.Connect(ctx, serverTransport, nil)
	if err != nil {
		log.Fatal(err)
	}

	client := mcp.NewClient(&mcp.Implementation{Name: "client"}, nil)
	clientSession, err := client.Connect(ctx, clientTransport, nil)
	if err != nil {
		log.Fatal(err)
	}

	res, err := clientSession.CallTool(ctx, &mcp.CallToolParams{
		Name:      "greet",
		Arguments: map[string]any{"name": "user"},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res.Content[0].(*mcp.TextContent).Text)

	clientSession.Close()
	serverSession.Wait()

	// Output: Hi user
}

sdk中用了InMemoryTransports,它实际是一个 Pipe

Pipe 创建一个同步的(synchronous)、内存中的全双工(full duplex)网络连接;连接的两端均实现了 [Conn] 接口

一端的读取操作会与另一端的写入操作相匹配,数据直接在两端之间复制;不存在内部缓冲

go 复制代码
// NewInMemoryTransports returns two [InMemoryTransports] that connect to each
// other.
func NewInMemoryTransports() (*InMemoryTransport, *InMemoryTransport) {
	c1, c2 := net.Pipe()
	return &InMemoryTransport{c1}, &InMemoryTransport{c2}
}

客户端 list 服务端提供的 tool

客户端代码

sh 复制代码
go build -o listfeatures main.go
./listfeatures go run github.com/modelcontextprotocol/go-sdk/examples/server/hello
go 复制代码
package main

import (
	"context"
	"flag"
	"fmt"
	"iter"
	"log"
	"os"
	"os/exec"

	"github.com/modelcontextprotocol/go-sdk/mcp"
)

func main() {
	flag.Parse()
	args := flag.Args()
	if len(args) == 0 {
		fmt.Fprintf(os.Stderr, "Usage: listfeatures <command> [<args>]")
		fmt.Fprintf(os.Stderr, "List all features for a stdio MCP server")
		fmt.Fprintln(os.Stderr)
		fmt.Fprintf(os.Stderr, "Example: listfeatures npx @modelcontextprotocol/server-everything")
		os.Exit(2)
	}

	ctx := context.Background()
	cmd := exec.Command(args[0], args[1:]...)
	client := mcp.NewClient(&mcp.Implementation{Name: "mcp-client", Version: "v1.0.0"}, nil)
	cs, err := client.Connect(ctx, &mcp.CommandTransport{Command: cmd}, nil)
	if err != nil {
		log.Fatal(err)
	}
	defer cs.Close()

	printSection("tools", cs.Tools(ctx, nil), func(t *mcp.Tool) string { return t.Name })
	printSection("resources", cs.Resources(ctx, nil), func(r *mcp.Resource) string { return r.Name })
	printSection("resource templates", cs.ResourceTemplates(ctx, nil), func(r *mcp.ResourceTemplate) string { return r.Name })
	printSection("prompts", cs.Prompts(ctx, nil), func(p *mcp.Prompt) string { return p.Name })
}

func printSection[T any](name string, features iter.Seq2[T, error], featName func(T) string) {
	fmt.Printf("%s:\n", name)
	for feat, err := range features {
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("\t%s\n", featName(feat))
	}
	fmt.Println()
}

对应的服务端代码

go 复制代码
// The hello server contains a single tool that says hi to the user.
//
// It runs over the stdio transport.

package main

import (
	"context"
	"log"

	"github.com/modelcontextprotocol/go-sdk/mcp"
)

func main() {
	// Create a server with a single tool that says "Hi".
	server := mcp.NewServer(&mcp.Implementation{Name: "greeter"}, nil)

	// Using the generic AddTool automatically populates the input and output
	// schema of the tool.
	//
	// The schema considers 'json' and 'jsonschema' struct tags to get argument
	// names and descriptions.
	// schema 考虑 json 和 jsonschema tag,来获取参数 name 和其 description
	type args struct {
		Name string `json:"name" jsonschema:"the person to greet"`
	}
	mcp.AddTool(server, &mcp.Tool{
		Name:        "greet",
		Description: "say hi",
	}, func(ctx context.Context, req *mcp.CallToolRequest, args args) (*mcp.CallToolResult, any, error) {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				&mcp.TextContent{Text: "Hi " + args.Name},
			},
		}, nil, nil
	})

	// server.Run runs the server on the given transport.
	//
	// In this case, the server communicates over stdin/stdout.
	if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
		log.Printf("Server failed: %v", err)
	}
}

注意,这里用了范型 AddTool,自动推导 In、Out

go 复制代码
// AddTool adds a tool and typed tool handler to the server.
//
// If the tool's input schema is nil, it is set to the schema inferred from the
// In type parameter, using [jsonschema.For]. The In type parameter must be a
// map or a struct, so that its inferred JSON Schema has type "object".
//
// For tools that don't return structured output, Out should be 'any'.
// Otherwise, if the tool's output schema is nil the output schema is set to
// the schema inferred from Out, which must be a map or a struct.
//
// It is a convenience for s.AddTool(ToolFor(t, h)).
func AddTool[In, Out any](s *Server, t *Tool, h ToolHandlerFor[In, Out]) {
	s.AddTool(ToolFor(t, h))
}

// A ToolHandlerFor handles a call to tools/call with typed arguments and results.
type ToolHandlerFor[In, Out any] func(context.Context, *CallToolRequest, In) (*CallToolResult, Out, error)

引导

服务端主动向客户端请求补充信息,并通过标准化的交互完成数据收集

go 复制代码
import (
	"context"
	"fmt"
	"log"

	"github.com/google/jsonschema-go/jsonschema"
	"github.com/modelcontextprotocol/go-sdk/mcp"
)

func main() {
	ctx := context.Background()
	clientTransport, serverTransport := mcp.NewInMemoryTransports()

	// Create server
	server := mcp.NewServer(&mcp.Implementation{Name: "config-server", Version: "v1.0.0"}, nil)

	serverSession, err := server.Connect(ctx, serverTransport, nil)
	if err != nil {
		log.Fatal(err)
	}

	// Create client with elicitation handler
	// Note: Never use elicitation for sensitive data like API keys or passwords
	client := mcp.NewClient(&mcp.Implementation{Name: "config-client", Version: "v1.0.0"}, &mcp.ClientOptions{
		// 这是客户端接收并响应服务端信息请求的核心回调函数
		ElicitationHandler: func(ctx context.Context, request *mcp.ElicitRequest) (*mcp.ElicitResult, error) {
			fmt.Printf("Server requests: %s\n", request.Params.Message)

			// In a real application, this would prompt the user for input
			// Here we simulate user providing configuration data
			return &mcp.ElicitResult{
				Action: "accept",
				Content: map[string]any{
					"serverEndpoint": "https://api.example.com",
					"maxRetries":     float64(3),
					"enableLogs":     true,
				},
			}, nil
		},
	})

	_, err = client.Connect(ctx, clientTransport, nil)
	if err != nil {
		log.Fatal(err)
	}

	// 服务端首先定义需要客户端提供的配置数据结构(通过 JSON Schema 描述)
	// 这个 Schema 用于告诉客户端:"需要提供哪些字段,格式是什么,是否必填"
	// Server requests user configuration via elicitation
	configSchema := &jsonschema.Schema{
		Type: "object",
		Properties: map[string]*jsonschema.Schema{
			"serverEndpoint": {Type: "string", Description: "Server endpoint URL"},
			"maxRetries":     {Type: "number", Minimum: ptr(1.0), Maximum: ptr(10.0)},
			"enableLogs":     {Type: "boolean", Description: "Enable debug logging"},
		},
		Required: []string{"serverEndpoint"},
	}

	// 服务端通过 serverSession.Elicit() 方法向客户端发送信息请求
	// MCP 协议会将该请求通过内存传输层发送给客户端
	result, err := serverSession.Elicit(ctx, &mcp.ElicitParams{
		Message:         "Please provide your configuration settings", // 人类可读的请求说明
		RequestedSchema: configSchema, // 上述定义的Schema,约束客户端返回的数据格式
	})
	if err != nil {
		log.Fatal(err)
	}
	
	// 客户端的 ElicitationHandler 回调函数接收到服务端的请求,参数 *mcp.ElicitRequest 包含服务端的 Message 和 RequestedSchema
	// 打印引导信息,并鼓励用户交互输入(本示例中,ElicitationHandler 中直接写死了交互结果,并向服务端返回消息)

	// 从消息中取出正确的参数
	if result.Action == "accept" {
		fmt.Printf("Configuration received: Endpoint: %v, Max Retries: %.0f, Logs: %v\n",
			result.Content["serverEndpoint"],
			result.Content["maxRetries"],
			result.Content["enableLogs"])
	}

	// Output:
	// Server requests: Please provide your configuration settings
	// Configuration received: Endpoint: https://api.example.com, Max Retries: 3, Logs: true
}

// ptr is a helper function to create pointers for schema constraints
func ptr[T any](v T) *T {
	return &v
}

仅服务端 - CompletionHandler的配置

go 复制代码
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/modelcontextprotocol/go-sdk/mcp"
)

// This example demonstrates the minimal code to declare and assign
// a CompletionHandler to an MCP Server's options.
func main() {
	// Define your custom CompletionHandler logic.
	myCompletionHandler := func(_ context.Context, req *mcp.CompleteRequest) (*mcp.CompleteResult, error) {
		// In a real application, you'd implement actual completion logic here.
		// For this example, we return a fixed set of suggestions.
		var suggestions []string
		switch req.Params.Ref.Type {
		case "ref/prompt":
			suggestions = []string{"suggestion1", "suggestion2", "suggestion3"}
		case "ref/resource":
			suggestions = []string{"suggestion4", "suggestion5", "suggestion6"}
		default:
			return nil, fmt.Errorf("unrecognized content type %s", req.Params.Ref.Type)
		}

		return &mcp.CompleteResult{
			Completion: mcp.CompletionResultDetails{
				HasMore: false,
				Total:   len(suggestions),
				Values:  suggestions,
			},
		}, nil
	}

	// Create the MCP Server instance and assign the handler.
	// No server running, just showing the configuration.
	_ = mcp.NewServer(&mcp.Implementation{Name: "server"}, &mcp.ServerOptions{
		CompletionHandler: myCompletionHandler,
	})

	log.Println("MCP Server instance created with a CompletionHandler assigned (but not running).")
	log.Println("This example demonstrates configuration, not live interaction.")
}

附录

MCP 提供的 ClientOptions

go 复制代码
type ClientOptions struct {
	// Handler for sampling.
	// Called when a server calls CreateMessage.
	CreateMessageHandler func(context.Context, *CreateMessageRequest) (*CreateMessageResult, error)
	
	// Handler for elicitation.
	// Called when a server requests user input via Elicit.
	ElicitationHandler func(context.Context, *ElicitRequest) (*ElicitResult, error)
	
	// Handlers for notifications from the server.
	ToolListChangedHandler      func(context.Context, *ToolListChangedRequest)
	PromptListChangedHandler    func(context.Context, *PromptListChangedRequest)
	ResourceListChangedHandler  func(context.Context, *ResourceListChangedRequest)
	ResourceUpdatedHandler      func(context.Context, *ResourceUpdatedNotificationRequest)
	LoggingMessageHandler       func(context.Context, *LoggingMessageRequest)
	ProgressNotificationHandler func(context.Context, *ProgressNotificationClientRequest)
	
	// If non-zero, 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.
	KeepAlive time.Duration
}

MCP 之 ElicitationHandler 信息诱导

ElicitationHandler(通常翻译为 "启发 / 诱导处理器")是用于处理 "客户端需要补充信息" 场景的核心组件

主要解决服务端在处理请求时发现信息不完整的问题。

当服务端在处理客户端请求(如工具调用、内容补全)时,发现必要信息缺失(例如调用 "天气查询" 工具但缺少 "城市名称" 参数),ElicitationHandler 会触发一个 "信息诱导流程"

信息诱导流程

  • 服务端通过该处理器生成 "补充信息的请求"(如 "请提供需要查询天气的城市名称")
  • 客户端接收后,收集所需信息并重新发起请求
  • 服务端利用补充的信息完成后续处理

典型使用场景

工具调用参数不全

客户端调用需要多参数的工具(如 "转账" 工具需要 "金额""收款人" 参数),但只提供了部分参数。此时 ElicitationHandler 会自动生成提示,要求客户端补充缺失的参数。

补全请求上下文不足

客户端请求生成 "邮件回复",但未提供原始邮件内容。服务端通过 ElicitationHandler 询问:"请提供需要回复的原始邮件内容,以便生成合适的回复。"

权限或格式校验不通过

若客户端提供的参数格式错误(如 "日期" 参数应为 YYYY-MM-DD 却传入 2024/8/31),处理器会返回格式要求,引导客户端修正。

与其他处理器的区别

和 CompletionHandler 不同:CompletionHandler 专注于生成补全内容,而 ElicitationHandler 专注于 "发现信息缺失并引导补充"

和 CallToolHandler 不同:工具处理器执行具体功能,而 ElicitationHandler 是在功能执行前的 "信息校验与补充流程"

工作流程示例

  • 客户端调用 greet 工具,但未提供 name 参数
  • 服务端检测到参数缺失,触发 ElicitationHandler
  • 处理器生成诱导信息:{"elicit": {"field": "name", "message": "请提供您的姓名以便问候"}}
  • 客户端接收后,补充 name: "Bob" 并重新调用
  • 服务端完成处理,返回 Hi Bob

总结

ElicitationHandler 是 MCP 中实现 "交互式信息补充" 的关键,让服务端能动态引导客户端完善请求,避免因信息不全导致的请求失败,提升交互的流畅性

相关推荐
lxl130710 小时前
学习数据结构(15)插入排序+选择排序(上)
数据结构·学习·排序算法
LiuYaoheng11 小时前
【Android】Notification 的基本使用
android·java·笔记·学习
~kiss~12 小时前
MCP SDK 学习二
学习
ShineWinsu13 小时前
对于牛客网—语言学习篇—编程初学者入门训练—复合类型:BC136 KiKi判断上三角矩阵及BC139 矩阵交换题目的解析
c语言·c++·学习·算法·矩阵·数组·牛客网
limengshi13839214 小时前
人工智能学习:Python相关面试题
jvm·python·学习
与己斗其乐无穷16 小时前
C++学习记录(5)string的介绍和使用
学习
weixin_5500831521 小时前
大模型入门学习微调实战:基于PyTorch和Hugging Face电影评价情感分析模型微调全流程(附完整代码)手把手教你做
人工智能·pytorch·学习
2006yu1 天前
从零开始学习单片机18
单片机·嵌入式硬件·学习
宁清明1 天前
【小宁学习日记5 PCB】电路定理
学习·pcb工艺