首页 › tutorials › MCP服务器远程部署实战

Claude Code MCP 服务器开发:把本地工具推上 Streamable HTTP

概述

本教程带你完成 MCP 服务器从「本地 stdio 子进程」到「远程 Streamable HTTP 服务」的关键一跃。你会搞清三种传输方式的取舍,用 FastMCP 写出同一份代码、靠一个参数切换传输,再用 Docker 容器化部署,最后让团队里任意一台机器上的 Claude Code 通过带鉴权的 HTTP 端点接入。适合已能用 stdio 跑通 MCP、想让工具被多人/CI 共享的后端与平台工程师。

前置条件


一、为什么要把 MCP 推上「远程」

很多教程止步于 stdio:Claude Code 在本机 fork 一个子进程,通过标准输入输出收发 JSON-RPC。这对个人本地工具够用,但有三个硬伤,正是 Streamable HTTP 要解决的:

  1. 后端状态无法共享:stdio 服务器活在单个客户端进程里,工程师 A 的会话和工程师 B 的会话互不可见。一个「团队变更日志」工具,每人存一份本地 JSON,永远对不齐。
  2. 机密与连通性下沉不到本机:内网数据库、K8s 集群、CI 系统,不该也不能铺到每个工程师的笔记本上。你想要的是「一台服务器握着凭证,多个客户端来问」。
  3. CI 与无头场景接入麻烦:CI runner 想调你的工具,再去 fork 一个本地进程、装一堆依赖,又慢又脆。一个 HTTP 端点对 CI 友好得多。

一句话:stdio 是「我自己的工具」,远程 HTTP 是「团队/平台的工具」。


二、三种传输方式:一次讲清取舍

MCP 规范(截至 2025-06-18 修订版)定义了三类传输:

传输方式 规范版本 拓扑 会话 典型场景 状态
stdio 最早 客户端 fork 子进程 进程内 本地个人工具、文件操作 当前推荐(本地)
HTTP+SSE 2024-11-05 双端点(POST + SSE GET) 有状态 早期远程服务器 已废弃(2025-03-26 起)
Streamable HTTP 2025-03-26 单端点 /mcp,POST+GET+DELETE 可有可无 远程、Serverless、负载均衡 当前推荐(远程)

2.1 Streamable HTTP 的核心机制

旧的 HTTP+SSE 需要两个端点(一个 POST 发请求、一个长连 SSE 收推送),状态与连接强绑定,横向扩展痛苦。Streamable HTTP 把它收敛为单端点:

安全上规范要求:远程服务必须做 Origin 校验(防 DNS rebinding),必须上鉴权(OAuth 2.1 Bearer 是事实标准);本地调试时务必只绑 127.0.0.1 而非 0.0.0.0。


三、实战:同一个团队变更日志服务器

目标:写一个「团队变更日志」MCP 服务器,背后是一份共享的 SQLite。所有人接同一个远程端点,看到的就是同一份数据——这是 stdio 永远做不到的。

工具集:

3.1 项目骨架

mkdir changelog-mcp && cd changelog-mcp
uv init --no-readme
uv add fastmcp

3.2 服务器代码 server.py

# server.py —— 同一份代码,传输方式由命令行参数决定
import sqlite3, time, uuid
from contextlib import closing
from fastmcp import FastMCP

DB = "/data/changelog.db"  # 容器内持久化路径;本机调试可改 ./changelog.db

def _conn():
    c = sqlite3.connect(DB)
    c.execute("""CREATE TABLE IF NOT EXISTS changes(
        id TEXT PRIMARY KEY, ts INTEGER, title TEXT, author TEXT, detail TEXT)""")
    return c

mcp = FastMCP("changelog")

@mcp.tool()
def add_change(title: str, author: str, detail: str = "") -> str:
    """新增一条团队变更记录。title 简述、author 提交人、detail 可选正文。"""
    cid = uuid.uuid4().hex[:8]
    with closing(_conn()) as c:
        c.execute("INSERT INTO changes VALUES(?,?,?,?,?)",
                  (cid, int(time.time()), title, author, detail))
        c.commit()
    return f"已记录 {cid}: {title}({author})"

