本教程带你用 Python 的 FastMCP 框架,从零打造一个 HTTP 接口探针 MCP 服务器,把 http_get / http_post / http_put / http_delete 四个工具暴露给 Claude Code。完成后,Claude 可以直接在你的对话里"按需发请求、解析响应、定位报错"——相当于给它装了一个内置 Postman。适合已经能跑通 Claude Code、想理解 MCP 工具开发闭环、并希望落地一个真实可用工具的进阶读者。
claude 命令进入交互。pip / uv 管理虚拟环境,会读简单的 Python。https://jsonplaceholder.typicode.com 和 https://httpbin.org)。很多人第一反应是:Claude Code 不是有 WebFetch、Bash 吗,为什么还要单独造一个 HTTP MCP?
三个理由:
curl 能做,但每次都要 Claude 自己拼命令,容易拼错、且无法做结构化的超时与错误归因。curl 的自由文本可靠得多。理解了动机,下面进入实操。
在动手前,先把心智模型建对。MCP(Model Context Protocol)是 Anthropic 主导的开放协议,它定义了"宿主(Host)/ 客户端(Client)/ 服务器(Server)"三者关系:
flowchart LR
U["你(在 Claude Code 里对话)"] -->|"自然语言"| CC[Claude Code 主进程]
CC -->|"MCP 协议 stdio"| S["你的 MCP Server\nhttp_probe.py"]
S -->|"HTTP 请求"| API["外部 REST API"]
API -->|"响应"| S
S -->|"结构化结果"| CC
CC -->|"自然语言答复"| U
在 Claude Code 里,Claude Code 既是宿主又是客户端,它通过 stdio(标准输入输出)拉起你的 Python 进程作为 Server,用 JSON-RPC 消息来通信。你写的每一个 @mcp.tool 函数,都会变成 Claude 工具列表里的一项;当 Claude 决定调用它时,它收到一个 tools/call 请求,执行后返回结果。
关键认知:你的函数不是被你的代码直接调用,而是被 Claude 在对话中"决定调用"。 所以函数签名、docstring、返回结构都要写得让模型容易正确使用。
创建一个干净的项目目录:
mkdir http-probe-mcp && cd http-probe-mcp
python3 -m venv .venv
source .venv/bin/activate # Windows 用 .venv\Scripts\activate
pip install "fastmcp>=2.3" httpx
为什么用 httpx 而不是 requests?因为 httpx 同时支持同步和异步、默认更好的超时语义,并且 FastMCP 的异步工具能直接 await 异步请求,吞吐更高。本教程用同步风格降低门槛,但代码结构随时能升级成异步。
新建 http_probe.py,先写最小骨架并跑通"一个工具":
# http_probe.py
import time
import json
import httpx
from fastmcp import FastMCP
mcp = FastMCP("http-probe")
@mcp.tool()
def http_get(url: str, timeout: float = 10.0) -> dict:
"""向指定 URL 发送 GET 请求,返回状态码、耗时与响应体。
适用于查询 REST 接口、拉取 JSON 数据、做接口连通性探测。
Args:
url: 完整的 http(s) 地址,例如 https://api.example.com/users
timeout: 超时秒数,默认 10 秒
"""
started = time.perf_counter()
try:
with httpx.Client(timeout=timeout, follow_redirects=True) as client:
resp = client.get(url)
elapsed = round((time.perf_counter() - started) * 1000, 1)
return _shape(resp, elapsed)
except httpx.HTTPError as e:
return {"ok": False, "error": f"{type(e).__name__}: {e}", "url": url}
def _shape(resp: httpx.Response, elapsed: float) -> dict:
body = resp.text
# 防止把超大响应塞进上下文,统一截断到 4000 字符
if len(body) > 4000:
body = body[:4000] + f"\n…[truncated, total {len(body)} chars]"
return {
"ok": True,
"status_code": resp.status_code,
"elapsed_ms": elapsed,
"url": str(resp.url),
"headers": dict(resp.headers),
"body": body,
}
if __name__ == "__main__":
mcp.run()
注意几个设计点:
_shape),让所有动词工具返回结构一致,模型解析成本最低。elapsed_ms 这种元信息对"接口慢不慢"的判断极有价值,是 curl 给不了的。FastMCP 自带 CLI 可以在终端直接调用工具,不用先接进 Claude:
# 安装 fastmcp 后会带一个 fastmcp 命令
fastmcp run http_probe.py
更实用的是写一个临时调用脚本 smoke.py 验证逻辑:
# smoke.py —— 仅用于本地验证,不是 MCP 的一部分
from http_probe import http_get
import json
r = http_get("https://jsonplaceholder.typicode.com/posts/1")
print(json.dumps(r, ensure_ascii=False, indent=2))
python smoke.py
预期输出(节选):
{
"ok": true,
"status_code": 200,
"elapsed_ms": 312.4,
"url": "https://jsonplaceholder.typicode.com/posts/1",
"body": "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"sunt aut facere...\",\n \"body\": \"quia et suscipit...\"\n}"
}
看到 ok: true、status_code: 200、elapsed_ms 有值,说明工具逻辑跑通。
ModuleNotFoundError: fastmcp:你没激活虚拟环境,或者装到了别的 Python。which python 确认一下。真实接口几乎都要带 Header、Token、JSON body。把工具补全:
@mcp.tool()
def http_post(
url: str,
body: dict | None = None,
headers: dict | None = None,
timeout: float = 10.0,
) -> dict:
"""向指定 URL 发送 POST 请求,body 会被序列化为 JSON。
适用于创建资源、提交表单、调用需要 JSON body 的接口。
Args:
url: 目标地址
body: 要发送的 JSON 对象,可为空
headers: 自定义请求头,例如 {"Authorization": "Bearer xxx"}
timeout: 超时秒数
"""
return _request("POST", url, json_body=body, headers=headers, timeout=timeout)
@mcp.tool()
def http_put(
url: str, body: dict | None = None, headers: dict | None = None, timeout: float = 10.0
) -> dict:
"""发送 PUT 请求,通常用于整体更新某个资源。"""
return _request("PUT", url, json_body=body, headers=headers, timeout=timeout)
@mcp.tool()
def http_delete(url: str, headers: dict | None = None, timeout: float = 10.0) -> dict:
"""发送 DELETE 请求,删除指定 URL 对应的资源。"""
return _request("DELETE", url, headers=headers, timeout=timeout)
def _request(method, url, json_body=None, headers=None, timeout=10.0) -> dict:
started = time.perf_counter()
try:
with httpx.Client(timeout=timeout, follow_redirects=True) as client:
resp = client.request(method, url, json=json_body, headers=headers or {})
elapsed = round((time.perf_counter() - started) * 1000, 1)
return _shape(resp, elapsed)
except httpx.HTTPError as e:
return {"ok": False, "error": f"{type(e).__name__}: {e}", "url": url, "method": method}
再加一个高频"组合工具"——健康检查,它专门回答"这个接口到底活没活":
@mcp.tool()
def health_check(url: str, expect_code: int = 200, timeout: float = 5.0) -> dict:
"""轻量探活:只发 HEAD/GET,判断状态码是否符合预期。
适合做定时巡检、CI 里验证服务是否起来。返回 alive 布尔值便于模型直接判断。
"""
started = time.perf_counter()
try:
with httpx.Client(timeout=timeout) as client:
resp = client.head(url)
if resp.status_code == 405: # 有些接口不支持 HEAD
resp = client.get(url)
elapsed = round((time.perf_counter() - started) * 1000, 1)
return {
"ok": True,
"alive": resp.status_code == expect_code,
"status_code": resp.status_code,
"expected": expect_code,
"elapsed_ms": elapsed,
"url": url,
}
except httpx.HTTPError as e:
return {"ok": False, "alive": False, "error": f"{type(e).__name__}: {e}", "url": url}
health_check 的精髓在于:它把"接口健康"这个业务判断下沉到了工具里(返回 alive: bool),而不是让模型去解读状态码。模型判断布尔值远比解读 HTTP 语义可靠——这是设计 MCP 工具的核心心法:把领域判断固化进工具,给模型留下"显而易见的信号"。
python -c "from http_probe import health_check; print(health_check('https://httpbin.org/status/200'))"
输出:
{"ok": true, "alive": true, "status_code": 200, "expected": 200, "elapsed_ms": 245.8, "url": "https://httpbin.org/status/200"}
headers={"Authorization": "Bearer ..."} 传进来。不要把 Token 写死在代码里——硬编码后整个团队谁都能拿到。最佳实践是代码里 os.environ.get("API_TOKEN"),在 claude mcp add 时用 -e 注入。405 Method Not Allowed:你的 health_check 已经处理了,但 PUT/DELETE 到只读资源也会报这个;告诉模型这是接口本身不支持,不是工具 bug。让 AI 能发任意 HTTP 请求是有风险的——它可能把请求打到内网管理后台、打到元数据服务(如云厂商的 169.254.169.254)。在 _request 最前面加一道闸:
import os
from urllib.parse import urlparse
# 环境变量里配允许的域名,逗号分隔;不配则允许全部(仅本地开发用)
ALLOWED = os.environ.get("HTTP_PROBE_ALLOW", "").split(",")
ALLOWED = {d.strip() for d in ALLOWED if d.strip()}
def _guard(url: str) -> str | None:
host = urlparse(url).hostname or ""
if not ALLOWED: # 未配置白名单:放行(仅建议本地)
return None
if host in ALLOWED:
return None
return f"域名 {host} 不在白名单内,已拦截"
然后在每个工具入口调用 _guard,非 None 直接返回错误。生产环境务必配 HTTP_PROBE_ALLOW=api.yourcompany.com,internal.corp,把攻击面收紧。
flowchart TD
Call["Claude 调用 http_post"] --> G{"_guard 域名检查"}
G -->|"在白名单"| Send["httpx 发请求"]
G -->|"不在白名单"| Block["返回 ok:false, error: 拦截"]
Send --> Shape["_shape 结构化"]
Shape --> Ret["返回给 Claude"]
Block --> Ret
工具写好了,现在让 Claude Code 认识它。最稳妥的是用 stdio 传输——Claude Code 自己拉起你的 Python 进程。
# 用绝对路径,避免 Claude Code 找不到解释器
claude mcp add http-probe -- /home/you/http-probe-mcp/.venv/bin/python /home/you/http-probe-mcp/http_probe.py
如果你需要注入鉴权环境变量或白名单:
claude mcp add http-probe \
-e API_TOKEN=sk-xxxx \
-e HTTP_PROBE_ALLOW=api.yourcompany.com \
-- /home/you/http-probe-mcp/.venv/bin/python /home/you/http-probe-mcp/http_probe.py
作用域参数 -s:
- -s local(默认):只对当前项目目录生效。
- -s user:对你所有项目生效。
- -s project:写入项目根 .mcp.json,可提交到 Git,团队共享(注意:不要把 secret 写进 .mcp.json)。
验证注册成功:
claude mcp list # 列出所有已注册 server,应看到 http-probe
claude mcp get http-probe # 查看该 server 的命令与工具数
进入交互界面后输入 /mcp,应该能看到 http-probe 是 connected 状态,并列出 5 个工具:http_get / http_post / http_put / http_delete / health_check。
/mcp 里显示 failed:99% 是命令路径错或依赖没装。手动跑一遍 python /abs/path/http_probe.py,确认不报错。注意 stdio 模式下进程会等待输入不退出,这是正常的;按 Ctrl+C 退出即可。claude mcp add 路径:用正斜杠或双反斜杠,避免转义问题。场景:你在对接一个公开的 https://jsonplaceholder.typicode.com 假数据 API,想快速验证"创建一篇 post → 拿到 id → 再 GET 回来看是否一致"这条链路。
在 Claude Code 里直接说:
用 http_post 给 https://jsonplaceholder.typicode.com/posts 创建一篇文章,标题"测试 MCP",正文"hello from claude",拿到返回的 id 后,用 http_get 查一次确认字段都在,最后用 health_check 探活整个域名。
Claude 会自动按顺序调用 http_post →(从返回里取 id)→ http_get → health_check,并把三步结果汇总成一段结论。你会看到类似这样的工具调用序列(Claude 内部):
http_post(url=".../posts", body={"title":"测试 MCP","body":"hello from claude","userId":1}) → 返回 status_code: 201,body 里含 "id": 101http_get(url=".../posts/101") → 返回刚才的字段health_check(url="https://jsonplaceholder.typicode.com") → alive: true整个联调你一行命令都没敲,全部由 Claude 通过你造的 MCP 工具完成。这就是"工具下沉"的威力——把接口交互的脏活封装好,模型只负责编排和解读。
进阶玩法:让它做回归巡检。"用 health_check 并行探活这 5 个域名,把挂掉的列出来"——MCP 工具可以被模型在同一个回合里多次调用,天然适合批量。
写 MCP 最痛的是"看不见它在干嘛"。三个调试技巧:
fastmcp dev http_probe.py 会启动一个交互式 Inspector,能在浏览器里手动点按钮调用每个工具、看原始 JSON-RPC 报文。这是定位"工具签名/返回结构"问题最快的途径。_shape 里 print(json.dumps({...}), flush=True) 到 stderr。stdio 模式下 stdout 是协议通道不能污染,但 stderr 是自由的,日志写 stderr 不影响 Claude 通信。python smoke.py,确认基础工具仍返回 ok: true,再接进 Claude。发布层面,如果你的工具要给团队用,推荐用 -s project 生成 .mcp.json 提交到仓库,但 secret 全部走 -e 环境变量或团队密钥管理,绝不进 Git。完整文件结构:
http-probe-mcp/
├── http_probe.py # 服务器主文件(所有 @mcp.tool)
├── smoke.py # 本地冒烟测试
├── .mcp.json # 团队共享配置(无 secret)
├── requirements.txt # fastmcp>=2.3, httpx
└── .venv/ # 虚拟环境(gitignore)
把整篇浓缩成五条可复用的心法,适用于你未来造任何 MCP 工具:
_shape,给模型最小解析成本。health_check 那样返回 alive: bool,别让模型去解读语义。modelcontextprotocol.io/docs/develop/build-server——官方从零建 server 的权威教程,值得对照本篇看协议细节。github.com/jlowin/fastmcp——已被官方 Python SDK 整合,看最新 API 演进。github.com/modelcontextprotocol/python-sdk——不用 FastMCP 的底层写法,理解协议本质。fastmcp dev 的底层是官方 Inspector,github.com/modelcontextprotocol/inspector,可视化调试必备。/mcp 看到 connected。[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
