github.com/mark3labs/mcp-go库学习

MCP的Go实现

A Go implementation of the Model Context Protocol (MCP), enabling seamless integration between LLM applications and external data sources and tools.
在这里插入图片描述

seamless integration 无缝集成
standardized ˈstændədaɪzd ˈstændərdaɪzd 标准的;标准化的;定型的;使合乎标准;按标准校准;制定标准
sort of 有点
sort of like 有点像
otherwise 否则

MCP Go handles all the complex protocol details and server management, so you can focus on building great tools.
It aims to be high-level and easy to use.

The MCP lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can:

  • Expose data through Resources (think of these sort of like GET endpoints; they are used to load information into the LLM’s context)
  • 通过Resources暴露data
  • Provide functionality through Tools (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
  • 通过Tools提供功能
  • Define interaction patterns through Prompts (reusable templates for LLM interactions)
  • 通过Prompts定义交互模板

关键特征 - Key features

开发节奏快、简单、完整的协议实现
Fast: High-level interface means less code and faster development
Simple: Build MCP servers with minimal boilerplate
Complete*: MCP Go aims to provide a full implementation of the core MCP specification

boilerplate ˈbɔɪləpleɪt 读作波伊勒plate 通用模板、标准文本
expose ɪkˈspəʊz ɪkˈspoʊz 暴露,使显露;使面临,使遭受(危险或不快);揭露,揭发;接触,体验;露出,显露(感情);揭露,曝光

示例1

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
}

示例2

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)
    }
}

其他

Transports - 传输

MCP-Go supports stdio, SSE and streamable-HTTP transport layers. 三种传输方式

For SSE transport, you can use SetConnectionLostHandler() to detect and handle HTTP/2 idle timeout disconnections (NO_ERROR) for implementing reconnection logic.
检测和处理 HTTP/2 的 idle 超时断连,用于实现重连逻辑

Session Management

MCP-Go provides a robust session management system that allows you to:

  • Maintain separate state for each connected client
  • 为每个已连接的客户端维持各自的状态
  • Register and track client sessions
  • 注册和跟踪客户端 session
  • Send notifications to specific clients
  • 给特定 client 发送通知
  • Provide per-session tool customization
  • 提供 per-session 维护的 tool 定制

Show Session Management Examples

Basic Session Handling

// 功能点1 : 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)
}

// 功能点2 : 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)
}

// 功能点3 : Unregister session when done
s.UnregisterSession(context.Background(), session.SessionID())

Per-Session Tools

For more advanced use cases, you can implement the SessionWithTools interface to support per-session tool customization:
支持 per-session 的 tool 定制化

// 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)
}

Tool Filtering

You can also apply filters to control which tools are available to certain sessions:
也可以应用 filter 来控制哪些 tools 对特定的 session 是可用的

// 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
        }
    }),
)

Working with Context

The session context is automatically passed to tool and resource handlers:
session context 会被自动传递给 tool 和 resource handler

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
    // ...
}

Request Hooks

telemetry təˈlemətri təˈlemətri 遥测技术;遥感勘测;自动测量记录传导

Hook into the request lifecycle by creating a Hooks object with your selection among the possible callbacks.
通过在可能的回调中选择创建一个 Hooks 对象来 Hook 到请求生命周期

This enables telemetry across all functionality, and observability of various facts,
for example the ability to count improperly-formatted requests, or to log the agent identity during initialization.
这支持跨所有功能的遥测(telemetry)和各种事实的可观察性(observability),
例如如下能力:

  • 计次格式不正确的请求
  • 在初始化期间记录代理身份的能力。

Add the Hooks to the server at the time of creation using the server.WithHooks option.
在使用 server.WithHooks option 时将 Hooks 添加到 server

Tool Handler Middleware

Add middleware to tool call handlers using the server.
使用 server 向 tool 调用处理程序添加中间件.

WithToolHandlerMiddleware option.
Middlewares can be registered on server creation and are applied on every tool call.
中间件可以在服务器创建时注册,并应用于每次 tool 调用

A recovery middleware option is available to recover from panics in a tool call and can be added to the server with the server.WithRecovery option.
recovery 中间件 option 可用于从 tool 调用中的 panic 中恢复
它通过 server.WithRecovery 添加到 server

