如何设计一个手写一个 MCP 服务
什么是 MCP 服务
MCP(Model Context Protocol)是一个让 AI Agent 连接外部系统的标准协议。
Agent 本身能读写代码、操作 Git、搜索文件,但它访问不了禅道、Jira、数据库这类外部系统。MCP 服务就是中间那座桥------你用任意语言写一个小服务,声明几个 Tool(函数),Agent 就能调用它们。
工作原理很简单:Agent 通过 stdio(标准输入输出)向你的 MCP 服务发一个 JSON 请求,比如「调用 zentao_get_bug,参数 bugID=1024」,你的服务去调禅道 API,拿到结果,格式化后返回。Agent 拿到结果后继续它的工作。
Agent ──stdio──> 你的 MCP 服务 ──HTTP──> 禅道服务器
Agent <──stdio── 你的 MCP 服务 <──HTTP── 禅道服务器
一个 MCP 服务就是一个独立进程,核心就三件事:定义 Tool(函数签名 + 描述)、实现 Tool(调外部 API)、返回结果(格式化纯文本)。
从最简单的例子开始
先只做一个 Tool:zentao_get_bug------根据 Bug ID 获取详情。跑通这一个,其余都是同样的套路。
安装依赖
bash
pip install mcp httpx
完整代码
python
# server.py
import os, httpx
from mcp.server.fastmcp import FastMCP
# 配置:从环境变量读取
ZENTAO_BASE = os.environ.get("ZENTAO_BASE_URL", "https://zentao.example.com")
# Token 管理:懒加载 + 缓存
_token = None
def get_token():
global _token
if _token is None:
resp = httpx.post(f"{ZENTAO_BASE}/api.php/v1/tokens", json={
"account": os.environ["ZENTAO_ACCOUNT"],
"password": os.environ["ZENTAO_PASSWORD"]
})
_token = resp.json()["token"]
return _token
def zentao_get(path):
return httpx.get(f"{ZENTAO_BASE}{path}",
headers={"Token": get_token()}).json()
# MCP 服务定义
mcp = FastMCP("zentao", description="禅道 MCP 服务")
@mcp.tool()
def zentao_get_bug(bugID: int) -> str:
"""获取禅道中某个 Bug 的完整详情,包括重现步骤和严重程度。
当用户提到 Bug 编号、要求查看 Bug 细节时使用。"""
bug = zentao_get(f"/api.php/v1/bugs/{bugID}")
severity = {1: "致命", 2: "严重", 3: "一般", 4: "轻微"}
return (
f"Bug #{bug['id']}: {bug['title']}\n"
f"严重程度: {severity.get(bug['severity'], '未知')}\n"
f"状态: {bug['status']} | 指派: {bug['assignedTo']}\n"
f"重现步骤:\n{bug.get('steps', '无')}"
)
if __name__ == "__main__":
mcp.run()
不到 40 行。逐块看:
get_token() --- 首次调用时用账号密码换 Token,之后复用缓存。禅道 Token 有效期通常 30 分钟,简单场景够用。
@mcp.tool() --- 这个装饰器把普通函数注册为 MCP Tool。函数签名就是参数定义(bugID: int),docstring 就是 Agent 看到的 Tool 描述。docstring 不是给人看的文档,是给 Agent 看的触发条件。 所以不只写「获取 Bug 详情」,还写「当用户提到 Bug 编号时使用」------Agent 靠这句话决定要不要调用。
返回值 --- 格式化纯文本,不是 JSON。Agent 上下文窗口宝贵,纯文本比 JSON 省 token,信息密度更高。
前提
跑通这个例子之前,需要确认三件事:
第一,禅道版本 15.0+,且管理员在「后台 → 系统 → 参数 → API」中开启了 REST API。
第二,有一个有 API 权限的账号。建议创建专用账号(如 mcp-bot),不要复用个人账号。
第三,了解禅道的数据层级。Bug 挂在产品下(需要 productID),任务挂在迭代下(需要 executionID)。这直接决定 Tool 的参数怎么设计。
部署到 Cursor
打开 Cursor Settings → Customize → MCP Servers,添加配置:
json
{
"mcpServers": {
"zentao": {
"command": "python",
"args": ["/你的路径/zentao-mcp/server.py"],
"env": {
"ZENTAO_BASE_URL": "https://你的禅道地址",
"ZENTAO_ACCOUNT": "mcp-bot",
"ZENTAO_PASSWORD": "你的密码"
}
}
}
}
Cursor 会自动启动这个 Python 进程。配好后在 Chat 里说「帮我看看 Bug #1024」,Agent 就会调用 zentao_get_bug,拿到详情后直接开始工作。
完整工作流
来看这个 MCP 服务在真实场景中是怎么工作的。你在 Cursor 里说:
「帮我查看 Bug #1024 的详情」
Agent 的执行链路如下:
实际效果如下(在 Cursor 中输入「查看 bug 5620」,Agent 调用 MCP 服务后返回的 Bug 详情):

