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 中实现 "交互式信息补充" 的关键,让服务端能动态引导客户端完善请求,避免因信息不全导致的请求失败,提升交互的流畅性

相关推荐
西岸行者8 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意8 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码8 天前
嵌入式学习路线
学习
毛小茛8 天前
计算机系统概论——校验码
学习
babe小鑫8 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms8 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下8 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。8 天前
2026.2.25监控学习
学习
im_AMBER8 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J8 天前
从“Hello World“ 开始 C++
c语言·c++·学习