当前位置:首页>python>MCP 到底是个啥,我亲手用 Go 和 Python 各接了一遍

MCP 到底是个啥,我亲手用 Go 和 Python 各接了一遍

  • 2026-09-03 21:28:59
MCP 到底是个啥,我亲手用 Go 和 Python 各接了一遍

前阵子 Agent 这词满天飞,配套总提一个词:MCP。我本职是写 PHP 的,一开始懒得看文档,后来实在绕不开,干脆自己用 Go 写一个小 server、再用 Python 写一份做对比,才真正弄懂这玩意儿到底解决了啥。

一句话说清 MCP

说白了,MCP 就是给大模型接外部工具的"标准插头"。以前你想让 AI 查数据库、读文件,得自己写一堆胶水代码;MCP 把"工具怎么描述、怎么调用"这套东西统一了,谁都能照着协议挂一个。官方常把它比喻成"AI 的 USB-C 口"——插上了,模型就能用你的数据和能力。

协议长啥样(不依赖 SDK 也看得懂)

据 2026 年官方文档,MCP 当前协议版本是 2025-11-25,所有消息都基于 JSON-RPC 2.0。它定义了工具(tools)、资源(resources)、提示词(prompts)、采样(sampling)等几类能力。客户端和服务器之间靠两种传输方式通信:一种是 stdio(标准输入输出),客户端把 server 当子进程拉起来,JSON-RPC 消息走 stdin/stdout,按行分隔、不能夹换行;另一种是 HTTP + SSE。文档里还特意提醒:stdio 模式下 server 往 stdout 写的每条消息都必须是合法 MCP 报文,不能顺手塞点调试日志进去,不然客户端就解析崩了。握手阶段客户端先发 initialize、再拉 tools/list,调用时走 tools/call,这套生命周期是固定的。

我用 Go 写个 server

我们后端是 Go 和 PHP 混着用,我就用 Go 起了一个进程,走默认的 stdio 传输,暴露一个工具给客户端调用。这里用的是社区里较常用的 mark3labs/mcp-go 这个库(go get github.com/mark3labs/mcp-go),它把 JSON-RPC 和生命周期管理都封装好了,我只需关心业务逻辑。

go
package main
import (
    "context"
    "fmt"
    "github.com/mark3labs/mcp-go/mcp"
    "github.com/mark3labs/mcp-go/server"
    "github.com/shirou/gopsutil/v3/load"
    "github.com/shirou/gopsutil/v3/mem"
)
func main() {
    // 起一个 MCP server,名字和版本随便填
    s := server.NewMCPServer("server-watch", "1.0.0",
        server.WithToolCapabilities(true))
    // 定义一个工具,description 是模型判断"要不要调你"的依据
    tool := mcp.NewTool("server_load",
        mcp.WithDescription("当有人问线上机器卡不卡、压力大不大时调用,返回当前 CPU 负载和内存占用"),
    )
    s.AddTool(tool, serverLoadHandler)
    // 走 stdio:客户端拉起本进程,通过 stdin/stdout 通信
    if err := server.ServeStdio(s); err != nil {
        fmt.Printf("server error: %v\n", err)
    }
}
func serverLoadHandler(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
    cpu, _ := load.Avg()          // 1 分钟平均负载
    ram, _ := mem.VirtualMemory() // 内存占用百分比
    text := fmt.Sprintf("CPU 1分钟负载=%.2f,内存已用=%.1f%%", cpu.Load1, ram.UsedPercent)
    return mcp.NewToolResultText(text), nil
}

编译出来是个单文件二进制,丢给支持 MCP 的客户端(比如 Claude Desktop、Cursor)配一下启动命令就能用。Go 的 goroutine 模型对并发工具调用也友好,单进程扛多个会话压力不大。

再用 Python 写一遍做对比

Python 这边我用了 FastMCP。这个库挺有意思:它 1.0 版在 2024 年被并进了官方 MCP Python SDK,现在的 2.x 由 Prefect 团队维护,是 Python 里搭 MCP server 的常见选择之一。写法比 Go 更轻——一个装饰器就搞定。

python
from fastmcp import FastMCP
import psutil
mcp = FastMCP("server-watch")
@mcp.tool()
def server_load() -> str:
    """当有人问线上机器卡不卡、压力大不大时调用,返回当前 CPU 负载和内存占用。"""
    try:
        cpu = psutil.getloadavg()[0]
        ram = psutil.virtual_memory().percent
    except Exception as e:
        return f"取负载失败: {e}"
    return f"CPU 1分钟负载={cpu:.2f},内存已用={ram:.1f}%"
if __name__ == "__main__":
    mcp.run()  # 默认 stdio 传输

两种写法对比很明显:Go 胜在单文件部署、类型安全、生产环境稳;Python 胜在几行就能跑通,适合快速验证想法。对我们这种 PHP 栈的小团队,挂个内部工具成本一下低了——哪天真要上线,把 Python 原型照着 Go 版重写一遍就行。

客户端怎么把工具用起来

在支持 MCP 的客户端里填上这个 server 的启动命令(比如 go run main.go 或 python server.py),客户端启动后自动发起 initialize 握手、拉到 server_load 这个工具列表。之后我问"线上机器现在压力大吗",客户端把问题加工具列表发给模型,模型决定调 server_load,拿到数值再组织成回答。整个过程我一行胶水代码都没写。

工具描述怎么写才不翻车

踩过挺坑的一处是工具描述。description 这一句不是写给人看的,是写给模型看的——它全靠这段描述决定"什么时候该调你"。我初版只写了句"获取负载",结果它经常乱调;改成"当有人问服务器卡不卡、压力大不大时调用",命中率立马高了不少。描述里尽量带上触发场景,比光写功能名管用。

几个实操提醒

- stdio 模式下别往 stdout 乱写日志,需要调试就写到 stderr,否则客户端解析会挂。

- 工具返回的字段挑关键的回,别把一整坨系统信息塞进去,模型消化不了还费 token。

- 同一份工具逻辑,Go 和 Python 都能挂,选你团队顺手的语言就行,协议本身是语言无关的。

所以 MCP 对我意味着啥

以前接一个新数据源,我得给每个 AI 客户端单独适配;现在按 MCP 写一次,谁支持谁就能用。对我们这种 PHP 栈的小团队,挂个内部工具成本一下低了。它没那么玄,就是个把"模型"和"我的系统"连起来的标准接口。

你用过 MCP 吗?拿 Go 还是 Python 写的,评论区聊聊你踩过的坑。

最新文章

随机文章