让 Agent 替你看 Bug:简单示例读懂 MCP 服务

如何设计一个手写一个 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 的执行链路如下:

sequenceDiagram participant U as 用户(Cursor) participant A as Agent participant MCP as 禅道 MCP 服务 participant Z as 禅道服务器 U->>A: 帮我查看 Bug #1024 的详情 A->>MCP: zentao_get_bug(bugID=1024) MCP->>Z: GET /api.php/v1/bugs/1024 Z-->>MCP: Bug 详情 JSON MCP-->>A: 格式化后的 Bug 详情文本 A-->>U: 展示 Bug 详情

实际效果如下(在 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 写就好了。

相关推荐
AC赳赳老秦1 小时前
公开音频转写信息提取:OpenClaw 处理发布会与听证会文本并提取核心决策信息
大数据·开发语言·汇编·数据库·人工智能·deepseek·openclaw
四六的六1 小时前
Agent 长会话设计实战:从对话上下文到持久状态,把记忆写进检查清单
人工智能·个人开发·ai编程·ai产品·长上下文·ai代码生成·ai会话
u1301301 小时前
GitHub 热榜项目:周榜(2026-09-27)
人工智能·github
AI搅拌机1 小时前
MiniMax H3导演台Bug修复指南:二采、首尾帧生视频和图生视频优化!全能工作流分享~
人工智能
老纪的技术唠嗑局1 小时前
Tibo 谈 Codex:harness 总比模型快一步
数据库·人工智能
程序员于老七1 小时前
漫话大模型:7 家中国公司被点名「蒸馏」,他们到底偷走了什么?
人工智能
YOLO数据集集合1 小时前
风机叶片表面损伤检测数据集 | 风机叶片 表面损伤 污渍检测 无人机巡检 风电运维9119期
运维·人工智能·计算机视觉·目标跟踪·无人机·智慧城市·电力巡检
程序员于老七1 小时前
漫话大模型:反 LLM 新物种:0.1 秒出结果、零幻觉的 Jev 是什么
人工智能
tellmewhoisi1 小时前
机器学习:集成学习4(XGBoost前置知识泰勒展开式4)
人工智能·机器学习·集成学习