前面我们聊过大模型、Agent、Function Calling这些核心概念,不知道你有没有发现一个问题
最早的大模型只能对话,后来大模型逐渐会调用工具了
前Function Calling时代
在 OpenAI 等官方推出 Function Calling 之前,大家是这样 “逼模型调用工具” 的:
核心思路
- 后端写死规则:关键词匹配 / 正则 → 判断要不要调工具
- 或在 Prompt 里强制要求模型输出固定 JSON 格式
典型 Prompt(2022 年风格)
你是一个助手。
如果用户问天气,请严格按以下格式返回,不要加其他文字:
{"action":"get_weather","params":{"city":"北京"}}
否则直接回答问题。
用户问:
北京今天天气如何?
模型理想输出:
{"action":"get_weather","params":{"city":"北京"}}
后端流程
- 用户提问 → 传给 LLM
- LLM 输出 JSON(或乱文本)
- 后端解析 JSON
- 成功 → 调用天气 API → 把结果再给 LLM 总结
- 失败 → 报错或重试
问题(非常明显)
- 格式极不稳定:经常加自然语言、注释、错别字、格式残缺
- 容易 “幻觉”:编不存在的 action/params
- 复杂意图识别很差:“我明天去上海出差要带伞吗?” 经常不触发天气
- 工具多了 Prompt 巨长
Function Calling时代
最早是由openai推出的标准,也是后来所有厂商的参照原型。
接口形态
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-3.5-turbo-0613",
"messages": [
{"role": "user", "content": "波士顿天气怎么样?"}
],
"functions": [
{
"name": "get_current_weather",
"description": "获取指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市,如波士顿"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"]
}
}
]
}'
模型返回
{
"choices": [
{
"message": {
"role": "assistant",
"content": null,
"function_call": {
"name": "get_current_weather",
"arguments": "{\n \"location\": \"波士顿\"\n}"
}
},
"finish_reason": "function_call"
}
]
}
- 定义函数:用 JSON Schema 告诉模型有哪些工具、参数、含义
- 模型决策:
- 判断是否需要调用
- 选哪个函数
- 生成合法参数 JSON
- 执行调用:后端本地 / 远程执行函数
- 结果回传:把函数结果放回
messages再请求模型 - 生成回答:模型用工具结果生成自然语言回复
MCP到底是什么?
不同厂商的大模型,调用工具的方式五花八门,就像“前USB时代”每一家都有自己单独的充电线,让开发者头疼不已。
而MCP的出现,就相当于给大模型世界制定了“USB通用接口”,彻底解决了这个痛点

MCP的全称是Model Context Protocol,也就是模型上下文协议,是Anthropic在2024年11月25日正式推出并开源的一套标准化协议。它不是一个新的大模型,也不是一个工具,而是一套“规则”——用来统一大模型与外部工具、系统之间的交互规范,让不同厂商的大模型、不同类型的工具,都能“听懂彼此的话”。
简单说,MCP的核心目标就一个:让Agent(智能体)不用再挨个适配不同大模型的调用格式,一套接口通吃所有大模型,大大降低开发和适配成本。
MCP通信机制

MCP协议支持两种主要的通信机制:基于标准输入输出stdio的本地通信和基于SSE(Server-SentEvents)的远程通信。这两种机制都使用JSON-RPC 2.0格式进行消息传输,确保了通信的标准化和可扩展性。
- 本地通信:通过stdio传输数据,适用于在同一台机器上运行的客户端和服务器之间的通信。
- 远程通信:利用SSE与HTTP结合,实现跨网络的实时数据传输,适用于需要访问远程资源或分布式部署的场景。
MCP Server