Regenerating Server Code

Server hooks and request handlers are generated. Regenerate them by running:
Server 的钩子和请求 handler 是命令行生成的

go generate ./...

You need go installed and the goimports tool available.
The generator runs goimports automatically to format and fix imports.

核心功能

Prompts

Prompts are reusable templates that help LLMs interact with your server effectively.
They’re like “best practices” encoded into your server.
Here are some examples:

// Simple greeting prompt
// 简单问候的的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, // AI 对话模型中的 “助手角色”
                mcp.NewTextContent(fmt.Sprintf("Hello, %s! How can I help you today?", name)),
            ),
        },
    ), nil
})

// Code review prompt with embedded resource
// 带嵌入式 resource 的 cr prompt
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
// db 查询构造 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
})

Prompts include (Prompt包含的内容):

  • System instructions 系统建议
  • Required arguments 要求的参数
  • Embedded resources 嵌入的资源
  • Multiple messages 多消息
  • Different content types (text, images, etc.) 不同的内容类型
  • Custom URI schemes

EmbeddedResource(嵌入式资源)
指将外部资源(比如 Git 拉取请求的 diff 内容)直接嵌入到 Prompt 中的一种方式,用于为 AI 模型提供额外的上下文数据。

以上文 git-diff为例:

作用:
核心目的是将需要 AI 处理的具体内容(这里是pr_number对应的 PRdiff 文件)“附加” 到 Prompt 中
让 AI 在生成回复时能直接基于这些资源内容进行处理
例如,代码中通过 git://pulls/%s/diff 获取 PR 的 diff 内容,作为 AI 进行代码审查的依据

构成要素:EmbeddedResource通过mcp.ResourceContents定义,包含两个关键信息:

  • URI:资源的唯一标识(这里是 Git PR diff 的路径,用于定位要获取的 diff 内容)
  • MIMEType:资源的媒体类型(这里是text/x-diff,表明这是一个 diff 格式的文本,帮助 AI 识别内容格式)

与 Prompt 的关系:
EmbeddedResource被包裹在mcp.NewPromptMessage中,角色为mcp.RoleAssistant(通常代表 AI 的 “上下文数据”),与用户的提示(Review the changes…)共同组成完整的提示信息
AI 会结合用户的指令和嵌入的资源内容(diff 文件),生成针对性的代码审查反馈

简单说
EmbeddedResource就是 “给 AI 的附加材料”,让 AI 在处理任务(如代码审查)时,有具体的内容可参考,而不是凭空生成回复

Tools

Tools let LLMs take actions through your server. 通过 server 的 Tool 让 LLM 执行行动
Unlike resources, tools are expected to perform computation and have side effects. 执行计算,同时有 side effects
They’re similar to POST endpoints in a REST API.

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 request 示例:

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
})

Tools can be used for any kind of computation or side effect:

  • Database queries DB查询
  • File operations 文件操作
  • External API calls 外部API调用
  • Calculations 计算
  • System operations OS操作

Each tool should:

  • Have a clear description 清晰明了的描述
  • Validate inputs 有效的输入
  • Handle errors gracefully 优雅的处理error
  • Return structured responses 返回结构化的resp
  • Use appropriate result types 使用合理的 result 类型

Resources

Resources are how you expose data to LLMs.
They can be anything - files, API responses, database queries, system information, etc.

Resources can be:

  • Static (fixed URI) 静态URI
  • Dynamic (using URI templates) 动态的

Here’s a simple example of a static resource:

// 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
})

And here’s an example of a dynamic resource using a template:
使用 template 的动态资源

// 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
    // server 自动匹配 URIs 给 template
    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
})

The examples are simple but demonstrate the core concepts.
Resources can be much more sophisticated - serving multiple contents, integrating with databases or external APIs, etc.

sophisticated səˈfɪstɪkeɪtɪd 见多识广的,老练的,见过世面的;复杂巧妙的,先进的,精密的;水平高的,在行的;时尚的,精致的;使老于世故;使(设备、技术等)更复杂先进

Server

The server is your core interface to the MCP protocol.
It handles connection management, protocol compliance, and message routing:

// 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)
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值