<!-- title: 造HTTP探针MCP让Claude秒测REST接口 -->

# 用 FastMCP 造一个 HTTP 接口探针：让 Claude Code 秒测 REST API

## 概述

本教程带你用 Python 的 FastMCP 框架，从零打造一个 **HTTP 接口探针 MCP 服务器**，把 `http_get` / `http_post` / `http_put` / `http_delete` 四个工具暴露给 Claude Code。完成后，Claude 可以直接在你的对话里"按需发请求、解析响应、定位报错"——相当于给它装了一个内置 Postman。适合已经能跑通 Claude Code、想理解 MCP 工具开发闭环、并希望落地一个真实可用工具的进阶读者。

## 前置条件

- 已安装并跑通 Claude Code（CLI 版本），能用 `claude` 命令进入交互。
- Python 3.10 及以上（FastMCP v2 需要较新的类型注解支持）。
- 会用 `pip` / `uv` 管理虚拟环境，会读简单的 Python。
- 一台能联网的机器，准备一个可测的公开接口（本教程用 `https://jsonplaceholder.typicode.com` 和 `https://httpbin.org`）。
- 不需要你写过 MCP，但要理解"工具（tool）就是 AI 可以调用的函数"这个核心抽象。

## 为什么需要一个 HTTP 探针 MCP

很多人第一反应是：Claude Code 不是有 WebFetch、Bash 吗，为什么还要单独造一个 HTTP MCP？

三个理由：

1. **可控的参数语义**。WebFetch 偏向"抓网页正文"，对 JSON 接口、自定义 Header、鉴权 Token、POST body 的支持很弱；Bash 里 `curl` 能做，但每次都要 Claude 自己拼命令，容易拼错、且无法做结构化的超时与错误归因。
2. **结构化返回**。MCP 工具返回的是带类型的数据，我们可以把"状态码、耗时、响应头、截断后的 body"打包成一个 dict，Claude 解析起来比解析 `curl` 的自由文本可靠得多。
3. **权限边界**。通过一个专门的 MCP 工具，你可以在代码里做白名单域名、超时上限、敏感字段脱敏——这是把"AI 能联网"这件事关进笼子里的最干净方式。

理解了动机，下面进入实操。

## 第一步：理解 MCP 的三层抽象

在动手前，先把心智模型建对。MCP（Model Context Protocol）是 Anthropic 主导的开放协议，它定义了"宿主（Host）/ 客户端（Client）/ 服务器（Server）"三者关系：