MCP Server是 MCP架构中的关键组件,它可以提供3种主要类型的功能:
- 资源(Resources):类似文件的数据,可以被客户端读取,如API响应或文件内容。
- 工具(Tools):可以被LLM调用的函数(需要用户批准)。
- 提示(Prompts):预先编写的模板,帮助用户完成特定任务。
这些功能使MCP server能够为Al应用提供丰富的上下文信息和操作能力,从而增强LLM的实用性和灵活性。
mcp服务端开发
网上的mcp工具,一般都是js和python的比较多
本课程主要以go为主,顺带介绍一下python和js的mcp服务端编写,原理是相通的
go mcp服务端
以go语言为例,可以使用github.com/mark3labs/mcp-go这个库完成mcp适配
stdio本地通信模式
package main
import (
"context"
"fmt"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
// 创建一个mcp服务
s := server.NewMCPServer(
"我的mcp服务",
"1.0.0",
server.WithToolCapabilities(false),
)
// 注册工具
s.AddTool(mcp.NewTool("hello_world",
mcp.WithDescription("工具描述"),
mcp.WithString("name",
mcp.Required(),
mcp.Description("工具参数的描述"),
),
), helloHandler)
err := server.ServeStdio(s)
if err != nil {
fmt.Println("stdio监听失败", 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
}
写完之后,需要编译成可执行文件
配置文件
在cc switch里面加上mcp配置
{
"command": "E:\\IT\\fengfeng\\test\\mcp_study_go\\mcp_server_stdio.exe",
"type": "stdio"
}
在apifox里面,直接写 "E:\IT\fengfeng\test\mcp_study_go\mcp_server_stdio.exe" 就可以连接mcp了
stdio只能在本地运行,靠标准输入输出进行进程通信
sse模式
如果你想给你的网站加上scp服务,你本地的agent想调用远端的scp服务
就需要sse模式
package main
import (
"context"
"fmt"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"net/http"
)
func main() {
// 创建一个mcp服务
s := server.NewMCPServer(
"我的mcp服务",
"1.0.0",
server.WithToolCapabilities(false),
)
// 注册工具
s.AddTool(mcp.NewTool("hello_world",
mcp.WithDescription("工具描述"),
mcp.WithString("name",
mcp.Required(),
mcp.Description("工具参数的描述"),
),
), helloHandler)
sse := server.NewStreamableHTTPServer(s)
// Start the stdio server
fmt.Println("mcp server on :8080")
err := http.ListenAndServe(":8080", sse)
if err != nil {
fmt.Println("端口监听失败", err)
}
}
func helloHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
fmt.Printf("工具调用 %#v\n", request)
name, err := request.RequireString("name")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
return mcp.NewToolResultText(fmt.Sprintf("Hello, %s!", name)), nil
}
http模式有两种,一个是sse,一个是stream,需要注意一下
| 特性 | NewStreamableHTTPServer(推荐) | NewSSEServer(旧版 / 兼容) |
|---|---|---|
| 标准 | MCP 官方最新标准 | 早期草案版本 |
| 路径 | 固定/mcp |
固定/sse |
| 支持方法 | POST + GET(SSE) | 仅 GET (SSE) |
| 客户端兼容 | 所有新客户端(Apifox、Claude Code、Go Client) | 仅老客户端 |
如果是sse模式,连接的路径就是/sse,如果是http模式,连接的路径就是/mcp
配置文件
{
"headers": {
},
"type": "http",
"url": "http://127.0.0.1:8080/mcp"
}
python mcp服务
安装uv
windows用户,如果还没装过uv的话,可以尝试直接用winget安装
winget install uv
创建uv环境的python项目
uv init weather
cd weather
uv venv
.venv/bin/activate
uv add "mcp[cli]"
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("数学方法")
@mcp.tool()
async def add(a: int, b: int) -> int:
"""两数求和.
:param b: 需要求和的数
:param a: 需要求和的数
"""
return a + b
if __name__ == "__main__":
mcp.run(transport="stdio")
cc switch里面配置
{
"command": "python E:\\IT\\fengfeng\\test\\mcp_study\\mcp_study_python\\mcp_stdio.py",
"type": "stdio"
}
如果要用sse模式的话,这样改
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("数学方法", port=8081)
@mcp.tool()
async def add(a: int, b: int) -> int:
"""两数求和.
:param b: 需要求和的数
:param a: 需要求和的数
"""
return a + b
if __name__ == "__main__":
mcp.run(transport="streamable-http")
nodejs mcp服务
npm init -y
npm i @modelcontextprotocol/sdk
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
// 创建MCP服务器实例
const server = new Server(
{
name: "数学工具",
version: "1.0.0",
description: "一个用于数学运算的MCP服务器"
},
{
capabilities: {
tools: {},
},
}
);
// 定义工具列表
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "add",
description: "执行加法运算",
inputSchema: {
type: "object",
properties: {
a: {
type: "number",
description: "第一个加数"
},
b: {
type: "number",
description: "第二个加数"
}
},
required: ["a", "b"]
}
},
]
};
});
// 处理工具调用
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
let result;
switch (name) {
case "add":
result = args.a + args.b;
break;
}
return {
content: [
{
type: "text",
text: `计算结果: ${result}`
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `错误: ${error.message}`
}
],
isError: true
};
}
});
// 启动服务器
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Math MCP 服务器已启动");
}
main().catch(console.error);
cc switch里面配置
{
"command": "node",
"args": [
"E:\\IT\\fengfeng\\test\\mcp_study\\mcp_study_js\\mcp_stdio.js"
]
}
http的模式有点复杂
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
isInitializeRequest,
} from '@modelcontextprotocol/sdk/types.js';
import express from 'express';
// 为每个会话创建新的 MCP 服务器
function createServer() {
const server = new Server(
{
name: "数学工具",
version: "1.0.0",
description: "SSE 模式数学运算服务器"
},
{ capabilities: { tools: {} } }
);
// 注册工具
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "add",
description: "两个数字相加",
inputSchema: {
type: "object",
properties: {
a: { type: "number" },
b: { type: "number" }
},
required: ["a", "b"]
}
}
]
}));
// 处理工具调用
server.setRequestHandler(CallToolRequestSchema, async (req) => {
const { name, arguments: args } = req.params;
try {
if (name === "add") {
return {
content: [{ type: "text", text: `结果:${args.a + args.b}` }]
};
}
throw new Error(`未知工具:${name}`);
} catch (e) {
return {
content: [{ type: "text", text: `错误:${e.message}` }],
isError: true
};
}
});
return server;
}
// 存储 transports 和对应的 servers
const sessions = {};
const app = express();
// 解析 JSON 请求体
app.use(express.json());
// POST 处理 MCP 请求
app.post('/mcp', async (req, res) => {
console.log('Received MCP request:', req.body);
try {
const sessionId = req.headers['mcp-session-id'];
let session;
if (sessionId && sessions[sessionId]) {
// 复用现有 session
session = sessions[sessionId];
} else if (!sessionId && isInitializeRequest(req.body)) {
// 新初始化请求,创建新 session
const server = createServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(),
onsessioninitialized: (sid) => {
console.log(`Session initialized: ${sid}`);
sessions[sid] = session;
}
});
session = { transport, server };
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
return;
} else {
// 无效请求
res.status(400).json({
jsonrpc: '2.0',
error: { code: -32000, message: 'Bad Request: No valid session ID provided' },
id: null
});
return;
}
await session.transport.handleRequest(req, res, req.body);
} catch (error) {
console.error('Error:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: { code: -32603, message: 'Internal server error' },
id: null
});
}
}
});
// GET 处理 SSE 流
app.get('/mcp', async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
if (!sessionId || !sessions[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
console.log(`SSE stream for session ${sessionId}`);
await sessions[sessionId].transport.handleRequest(req, res);
});
// 启动服务器
const PORT = 3000;
app.listen(PORT, () => {
console.log("✅ MCP SSE 服务器运行成功");
console.log(`📡 连接地址:http://localhost:${PORT}/mcp`);
});
process.on('SIGINT', async () => {
console.log('Shutting down...');
for (const sessionId in sessions) {
await sessions[sessionId].transport.close();
delete sessions[sessionId];
}
process.exit(0);
});
补充:mcp的resources和prompts
很多基础课程里面没有提到的知识点
这两个东西用好之后,可以让你的mcp变得非常强大
- Resource(资源)=给 LLM 读数据的入口
- LLM 可以直接读取的 “文件 / 数据链接”
- 不用调用工具,直接
read就能拿内容 - 用途:读配置、读日志、读接口返回、读数据库结果、读文档
- Prompt(提示词模板)=LLM 可以直接调用的话术模板
就是你预先写好的专业提示词,比如:
- 代码审查模板
- 翻译模板
- 客服回复模板
- 数据分析模板
LLM 不需要你手动粘贴,直接调用这个 Prompt 模板,就能自动套用专业话术。
| 维度 | Tool(工具) | Resource(资源) | Prompt(提示模板) |
|---|---|---|---|
| 核心作用 | 执行动作、修改状态、产生结果 | 提供只读数据、补充上下文 | 提供专业话术 / 任务模板 |
| 谁来操作 | Agent 调用,MCP Server 执行 | Agent 读取,MCP Server 提供数据 | Agent 获取模板,交给 LLM 使用 |
| 有无副作用 | 有(可写、可删、可改、可调用外部) | 无(只读,不修改任何数据) | 无(仅提供文本模板) |
| 是否需要确认 | 需要(高风险操作) | 不需要(安全只读) | 不需要(无风险) |
| LLM 行为 | 决定 “调用哪个工具 + 传什么参数” | 决定 “读取哪个资源” | 决定 “使用哪个模板 + 填什么参数” |
| 典型场景 | 增删改查、执行命令、发送消息、写文件 | 读文件、读日志、读接口、读数据库、读文档 | 代码审查、翻译、总结、文案生成、专家角色 |
| 通信方法 | tools/call |
resources/read |
prompts/get |
| 风险等级 | 高 | 低 | 无 |
go语言 resources、prompts示例
package main
import (
"context"
"fmt"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"io"
"net/http"
"os"
)
func main() {
// 创建MCP服务
s := server.NewMCPServer(
"MCP三合一服务",
"1.0.0",
)
// ==============================================
// 1. 注册 Tool:传统工具调用
// ==============================================
s.AddTool(mcp.NewTool("hello_world",
mcp.WithDescription("打招呼工具"),
mcp.WithString("name", mcp.Required(), mcp.Description("姓名")),
), helloHandler)
// ==============================================
// 2. 注册 Resource:读取本地文件
// ==============================================
s.AddResource(mcp.NewResource(
"resource://local-file", // 资源URI(固定格式)
"本地配置文件", // 资源名称
mcp.WithResourceDescription("读取本地配置文件"),
), readLocalFile)
// ==============================================
// 3. 注册 Resource:读取远程接口
// ==============================================
s.AddResource(mcp.NewResource(
"resource://api-ip", // 资源URI
"IP信息接口", // 资源名称
mcp.WithResourceDescription("获取当前出口IP信息"),
), readRemoteAPI)
// ==============================================
// 4. 注册 Prompt:提示词模板(代码审查)
// ==============================================
s.AddPrompt(mcp.NewPrompt(
"code-review",
mcp.WithPromptDescription("专业Go代码审查,输出规范、bug、性能、安全"),
mcp.WithArgument("code",
mcp.ArgumentDescription("需要审查的代码内容"),
mcp.RequiredArgument(),
),
), codeReviewPromptHandler)
// --- 启动方式 1:stdio 本地模式 ---
// err := server.ServeStdio(s)
// --- 启动方式 2:SSE远程模式(推荐) ---
sseServer := server.NewStreamableHTTPServer(s)
fmt.Println("MCP server running on :8080")
err := http.ListenAndServe(":8080", sseServer)
if err != nil {
fmt.Println("启动失败:", err)
}
}
// ==============================================
// Tool 处理函数
// ==============================================
func helloHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
name, _ := request.RequireString("name")
return mcp.NewToolResultText(fmt.Sprintf("Hello %s!", name)), nil
}
// ==============================================
// Resource 处理函数:读取本地文件
// ==============================================
func readLocalFile(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
data, err := os.ReadFile("config.txt")
if err != nil {
return nil, err
}
return []mcp.ResourceContents{
&mcp.TextResourceContents{
Text: string(data),
},
}, nil
}
// ==============================================
// Resource 处理函数:读取远程接口
// ==============================================
func readRemoteAPI(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
resp, err := http.Get("https://httpbin.org/ip")
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
return []mcp.ResourceContents{
&mcp.TextResourceContents{
Text: string(body),
},
}, nil
}
// ==============================================
// Prompt 处理函数:代码审查模板
// ==============================================
func codeReviewPromptHandler(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
// 从请求参数中获取code参数
code, ok := request.Params.Arguments["code"]
if !ok || code == "" {
return nil, fmt.Errorf("code parameter is required")
}
promptText := fmt.Sprintf(`
你是专业的Go工程师,请审查以下代码:
--------------------------------
%s
--------------------------------
请输出:
1. 代码规范问题
2. 潜在Bug
3. 性能优化点
4. 安全风险
`, code)
return mcp.NewGetPromptResult(
"专业Go代码审查",
[]mcp.PromptMessage{
{
Role: mcp.RoleAssistant,
Content: mcp.NewTextContent(promptText),
},
},
), nil
}
有个问题,大模型更倾向于调用工具,需要一些特定的手段让大模型读资源
补充:mcp客户端开发
开发 MCP 服务端比较简单,因为不涉及大模型交互,只管根据输入返回输出就好了。
如果只是单纯开发 MCP 客户端,也很简单,只需要连接服务端、发起调用、获取结果就行。
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp"
)
func main() {
// 1. 创建 MCP 客户端(连接 SSE 服务端)
mcpClient, err := client.NewStreamableHttpClient(
"http://127.0.0.1:8080/mcp",
)
if err != nil {
panic(err)
}
// 2. 启动客户端
ctx := context.Background()
if err := mcpClient.Start(ctx); err != nil {
panic(err)
}
defer mcpClient.Close()
// 3. 初始化 MCP 协议握手
initReq := mcp.InitializeRequest{}
initReq.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initReq.Params.ClientInfo = mcp.Implementation{
Name: "my-mcp-client",
Version: "1.0.0",
}
_, err = mcpClient.Initialize(ctx, initReq)
if err != nil {
panic(fmt.Sprintf("初始化失败: %v", err))
}
fmt.Println("✅ MCP 初始化成功")
// 4. 获取服务端提供的工具列表
toolsResp, err := mcpClient.ListTools(ctx, mcp.ListToolsRequest{})
if err != nil {
panic(fmt.Sprintf("获取工具失败: %v", err))
}
fmt.Printf("🔧 可用工具: %d 个\n", len(toolsResp.Tools))
for _, tool := range toolsResp.Tools {
fmt.Printf(" - %s: %s\n", tool.Name, tool.Description)
}
resourcesResp, err := mcpClient.ListResources(ctx, mcp.ListResourcesRequest{})
if err != nil {
panic(fmt.Sprintf("获取资源失败: %v", err))
}
fmt.Printf("🔧 可用资源: %d 个\n", len(resourcesResp.Resources))
for _, resource := range resourcesResp.Resources {
fmt.Printf(" - %s(%s): %s\n", resource.Name, resource.URI, resource.Description)
}
promptsResp, err := mcpClient.ListPrompts(ctx, mcp.ListPromptsRequest{})
if err != nil {
panic(fmt.Sprintf("获取提示词失败: %v", err))
}
fmt.Printf("🔧 可用提示词: %d 个\n", len(promptsResp.Prompts))
for _, tool := range promptsResp.Prompts {
fmt.Printf(" - %s: %s\n", tool.Name, tool.Description)
}
// 5. 构造参数,调用工具(hello_world)
callReq := mcp.CallToolRequest{}
callReq.Params.Name = "hello_world"
callReq.Params.Arguments = map[string]any{
"name": "枫枫网盘",
}
// 调用 MCP Server 工具
result, err := mcpClient.CallTool(ctx, callReq)
if err != nil {
panic(fmt.Sprintf("调用失败: %v", err))
}
// 6. 输出结果
fmt.Println("\n📤 工具返回结果:")
data, _ := json.MarshalIndent(result, "", " ")
fmt.Println(string(data))
resource, err := mcpClient.ReadResource(ctx, mcp.ReadResourceRequest{
Params: mcp.ReadResourceParams{
URI: "resource://local-file",
},
})
if err != nil {
panic(fmt.Sprintf("调用失败: %v", err))
}
fmt.Println("\n📤 资源返回结果:")
data, _ = json.MarshalIndent(resource, "", " ")
fmt.Println(string(data))
}
读取资源这里有一个坑点,直接读取会报错,需要在服务端那边改一下
mcp-go库在设计客户端ReadResource的返回处理时,会将 JSON 映射回结构体。如果服务端返回的 JSON 缺失了uri,客户端 SDK 的内部校验逻辑(Validation)会认为这是一个非法或不完整的响应。
func readLocalFile(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
data, err := os.ReadFile("config.txt")
fmt.Println("读取了", string(data))
if err != nil {
return nil, err
}
return []mcp.ResourceContents{
&mcp.TextResourceContents{
// 关键点:显式赋值 URI
// 很多版本的客户端库会校验返回结果中是否包含原始 URI
URI: request.Params.URI,
Text: string(data),
},
}, nil
}
但是,真实场景里 MCP 客户端绝不会单独出现,它一定是和Agent(智能体)+ 大模型 一起工作的。
一旦结合大模型,流程就会复杂很多,因为客户端需要:
- 调用大模型对话
- 解析模型输出的工具调用意图
- 把意图转换成标准 MCP 协议请求
- 分别处理 Tool / Resource / Prompt 三种能力
- 把执行结果放回上下文,再交给大模型继续推理
结合大模型使用mcp客户端
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp"
openai "github.com/sashabaranov/go-openai"
"io"
)
func main() {
// ======================
// 1. 初始化 MCP 客户端
// ======================
client, err := client.NewStreamableHttpClient("http://127.0.0.1:8080/mcp")
if err != nil {
panic(err)
}
ctx := context.Background()
_ = client.Start(ctx)
defer client.Close()
// 握手
initReq := mcp.InitializeRequest{}
initReq.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initReq.Params.ClientInfo = mcp.Implementation{Name: "go-agent", Version: "1.0.0"}
_, _ = client.Initialize(ctx, initReq)
// ======================
// 2. 获取 MCP 工具列表
// ======================
toolsResp, err := client.ListTools(ctx, mcp.ListToolsRequest{})
if err != nil {
fmt.Println(err)
return
}
openAITools := convertToOpenAITools(toolsResp.Tools)
// ======================
// 3. 调用大模型
// ======================
userQuery := "请帮我对 枫枫知道 打个招呼"
config := openai.DefaultConfig("") // api-key
config.BaseURL = "https://open.bigmodel.cn/api/paas/v4/" // 填openai格式的地址
clientOpenAI := openai.NewClientWithConfig(config)
stream, err := clientOpenAI.CreateChatCompletionStream(
context.Background(),
openai.ChatCompletionRequest{
Model: "GLM-4.7",
Messages: []openai.ChatCompletionMessage{{Role: "user", Content: userQuery}},
Tools: openAITools, // 把 MCP 工具喂给大模型
Stream: true,
},
)
if err != nil {
panic(err)
}
defer stream.Close()
// ======================
// 5. 接收流式输出
// ======================
fmt.Println("\n🤖 大模型流式输出:")
fullText := ""
var toolCall *openai.ToolCall
for {
recv, err := stream.Recv()
if err == io.EOF {
fmt.Println(err)
break
}
if err != nil {
fmt.Println(err)
panic(err)
}
delta := recv.Choices[0].Delta
if delta.Content != "" {
fullText += delta.Content
fmt.Print(delta.Content) // 实时打印
}
// 提取工具调用
if len(delta.ToolCalls) > 0 {
toolCall = &delta.ToolCalls[0]
}
}
fmt.Println("\n\n📝 完整回答:", fullText)
if toolCall == nil {
return
}
// ======================
// 6. 解析并调用 MCP 工具
// ======================
fmt.Println("\n🔧 模型决定调用工具:", toolCall.Function.Name)
var args map[string]any
_ = json.Unmarshal([]byte(toolCall.Function.Arguments), &args)
req := mcp.CallToolRequest{}
req.Params.Name = toolCall.Function.Name
req.Params.Arguments = args
// 执行 MCP
result, err := client.CallTool(ctx, req)
if err != nil {
panic(err)
}
fmt.Println("✅ MCP 返回结果:", result.Content)
}
// ======================
// 工具转换:MCP → OpenAI 格式
// ======================
func convertToOpenAITools(mcpTools []mcp.Tool) []openai.Tool {
var tools []openai.Tool
for _, t := range mcpTools {
tool := openai.Tool{
Type: openai.ToolTypeFunction,
Function: &openai.FunctionDefinition{
Name: t.Name,
Description: t.Description,
Parameters: t.InputSchema,
},
}
tools = append(tools, tool)
}
return tools
}
扩展——抓包分析agent与mcp服务端的通信
用wireshark抓到127.0.0.1:8080端口的流量
分析agent到mcp服务的通信过程
sequenceDiagram
title MCP Agent 与 MCP Server 通信时序图
participant Agent as Agent (Claude Code)
participant Server as MCP Server (127.0.0.1:8080)
%% 初始化阶段
rect rgb(240, 248, 255)
Note over Agent,Server: 阶段一:初始化 (Initialization)
Agent->>+Server: POST /mcp<br/>initialize<br/>{protocolVersion, capabilities, clientInfo}
Server-->>-Agent: 200 OK<br/>{protocolVersion, capabilities, serverInfo}<br/>Mcp-Session-Id
Agent->>Server: POST /mcp<br/>notifications/initialized
Server-->>Agent: 202 Accepted
Note over Agent,Server: 建立 SSE 长连接用于接收服务端消息
Agent->>+Server: GET /mcp (text/event-stream)
Server-->>-Agent: 200 OK (持续连接中...)
end
%% 工具发现阶段
rect rgb(255, 250, 240)
Note over Agent,Server: 阶段二:工具发现 (Tool Discovery)
Agent->>+Server: POST /mcp<br/>tools/list
Server-->>-Agent: 200 OK<br/>{tools: [{name: "add", ...}]}
end
%% 工具调用阶段
rect rgb(240, 255, 240)
Note over Agent,Server: 阶段三:工具调用 (Tool Invocation)
Agent->>+Server: POST /mcp<br/>tools/call<br/>{name: "add", arguments: {a: 111233101, b: 34423}}
Server-->>-Agent: 200 OK<br/>{content: [{type: "text", text: "111267524"}]}
end
初始化
POST /mcp HTTP/1.1
host: 127.0.0.1:8080
connection: keep-alive
accept: application/json, text/event-stream
content-type: application/json
user-agent: claude-code/2.1.92 (cli)
accept-language: *
sec-fetch-mode: cors
accept-encoding: gzip, deflate
content-length: 304
{"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{"roots":{},"elicitation":{}},"clientInfo":{"name":"claude-code","title":"Claude Code","version":"2.1.92","description":"Anthropic's agentic coding tool","websiteUrl":"https://claude.com/claude-code"}},"jsonrpc":"2.0","id":0}
HTTP/1.1 200 OK
Content-Type: application/json
Mcp-Session-Id: mcp-session-05a1cd0c-5dac-407d-8ab6-4ec289d6df7e
Date: Thu, 09 Apr 2026 01:49:45 GMT
Content-Length: 150
{"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-11-25","capabilities":{"tools":{}},"serverInfo":{"name":"............","version":"1.0.0"}}}
POST /mcp HTTP/1.1
host: 127.0.0.1:8080
connection: keep-alive
accept: application/json, text/event-stream
content-type: application/json
mcp-protocol-version: 2025-11-25
mcp-session-id: mcp-session-05a1cd0c-5dac-407d-8ab6-4ec289d6df7e
user-agent: claude-code/2.1.92 (cli)
accept-language: *
sec-fetch-mode: cors
accept-encoding: gzip, deflate
content-length: 54
{"method":"notifications/initialized","jsonrpc":"2.0"}
HTTP/1.1 202 Accepted
Date: Thu, 09 Apr 2026 01:49:45 GMT
Content-Length: 0
GET /mcp HTTP/1.1
host: 127.0.0.1:8080
connection: keep-alive
accept: text/event-stream
mcp-protocol-version: 2025-11-25
mcp-session-id: mcp-session-05a1cd0c-5dac-407d-8ab6-4ec289d6df7e
user-agent: claude-code/2.1.92 (cli)
accept-language: *
sec-fetch-mode: cors
accept-encoding: gzip, deflate
HTTP/1.1 200 OK
Cache-Control: no-cache
Connection: keep-alive
Content-Type: text/event-stream
Date: Thu, 09 Apr 2026 01:49:45 GMT
Transfer-Encoding: chunked
查询工具列表
POST /mcp HTTP/1.1
host: 127.0.0.1:8080
connection: keep-alive
accept: application/json, text/event-stream
content-type: application/json
mcp-protocol-version: 2025-11-25
mcp-session-id: mcp-session-05a1cd0c-5dac-407d-8ab6-4ec289d6df7e
user-agent: claude-code/2.1.92 (cli)
accept-language: *
sec-fetch-mode: cors
accept-encoding: gzip, deflate
content-length: 46
{"method":"tools/list","jsonrpc":"2.0","id":1}
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 09 Apr 2026 01:49:45 GMT
Content-Length: 352
{"jsonrpc":"2.0","id":1,"result":{"tools":[{"annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":true},"description":"............","inputSchema":{"properties":{"a":{"description":"......","type":"number"},"b":{"description":"......","type":"number"}},"required":["a","b"],"type":"object"},"name":"add"}]}}
调用时
POST /mcp HTTP/1.1
host: 127.0.0.1:8080
connection: keep-alive
accept: application/json, text/event-stream
content-type: application/json
mcp-protocol-version: 2025-11-25
mcp-session-id: mcp-session-05a1cd0c-5dac-407d-8ab6-4ec289d6df7e
user-agent: claude-code/2.1.92 (cli)
accept-language: *
sec-fetch-mode: cors
accept-encoding: gzip, deflate
content-length: 191
{"method":"tools/call","params":{"name":"add","arguments":{"a":111233101,"b":34423},"_meta":{"claudecode/toolUseId":"call_7a461a0e4a9b4fb0b010c197","progressToken":2}},"jsonrpc":"2.0","id":2}
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 09 Apr 2026 02:00:54 GMT
Content-Length: 83
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"111267524"}]}}
扩展——agent与大模型的通信
因为很多模型都是https的端点,直接抓包都是加了密的,没法看
不过我发现智谱的目标,可以http访问,我们可以直接抓出口网卡到智谱对应ip的流量,就可以直接看到明文数据了
如果目标只能https访问,那就只能自己写一个http代理,抓到代理的流量,这样也可以看到明文数据
package main
import (
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
"time"
)
// handleHTTP 处理 HTTP 代理请求
func handleHTTP(w http.ResponseWriter, r *http.Request) {
client := &http.Client{
Timeout: 30 * time.Second,
}
// 创建新的请求,从原始请求读取 body
req, err := http.NewRequest(r.Method, r.URL.String(), r.Body)
if err != nil {
http.Error(w, "创建请求失败: "+err.Error(), http.StatusInternalServerError)
return
}
req.Header = r.Header.Clone()
// Host 由 http.NewRequest 从 URL 自动处理
resp, err := client.Do(req)
if err != nil {
http.Error(w, "代理请求失败: "+err.Error(), http.StatusInternalServerError)
log.Println("HTTP转发错误:", err)
return
}
defer resp.Body.Close()
// 将目标服务器响应复制回客户端
for k, v := range resp.Header {
w.Header()[k] = v
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
}
// handleHTTPS 处理 HTTPS CONNECT 请求(建立隧道)
func handleHTTPS(w http.ResponseWriter, r *http.Request) {
log.Printf("CONNECT 请求: Host=%s", r.Host)
// 连接目标服务器
destConn, err := net.DialTimeout("tcp", r.Host, 10*time.Second)
if err != nil {
log.Printf("连接目标服务器失败: %v", err)
http.Error(w, "连接目标服务器失败: "+err.Error(), http.StatusServiceUnavailable)
return
}
// 劫持 HTTP 连接,直接进行 TCP 数据转发
hijacker, ok := w.(http.Hijacker)
if !ok {
log.Println("不支持 Hijack")
destConn.Close()
http.Error(w, "不支持 Hijack", http.StatusInternalServerError)
return
}
clientConn, _, err := hijacker.Hijack()
if err != nil {
log.Printf("劫持连接失败: %v", err)
destConn.Close()
http.Error(w, "劫持连接失败: "+err.Error(), http.StatusServiceUnavailable)
return
}
// 发送标准的 CONNECT 200 响应
_, err = clientConn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n"))
if err != nil {
log.Printf("发送 CONNECT 响应失败: %v", err)
clientConn.Close()
destConn.Close()
return
}
log.Printf("CONNECT 隧道已建立,开始转发数据")
// 设置读写超时,防止连接被长时间占用
_ = clientConn.SetDeadline(time.Now().Add(60 * time.Second))
_ = destConn.SetDeadline(time.Now().Add(60 * time.Second))
// 双向转发数据
done := make(chan struct{})
go func() {
io.Copy(destConn, clientConn)
destConn.Close()
close(done)
}()
io.Copy(clientConn, destConn)
clientConn.Close()
<-done
}
// proxyHandler 代理入口
func proxyHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("请求: Method=%s Host=%s RemoteAddr=%s", r.Method, r.Host, r.RemoteAddr)
if r.Method == http.MethodConnect {
handleHTTPS(w, r)
} else {
handleHTTP(w, r)
}
}
func main() {
port := flag.Int("port", 17890, "代理服务器监听端口")
flag.Parse()
listenAddr := fmt.Sprintf("0.0.0.0:%d", *port)
log.Println("代理服务器启动,监听地址:", listenAddr)
err := http.ListenAndServe(listenAddr, http.HandlerFunc(proxyHandler))
if err != nil {
log.Fatal("代理服务启动失败:", err)
}
}
然后把它配置到cc switch的代理上

