上周 Finix 发了个公告:他们的支付 API 现在可以通过 MCP 直接在 ChatGPT、Claude、Gemini 里调用了。开发者在聊天窗口里说一句"帮我查一下昨天的交易流水",AI 就能直接调 Finix 的 API 返回结果。

说白了,MCP 正在变成 AI 调用外部服务的标准协议。不光是 Finix,GitHub 也上线了官方 MCP Registry,各种数据库、云服务、内部工具都在往 MCP 上靠。

问题来了——你自己的 API 怎么办?

下面不讲概念,直接上手。我会用一个真实场景(封装一个 TODO 待办事项 API)演示怎么用 Python 写一个 MCP Server,让 Claude Code 或 Cursor 在对话中直接调用你的接口。

你需要准备什么

  • Python 3.10+
  • 一个终端
  • Claude Code 或 Cursor(用来测试连接)

安装依赖只需要一行:

pip install "mcp[cli]" httpx

mcp[cli] 是 Anthropic 官方的 Python SDK,自带一个 dev inspector 方便调试。httpx 用来发 HTTP 请求,比 requests 多了个原生异步支持。

先跑通最小 demo

创建 server.py

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("my-api-tools")

@mcp.tool()
def ping() -> str:
    """检查服务是否存活。"""
    return "pong"

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

这 8 行代码就是一个合法的 MCP Server。运行 mcp dev server.py,浏览器会弹出一个调试界面,你能看到 ping 工具已经注册好了,点一下就能调用。

几个注意点:

  • @mcp.tool() 装饰器把普通函数注册为 MCP 工具,函数名就是工具名
  • 函数的 type hint 会被 SDK 自动转成 JSON Schema,AI 读这个 schema 决定传什么参数
  • docstring 是工具描述,Claude 靠它判断什么时候该调这个工具。写得烂,AI 就不知道该不该用

封装一个真实的 REST API

光写 ping 没啥用。假设你有一个待办事项 REST API 跑在 http://localhost:8000,接口长这样:

接口 方法 说明
/todos GET 获取全部待办
/todos POST 新建待办
/todos/{id} PUT 更新状态
/todos/{id} DELETE 删除待办

我们要做的事情很直白:给每个接口写一个对应的 MCP tool。

import httpx
from mcp.server.fastmcp import FastMCP

API_BASE = "http://localhost:8000"
mcp = FastMCP("todo-mcp")

@mcp.tool()
async def list_todos() -> str:
    """获取所有待办事项,返回 JSON 列表。"""
    async with httpx.AsyncClient(timeout=10) as client:
        resp = await client.get(f"{API_BASE}/todos")
        resp.raise_for_status()
        return resp.text

@mcp.tool()
async def add_todo(title: str, priority: str = "medium") -> str:
    """新建一条待办事项。

    Args:
        title: 待办内容
        priority: 优先级,可选 low / medium / high
    """
    async with httpx.AsyncClient(timeout=10) as client:
        resp = await client.post(
            f"{API_BASE}/todos",
            json={"title": title, "priority": priority}
        )
        resp.raise_for_status()
        return resp.text

@mcp.tool()
async def complete_todo(todo_id: int) -> str:
    """把一条待办标记为已完成。

    Args:
        todo_id: 待办事项的 ID
    """
    async with httpx.AsyncClient(timeout=10) as client:
        resp = await client.put(
            f"{API_BASE}/todos/{todo_id}",
            json={"completed": True}
        )
        resp.raise_for_status()
        return resp.text

@mcp.tool()
async def delete_todo(todo_id: int) -> str:
    """删除一条待办事项。

    Args:
        todo_id: 待办事项的 ID
    """
    async with httpx.AsyncClient(timeout=10) as client:
        resp = await client.delete(f"{API_BASE}/todos/{todo_id}")
        resp.raise_for_status()
        return resp.text

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

50 行出头。每个函数做的事情都一样:接收参数,调 REST API,拿到结果返回给 AI。

写法上有几个细节。

参数的 type hint 必须写。 MCP SDK 拿 type hint 生成 JSON Schema,没有 hint 的参数 AI 不知道该传什么类型。priority: str = "medium" 这种写法,SDK 会自动把 medium 设为默认值。

docstring 决定 AI 会不会调用你的工具。 我在 add_todo 的 docstring 里写了"新建一条待办事项",Claude 在对话里听到用户说"帮我加个任务",就会匹配到这个工具。如果 docstring 写成"POST request to /todos endpoint",AI 大概率不知道什么时候该用它。

用 async。 MCP 的 stdio 传输本身是同步的,但 SDK 内部有事件循环。用 async 函数可以避免 HTTP 请求阻塞其他工具的调用。如果你的 API 响应慢(超过 2 秒),这个差别更明显。

