关于 MCP Go 🚀
MCP Go 是 Model Context Protocol 的 Go 语言实现,用于连接 LLM 应用与外部数据源和工具。项目面向 Go 开发者,目标是以较少样板代码构建 MCP 服务,目前仍在积极开发中。
主要功能
- 01高层接口,减少样板代码
- 02支持 Resources、Tools 与 Prompts
- 03支持 stdio、SSE 与 streamable-HTTP 传输
- 04支持异步任务工具与并发任务上限
- 05提供 OAuth 资源元数据与 CORS 配置
适用场景
- 01用 Go 构建暴露工具与数据的 MCP 服务
- 02为浏览器端 MCP 客户端配置 CORS 与 HTTP 传输
- 03构建需要长时间运行的异步工具
MCP 相关
MCP Go 是用于构建 MCP 服务的 Go SDK,可将 Resources、Tools 与 Prompts 暴露给 MCP 客户端。README 注明协议实现仍在完善中。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
package main
import (
"context"
"fmt"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
// Create a new MCP server
s := server.NewMCPServer(
"Demo 🚀",
"1.0.0",
server.WithToolCapabilities(false),
)
// Add tool
tool := mcp.NewTool("hello_world",
mcp.WithDescription("Say hello to someone"),
mcp.WithString("name",
mcp.Required(),
mcp.Description("Name of the person to greet"),
),
)
// Add tool handler
s.AddTool(tool, helloHandler)
// Start the stdio server
if err := server.ServeStdio(s); err != nil {
fmt.Printf("Server error: %v\n", err)
}
}
func helloHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
name, err := request.RequireString("name")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
return mcp.NewToolResultText(fmt.Sprintf("Hello, %s!", name)), nil
}
就是这么简单!
MCP Go 负责处理所有复杂的协议细节和服务管理,让你专注于构建出色的工具。它致力于提供高级、易用的接口。
主要功能:
- 快速:高级接口意味着代码更少、开发更快
- 简单:只需极少的样板代码即可构建 MCP 服务
- 完整*:MCP Go 致力于完整实现核心 MCP 规范
(*强调“致力于”)
🚨 🚧 🏗️ MCP Go 正在积极开发中,MCP 规范本身也在不断演进。核心功能已可使用,但部分高级功能仍在开发中。
目录
安装
go get github.com/mark3labs/mcp-go
快速入门
让我们创建一个简单的 MCP 服务,公开一个计算器工具和一些数据:
package main
import (
"context"
"fmt"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
// Create a new MCP server
s := server.NewMCPServer(
"Calculator Demo",
"1.0.0",
server.WithToolCapabilities(false),
server.WithRecovery(),
)
// Add a calculator tool
calculatorTool := mcp.NewTool("calculate",
mcp.WithDescription("Perform basic arithmetic operations"),
mcp.WithString("operation",
mcp.Required(),
mcp.Description("The operation to perform (add, subtract, multiply, divide)"),
mcp.Enum("add", "subtract", "multiply", "divide"),
),
mcp.WithNumber("x",
mcp.Required(),
mcp.Description("First number"),
),
mcp.WithNumber("y",
mcp.Required(),
mcp.Description("Second number"),
),
)
// Add the calculator handler
s.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// Using helper functions for type-safe argument access
op, err := request.RequireString("operation")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
x, err := request.RequireFloat("x")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
y, err := request.RequireFloat("y")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
var result float64
switch op {
case "add":
result = x + y
case "subtract":
result = x - y
case "multiply":
result = x * y
case "divide":
if y == 0 {
return mcp.NewToolResultError("cannot divide by zero"), nil
}
result = x / y
}
return mcp.NewToolResultText(fmt.Sprintf("%.2f", result)), nil
})
// Start the server
if err := server.ServeStdio(s); err != nil {
fmt.Printf("Server error: %v\n", err)
}
}
什么是 MCP?
Model Context Protocol(MCP) 让你能够构建以安全、标准化方式向大模型应用公开数据和功能的服务。可以把它看作 Web API,但它专为大模型交互而设计。
MCP 服务可以:
- 通过资源公开数据(可以把它们想象成 GET 端点;它们用于将信息加载到大模型的上下文中)
- 通过工具提供功能(可以把它们想象成 POST 端点;它们用于执行代码或以其他方式产生副作用)
- 通过提示词定义交互模式(用于大模型交互的可复用模板)
- 还有更多!
mcp-go 实现了版本 2025-11-25 的 Model Context Protocol 规范,并向后兼容版本 2025-06-18、2025-03-26 和 2024-11-05。
核心概念
服务端
显示服务端示例
服务端是你使用 MCP 协议的核心接口。它负责连接管理、协议合规和消息路由:
// Create a basic server
s := server.NewMCPServer(
"My Server", // Server name
"1.0.0", // Version
)
// Start the server using stdio
if err := server.ServeStdio(s); err != nil {
log.Fatalf("Server error: %v", err)
}
显示资源示例
资源是向大模型公开数据的方式。资源可以是任何内容,例如文件、API 响应、数据库查询、系统信息等。资源可以是:- 静态(固定 URI)
- 动态(使用 URI 模板)
下面是一个简单的静态资源示例:
// Static resource example - exposing a README file
resource := mcp.NewResource(
"docs://readme",
"Project README",
mcp.WithResourceDescription("The project's README file"),
mcp.WithMIMEType("text/markdown"),
)
// Add resource with its handler
s.AddResource(resource, func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
content, err := os.ReadFile("README.md")
if err != nil {
return nil, err
}
return []mcp.ResourceContents{
mcp.TextResourceContents{
URI: "docs://readme",
MIMEType: "text/markdown",
Text: string(content),
},
}, nil
})
下面是一个使用模板的动态资源示例:
// Dynamic resource example - user profiles by ID
template := mcp.NewResourceTemplate(
"users://{id}/profile",
"User Profile",
mcp.WithTemplateDescription("Returns user profile information"),
mcp.WithTemplateMIMEType("application/json"),
)
// Add template with its handler
s.AddResourceTemplate(template, func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
// Extract ID from the URI using regex matching
// The server automatically matches URIs to templates
userID := extractIDFromURI(request.Params.URI)
profile, err := getUserProfile(userID) // Your DB/API call here
if err != nil {
return nil, err
}
return []mcp.ResourceContents{
mcp.TextResourceContents{
URI: request.Params.URI,
MIMEType: "application/json",
Text: profile,
},
}, nil
})
这些示例虽然简单,但展示了核心概念。资源可以复杂得多,例如提供多种内容、与数据库或外部 API 集成等。
工具
显示工具示例
工具允许大模型通过你的服务执行操作。与资源不同,工具预期会执行计算并产生副作用。它们类似于 REST API 中的 POST 端点。
任务增强型工具
任务增强型工具会异步执行,并通过轮询返回结果。这对于耗时较长、否则会阻塞或超时的操作很有用。任务工具支持三种模式:
- TaskSupportForbidden(默认):不能将该工具作为任务调用
- TaskSupportOptional:可以将该工具作为任务调用,也可以同步调用
- TaskSupportRequired:必须将该工具作为任务调用
// Example: A tool that requires task execution
processBatchTool := mcp.NewTool("process_batch",
mcp.WithDescription("Process a batch of items asynchronously"),
mcp.WithTaskSupport(mcp.TaskSupportRequired),
mcp.WithArray("items",
mcp.Description("Array of items to process"),
mcp.WithStringItems(),
mcp.Required(),
),
)
// Task tool handler returns CreateTaskResult instead of CallToolResult
s.AddTaskTool(processBatchTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CreateTaskResult, error) {
items := request.GetStringSlice("items", []string{})
// Long-running work here
for i, item := range items {
select {
case <-ctx.Done():
// Task was cancelled
return nil, ctx.Err()
default:
// Process item...
processItem(item)
}
}
// Return result - task ID and metadata are managed by the server
return &mcp.CreateTaskResult{
Task: mcp.Task{
// Task fields (ID, status, etc.) are populated by the server
},
}, nil
})
// Enable task capabilities when creating the server
s := server.NewMCPServer(
"Task Server",
"1.0.0",
server.WithTaskCapabilities(
true, // listTasks: allows clients to list all tasks
true, // cancel: allows clients to cancel running tasks
true, // toolCallTasks: enables task augmentation for tools
),
server.WithMaxConcurrentTasks(10), // Optional: limit concurrent running tasks
)
任务执行流程:
- 客户端在调用工具时传入任务参数
- 服务立即返回任务 ID
- 工具在后台异步执行
- 客户端轮询
tasks/result以获取结果 - 完成后,服务发送任务状态通知
对于可选任务工具,可以同步调用同一工具(不带任务参数),也可以异步调用(带任务参数):
// Tool with optional task support
analyzeTool := mcp.NewTool("analyze_data",
mcp.WithDescription("Analyze data - can run sync or async"),
mcp.WithTaskSupport(mcp.TaskSupportOptional),
mcp.WithString("data", mcp.Required()),
)
// Use AddTaskTool for hybrid tools that support both modes
s.AddTaskTool(analyzeTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CreateTaskResult, error) {
// This handler runs when called as a task
data := request.GetString("data", "")
result := analyzeData(data)
return &mcp.CreateTaskResult{
Task: mcp.Task{},
}, nil
})
// The server automatically handles both sync and async invocations
// When called without task param: executes handler and returns immediately
// When called with task param: executes handler asynchronously
限制并发任务数
为防止资源耗尽,你可以限制同时运行的任务数:
s := server.NewMCPServer(
"Task Server",
"1.0.0",
server.WithTaskCapabilities(true, true, true),
server.WithMaxConcurrentTasks(10), // Allow up to 10 concurrent running tasks
)
达到上限后,新的任务创建请求将失败并返回错误。已完成、失败或取消的任务不计入上限,只有处于“working”状态的任务才计入。如果未指定 WithMaxConcurrentTasks 或将其设为 0,则并发任务数不受限制。
对于执行后立即返回结果的传统同步工具:
简单计算示例:
calculatorTool := mcp.NewTool("calculate",
mcp.WithDescription("Perform basic arithmetic calculations"),
mcp.WithString("operation",
mcp.Required(),
mcp.Description("The arithmetic operation to perform"),
mcp.Enum("add", "subtract", "multiply", "divide"),
),
mcp.WithNumber("x",
mcp.Required(),
mcp.Description("First number"),
),
mcp.WithNumber("y",
mcp.Required(),
mcp.Description("Second number"),
),
)
s.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
args := request.GetArguments()
op := args["operation"].(string)
x := args["x"].(float64)
y := args["y"].(float64)
var result float64
switch op {
case "add":
result = x + y
case "subtract":
result = x - y
case "multiply":
result = x * y
case "divide":
if y == 0 {
return mcp.NewToolResultError("cannot divide by zero"), nil
}
result = x / y
}
return mcp.FormatNumberResult(result), nil
})
HTTP 请求示例:
httpTool := mcp.NewTool("http_request",
mcp.WithDescription("Make HTTP requests to external APIs"),
mcp.WithString("method",
mcp.Required(),
mcp.Description("HTTP method to use"),
mcp.Enum("GET", "POST", "PUT", "DELETE"),
),
mcp.WithString("url",
mcp.Required(),
mcp.Description("URL to send the request to"),
mcp.Pattern("^https?://.*"),
),
mcp.WithString("body",
mcp.Description("Request body (for POST/PUT)"),
),
)
s.AddTool(httpTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
args := request.GetArguments()
method := args["method"].(string)
url := args["url"].(string)
body := ""
if b, ok := args["body"].(string); ok {
body = b
}
// Create and send request
var req *http.Request
var err error
if body != "" {
req, err = http.NewRequest(method, url, strings.NewReader(body))
} else {
req, err = http.NewRequest(method, url, nil)
}
if err != nil {
return mcp.NewToolResultErrorFromErr("unable to create request", err), nil
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return mcp.NewToolResultErrorFromErr("unable to execute request", err), nil
}
defer resp.Body.Close()
// Return response
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return mcp.NewToolResultErrorFromErr("unable to read request response", err), nil
}
return mcp.NewToolResultText(fmt.Sprintf("Status: %d\nBody: %s", resp.StatusCode, string(respBody))), nil
})
工具可用于执行任何类型的计算或产生任何副作用:
- 数据库查询
- 文件操作
- 外部 API 调用
- 计算
- 系统操作
每个工具都应:
- 有清晰的描述
- 验证输入
- 妥善处理错误
- 返回结构化响应
- 使用合适的结果类型
提示词
显示提示词示例
提示词是可重复使用的模板,可帮助大模型有效地与你的服务交互。它们就像编码在服务中的“最佳实践”。以下是一些示例:
// Simple greeting prompt
s.AddPrompt(mcp.NewPrompt("greeting",
mcp.WithPromptDescription("A friendly greeting prompt"),
mcp.WithArgument("name",
mcp.ArgumentDescription("Name of the person to greet"),
),
), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
name := request.Params.Arguments["name"]
if name == "" {
name = "friend"
}
return mcp.NewGetPromptResult(
"A friendly greeting",
[]mcp.PromptMessage{
mcp.NewPromptMessage(
mcp.RoleAssistant,
mcp.NewTextContent(fmt.Sprintf("Hello, %s! How can I help you today?", name)),
),
},
), nil
})
// Code review prompt with embedded resource
s.AddPrompt(mcp.NewPrompt("code_review",
mcp.WithPromptDescription("Code review assistance"),
mcp.WithArgument("pr_number",
mcp.ArgumentDescription("Pull request number to review"),
mcp.RequiredArgument(),
),
), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
prNumber := request.Params.Arguments["pr_number"]
if prNumber == "" {
return nil, fmt.Errorf("pr_number is required")
}
return mcp.NewGetPromptResult(
"Code review assistance",
[]mcp.PromptMessage{
mcp.NewPromptMessage(
mcp.RoleUser,
mcp.NewTextContent("Review the changes and provide constructive feedback."),
),
mcp.NewPromptMessage(
mcp.RoleAssistant,
mcp.NewEmbeddedResource(mcp.ResourceContents{
URI: fmt.Sprintf("git://pulls/%s/diff", prNumber),
MIMEType: "text/x-diff",
}),
),
},
), nil
})
// Database query builder prompt
s.AddPrompt(mcp.NewPrompt("query_builder",
mcp.WithPromptDescription("SQL query builder assistance"),
mcp.WithArgument("table",
mcp.ArgumentDescription("Name of the table to query"),
mcp.RequiredArgument(),
),
), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
tableName := request.Params.Arguments["table"]
if tableName == "" {
return nil, fmt.Errorf("table name is required")
}
return mcp.NewGetPromptResult(
"SQL query builder assistance",
[]mcp.PromptMessage{
mcp.NewPromptMessage(
mcp.RoleUser,
mcp.NewTextContent("Help construct efficient and safe queries for the provided schema."),
),
mcp.NewPromptMessage(
mcp.RoleUser,
mcp.NewEmbeddedResource(mcp.ResourceContents{
URI: fmt.Sprintf("db://schema/%s", tableName),
MIMEType: "application/json",
}),
),
},
), nil
})
提示词可以包含:
- 系统指令
- 必需参数
- 嵌入的资源
- 多条消息
- 不同的内容类型(文本、图像等)
- 自定义 URI 方案
示例
示例请参阅 examples/ 目录。
重要示例包括:
examples/task_tool/- 展示使用 TaskSupportRequired 和 TaskSupportOptional 模式的任务增强型工具examples/structured_input_and_output/- 展示如何使用基于结构体的输入/输出架构和类型安全的工具处理器examples/typed_tools/- 展示带有强类型参数的类型安全工具处理器examples/custom_context/- 展示如何在工具处理器中使用自定义上下文- examples 目录中还有涵盖资源、提示词等内容的其他示例
扩展内容
传输方式
MCP-Go 支持 stdio、SSE 和 streamable-HTTP 传输层。对于 SSE 传输,你可以使用 SetConnectionLostHandler() 检测并处理断开连接,以实现重连逻辑。
在非 net/http 框架中嵌入 StreamableHTTP
StreamableHTTPServer 是一个 http.Handler,因此可以挂载到任何使用 net/http 的路由器中。若要将其嵌入不使用 net/http 的框架(例如 fasthttp 或 fiber),且不通过适配器缓冲响应,请使用与传输无关的 Handle 入口:
func (s *StreamableHTTPServer) Handle(w HTTPResponseWriter, r *HTTPRequest)
HTTPRequest 是一个普通结构体(Method、URL、Header、Body、Context),而 HTTPResponseWriter 是一个小型接口(Header、WriteHeader、Write、Flush、CanStream)。底层传输无法流式传输的实现 MUST 从 CanStream 返回 false;此时服务端会以 405 Method Not Allowed 拒绝 GET(SSE 监听),并将 POST 响应保持为缓冲的 application/json,而不是升级为 text/event-stream。
完整的 fasthttp/fiber 适配器示例,请参阅 HTTP 传输文档。ServeHTTP 保持不变,仍是常规的 net/http 入口。
OAuth 受保护资源元数据
需要 OAuth 的服务端可通过 MCP 授权规范引用的 RFC 9728 /.well-known/oauth-protected-resource 端点公布其授权要求。使用 server.WithProtectedResourceMetadata(或 server.WithSSEProtectedResourceMetadata)自动挂载该端点,或者使用 server.NewProtectedResourceMetadataHandler 将其接入自定义路由器。示例请参阅 HTTP 传输文档。
httpServer := server.NewStreamableHTTPServer(mcpServer,
server.WithProtectedResourceMetadata(server.ProtectedResourceMetadataConfig{
Resource: "https://my-mcp-server.com",
AuthorizationServers: []string{"https://auth.example.com"},
ScopesSupported: []string{"mcp:read", "mcp:write"},
}),
)
面向浏览器客户端的 CORS
向基于浏览器的 MCP 客户端开放的服务端,可选择在任一 HTTP 传输上启用跨源资源共享处理。默认情况下禁用 CORS;请通过 server.WithStreamableHTTPCORS 或 server.WithSSECORS 显式配置:
httpServer := server.NewStreamableHTTPServer(mcpServer,
server.WithEndpointPath("/mcp"),
server.WithStreamableHTTPCORS(
server.WithCORSAllowedOrigins("https://my-ai-app.com", "http://localhost:3000"),
server.WithCORSAllowCredentials(),
server.WithCORSMaxAge(300),
),
)
传输会直接响应预检(OPTIONS)请求,并为简单响应添加相应的 Access-Control-Allow-Origin、Access-Control-Allow-Credentials、Access-Control-Expose-Headers 和 Vary 标头。省略相应选项时会使用合理的默认值(方法为 GET, POST, DELETE, OPTIONS;请求标头为 Content-Type, Mcp-Session-Id, Last-Event-ID, Authorization;公开的标头为 Mcp-Session-Id)。将 WithCORSAllowedOrigins("*") 与 WithCORSAllowCredentials() 结合使用时,会回显请求中的 Origin,以保持符合规范。
localhost 服务端的 DNS 重绑定防护
两种 HTTP 传输都会自动保护本地服务端免受 DNS 重绑定攻击:通过环回连接(127.0.0.1、[::1])到达、且 Host 标头不是 localhost 值的请求,会被拒绝并返回 403 Forbidden。该检查会在运行时根据连接的本地地址进行,因此无论服务端监听的是 localhost 还是 0.0.0.0 都适用,并且绝不会影响通过非环回地址到达的请求。
如果同一主机上的反向代理通过 localhost 转发请求,同时保留原始 Host 标头,请将代理配置为把 Host 标头重写为 localhost,或显式选择退出:
httpServer := server.NewStreamableHTTPServer(mcpServer,
// Or server.WithSSEDisableLocalhostProtection(true) on NewSSEServer.
server.WithDisableLocalhostProtection(true),
)
详情请参阅 HTTP 传输文档,其中也说明了与框架无关的 Handle 入口的注意事项。
会话管理
MCP-Go 提供了可靠的会话管理系统,可帮助你:
- 为每个连接的客户端维护独立状态
- 注册并跟踪客户端会话
- 向特定客户端发送通知
- 为每个会话提供自定义工具
显示会话管理示例
#### 基本会话处理// Create a server with session capabilities
s := server.NewMCPServer(
"Session Demo",
"1.0.0",
server.WithToolCapabilities(true),
)
// Implement your own ClientSession
type MySession struct {
id string
notifChannel chan mcp.JSONRPCNotification
isInitialized bool
// Add custom fields for your application
}
// Implement the ClientSession interface
func (s *MySession) SessionID() string {
return s.id
}
func (s *MySession) NotificationChannel() chan<- mcp.JSONRPCNotification {
return s.notifChannel
}
func (s *MySession) Initialize() {
s.isInitialized = true
}
func (s *MySession) Initialized() bool {
return s.isInitialized
}
// Register a session
session := &MySession{
id: "user-123",
notifChannel: make(chan mcp.JSONRPCNotification, 10),
}
if err := s.RegisterSession(context.Background(), session); err != nil {
log.Printf("Failed to register session: %v", err)
}
// Send notification to a specific client
err := s.SendNotificationToSpecificClient(
session.SessionID(),
"notification/update",
map[string]any{"message": "New data available!"},
)
if err != nil {
log.Printf("Failed to send notification: %v", err)
}
// Unregister session when done
s.UnregisterSession(context.Background(), session.SessionID())
每个会话的工具
对于更高级的用例,你可以实现 SessionWithTools 接口,以支持按会话自定义工具:
// Implement SessionWithTools interface for per-session tools
type MyAdvancedSession struct {
MySession // Embed the basic session
sessionTools map[string]server.ServerTool
}
// Implement additional methods for SessionWithTools
func (s *MyAdvancedSession) GetSessionTools() map[string]server.ServerTool {
return s.sessionTools
}
func (s *MyAdvancedSession) SetSessionTools(tools map[string]server.ServerTool) {
s.sessionTools = tools
}
// Create and register a session with tools support
advSession := &MyAdvancedSession{
MySession: MySession{
id: "user-456",
notifChannel: make(chan mcp.JSONRPCNotification, 10),
},
sessionTools: make(map[string]server.ServerTool),
}
if err := s.RegisterSession(context.Background(), advSession); err != nil {
log.Printf("Failed to register session: %v", err)
}
// Add session-specific tools
userSpecificTool := mcp.NewTool(
"user_data",
mcp.WithDescription("Access user-specific data"),
)
// You can use AddSessionTool (similar to AddTool)
err := s.AddSessionTool(
advSession.SessionID(),
userSpecificTool,
func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// This handler is only available to this specific session
return mcp.NewToolResultText("User-specific data for " + advSession.SessionID()), nil
},
)
if err != nil {
log.Printf("Failed to add session tool: %v", err)
}
// Or use AddSessionTools directly with ServerTool
/*
err := s.AddSessionTools(
advSession.SessionID(),
server.ServerTool{
Tool: userSpecificTool,
Handler: func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// This handler is only available to this specific session
return mcp.NewToolResultText("User-specific data for " + advSession.SessionID()), nil
},
},
)
if err != nil {
log.Printf("Failed to add session tool: %v", err)
}
*/
// Delete session-specific tools when no longer needed
err = s.DeleteSessionTools(advSession.SessionID(), "user_data")
if err != nil {
log.Printf("Failed to delete session tool: %v", err)
}
工具筛选
你还可以应用筛选器,控制特定会话可使用哪些工具:
// Add a tool filter that only shows tools with certain prefixes
s := server.NewMCPServer(
"Tool Filtering Demo",
"1.0.0",
server.WithToolCapabilities(true),
server.WithToolFilter(func(ctx context.Context, tools []mcp.Tool) []mcp.Tool {
// Get session from context
session := server.ClientSessionFromContext(ctx)
if session == nil {
return tools // Return all tools if no session
}
// Example: filter tools based on session ID prefix
if strings.HasPrefix(session.SessionID(), "admin-") {
// Admin users get all tools
return tools
} else {
// Regular users only get tools with "public-" prefix
var filteredTools []mcp.Tool
for _, tool := range tools {
if strings.HasPrefix(tool.Name, "public-") {
filteredTools = append(filteredTools, tool)
}
}
return filteredTools
}
}),
)
使用上下文
会话上下文会自动传递给工具和资源处理程序:
s.AddTool(mcp.NewTool("session_aware"), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// Get the current session from context
session := server.ClientSessionFromContext(ctx)
if session == nil {
return mcp.NewToolResultError("No active session"), nil
}
return mcp.NewToolResultText("Hello, session " + session.SessionID()), nil
})
// When using handlers in HTTP/SSE servers, you need to pass the context with the session
httpHandler := func(w http.ResponseWriter, r *http.Request) {
// Get session from somewhere (like a cookie or header)
session := getSessionFromRequest(r)
// Add session to context
ctx := s.WithContext(r.Context(), session)
// Use this context when handling requests
// ...
}
请求钩子
创建一个 Hooks 对象,并从可用回调中选择所需项,即可接入请求生命周期。这样可以对所有功能进行遥测,并观察各种情况,例如统计格式不正确的请求,或在初始化期间记录 Agent 身份。
创建服务时,使用 server.WithHooks 选项将 Hooks 添加到服务中。
工具处理程序中间件
使用 server.WithToolHandlerMiddleware 选项为工具调用处理程序添加中间件。可以在创建服务时注册中间件,它们会应用于每次工具调用。
你可以使用恢复中间件选项,从工具调用中的 panic 中恢复,并通过 server.WithRecovery 选项将其添加到服务中。
提示词处理程序中间件
使用 server.WithPromptHandlerMiddleware 选项为提示词处理程序添加中间件。可以在创建服务时注册中间件,它们会应用于每次 prompts/get 调用。
提示词筛选
使用 server.WithPromptFilter 选项,根据上下文筛选提示词。其工作方式与工具筛选相同,但会应用于 prompts/list 结果。
重新生成服务端代码
服务端钩子和请求处理程序由生成器生成。运行以下命令即可重新生成:
go generate ./...
你需要安装 go,并确保 goimports 工具可用。生成器会自动运行 goimports 来格式化代码并修正导入项。
自动补全
当用户为特定提示词(由名称标识)或资源模板(由 URI 标识)填写参数值时,服务端可以提供上下文相关的建议。
若要启用补全支持,请在创建服务时使用 server.WithCompletions() 选项。
补全提供程序
你可以通过实现相应接口并将其作为选项传递给服务端,为提示词参数和资源模板参数提供补全逻辑。
显示补全提供程序示例
type MyPromptCompletionProvider struct{}
func (p *MyPromptCompletionProvider) CompletePromptArgument(
ctx context.Context,
promptName string,
argument mcp.CompleteArgument,
context mcp.CompleteContext,
) (*mcp.Completion, error) {
// Example: provide style suggestions for a "code_review" prompt
if promptName == "code_review" && argument.Name == "style" {
styles := []string{"formal", "casual", "technical", "creative"}
var suggestions []string
// Filter based on current input
for _, style := range styles {
if strings.HasPrefix(style, argument.Value) {
suggestions = append(suggestions, style)
}
}
return &mcp.Completion{
Values: suggestions,
}, nil
}
// Return empty suggestions for unhandled cases
return &mcp.Completion{Values: []string{}}, nil
}
type MyResourceCompletionProvider struct{}
func (p *MyResourceCompletionProvider) CompleteResourceArgument(
ctx context.Context,
uri string,
argument mcp.CompleteArgument,
context mcp.CompleteContext,
) (*mcp.Completion, error) {
// Example: provide file path completions
if uri == "file:///{path}" && argument.Name == "path" {
// You can access previously completed arguments from context.Arguments
// context.Arguments is a map[string]string of already-resolved arguments
paths := getMatchingPaths(argument.Value) // Your custom logic
return &mcp.Completion{
Values: paths[:min(len(paths), 100)], // Max 100 items
Total: len(paths), // Total available matches
HasMore: len(paths) > 100, // More results available
}, nil
}
return &mcp.Completion{Values: []string{}}, nil
}
// Register the provider
mcpServer := server.NewMCPServer(
"my-server",
"1.0.0",
server.WithCompletions(),
server.WithPromptCompletionProvider(&MyPromptCompletionProvider{}),
server.WithResourceCompletionProvider(&MyResourceCompletionProvider{}),
)
补全上下文
对于包含多个参数的提示词或资源模板,CompleteContext 参数可以访问先前已补全的参数。这样,你就可以根据先前的选择提供上下文相关的建议。
显示补全上下文示例
func (p *MyProvider) CompleteResourceArgument(
ctx context.Context,
uri string,
argument mcp.CompleteArgument,
context mcp.CompleteContext,
) (*mcp.Completion, error) {
// Access previously completed arguments
if previousValue, ok := context.Arguments["previous_arg"]; ok {
// Provide suggestions based on previous_arg value
return getSuggestionsFor(argument.Value, previousValue), nil
}
return &mcp.Completion{Values: []string{}}, nil
}
响应约束
返回补全结果时:
- 每个响应最多包含 100 项
- 使用
Total表示可用匹配项的总数 - 使用
HasMore表示返回值之外是否还有更多结果
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。
