让 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 写就好了。

相关推荐
SelectDB14 分钟前
数据不能出境的数仓选型与迁移落地:四问框架、可复算成本与迁移三段式
人工智能
李青菜21 分钟前
一个人 + AI,用 Tauri 2 和 Rust 写了个开源的 Windows 效率工具:提醒 + 笔记 + 启动器
人工智能
泯泷27 分钟前
做 AI 产品,路线图该数“实验”,而不是数“功能”
人工智能·llm·产品
百度Geek说38 分钟前
AI 内容核查怎么做:智能内容 AI 核查系统实践
人工智能
武子康1 小时前
Qwen-Drive 到底是什么:4B 级 VLM 如何成为驾驶策略底座
人工智能·算法·机器学习
落798.1 小时前
《启元 PrimeSim 如何用生成视频评估机器人策略》
人工智能·机器人·启元·具身应用
老纪的技术唠嗑局2 小时前
token 暴降75%!减少长程 Agent 任务中的重复开销,PowerContext v1.5.0 发布
人工智能
智塑未来2 小时前
明源云AI走播机器人,打造智能直播新体验
人工智能·机器人
brave10252 小时前
LLMGuide 大模型面试指南
人工智能·ai·面试·ai编程
机 _ 长3 小时前
05 智算运维|8 张卡怎么才算「捆成一张」:NCCL 实测与拓扑
运维·人工智能·算法·智算运维