🦾 AI Agent 🟡 进阶 ⏱ 10 分钟

🛠️ MCP Server 怎么搭?从 15 行代码到接入宿主

别先啃协议——搭一个 MCP Server 核心就是一个 Python 文件加几个装饰器。先跑起来,再一步步接到 Claude/Cursor、部署到远程。

1

先跑起来:15 行一个能用的 Server

⚡ 别被协议吓到

搭 MCP Server 不需要自己实现 JSON-RPC、握手、传输——官方 Python SDK 全帮你干了。

你要做的核心只有一件事:用装饰器把函数注册成能力。

下面 15 行就是一个完整可跑的 Server。

📦 先装 SDK

pip install "mcp[cli]"

mcp 是核心库,[cli] 额外给你 mcp dev / mcp run / mcp install 命令行工具。

没有它你连写都不用写。

💻 server.py · 一个完整可跑的 Server
from mcp.server import MCPServer

mcp = MCPServer("我的工具箱")

@mcp.tool()
def add(a: int, b: int) -> int:
    """两个数相加。"""
    return a + b

@mcp.resource("config://{name}")
def get_config(name: str) -> str:
    """按名字读一条配置。"""
    return f"{name} 的配置"

保存成 server.py,终端里跑:

mcp dev server.py

会自动打开 MCP Inspector(一个网页调试台),你在里面点 add(a=1, b=2) 返回 3——第一个 MCP Server 就这样跑通了。

🔑
MCPServer("名字")

创建 Server 实例。这个名字会出现在宿主(Claude / Cursor)的工具列表里。

🧩
@mcp.tool()

把下面的普通函数注册成一个工具。函数名 = 工具名,返回给模型。

📋
docstring = 描述

函数说明文字就是模型的「工具描述」,决定模型什么时候调它。写清楚使用场景。

🧬
类型注解 = Schema

a: int, b: int 会自动变成 JSON Schema——你不用手写参数结构定义。

2

写一个真工具:docstring 是描述,注解是 Schema

最小版够跑,但真实工具要带参数、写清 docstring。看一个查天气的工具:

💻 weather_server.py · 带参数的真工具
from mcp.server import MCPServer
import json

mcp = MCPServer("天气助手")

@mcp.tool()
def get_weather(city: str, unit: str = "celsius") -> str:
    """查询指定城市的当前天气。city 是城市名,unit 可选 celsius 或 fahrenheit。"""
    # 这里通常去调真实天气 API,为演示直接返回
    data = {"city": city, "temp": 25, "unit": unit, "desc": "晴"}
    return json.dumps(data, ensure_ascii=False)
🎬 @mcp.tool() 内部发生了什么
① 注册
启动时把 get_weather 登记进「能力清单」
② 出 Schema
宿主调 tools/list,SDK 用你的函数签名自动生成 JSON Schema(city 必填 string,unit 默认 celsius)
③ 被调用
模型决策要查天气 → 宿主调 tools/call 带参数 {'city':'北京'} → SDK 帮你反序列化、调你的函数
④ 回传
你的返回值变成工具结果,塞回给模型
写工具的黄金习惯为什么
docstring 写清「何时用 + 参数含义」模型靠描述决定调不调、怎么填参
参数类型 / 默认值标清楚自动生成正确的 Schema,模型少填错
返回 JSON 而非裸字符串结构化的结果更好被模型消化
3

Tool / Resource / Prompt:三种能力怎么选

🛠️ Tool:有副作用

会改变世界 → 用 Tool。
创建文件、发消息、改数据库。
注册:@mcp.tool()

📄 Resource:只读数据

只是读取上下文 → 用 Resource。
读 README、查 schema、看日志。
注册:@mcp.resource("uri")

📝 Prompt:提示模板

标准化一段指令 → 用 Prompt。
代码审查、日报模板等最佳实践。
注册:@mcp.prompt()

💻 三种能力一起注册
@mcp.resource("note://{id}")
def get_note(id: str) -> str:
    """按 id 读一条笔记(只读)。"""
    return f"笔记 {id} 的内容"

@mcp.prompt()
def code_review(pr: int) -> str:
    """生成一段 code review 指令模板。"""
    return f"请 review PR #{pr},重点看:正确性、可读性、安全性。"
🎬 拿不准就自问一句
✅ 该用 Tool
调用了会改变外部状态吗?(发消息 / 写文件 / 改库)→ Tool
✅ 该用 Resource
只是把已有数据喂给模型看?(读文档 / 查记录)→ Resource
✅ 该用 Prompt
想让模型按固定套路干活?(审查 / 日报 / 规范)→ Prompt
4

接进宿主:Claude Desktop / Cursor

本地部署走 stdio——宿主把你的 Server 当子进程拉起来,通过标准输入输出通信,不用写 HTTP 服务。

以 Claude Desktop 为例,在它的配置文件 claude_desktop_config.json 里加一段:

💻 claude_desktop_config.json · 接入宿主
{
  "mcpServers": {
    "weather": {
      "command": "python",
      "args": ["/绝对路径/weather_server.py"]
    }
  }
}
🎬 三种跑法
mcp dev server.py
开发调试:打开 Inspector 网页,逐个调工具看返回
mcp run server.py
在 stdio 上跑起来,给命令行客户端用
配进 config
重启宿主后,工具自动出现在对话里,模型自己会调
5

远程部署:Streamable HTTP

本地 stdio 只能给「同一台机器」的宿主用。要多端 / 远程 用,就用 Streamable HTTP——把 Server 挂成一个 HTTP 端点,宿主填 URL 连接。

💻 main.py · 挂成 HTTP 端点
from mcp.server import MCPServer
from fastapi import FastAPI

mcp = MCPServer("天气助手")
# ... 上面那些 @mcp.tool() / @mcp.resource() 保持不变 ...

app = FastAPI()
app.mount("/mcp", mcp.streamable_http_app())
# 启动:uvicorn main:app --host 0.0.0.0 --port 8000
stdio

本地

宿主当子进程拉起
无需网络、无需鉴权
适合桌面客户端
配置写 command + args

VS
Streamable HTTP

远程

挂成 URL 端点
跨设备、可鉴权
适合多端 / 服务端
配置写 url

本地开发用 stdio · 对外提供服务用 Streamable HTTP

远程配置就一行:

{"mcpServers":{"weather":{"url":"http://服务器:8000/mcp"}}}

6

踩坑 & 工具多了的 Token 优化

✂️
docstring 别啰嗦

描述既是「模型怎么用它」的说明书,也是要占 token 的。写清使用场景即可,别堆形容词——太长既费 token 又让模型乱调。

🎯
按需注入 top-K

工具几十上百个时,别全量塞给模型。根据用户意图先做 embedding 召回,只注入命中的 top-K 个工具,其余不占上下文。

🗂️
按模块分组路由

把工具按功能分组(订单/用户/物流),用户意图先路由到某组,再只加载该组的工具定义。

⚡
懒加载 + 复用连接

不常用的工具不预注册,用到再 list_tools 拉取;HTTP 部署复用连接、避免重复握手。

手段效果
精简 docstring减少每个工具占的字数
按需注入 top-K(embedding 召回)全量注入 → 只注入命中的工具
按模块分组路由只加载相关那组的工具
懒加载 + 复用连接不常用的不占上下文,省握手
一句话总结

搭 MCP Server 核心三步:装 SDK(pip install "mcp[cli]")→ 用 @mcp.tool()/@mcp.resource()/@mcp.prompt() 把函数注册成能力(docstring 是描述、类型注解自动生成 Schema)→ 用 mcp dev 测试 / 配 config 接入宿主(本地 stdio)或挂 FastAPI 用 Streamable HTTP 远程部署。工具多了会占 token,靠精简描述、按需注入、按模块分组、懒加载来降开销。

🎤 面试问答 · 口语化回答
这篇是动手向,面试官很喜欢问「你搭过吗」,要能讲出真实搭建的步骤和坑:
面试官MCP Server 一般怎么搭?
你

我一般装官方 Python SDK(pip install "mcp[cli]"),然后写一个 Python 文件:创建一个 MCPServer 实例,用 @mcp.tool() 装饰器把函数注册成工具。函数签名就是参数定义、docstring 就是工具描述,SDK 会自动生成 JSON Schema,完全不用手写协议。写完用 mcp dev server.py 打开 Inspector 测试;本地给宿主配 stdio 的 command+args,远程就挂 FastAPI 用 Streamable HTTP 填 URL。

💡 加分点:强调「不用手写协议、装饰器注册、类型注解=Schema、docstring=描述」这些实战细节,说明你真搭过。
面试官@mcp.tool() 注册的工具,是怎么被模型调起来的?
你

分四步。启动时工具被登记进能力清单;宿主连上后调 tools/list,SDK 用函数签名自动生成 JSON Schema 返回;模型决策要调用时输出工具名和参数,宿主调 tools/call 带着参数来;SDK 帮我把参数反序列化、调我的函数,返回值再回传塞给模型。核心是模型只出意图,真正执行的是我的函数。

💡 加分点:把「tools/list 发现 → 生成 Schema → tools/call 执行 → 结果回传」链路讲清,并点出「模型只决策、函数执行」。
面试官MCP 工具多、token 占用大,怎么办?
你

核心是按需加载,别全量塞。我主要用:精简 docstring(描述既是说明书也占 token,写清场景别啰嗦);按用户意图 embedding 召回 top-K 个工具只注入命中的;按模块分组路由,先路由到某组只加载那组的工具;懒加载不常用的工具、用到再拉取。远程 HTTP 复用连接避免重复握手。

💡 加分点:能说出「按需加载 + 精简描述 + 分组路由 + 懒加载」这套组合,说明你做过大工具量项目。
📝 读完打卡 · 写下你的收获 读完了?来打卡吧
用一句话写下你从这篇学到的最大收获,检验自己是否真的懂了 👇
⏱ 00:00 🔥0分