用 Go 語言輕鬆構建 MCP 客戶端與服務器
前言
模型上下文協議(Model Context Protocol
,簡稱 MCP
)是一種開放標準,旨在標準化大型語言模型(LLM
)與外部數據源和工具之間的交互方式。隨着 MCP
越來越受歡迎,Go MCP
庫應運而生。本文將介紹如何在 Go
語言裏面輕鬆構建 MCP
客戶端和服務器。
如果你不熟悉 MCP
協議,可以看我之前寫的這篇文章:一文掌握 MCP 上下文協議:從理論到實踐。
準備好了嗎?準備一杯你最喜歡的咖啡或茶,隨着本文一探究竟吧。
mcp-go
要構建 MCP
客戶端和服務器,我們需要使用 mcp-go
庫。
mcp-go
是 Go
語言實現的 Model Context Protocol
(MCP
)庫,通過這個庫可以實現 LLM
應用與外部數據源和工具之間的無縫集成。
主要特點
-
快速:高級接口意味着更少的代碼和更快的開發速度
-
簡單:使用極少的樣板代碼構建
MCP
服務器 -
完整:
MCP Go
旨在提供MCP
核心規範的完整實現
安裝 MCP 庫
在 Go
項目根目錄下,執行以下命令:
go get github.com/mark3labs/mcp-go
構建 MCP 服務器
接下來,我們使用 mcp-go
提供的 server
模塊,構建一個通過 stidio
方式連接的 MCP
服務器。
創建 server 對象
s := server.NewMCPServer(
"Server Demo",
"1.0.0",
)
創建 server
對象時,我們可以指定 服務器名,版本號 等參數。
添加工具(tools)
以下是一個示例,用於創建並註冊一個簡單的計算器工具:
calculatorTool := mcp.NewTool("calculate",
mcp.WithDescription("執行基本的算術運算"),
mcp.WithString("operation",
mcp.Required(),
mcp.Description("要執行的算術運算類型"),
mcp.Enum("add", "subtract", "multiply", "divide"), // 保持英文
),
mcp.WithNumber("x",
mcp.Required(),
mcp.Description("第一個數字"),
),
mcp.WithNumber("y",
mcp.Required(),
mcp.Description("第二個數字"),
),
)
s.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
op := request.Params.Arguments["operation"].(string)
x := request.Params.Arguments["x"].(float64)
y := request.Params.Arguments["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 {
returnnil, errors.New("不允許除以零")
}
result = x / y
}
return mcp.FormatNumberResult(result), nil
})
添加工具的步驟如下:
-
創建工具對象 使用
mcp.NewTool
創建一個工具實例。 -
mcp.WithDescription(...)
添加工具描述; -
mcp.WithString(...)
或mcp.WithNumber(...)
定義參數及其規則(如是否必填、參數說明、枚舉限制等)。 -
第一個參數是工具名稱(必須),例如
"calculate"
。 -
其餘參數通過函數選項(
functional options
)方式傳入,例如: -
註冊工具到服務器 通過
s.AddTool
方法將工具註冊到MCP
服務中。 -
第一個參數是上一步創建的工具對象;
-
第二個參數是該工具的處理函數(handler),用於實現工具的具體邏輯,如參數解析、運算執行、返回結果等。
添加資源(Resources)
下面的示例展示瞭如何創建並註冊一個靜態資源,用於讀取並提供 README.md
文件的內容。
resource := mcp.NewResource(
"docs://readme",
"項目說明文檔",
mcp.WithResourceDescription("項目的 README 文件"),
mcp.WithMIMEType("text/markdown"),
)
s.AddResource(resource, func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
content, err := os.ReadFile("README.md")
if err != nil {
returnnil, err
}
return []mcp.ResourceContents{
mcp.TextResourceContents{
URI: "docs://readme",
MIMEType: "text/markdown",
Text: string(content),
},
}, nil
})
添加資源的步驟如下:
-
創建資源對象
使用mcp.NewResource
函數創建資源實例。 -
mcp.WithResourceDescription(...)
設置資源描述; -
mcp.WithMIMEType(...)
指定資源的 MIME 類型。 -
第一個參數爲資源 URI,用於標識資源;
-
第二個參數爲資源名稱;
-
通過函數選項補充更多信息,例如:
-
註冊資源處理函數
使用s.AddResource
將資源對象註冊到服務器,並提供一個處理函數: -
該處理函數會在資源被訪問時執行;
-
返回值是資源內容的數組(例如讀取本地文件內容並封裝爲
TextResourceContents
)。
添加提示詞(Prompts)
以下示例展示瞭如何創建並添加一個帶參數的簡單提示詞,用於生成個性化的問候語。
s.AddPrompt(mcp.NewPrompt("greeting",
mcp.WithPromptDescription("一個友好的問候提示"),
mcp.WithArgument("name",
mcp.ArgumentDescription("要問候的人的名字"),
),
), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
name := request.Params.Arguments["name"]
if name == "" {
name = "朋友"
}
return mcp.NewGetPromptResult(
"友好的問候",
[]mcp.PromptMessage{
mcp.NewPromptMessage(
mcp.RoleAssistant,
mcp.NewTextContent(fmt.Sprintf("你好,%s!今天有什麼可以幫您的嗎?", name)),
),
},
), nil
})
添加提示詞的步驟如下:
-
創建提示詞對象
通過mcp.NewPrompt
創建一個提示詞定義。 -
第一個參數是提示詞名稱;
-
可通過
mcp.WithPromptDescription(...)
添加描述; -
使用
mcp.WithArgument(...)
定義參數及其說明(如提示詞中需要動態插值的內容)。 -
註冊提示詞處理函數
使用s.AddPrompt
將提示詞對象註冊到服務器,並提供對應的處理邏輯函數: -
函數接收用戶輸入參數;
-
返回一個結構化的提示詞響應(如構造一個帶有用戶名字的問候消息)。
啓動基於 stdio
傳輸類型的服務器
// 啓動基於 stdio 的服務器
if err := server.ServeStdio(s); err != nil {
fmt.Printf("Server error: %v\n", err)
}
使用 server.ServeStdio
方法可以啓動一個基於標準輸入 / 輸出(stdio
)的 MCP
服務器。
這種方式適用於本地集成與命令行工具。
啓動基於 sse
(Server-Sent Events)傳輸類型的服務器
如果需要通過 HTTP
的方式提供服務,支持服務端推送數據,可以使用 SS
E(Server-Sent Events
)傳輸模式。
s := server.NewMCPServer(
"My Server", // Server 名稱
"1.0.0", // 版本號
)
// 創建基於 SSE 的服務器實例
sseServer := server.NewSSEServer(s)
// 啓動服務器,監聽指定端口(如 :8080)
err := sseServer.Start(":8080")
if err != nil {
panic(err)
}
與 stdio
不同,sse
模式基於 HTTP
協議,更適合 Web
應用中的長連接場景,支持服務端推送數據。
完整的 stdio 代碼示例
package main
import (
"context"
"errors"
"fmt"
"os"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
s := server.NewMCPServer(
"Server Demo",
"1.0.0",
)
// 添加工具
{
calculatorTool := mcp.NewTool("calculate",
mcp.WithDescription("執行基本的算術運算"),
mcp.WithString("operation",
mcp.Required(),
mcp.Description("要執行的算術運算類型"),
mcp.Enum("add", "subtract", "multiply", "divide"), // 保持英文
),
mcp.WithNumber("x",
mcp.Required(),
mcp.Description("第一個數字"),
),
mcp.WithNumber("y",
mcp.Required(),
mcp.Description("第二個數字"),
),
)
s.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
op := request.Params.Arguments["operation"].(string)
x := request.Params.Arguments["x"].(float64)
y := request.Params.Arguments["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 {
returnnil, errors.New("不允許除以零")
}
result = x / y
}
return mcp.FormatNumberResult(result), nil
})
}
// 添加資源
{
// 靜態資源示例 - 暴露一個 README 文件
resource := mcp.NewResource(
"docs://readme",
"項目說明文檔",
mcp.WithResourceDescription("項目的 README 文件"),
mcp.WithMIMEType("text/markdown"),
)
// 添加資源及其處理函數
s.AddResource(resource, func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
content, err := os.ReadFile("README.md")
if err != nil {
returnnil, err
}
return []mcp.ResourceContents{
mcp.TextResourceContents{
URI: "docs://readme",
MIMEType: "text/markdown",
Text: string(content),
},
}, nil
})
}
// 添加提示詞
{
// 簡單問候提示
s.AddPrompt(mcp.NewPrompt("greeting",
mcp.WithPromptDescription("一個友好的問候提示"),
mcp.WithArgument("name",
mcp.ArgumentDescription("要問候的人的名字"),
),
), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
name := request.Params.Arguments["name"]
if name == "" {
name = "朋友"
}
return mcp.NewGetPromptResult(
"友好的問候",
[]mcp.PromptMessage{
mcp.NewPromptMessage(
mcp.RoleAssistant,
mcp.NewTextContent(fmt.Sprintf("你好,%s!今天有什麼可以幫您的嗎?", name)),
),
},
), nil
})
}
// 啓動基於 stdio 的服務器
if err := server.ServeStdio(s); err != nil {
fmt.Printf("Server error: %v\n", err)
}
}
構建 MCP 客戶端
接下來,我們使用 mcp-go
提供的 client
模塊,構建一個通過 stdio
方式連接到前面打包好的 MCP
服務器的客戶端。
該客戶端將展示以下功能:
-
初始化客戶端並連接服務器
-
獲取提示詞、資源、工具列表
-
調用遠程工具(tool)
創建 MCP 客戶端
mcpClient, err := client.NewStdioMCPClient(
"./client/server", // 服務器可執行文件路徑
[]string{}, // 啓動參數(如果有)
)
if err != nil {
panic(err)
}
defer mcpClient.Close()
通過 client.NewStdioMCPClient
方法可以創建一個基於 stdio
傳輸的客戶端,並連接到指定的 MCP
服務器可執行文件。
初始化客戶端連接
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
initRequest := mcp.InitializeRequest{}
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.ClientInfo = mcp.Implementation{
Name: "Client Demo",
Version: "1.0.0",
}
initResult, err := mcpClient.Initialize(ctx, initRequest)
if err != nil {
panic(err)
}
fmt.Printf("初始化成功,服務器信息: %s %s\n", initResult.ServerInfo.Name, initResult.ServerInfo.Version)
初始化操作通過 Initialize
方法完成,需指定協議版本及客戶端信息。
獲取提示詞(Prompts)列表
promptsRequest := mcp.ListPromptsRequest{}
prompts, err := mcpClient.ListPrompts(ctx, promptsRequest)
if err != nil {
panic(err)
}
for _, prompt := range prompts.Prompts {
fmt.Printf("- %s: %s\n", prompt.Name, prompt.Description)
fmt.Println("參數:", prompt.Arguments)
}
客戶端可以使用 ListPrompts
獲取服務器上定義的所有提示詞,包括名稱、描述和參數結構。
獲取資源(Resources)列表
resourcesRequest := mcp.ListResourcesRequest{}
resources, err := mcpClient.ListResources(ctx, resourcesRequest)
if err != nil {
panic(err)
}
for _, resource := range resources.Resources {
fmt.Printf("- uri: %s, name: %s, description: %s, MIME類型: %s\n",
resource.URI, resource.Name, resource.Description, resource.MIMEType)
}
通過 ListResources
方法,客戶端可以查看服務器上可用的靜態或動態資源信息。
獲取工具(Tools)列表
toolsRequest := mcp.ListToolsRequest{}
tools, err := mcpClient.ListTools(ctx, toolsRequest)
if err != nil {
panic(err)
}
for _, tool := range tools.Tools {
fmt.Printf("- %s: %s\n", tool.Name, tool.Description)
fmt.Println("參數:", tool.InputSchema.Properties)
}
通過 ListTools
,客戶端可以獲取所有註冊的工具信息,方便用戶交互式選擇或自動生成表單調用。
調用工具(Tool)
toolRequest := mcp.CallToolRequest{
Request: mcp.Request{
Method: "tools/call",
},
}
toolRequest.Params.Name = "calculate"
toolRequest.Params.Arguments = map[string]any{
"operation": "add",
"x": 1,
"y": 1,
}
result, err := mcpClient.CallTool(ctx, toolRequest)
if err != nil {
panic(err)
}
fmt.Println("調用工具結果:", result.Content[0].(mcp.TextContent).Text)
通過構造 CallToolRequest
,客戶端可以向 MCP
服務器發起工具調用請求,並獲取返回的結構化結果。
在此示例中,我們調用了服務器端註冊的 calculate
工具,實現 1 + 1
運算。
完整代碼示例
package main
import (
"context"
"fmt"
"time"
"github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp"
)
func main() {
// 創建一個基於 stdio 的MCP客戶端
mcpClient, err := client.NewStdioMCPClient(
"./client/server",
[]string{},
)
if err != nil {
panic(err)
}
defer mcpClient.Close()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
fmt.Println("初始化 mcp 客戶端...")
initRequest := mcp.InitializeRequest{}
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.ClientInfo = mcp.Implementation{
Name: "Client Demo",
Version: "1.0.0",
}
// 初始化MCP客戶端並連接到服務器
initResult, err := mcpClient.Initialize(ctx, initRequest)
if err != nil {
panic(err)
}
fmt.Printf(
"\n初始化成功,服務器信息: %s %s\n\n",
initResult.ServerInfo.Name,
initResult.ServerInfo.Version,
)
// 從服務器獲取提示詞列表
fmt.Println("提示詞列表:")
promptsRequest := mcp.ListPromptsRequest{}
prompts, err := mcpClient.ListPrompts(ctx, promptsRequest)
if err != nil {
panic(err)
}
for _, prompt := range prompts.Prompts {
fmt.Printf("- %s: %s\n", prompt.Name, prompt.Description)
fmt.Println("參數:", prompt.Arguments)
}
// 從服務器獲取資源列表
fmt.Println()
fmt.Println("資源列表:")
resourcesRequest := mcp.ListResourcesRequest{}
resources, err := mcpClient.ListResources(ctx, resourcesRequest)
if err != nil {
panic(err)
}
for _, resource := range resources.Resources {
fmt.Printf("- uri: %s, name: %s, description: %s, MIME類型: %s\n", resource.URI, resource.Name, resource.Description, resource.MIMEType)
}
// 從服務器獲取工具列表
fmt.Println()
fmt.Println("可用工具列表:")
toolsRequest := mcp.ListToolsRequest{}
tools, err := mcpClient.ListTools(ctx, toolsRequest)
if err != nil {
panic(err)
}
for _, tool := range tools.Tools {
fmt.Printf("- %s: %s\n", tool.Name, tool.Description)
fmt.Println("參數:", tool.InputSchema.Properties)
}
fmt.Println()
// 調用工具
fmt.Println("調用工具: calculate")
toolRequest := mcp.CallToolRequest{
Request: mcp.Request{
Method: "tools/call",
},
}
toolRequest.Params.Name = "calculate"
toolRequest.Params.Arguments = map[string]any{
"operation": "add",
"x": 1,
"y": 1,
}
// Call the tool
result, err := mcpClient.CallTool(ctx, toolRequest)
if err != nil {
panic(err)
}
fmt.Println("調用工具結果:", result.Content[0].(mcp.TextContent).Text)
}
小結
本文介紹瞭如何使用 mcp-go
構建一個完整的 MCP
應用,包括服務端和客戶端兩部分。
-
服務端支持註冊工具(Tool)、資源(Resource)和提示詞(Prompt),並可通過
stdio
或sse
模式對外提供服務; -
客戶端通過
stdio
連接服務器,支持初始化、列出服務內容、調用遠程工具等操作。
你好,我是陳明勇,一名熱愛技術、樂於分享的開發者,同時也是開源愛好者。
成功的路上並不擁擠,有沒有興趣結個伴?
關注公衆號,即可獲取我的微信二維碼,加我好友,一起學習一起進步!
如果想和大家一起交流技術的讀者,可以關注公衆號,回覆【加羣】,獲取進羣鏈接。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/8SRAgeLUPJ7US52ICWmmJA