连接到 Claude Code

写完 server.py,下一步是让 Claude Code 识别它。

在你的项目根目录创建 .claude/settings.json

{
  "mcpServers": {
    "todo-mcp": {
      "command": "/path/to/your/venv/bin/python",
      "args": ["server.py"],
      "cwd": "/path/to/your/mcp-server"
    }
  }
}

两个坑要提前踩掉:

  1. command 必须用 venv 里的 python 绝对路径。python3 或者 python 会用系统默认的解释器,找不到你 pip install 的 mcp 包。用 which python(在你的 venv 激活状态下)拿到完整路径。

  2. cwd 填 server.py 所在目录的绝对路径。 Claude Code 启动子进程时不一定在你预期的工作目录。

保存后重启 Claude Code。在对话里输入 /mcp,你应该能看到 todo-mcp 和它下面注册的 4 个工具。

试一下:"帮我看看现在有哪些待办事项"——Claude 会调用 list_todos,返回你 API 的 JSON 数据。

如果你用 Cursor

Cursor 的 MCP 配置稍有不同。打开 Settings → MCP Servers,点 Add Server,填:

{
  "name": "todo-mcp",
  "command": "/path/to/venv/bin/python",
  "args": ["server.py"],
  "cwd": "/path/to/mcp-server"
}

跟 Claude Code 的字段基本一样。Cursor 会在 Agent 模式下自动发现你的 MCP 工具。

实际用的时候会踩哪些坑

我自己封装了好几个内部 API 做 MCP Server,下面是踩过的坑:

超时问题。 httpx 默认超时是 5 秒,如果你的 API 比较慢,改成 timeout=30 或者按需调整。MCP 协议本身没有超时限制,但 Claude Code 等客户端有自己的耐心上限——大概 60 秒,超了会认为工具挂了。

错误处理不能只 raise。 如果你的工具抛了异常,Claude 会收到一个模糊的错误消息。更好的做法是 try/except 之后返回一条人能读懂的错误信息:

@mcp.tool()
async def list_todos() -> str:
    """获取所有待办事项。"""
    try:
        async with httpx.AsyncClient(timeout=10) as client:
            resp = await client.get(f"{API_BASE}/todos")
            resp.raise_for_status()
            return resp.text
    except httpx.HTTPStatusError as e:
        return f"API 返回错误: {e.response.status_code}"
    except httpx.ConnectError:
        return "连不上 API 服务,确认一下 localhost:8000 是否在运行"

这样 Claude 拿到的是一段中文说明,它可以告诉用户哪里出了问题,甚至自己尝试修复。

返回数据太大。 如果你的 API 一次返回几万条数据,全塞给 AI 会撑爆上下文窗口。两个办法:一是在工具函数里做分页(加 limit 和 offset 参数),二是只返回摘要信息。

认证怎么处理。 如果你的 API 需要 token,别硬编码在代码里。用环境变量:

import os
API_TOKEN = os.environ.get("API_TOKEN", "")

# 在请求里带上
headers = {"Authorization": f"Bearer {API_TOKEN}"}
resp = await client.get(url, headers=headers)

启动 MCP Server 前 export API_TOKEN=xxx 就行。Claude Code 的配置也支持 env 字段:

{
  "mcpServers": {
    "todo-mcp": {
      "command": "python",
      "args": ["server.py"],
      "env": {
        "API_TOKEN": "your-token-here"
      }
    }
  }
}

调试利器:mcp dev

开发过程中别直接连 Claude 测。先用 SDK 自带的 inspector:

mcp dev server.py

浏览器打开后能看到所有注册的工具,点进去可以填参数、手动调用、看返回结果。JSON Schema 也在这个页面上,能直接检查 AI 会看到什么样的参数定义。

我一般的开发流程是:写工具函数 → mcp dev 验证 → 确认没问题了再改 Claude Code 配置 → 重启 Claude Code 测试完整对话。比在聊天里来回调试效率高不少。

总结

整个流程回头看不复杂:

  1. pip install "mcp[cli]" httpx
  2. @mcp.tool() 把你的 API 调用包成函数
  3. 给每个函数写好 type hint 和 docstring
  4. 配置 Claude Code 或 Cursor 的 MCP 连接
  5. 对话里直接用

MCP 的 Python SDK 文档在 https://github.com/modelcontextprotocol/python-sdk ,FastMCP 的 API 在 https://gofastmcp.com/getting-started/welcome 。想看更多实际项目可以逛 GitHub 的 MCP Registry(https://github.com/mcp),上面已经有几百个社区做的 MCP Server 了。

如果你的公司有内部 API,花半小时包一个 MCP Server 出来,让 AI 助手能直接操作业务数据。比在聊天窗口里反复复制粘贴 curl 结果,舒服得多。

更多推荐