扩展更多 Tool
跑通第一个后,后面的都是复制粘贴。
查询 Bug 列表:
python
@mcp.tool()
def zentao_list_bugs(productID: int, status: str = "active", limit: int = 20) -> str:
"""查询禅道中指定产品的 Bug 列表。可按状态筛选。
当用户询问'有哪些未解决的 Bug'、'严重 Bug 有哪些'时使用。"""
data = zentao_get(f"/api.php/v1/products/{productID}/bugs")
bugs = data.get("bugs", [])[:limit]
if not bugs:
return "当前没有符合条件的 Bug。"
return "\n".join(
f"#{b['id']} {b['title']} | 指派: {b['assignedTo']}" for b in bugs
)
注意 status 和 limit 都有默认值,只有 productID 是必填的。如果 status 也必填,Agent 每次都要先问用户「你要查什么状态」,白白多一轮对话。
更新任务状态:
python
@mcp.tool()
def zentao_update_task(taskID: int, status: str, consumed: float = None) -> str:
"""更新禅道任务的状态或工时。当用户要求'标记任务完成'、'更新进度'时使用。"""
body = {"status": status}
if consumed is not None:
body["consumed"] = consumed
httpx.put(f"{ZENTAO_BASE}/api.php/v1/tasks/{taskID}",
json=body, headers={"Token": get_token()})
return f"任务 #{taskID} 已更新为 {status}"
写操作要更保守------status 只允许 doing/done/pause,不包含 cancel 和 closed,避免 Agent 误操作。
四条设计原则
从一个 Tool 到多个,贯穿的原则就四条:
按意图拆分 Tool。 list_bugs、get_bug、list_tasks 各管各的,不要做成一个万能查询接口。Agent 选 Tool 靠名字和描述,语义越清晰越不容易选错。
docstring 写触发场景。 「获取 Bug 详情」是功能说明,「当用户提到 Bug 编号时使用」才是 Agent 判断调不调用的依据。
只把真正必填的放 required。 可选参数给合理默认值,减少 Agent 追问次数。
返回纯文本,不返回 JSON。 MCP 服务是 Agent 和外部系统之间的翻译层。
最后
一个 MCP 服务就是一个独立进程,核心是定义 Tool、写清描述、调外部 API、返回格式化结果。
而且你完全可以不自己写。告诉 AI:「帮我写一个禅道 MCP 服务,需要查 Bug、看详情、查任务、更新状态,禅道地址是 xxx」,AI 能生成完整代码。你的价值在于:知道团队最痛的场景是什么,定义 Tool 清单划定能力边界,用真实场景验证结果是否好用。
设计 MCP 服务的核心不是写代码,是理解 Agent 怎么工作、外部系统数据长什么样、两者之间怎么对接。代码让 AI 写就好了。