然后就可以用wireshark抓127.0.0.1:17890上的流量了

扩展——好用mcp服务分享
可以在这些网站搜自己需要的mcp服务
mcp市场:https://app-7z9xujpw4t1d.appmiaoda.com/marketplace
mcp盒子:https://mcpbox.net/
mcp搜索:https://mcps.live/
mcpso:https://mcp.so/
阿里云mcp:https://bailian.console.aliyun.com/cn-beijing?tab=mcp#/mcp-market
github上的mcp广场:https://github.com/punkpeye/awesome-mcp-servers
好用的mcp推荐
- MCP Server Playwright
https://github.com/VikashLoomba/MCP-Server-Playwright?tab=readme-ov-file#tools
浏览器自动化的mcp,可以直接操作浏览器,绝对夯爆了,为AI助手提供完整的浏览器控制能力。这个服务适合需要AI执行复杂网页交互任务的场景,如自动化测试、数据采集、表单填写等。
- GitHub MCP Server
GitHub MCP Server是开发者使用最广泛的MCP服务之一,它允许AI助手直接与GitHub仓库进行交互。通过这个服务,用户可以自动化代码审查、处理拉取请求、管理代码仓库等操作。开发者只需用自然语言描述需求,AI就能自动执行相应的GitHub操作,例如创建分支、提交代码、审核PR等
- PostgreSQL MCP Server
PostgreSQL MCP Server为AI助手提供了直接查询和管理PostgreSQL数据库的能力。用户可以通过自然语言描述数据查询需求,AI自动生成SQL语句并执行查询。这个服务对于需要频繁与数据库交互的数据分析师、后端开发者来说非常实用,能够显著减少编写SQL语句的时间,同时提高查询的准确性。
参考文档
mcp中文站 https://mcpcn.com/