@mcp.tool()
def list_changes(days: int = 7) -> str:
    """列出近 days 天内的变更,默认 7 天。"""
    cutoff = int(time.time()) - days * 86400
    with closing(_conn()) as c:
        rows = c.execute(
            "SELECT id, ts, title, author FROM changes WHERE ts>=? ORDER BY ts DESC",
            (cutoff,)).fetchall()
    if not rows:
        return f"近 {days} 天无变更记录。"
    return "\n".join(f"[{r[0]}] {time.strftime('%m-%d', time.localtime(r[1]))} {r[2]} @{r[3]}" for r in rows)

@mcp.tool()
def get_change(change_id: str) -> str:
    """按 id 查单条变更详情。"""
    with closing(_conn()) as c:
        r = c.execute("SELECT id, ts, title, author, detail FROM changes WHERE id=?",
                      (change_id,)).fetchone()
    if not r:
        return f"找不到 id={change_id}"
    return f"[{r[0]}] {r[2]}\n提交人: {r[3]}\n时间: {time.strftime('%Y-%m-%d %H:%M', time.localtime(r[1]))}\n详情: {r[4] or '(无)'}"

if __name__ == "__main__":
    mcp.run()  # 默认 stdio;传输由 CLI 参数覆盖

3.3 关键一步:一份代码,两种传输

FastMCP 2.x 的设计精髓就在这里——代码不写死传输,命令行切:

# 方式 A:stdio(本机自用,与 stdio 教程里的写法一致)
fastmcp run server.py

# 方式 B:Streamable HTTP(远程部署,团队共享)
fastmcp run server.py \
  --transport streamable-http \
  --host 0.0.0.0 --port 8000

预期结果:方式 B 启动后日志会出现类似

FastMCP streamable-http server running on http://0.0.0.0:8000/mcp

访问端点是 http://<host>:8000/mcp(注意带 /mcp 路径,这是 Streamable HTTP 的单端点)。

也可用 fastmcp dev server.py 启动并自动打开 MCP Inspector,在浏览器里手动 POST 一条 initialize 请求,直观看到 Mcp-Session-Id 头如何下发、后续请求如何回带——这是理解协议最快的办法。


四、容器化部署

4.1 Dockerfile

# Dockerfile
FROM python:3.12-slim
WORKDIR /app
RUN pip install --no-cache-dir uv
COPY pyproject.toml uv.lock* ./
COPY server.py ./
RUN uv pip install --system --no-cache .
# 持久化目录(数据库落盘处)
RUN mkdir -p /data
EXPOSE 8000
CMD ["fastmcp", "run", "server.py", \
     "--transport", "streamable-http", \
     "--host", "0.0.0.0", "--port", "8000"]

4.2 构建与运行

docker build -t changelog-mcp:latest .

# 仅本机访问 → 绑 127.0.0.1;团队访问 → 0.0.0.0 + 反向代理上 TLS
docker run -d --name changelog \
  -p 8000:8000 \
  -v $(pwd)/data:/data \
  changelog-mcp:latest

# 健康探活:发一条 initialize
curl -sS -X POST http://localhost:8000/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize",
       "params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'

预期结果:响应头里能看到 Mcp-Session-Id: <uuid>,JSON 体里 result.capabilities 列出服务器支持的能力。拿到这个响应,说明传输层已通。

生产环境务必在前面加一层 nginx/Caddy 做 TLS 终结与 Origin 校验,再签发长期 Bearer Token,而不是裸跑 0.0.0.0:8000。


五、Claude Code 接入远程 MCP

5.1 用命令行接入(推荐临时/个人验证)

# 先对比:本地 stdio 接法
claude mcp add --transport stdio changelog-local -- uv run server.py

# 远程 HTTP 接法:注意选项必须在名字和 URL 之前
export MCP_TOKEN="$(openssl rand -hex 24)"   # 与服务器侧约定
claude mcp add \
  --transport http \
  --scope user \
  --header "Authorization: Bearer ${MCP_TOKEN}" \
  changelog https://changelog.internal.corp/mcp

常用管理命令:

claude mcp list          # 列出所有已配服务器及连接状态
claude mcp get changelog # 看单个服务器详情
claude mcp remove changelog

5.2 用 .mcp.json 接入(推荐团队/项目共享)

在项目根放 .mcp.json,提交到仓库,全队 clone 即用:

{
  "mcpServers": {
    "changelog": {
      "type": "http",
      "url": "https://changelog.internal.corp/mcp",
      "headers": {
        "Authorization": "Bearer ${MCP_TOKEN}"
      }
    }
  }
}

${MCP_TOKEN} 从环境变量注入,避免凭证入库。首次进入项目,Claude Code 会提示是否信任并连接该 MCP。

5.3 验证

在 Claude Code 里输入 /mcp,应看到 changelog 状态为 connected,并列出 add_change / list_changes / get_change 三个工具。然后直接对话测试:

「帮我看看近 3 天团队都改了什么」

Claude 会调用 list_changes(days=3),返回共享数据库里的真实数据——工程师 A 刚提交的记录,工程师 B 此刻也能看到。这就是远程化的全部价值。


六、常见问题与排查

  1. 401 Unauthorized / Token 没带上:检查 --header 是否在位置参数之前;.mcp.json 里 headers 键名是否正确(不是 header);${MCP_TOKEN} 是否真的在启动 Claude 的 shell 里 export 过。
  2. 403 / Origin 被拒(DNS rebinding 防护):远程服务器默认校验 Origin/Host 头。浏览器调试时会被拦,需在服务器侧显式放行可信 Origin;生产环境务必保留校验。
  3. 负载均衡下会话丢失:若用了有状态会话(Mcp-Session-Id),LB 必须开 sticky session;或干脆让服务器走无状态模式(不返回/不校验 session id),任意副本可处理任意请求——这才是 Serverless 友好的姿势。
  4. curl 探活收到 SSE 流而非 JSON:正常。服务器对 initialize/tools/call 可能返回流式响应。加 Accept: application/json, text/event-stream 头即可,客户端 SDK 会自动处理。
  5. 容器里数据库「丢」了:SQLite 路径 /data 没挂卷(-v ./data:/data),容器重启数据归零。生产建议换成 Postgres 等外部库。
  6. 看到 SSE 相关 deprecation 警告:说明你还在用旧的 HTTP+SSE 端点(双端点)。迁到单端点 Streamable HTTP 即可消除。

七、工作流与架构总览

flowchart LR
    subgraph Dev["工程师 A 本机"]
        CC1[Claude Code]
    end
    subgraph CI["CI Runner"]
        CC2[claude -p 无头]
    end
    subgraph Dev2["工程师 B 本机"]
        CC3[Claude Code]
    end

    CC1 -->|"HTTPS + Bearer"| Proxy
    CC2 -->|"HTTPS + Bearer"| Proxy
    CC3 -->|"HTTPS + Bearer"| Proxy

    Proxy["反向代理 nginx/Caddy\nTLS 终结 + Origin 校验"]
    Proxy --> LB["负载均衡\n无状态可水平扩"]
    LB --> S1["FastMCP 容器 1\n/mcp"]
    LB --> S2["FastMCP 容器 2\n/mcp"]
    S1 --> DB[("共享 PostgreSQL\n团队变更日志")]
    S2 --> DB

Streamable HTTP 握手与一次工具调用的时序:

sequenceDiagram
    participant CC as Claude Code
    participant S as FastMCP /mcp
    CC->>S: POST initialize (protocolVersion, capabilities)
    S-->>CC: 200 + Mcp-Session-Id: abc123 (可选)
    CC->>S: POST notifications/initialized
    CC->>S: POST tools/list
    S-->>CC: [add_change, list_changes, get_change]
    CC->>S: POST tools/call add_change(...)
    S-->>CC: text/event-stream (流式结果) 或 application/json
    Note over CC,S: 长任务时: GET /mcp 开 SSE 收进度推送

八、延伸阅读


九、训练营适用性评估

参考来源

  1. Transports — Model Context Protocol 官方规范 — stdio / Streamable HTTP / 旧版 SSE 三种传输的权威定义与会话机制
  2. Connect Claude Code to tools via MCP — claude mcp add 的 transport/scope/header 全部选项与 .mcp.json 配置
  3. FastMCP — HTTP Deployment — streamable-http 部署参数、有状态与无状态取舍
  4. Claude Code Gains Support for Remote MCP Servers over Streamable HTTP — InfoQ — 远程 MCP 支持的工程背景与发布说明
  5. MCP Transport: Stdio vs Streamable HTTP — TrueFoundry — 企业级延迟、负载均衡、扩展性对比

[!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学通信知识星球海报-扫码加入