写一个 MCP 服务,让 AI 调用你的工具

MCP 的核心是把你的能力标准化成 AI 能理解的工具。这篇从写第一个工具,到部署成网络服务、让别人和别的 AI 都能调用,走一遍完整流程。

写一个 MCP 服务,让 AI 调用你的工具

MCP(Model Context Protocol)现在谈的人很多,但大部分文章停在”它是什么”。这篇假设你已经知道概念了,讲的是落地时真正会遇到的问题:写一个 MCP 服务,怎么测试,怎么部署,怎么让别人和别的 AI 调到。

先把心法说清楚

MCP 服务的核心是编写一个或多个工具(Tool),让 AI 能调用它们完成任务。

“工具”这个词容易让人想复杂。它就是一个带类型签名的函数,外加一段给模型看的描述。模型读描述来决定什么时候调用、传什么参数,所以描述写得好不好,直接决定你的工具会不会被用到

选语言的话:想快速跑通用 Python 的 FastMCP;强类型需求或者团队是 TS 栈,用官方 TypeScript SDK;Go 和 Java 也都有官方 SDK(Java 走 Spring AI MCP,适合 Spring 技术栈)。

第一个工具

用 FastMCP 十几行就能跑起来:

from fastmcp import FastMCP

mcp = FastMCP(name="My First MCP Server")

@mcp.tool
def say_hello(name: str) -> str:
    """向用户说你好"""
    return f"Hello, {name}!"

if __name__ == "__main__":
    mcp.run()

注意 docstring 那一行。"""向用户说你好""" 不是注释,是给模型看的工具说明。模型根据这段文字判断这个工具是干嘛的、什么时候该用。写得含糊,模型要么不用它,要么在错误的场景用。

写完之后别急着接客户端,先过一遍官方的调试工具:

npx @modelcontextprotocol/inspector node build/index.js

Inspector 是个交互式界面,能看到你的工具列表、参数 schema,还能直接调用看返回。在接任何客户端之前先在这里跑通,能省掉后面大量的扯皮——工具定义有问题的时候,客户端只会报一堆看不懂的错误,而 Inspector 会告诉你到底哪里不对。

部署:从本地到让别人调用

这是 MCP 最容易迷糊的地方,因为调用方式决定了部署方式。分三个阶段,能力逐级增强。

阶段一:本地客户端,用 stdio

给 Claude Desktop、Cursor 这类本地客户端用,走 stdio(标准输入输出),不存在远程调用。客户端通过配置文件直接拉起你的进程:

{
  "mcpServers": {
    "my-tools": {
      "command": "python",
      "args": ["path/to/your_mcp_server.py"]
    }
  }
}

这一步零部署。但它的局限也明显:只有本机这个客户端能用。

阶段二:部署成网络服务,HTTP

要让别的开发者、或者网页端的 AI 应用调用,就得走网络。MCP 官方现在的推荐传输方式是 Streamable HTTP,较早的 SSE 方式已经弃用——网上很多教程还在教 SSE,注意别照着老文章配。

Python 侧改动很小:

mcp.run(transport="streamable-http", host="0.0.0.0")

跑起来之后把它扔到有公网 IP 的服务器上(容器化托管到云平台,或者一台 VPS)就行。

这里有个很多文章不讲的路子:如果你已经有一堆现成的 HTTP API,不一定非要重写成 MCP 服务。用 Higress 这类网关可以把存量 API”包装”成符合 MCP 标准的接口,顺便把鉴权、限流、用户体系这些企业里绕不开的东西一起处理掉。重写和包装之间,大部分场景应该选包装。

阶段三:注册中心

MCP 服务数量多了之后会遇到管理问题:调用方怎么找到服务?地址变了怎么办?

解法是引入注册中心,比如 Nacos。服务启动时把自己注册上去,调用方通过 Nacos 查询地址。这就把静态配置变成了动态发现,服务可以随意扩缩容和迁移。

这一步不是一开始就需要的。个人项目用阶段二就够了,别为了架构而架构。

客户端怎么调

部署好之后,调用方有三种接法。

最省事:直接配进 AI 客户端。 上面那个 claude_desktop_config.json 就是。适合自用。

Python 调远程服务

from py_mcp_client import MCPClient

client = MCPClient("http://your-server.com/sse")
client.connect()
init_response, tools = client.initialize()
result = client.run_tool("say_hello", {"name": "World"})

TypeScript 调本地 stdio 服务

import { MCPClient } from "mcp-client";

const client = new MCPClient({ name: "MyClient", version: "1.0.0" });
await client.connect({ type: "stdio", command: "python", args: ["your_mcp_server.py"] });
const result = await client.callTool({ name: "say_hello", arguments: { name: "MCP" } });

从零到一的完整清单

把上面压缩成一个可执行的顺序:

  1. 选语言:原型用 Python,正式项目看团队栈
  2. 装 SDK:pip install fastmcpnpm install @modelcontextprotocol/sdk
  3. 写工具,认真写每个工具的描述
  4. 用 Inspector 本地跑通,确认工具列表和调用结果都对
  5. 选部署方式:仅自用走 stdio,要远程走 streamable-http
  6. 有存量 REST API 的话,评估用网关包装而不是重写
  7. 接客户端:配置文件或者 Client SDK

几个我踩过的点

描述写不好,工具就是死的。 我写过一个工具,描述里只写了”查询数据”,结果模型在明确应该调用它的场景里从来不用。把描述改成”查询用户的订单记录,传入用户 ID 和时间范围,返回订单列表”之后,调用率立刻正常。这十几二十个字的差别,比换什么模型都大。

stdio 和 HTTP 的工具定义是同一份,但调试方式完全不同。 stdio 出问题可以先怀疑传输层,HTTP 出问题先看网络和鉴权。别混着调。

别急着上注册中心。 我见过两个服务就上 Nacos 的。先跑起来,等服务数量和多团队协作真的成为痛点再说。

MCP 官方资料在 modelcontextprotocol.io,协议规范和安全指南都在那里。找现成的服务参考可以搜 “awesome-mcp-servers”。

奇妙感 本文采用署名-非商业性使用-相同方式共享协议,转载请注明出处与作者。

目录