以github.com/mark3labs/mcp-go为例
开始以一个简单的查看文件列表工具作为范例,来展示该如何开发mcp server
func main() {
// 创建 MCP 服务器
mcpServer := server.NewMCPServer(
"file-server",
"1.0.0",
server.WithResourceCapabilities(true, true),
server.WithPromptCapabilities(true),
server.WithToolCapabilities(true),
)
// 添加查看文件列表的工具
mcpServer.AddTool(mcp.NewTool(
ToolListFiles,
mcp.WithDescription("List files in ~/Documents"),
), handleListFiles)
// 创建 SSE 服务器
sseServer := server.NewSSEServer(mcpServer,
server.WithBaseURL("http://localhost:8080"),
server.WithSSEEndpoint("/sse"),
server.WithMessageEndpoint("/message"),
)
// 启动服务器
go func() {
if err := sseServer.Start(":8080"); err != nil && err != http.ErrServerClosed {
fmt.Printf("Failed to start server: %v\n", err)
}
}()
fmt.Println("Server started on :8080")
// 保持程序运行
select {}
}
// 处理查看文件列表的工具请求
func handleListFiles(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
documentsPath, err := os.UserHomeDir()
if err != nil {
return nil, err
}
documentsPath = filepath.Join(documentsPath, "Documents")
files, err := ioutil.ReadDir(documentsPath)
if err != nil {
return nil, err
}
var fileNames []string
for _, file := range files {
fileNames = append(fileNames, file.Name())
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("%v", fileNames),
},
},
}, nil
}
接收服务器的名称、版本和一系列可选参数,用于配置服务器的功能和能力。通过传入不同的 ServerOption
,可以启用或禁用特定的功能,如资源管理、工具调用、提示处理等
// NewMCPServer creates a new MCP server instance with the given name, version and options
func NewMCPServer(
name, version string,
opts ...ServerOption,
) *MCPServer {
s := &MCPServer{
resources: make(map[string]resourceEntry),
resourceTemplates: make(map[string]resourceTemplateEntry),
prompts: make(map[string]mcp.Prompt),
promptHandlers: make(map[string]PromptHandlerFunc),
tools: make(map[string]ServerTool),
name: name,
version: version,
notificationHandlers: make(map[string]NotificationHandlerFunc),
capabilities: serverCapabilities{
tools: nil,
resources: nil,
prompts: nil,
logging: false,
},
}
for _, opt := range opts {
opt(s)
}
return s
}
MCP 服务器可以暴露各种资源和功能,包括资源(Resources)、工具(Tools)和提示(Prompts)。这些资源和功能需要在服务器启动前进行注册。
资源是MCP中的上下文对象,用于跨步骤携带信息、文件、图片、结构化数据等
{
"resources": [
{
"id": "logs-result-123",
"type": "text",
"name": "分析结果",
"data": "这里是日志分析结果..."
}
]
}
将资源和对应的处理函数关联起来,当客户端请求读取该资源时,服务器会调用相应的处理函数来返回资源内容。
// AddResource registers a new resource and its handler
func (s *MCPServer) AddResource(
resource mcp.Resource,
handler ResourceHandlerFunc,
) {
s.capabilitiesMu.Lock()
if s.capabilities.resources == nil {
s.capabilities.resources = &resourceCapabilities{}
}
s.capabilitiesMu.Unlock()
s.resourcesMu.Lock()
defer s.resourcesMu.Unlock()
s.resources[resource.URI] = resourceEntry{
resource: resource,
handler: handler,
}
}
工具是 MCP 中的一种功能,允许client调用mcp server上的特定操作。
// AddTool registers a new tool and its handler
func (s *MCPServer) AddTool(tool mcp.Tool, handler ToolHandlerFunc) {
s.AddTools(ServerTool{Tool: tool, Handler: handler})
}
// AddTools registers multiple tools at once
func (s *MCPServer) AddTools(tools ...ServerTool) {
s.capabilitiesMu.Lock()
if s.capabilities.tools == nil {
s.capabilities.tools = &toolCapabilities{}
}
s.capabilitiesMu.Unlock()
s.toolsMu.Lock()
for _, entry := range tools {
s.tools[entry.Tool.Name] = entry
}
s.toolsMu.Unlock()
// Send notification to all initialized sessions
s.sendNotificationToAllClients("notifications/tools/list_changed", nil)
}
HandleMessage
方法是 MCP 服务器处理传入 JSON - RPC 消息的核心方法,它会根据消息的类型和方法进行不同的处理,并返回相应的响应。比如初始化请求、心跳请求、列出资源请求、列出工具请求、调用工具等
// HandleMessage processes an incoming JSON-RPC message and returns an appropriate response
func (s *MCPServer) HandleMessage(
ctx context.Context,
message json.RawMessage,
) mcp.JSONRPCMessage {
// Add server to context
ctx = context.WithValue(ctx, serverKey{}, s)
var err *requestError
var baseMessage struct {
JSONRPC string `json:"jsonrpc"`
Method mcp.MCPMethod `json:"method"`
ID any `json:"id,omitempty"`
}
if err := json.Unmarshal(message, &baseMessage); err != nil {
return createErrorResponse(
nil,
mcp.PARSE_ERROR,
"Failed to parse message",
)
}
// Check for valid JSONRPC version
if baseMessage.JSONRPC != mcp.JSONRPC_VERSION {
return createErrorResponse(
baseMessage.ID,
mcp.INVALID_REQUEST,
"Invalid JSON-RPC version",
)
}
// 如果消息 ID 为空,说明这是一个通知消息
if baseMessage.ID == nil {
var notification mcp.JSONRPCNotification
// 尝试将消息解析为通知消息结构体
if err := json.Unmarshal(message, ¬ification); err != nil {
// 如果解析通知消息失败,返回一个解析错误响应
return createErrorResponse(
nil,
mcp.PARSE_ERROR,
"Failed to parse notification",
)
}
// 处理通知消息
s.handleNotification(ctx, notification)
// 通知消息不需要响应,返回 nil
return nil
}
switch baseMessage.Method { // 根据不同的方法名进行不同的处理
switch baseMessage.Method {
case mcp.MethodInitialize:
// 初始化请求
var request mcp.InitializeRequest
var result *mcp.InitializeResult
// 尝试将消息解析为初始化请求结构体
if unmarshalErr := json.Unmarshal(message, &request); unmarshalErr != nil {
// 如果解析失败,记录错误信息
err = &requestError{
id: baseMessage.ID,
code: mcp.INVALID_REQUEST,
err: &UnparseableMessageError{message: message, err: unmarshalErr, method: baseMessage.Method},
}
} else {
// 调用钩子函数,在处理初始化请求前执行一些操作
s.hooks.beforeInitialize(ctx, baseMessage.ID, &request)
// 处理初始化请求
result, err = s.handleInitialize(ctx, baseMessage.ID, request)
}
if err != nil {
// 如果处理过程中出现错误,调用钩子函数记录错误信息,并返回错误响应
s.hooks.onError(ctx, baseMessage.ID, baseMessage.Method, &request, err)
return err.ToJSONRPCError()
}
// 调用钩子函数,在处理初始化请求后执行一些操作
s.hooks.afterInitialize(ctx, baseMessage.ID, &request, result)
// 返回初始化请求的响应
return createResponse(baseMessage.ID, *result)
case mcp.MethodPing:
// 心跳请求
var request mcp.PingRequest
var result *mcp.EmptyResult
// 尝试将消息解析为心跳请求结构体
if unmarshalErr := json.Unmarshal(message, &request); unmarshalErr != nil {
// 如果解析失败,记录错误信息
err = &requestError{
id: baseMessage.ID,
code: mcp.INVALID_REQUEST,
err: &UnparseableMessageError{message: message, err: unmarshalErr, method: baseMessage.Method},
}
} else {
// 调用钩子函数,在处理心跳请求前执行一些操作
s.hooks.beforePing(ctx, baseMessage.ID, &request)
// 处理心跳请求
result, err = s.handlePing(ctx, baseMessage.ID, request)
}
if err != nil {
// 如果处理过程中出现错误,调用钩子函数记录错误信息,并返回错误响应
s.hooks.onError(ctx, baseMessage.ID, baseMessage.Method, &request, err)
return err.ToJSONRPCError()
}
// 调用钩子函数,在处理心跳请求后执行一些操作
s.hooks.afterPing(ctx, baseMessage.ID, &request, result)
// 返回心跳请求的响应
return createResponse(baseMessage.ID, *result)
case mcp.MethodResourcesList:
// 列出资源请求
var request mcp.ListResourcesRequest
var result *mcp.ListResourcesResult
// 检查服务器是否支持资源功能
if s.capabilities.resources == nil {
// 如果不支持,记录方法未找到错误信息
err = &requestError{
id: baseMessage.ID,
code: mcp.METHOD_NOT_FOUND,
err: fmt.Errorf("resources %w", ErrUnsupported),
}
} else if unmarshalErr := json.Unmarshal(message, &request); unmarshalErr != nil {
// 如果解析失败,记录错误信息
err = &requestError{
id: baseMessage.ID,
code: mcp.INVALID_REQUEST,
err: &UnparseableMessageError{message: message, err: unmarshalErr, method: baseMessage.Method},
}
} else {
// 调用钩子函数,在处理列出资源请求前执行一些操作
s.hooks.beforeListResources(ctx, baseMessage.ID, &request)
// 处理列出资源请求
result, err = s.handleListResources(ctx, baseMessage.ID, request)
}
if err != nil {
// 如果处理过程中出现错误,调用钩子函数记录错误信息,并返回错误响应
s.hooks.onError(ctx, baseMessage.ID, baseMessage.Method, &request, err)
return err.ToJSONRPCError()
}
// 调用钩子函数,在处理列出资源请求后执行一些操作
s.hooks.afterListResources(ctx, baseMessage.ID, &request, result)
// 返回列出资源请求的响应
return createResponse(baseMessage.ID, *result)
case mcp.MethodResourcesTemplatesList:
// 列出资源模板请求
var request mcp.ListResourceTemplatesRequest
var result *mcp.ListResourceTemplatesResult
// 检查服务器是否支持资源功能
if s.capabilities.resources == nil {
// 如果不支持,记录方法未找到错误信息
err = &requestError{
id: baseMessage.ID,
code: mcp.METHOD_NOT_FOUND,
err: fmt.Errorf("resources %w", ErrUnsupported),
}
} else if unmarshalErr := json.Unmarshal(message, &request); unmarshalErr != nil {
// 如果解析失败,记录错误信息
err = &requestError{
id: baseMessage.ID,
code: mcp.INVALID_REQUEST,
err: &UnparseableMessageError{message: message, err: unmarshalErr, method: baseMessage.Method},
}
} else {
// 调用钩子函数,在处理列出资源模板请求前执行一些操作
s.hooks.beforeListResourceTemplates(ctx, baseMessage.ID, &request)
// 处理列出资源模板请求
result, err = s.handleListResourceTemplates(ctx, baseMessage.ID, request)
}
if err != nil {
// 如果处理过程中出现错误,调用钩子函数记录错误信息,并返回错误响应
s.hooks.onError(ctx, baseMessage.ID, baseMessage.Method, &request, err)
return err.ToJSONRPCError()
}
// 调用钩子函数,在处理列出资源模板请求后执行一些操作
s.hooks.afterListResourceTemplates(ctx, baseMessage.ID, &request, result)
// 返回列出资源模板请求的响应
return createResponse(baseMessage.ID, *result)
case mcp.MethodResourcesRead:
// 读取资源请求
var request mcp.ReadResourceRequest
var result *mcp.ReadResourceResult
// 检查服务器是否支持资源功能
if s.capabilities.resources == nil {
// 如果不支持,记录方法未找到错误信息
err = &requestError{
id: baseMessage.ID,
code: mcp.METHOD_NOT_FOUND,
err: fmt.Errorf("resources %w", ErrUnsupported),
}
} else if unmarshalErr := json.Unmarshal(message, &request); unmarshalErr != nil {
// 如果解析失败,记录错误信息
err = &requestError{
id: baseMessage.ID,
code: mcp.INVALID_REQUEST,
err: &UnparseableMessageError{message: message, err: unmarshalErr, method: baseMessage.Method},
}
} else {
// 调用钩子函数,在处理读取资源请求前执行一些操作
s.hooks.beforeReadResource(ctx, baseMessage.ID, &request)
// 处理读取资源请求
result, err = s.handleReadResource(ctx, baseMessage.ID, request)
}
if err != nil {
// 如果处理过程中出现错误,调用钩子函数记录错误信息,并返回错误响应
s.hooks.onError(ctx, baseMessage.ID, baseMessage.Method, &request, err)
return err.ToJSONRPCError()
}
// 调用钩子函数,在处理读取资源请求后执行一些操作
s.hooks.afterReadResource(ctx, baseMessage.ID, &request, result)
// 返回读取资源请求的响应
return createResponse(baseMessage.ID, *result)
case mcp.MethodPromptsList:
// 列出提示请求
var request mcp.ListPromptsRequest
var result *mcp.ListPromptsResult
// 检查服务器是否支持提示功能
if s.capabilities.prompts == nil {
// 如果不支持,记录方法未找到错误信息
err = &requestError{
id: baseMessage.ID,
code: mcp.METHOD_NOT_FOUND,
err: fmt.Errorf("prompts %w", ErrUnsupported),
}
} else if unmarshalErr := json.Unmarshal(message, &request); unmarshalErr != nil {
// 如果解析失败,记录错误信息
err = &requestError{
id: baseMessage.ID,
code: mcp.INVALID_REQUEST,
err: &UnparseableMessageError{message: message, err: unmarshalErr, method: baseMessage.Method},
}
} else {
// 调用钩子函数,在处理列出提示请求前执行一些操作
s.hooks.beforeListPrompts(ctx, baseMessage.ID, &request)
// 处理列出提示请求
result, err = s.handleListPrompts(ctx, baseMessage.ID, request)
}
if err != nil {
// 如果处理过程中出现错误,调用钩子函数记录错误信息,并返回错误响应
s.hooks.onError(ctx, baseMessage.ID, baseMessage.Method, &request, err)
return err.ToJSONRPCError()
}
// 调用钩子函数,在处理列出提示请求后执行一些操作
s.hooks.afterListPrompts(ctx, baseMessage.ID, &request, result)
// 返回列出提示请求的响应
return createResponse(baseMessage.ID, *result)
case mcp.MethodPromptsGet:
// 获取提示请求
var request mcp.GetPromptRequest
var result *mcp.GetPromptResult
// 检查服务器是否支持提示功能
if s.capabilities.prompts == nil {
// 如果不支持,记录方法未找到错误信息
err = &requestError{
id: baseMessage.ID,
code: mcp.METHOD_NOT_FOUND,
err: fmt.Errorf("prompts %w", ErrUnsupported),
}
} else if unmarshalErr := json.Unmarshal(message, &request); unmarshalErr != nil {
// 如果解析失败,记录错误信息
err = &requestError{
id: baseMessage.ID,
code: mcp.INVALID_REQUEST,
err: &UnparseableMessageError{message: message, err: unmarshalErr, method: baseMessage.Method},
}
} else {
// 调用钩子函数,在处理获取提示请求前执行一些操作
s.hooks.beforeGetPrompt(ctx, baseMessage.ID, &request)
// 处理获取提示请求
result, err = s.handleGetPrompt(ctx, baseMessage.ID, request)
}
if err != nil {
// 如果处理过程中出现错误,调用钩子函数记录错误信息,并返回错误响应
s.hooks.onError(ctx, baseMessage.ID, baseMessage.Method, &request, err)
return err.ToJSONRPCError()
}
// 调用钩子函数,在处理获取提示请求后执行一些操作
s.hooks.afterGetPrompt(ctx, baseMessage.ID, &request, result)
// 返回获取提示请求的响应
return createResponse(baseMessage.ID, *result)
case mcp.MethodToolsList:
// 列出工具请求
var request mcp.ListToolsRequest
var result *mcp.ListToolsResult
// 检查服务器是否支持工具功能
if s.capabilities.tools == nil {
// 如果不支持,记录方法未找到错误信息
err = &requestError{
id: baseMessage.ID,
code: mcp.METHOD_NOT_FOUND,
err: fmt.Errorf("tools %w", ErrUnsupported),
}
} else if unmarshalErr := json.Unmarshal(message, &request); unmarshalErr != nil {
// 如果解析失败,记录错误信息
err = &requestError{
id: baseMessage.ID,
code: mcp.INVALID_REQUEST,
err: &UnparseableMessageError{message: message, err: unmarshalErr, method: baseMessage.Method},
}
} else {
// 调用钩子函数,在处理列出工具请求前执行一些操作
s.hooks.beforeListTools(ctx, baseMessage.ID, &request)
// 处理列出工具请求
result, err = s.handleListTools(ctx, baseMessage.ID, request)
}
if err != nil {
// 如果处理过程中出现错误,调用钩子函数记录错误信息,并返回错误响应
s.hooks.onError(ctx, baseMessage.ID, baseMessage.Method, &request, err)
return err.ToJSONRPCError()
}
// 调用钩子函数,在处理列出工具请求后执行一些操作
s.hooks.afterListTools(ctx, baseMessage.ID, &request, result)
// 返回列出工具请求的响应
return createResponse(baseMessage.ID, *result)
case mcp.MethodToolsCall:
// 调用工具请求
var request mcp.CallToolRequest
var result *mcp.CallToolResult
// 检查服务器是否支持工具功能
if s.capabilities.tools == nil {
// 如果不支持,记录方法未找到错误信息
err = &requestError{
id: baseMessage.ID,
code: mcp.METHOD_NOT_FOUND,
err: fmt.Errorf("tools %w", ErrUnsupported),
}
} else if unmarshalErr := json.Unmarshal(message, &request); unmarshalErr != nil {
// 如果解析失败,记录错误信息
err = &requestError{
id: baseMessage.ID,
code: mcp.INVALID_REQUEST,
err: &UnparseableMessageError{message: message, err: unmarshalErr, method: baseMessage.Method},
}
} else {
// 调用钩子函数,在处理调用工具请求前执行一些操作
s.hooks.beforeCallTool(ctx, baseMessage.ID, &request)
// 处理调用工具请求
result, err = s.handleToolCall(ctx, baseMessage.ID, request)
}
if err != nil {
// 如果处理过程中出现错误,调用钩子函数记录错误信息,并返回错误响应
s.hooks.onError(ctx, baseMessage.ID, baseMessage.Method, &request, err)
return err.ToJSONRPCError()
}
// 调用钩子函数,在处理调用工具请求后执行一些操作
s.hooks.afterCallTool(ctx, baseMessage.ID, &request, result)
// 返回调用工具请求的响应
return createResponse(baseMessage.ID, *result)
default:
// 如果方法名不匹配任何已知方法,返回方法未找到错误响应
return createErrorResponse(
baseMessage.ID,
mcp.METHOD_NOT_FOUND,
fmt.Sprintf("Method %s not found", baseMessage.Method),
)
}
}
handleInitialize
方法用于处理初始化请求,根据服务器配置设置相应的功能能力(资源、提示、工具、日志),构建并返回初始化结果,同时若上下文中存在客户端会话则对其进行初始化。
func (s *MCPServer) handleInitialize(
ctx context.Context,
id interface{},
request mcp.InitializeRequest,
) (*mcp.InitializeResult, *requestError) {
capabilities := mcp.ServerCapabilities{}
// Only add resource capabilities if they're configured
if s.capabilities.resources != nil {
capabilities.Resources = &struct {
Subscribe bool `json:"subscribe,omitempty"`
ListChanged bool `json:"listChanged,omitempty"`
}{
Subscribe: s.capabilities.resources.subscribe,
ListChanged: s.capabilities.resources.listChanged,
}
}
// Only add prompt capabilities if they're configured
if s.capabilities.prompts != nil {
capabilities.Prompts = &struct {
ListChanged bool `json:"listChanged,omitempty"`
}{
ListChanged: s.capabilities.prompts.listChanged,
}
}
// Only add tool capabilities if they're configured
if s.capabilities.tools != nil {
capabilities.Tools = &struct {
ListChanged bool `json:"listChanged,omitempty"`
}{
ListChanged: s.capabilities.tools.listChanged,
}
}
if s.capabilities.logging {
capabilities.Logging = &struct{}{}
}
result := mcp.InitializeResult{
ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,
ServerInfo: mcp.Implementation{
Name: s.name,
Version: s.version,
},
Capabilities: capabilities,
Instructions: s.instructions,
}
if session := ClientSessionFromContext(ctx); session != nil {
session.Initialize()
}
return &result, nil
}
handleListResources
处理列出资源的请求。它从服务器中检索所有资源,对资源进行排序,并根据请求中的分页参数返回相应的资源列表
func (s *MCPServer) handleListResources(
ctx context.Context,
id interface{},
request mcp.ListResourcesRequest,
) (*mcp.ListResourcesResult, *requestError) {
// 对资源进行读锁定,防止在读取资源时其他 goroutine 修改资源
s.resourcesMu.RLock()
// 初始化一个切片,用于存储所有资源,切片的初始容量为服务器中资源的数量
resources := make([]mcp.Resource, 0, len(s.resources))
// 遍历服务器中存储的所有资源条目
for _, entry := range s.resources {
// 将每个资源条目中的资源添加到 resources 切片中
resources = append(resources, entry.resource)
}
// 释放资源的读锁定
s.resourcesMu.RUnlock()
// 对资源切片进行排序,按照资源的名称进行升序排序
sort.Slice(resources, func(i, j int) bool {
return resources[i].Name < resources[j].Name
})
// 根据请求中的游标参数进行分页,返回当前页的资源列表、下一页的游标和可能的错误
resourcesToReturn, nextCursor, err := listByPagination[mcp.Resource](ctx, s, request.Params.Cursor, resources)
if err != nil {
// 如果分页过程中出现错误,返回一个包含错误信息的请求错误对象
return nil, &requestError{
id: id,
code: mcp.INVALID_PARAMS,
err: err,
}
}
// 创建一个 ListResourcesResult 对象,包含当前页的资源列表和下一页的游标
result := mcp.ListResourcesResult{
Resources: resourcesToReturn,
PaginatedResult: mcp.PaginatedResult{
NextCursor: nextCursor,
},
}
// 返回包含资源列表和分页信息的结果对象,以及 nil 错误
return &result, nil
}
handleToolCall 处理调用工具的请求。它接收一个上下文、请求 ID 和调用工具的请求作为参数,然后查找对应的工具处理程序,应用中间件,并执行处理程序以获取工具调用的结果。
func (s *MCPServer) handleToolCall(
ctx context.Context,
id interface{},
request mcp.CallToolRequest,
) (*mcp.CallToolResult, *requestError) {
// 对工具进行读锁定,防止在查找工具时其他 goroutine 修改工具列表
s.toolsMu.RLock()
// 从服务器存储的工具中查找请求的工具
tool, ok := s.tools[request.Params.Name]
// 释放工具的读锁定
s.toolsMu.RUnlock()
// 如果未找到请求的工具,返回一个包含错误信息的请求错误对象
if !ok {
return nil, &requestError{
id: id,
code: mcp.INVALID_PARAMS,
err: fmt.Errorf("tool '%s' not found: %w", request.Params.Name, ErrToolNotFound),
}
}
// 获取找到的工具的处理程序
finalHandler := tool.Handler
// 对中间件进行读锁定,防止在获取中间件时其他 goroutine 修改中间件列表
s.middlewareMu.RLock()
// 获取服务器存储的所有工具处理程序中间件
mw := s.toolHandlerMiddlewares
// 释放中间件的读锁定
s.middlewareMu.RUnlock()
// 反向遍历中间件列表,将中间件依次应用到最终处理程序上
// 这样可以确保中间件按照添加的顺序依次执行
for i := len(mw) - 1; i >= 0; i-- {
finalHandler = mw[i](finalHandler)
}
// 使用最终处理程序处理工具调用请求,获取工具调用的结果和可能的错误
result, err := finalHandler(ctx, request)
// 如果处理过程中出现错误,返回一个包含错误信息的请求错误对象
if err != nil {
return nil, &requestError{
id: id,
code: mcp.INTERNAL_ERROR,
err: err,
}
}
// 返回工具调用的结果和 nil 错误
return result, nil
}