```mermaid
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、返回结构都要写得让模型容易正确使用。

## 第二步：建项目骨架

创建一个干净的项目目录：

```bash
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`，先写最小骨架并跑通"一个工具"：

```python
# 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()
```

注意几个设计点：

- **docstring 是给模型看的说明书。** 写清楚"适用场景"和参数语义，模型才会在对的时候选这个工具。
- **返回统一形状（`_shape`）**，让所有动词工具返回结构一致，模型解析成本最低。
- **`elapsed_ms`** 这种元信息对"接口慢不慢"的判断极有价值，是 curl 给不了的。
- **截断 body** 是 MCP 工具的铁律——把几十 KB 的 HTML 灌进上下文是新手最常犯的错。

### 预期结果：本地冒烟测试

FastMCP 自带 CLI 可以在终端直接调用工具，不用先接进 Claude：

```bash
# 安装 fastmcp 后会带一个 fastmcp 命令
fastmcp run http_probe.py
```

更实用的是写一个临时调用脚本 `smoke.py` 验证逻辑：

```python
# 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))
```

```bash
python smoke.py
```

预期输出（节选）：

```json
{
  "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` 确认一下。
- **请求 hang 住**：默认 10 秒超时，如果一直不返回，多半是网络问题，不要把 timeout 调大到几百秒——那只会让 Claude 的工具调用卡死。

## 第三步：补齐 POST / PUT / DELETE 与鉴权

真实接口几乎都要带 Header、Token、JSON body。把工具补全：

```python
@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}
```

再加一个高频"组合工具"——健康检查，它专门回答"这个接口到底活没活"：

```python
@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 工具的核心心法：**把领域判断固化进工具，给模型留下"显而易见的信号"。**

### 预期结果

```bash
python -c "from http_probe import health_check; print(health_check('https://httpbin.org/status/200'))"
```

输出：

```json
{"ok": true, "alive": true, "status_code": 200, "expected": 200, "elapsed_ms": 245.8, "url": "https://httpbin.org/status/200"}
```

### 常见问题

- **鉴权 Token 怎么传**：让模型在调用时把 `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` 最前面加一道闸：

```python
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`**，把攻击面收紧。

```mermaid
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

工具写好了，现在让 Claude Code 认识它。最稳妥的是用 **stdio 传输**——Claude Code 自己拉起你的 Python 进程。

```bash
# 用绝对路径，避免 Claude Code 找不到解释器
claude mcp add http-probe -- /home/you/http-probe-mcp/.venv/bin/python /home/you/http-probe-mcp/http_probe.py
```

如果你需要注入鉴权环境变量或白名单：

```bash
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`**）。

验证注册成功：

```bash
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 退出即可。
- **工具列出来了但调用报错**：多半是函数签名里用了模型不认识的类型（如自定义类）。MCP 工具参数只支持基础类型 + dict/list，复杂对象会被拒绝。
- **Windows 下 `claude mcp add` 路径**：用正斜杠或双反斜杠，避免转义问题。

## 第六步：实战案例——让 Claude 帮你联调一个真实接口

场景：你在对接一个公开的 `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 内部）：

1. `http_post(url=".../posts", body={"title":"测试 MCP","body":"hello from claude","userId":1})` → 返回 `status_code: 201`，body 里含 `"id": 101`
2. `http_get(url=".../posts/101")` → 返回刚才的字段
3. `health_check(url="https://jsonplaceholder.typicode.com")` → `alive: true`

整个联调你**一行命令都没敲**，全部由 Claude 通过你造的 MCP 工具完成。这就是"工具下沉"的威力——把接口交互的脏活封装好，模型只负责编排和解读。

进阶玩法：让它做**回归巡检**。"用 health_check 并行探活这 5 个域名，把挂掉的列出来"——MCP 工具可以被模型在同一个回合里多次调用，天然适合批量。

## 第七步：调试与发布的闭环

写 MCP 最痛的是"看不见它在干嘛"。三个调试技巧：

1. **`fastmcp dev http_probe.py`** 会启动一个交互式 Inspector，能在浏览器里手动点按钮调用每个工具、看原始 JSON-RPC 报文。这是定位"工具签名/返回结构"问题最快的途径。
2. **给工具加结构化日志**：在 `_shape` 里 `print(json.dumps({...}), flush=True)` 到 stderr。stdio 模式下 stdout 是协议通道不能污染，但 **stderr 是自由的**，日志写 stderr 不影响 Claude 通信。
3. **单测用 smoke 脚本**：任何改动后先跑 `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 工具：

1. **返回结构化、形状统一**：所有动词工具用同一个 `_shape`，给模型最小解析成本。
2. **把领域判断固化进工具**：像 `health_check` 那样返回 `alive: bool`，别让模型去解读语义。
3. **截断一切大返回**：body、列表、日志，超过几千字符就截，护住上下文窗口。
4. **安全边界写在代码里**：域名白名单、超时上限、敏感字段脱敏，不依赖模型自觉。
5. **docstring 当产品文档写**：写清楚"适用场景 / 不适用场景 / 参数语义"，这是模型选对工具的唯一依据。

## 延伸阅读

- **MCP 官方文档**：`modelcontextprotocol.io/docs/develop/build-server`——官方从零建 server 的权威教程，值得对照本篇看协议细节。
- **FastMCP 官方仓库**：`github.com/jlowin/fastmcp`——已被官方 Python SDK 整合，看最新 API 演进。
- **Anthropic MCP Python SDK**：`github.com/modelcontextprotocol/python-sdk`——不用 FastMCP 的底层写法，理解协议本质。
- **FastMCP Inspector**：`fastmcp dev` 的底层是官方 Inspector，`github.com/modelcontextprotocol/inspector`，可视化调试必备。

## 训练营适用性评估

- **适合类型**：进阶训练营（学员已跑通 Claude Code 基础用法），尤其适合"AI 工程化 / 内部工具集成"主题的 1–1.5 天工坊。
- **预计教学时长**：讲解 + 跟练约 **2.5 小时**（含环境调试预留 30 分钟）；拓展练习（加白名单、加异步、接真实鉴权接口）另需 1.5 小时。
- **分组建议**：2 人一组，一人写工具、一人当"产品经理"提需求，强制学员练习"把模糊需求翻译成 MCP 工具签名"。
- **考核点**：能否独立造一个"返回结构化 + 有安全围栏 + docstring 清晰"的 MCP 工具，并成功在 Claude Code 里 `/mcp` 看到 connected。

## 参考来源

1. [Build an MCP server — Model Context Protocol 官方文档](https://modelcontextprotocol.io/docs/develop/build-server) — 官方从零构建 MCP server 的权威教程，覆盖协议与传输细节
2. [MCP Server Tutorial: 12 Steps Python FastMCP (2026)](https://tech-insider.org/mcp-server-tutorial-python-fastmcp-claude-2026/) — 基于 FastMCP 官方 SDK 的 12 步实战指南，与本篇方法论一致
3. [FastMCP in Python: Build a Real MCP Server (2026 Guide)](https://www.danilchenko.dev/posts/fastmcp-mcp-server/) — FastMCP 当前版本特性与生产实践解读
4. [MCP Server for Claude Code Tutorial — Deep Dive (2026)](https://techbytes.app/posts/mcp-server-for-claude-code-tutorial-deep-dive-2026/) — 聚焦 Claude Code 接入与 stdio 传输的深入教程
5. [FastMCP Builder — Claude Code Skill (GitHub)](https://github.com/husniadil/fastmcp-builder) — 把 MCP 构建流程封装成 Claude Code Skill 的参考实现

> [!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 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
