MCP Server开发入门与协议调试生产部署
MCPServer开发需解决五个工程问题:多工具共享数据层;输入验证含枚举值、必填字段和业务规则;错误处理区分硬错误与软反馈;日志必须写入stderr避免破坏协议;Resources使LLM能动态获取上下文数据。以JiraServer为例,实现4个工具、1个资源和1个Prompt,覆盖完整测试用例。
从 MCP Server 入门到生产级部署实战
上一篇文章中的 demo server 虽然能够正常运行,但实际上只提供了 echo 和 add 两个基础工具。想要将一个 Server 真正部署到生产环境,还需要解决以下 5 个关键工程问题:

- 多个工具之间如何协同工作,数据如何实现共享
- 用户输入如何进行验证——不要以为 LLM 就不会传递错误参数
- 工具执行失败时,如何告知客户端,采用什么格式返回错误信息
- 日志应该输出到哪里——写错位置会直接破坏协议
- Resources 如何运用,让 LLM 能够动态获取上下文数据
本次 Demo 是一个完整的 Jira Server,内置了 mock 数据,无需真实的 Jira 账号即可运行。
MCP Server 整体架构设计
整个 Server 围绕 Jira 的核心业务操作展开,架构上包含了 4 个工具、1 个资源和 1 个 Prompt。工具分别是 search_issues(按关键字、项目或状态检索 issue)、get_issue(根据 issue key 获取详细信息)、create_issue(创建新 issue)和 update_issue(更新现有 issue)。资源方面暴露了一个 jira://projects 端点,LLM 可以通过读取它来了解有哪些合法的 project key。Prompt 则提供了一个 Bug 分析报告的模板。
模式 1:多工具协同工作机制
这 4 个工具共享同一个内存数据存储 ISSUES,形成了一个完整的读写操作闭环:
# 共享的数据层(生产环境对应真实数据库/API)
ISSUES: dict[str, dict] = {
"PROJ-101": {
"key": "PROJ-101",
"summary": "NullPointerException in parseInput() when config is null",
"status": "Open", "priority": "P1", "issue_type": "Bug",
...
},
...
}
# search_issues:查询
# get_issue:精确读取
# create_issue:写入新 Issue(递增 ID)
# update_issue:修改已有 Issue
在设计决策上,验证逻辑被提取为一个公共函数 validate_issue_key(),get_issue 和 update_issue 都调用该函数,有效避免了重复代码:
def validate_issue_key(key: str) -> str | None:
"""Return error message if key is invalid, None if valid."""
if not key or "-" not in key:
return f"Invalid issue key format: '{key}'. Expected format: PROJECT-123"
project = key.split("-")[0]
if project not in PROJECTS:
return f"Unknown project: '{project}'. A vailable projects: {', '.join(PROJECTS)}"
if key not in ISSUES:
return f"Issue '{key}' not found"
return None
模式 2:输入验证机制
不可忽视的是,LLM 确实可能传递错误参数:project key 拼写错误、status 使用了非法值、summary 为空。Server 必须进行验证,否则返回的错误信息对 LLM 来说毫无意义。
枚举值验证是基础操作:
VALID_STATUSES = {"Open", "In Progress", "In Review", "Done", "Closed"}
VALID_PRIORITIES = {"P0", "P1", "P2", "P3"}
VALID_ISSUE_TYPES = {"Bug", "Story", "Task", "Epic"}
if new_status and new_status not in VALID_STATUSES:
return [TextContent(type="text", text=(
f"Invalid status: '{new_status}'. Valid values: {', '.join(sorted(VALID_STATUSES))}"
), isError=True)]
必填字段同样需要严格检查:
if not summary:
return [TextContent(type="text", text="'summary' is required.", isError=True)]
同样关键的是业务规则验证,例如 summary 不能超过 255 个字符,或者 update_issue 时至少需要传递一个要更新的字段:
if len(summary) > 255:
return [TextContent(type="text", text="Summary exceeds 255 characters.", isError=True)]
# 至少要更新一个字段
if not any([new_status, new_assignee is not None, new_priority]):
return [TextContent(type="text", text=(
"No fields to update. Provide at least one of: status, assignee, priority."
), isError=True)]
模式 3:结构化错误处理策略
MCP 协议提供了两种表示"失败"的方式,选择错误会直接影响 LLM 的处理效果。
方式 A 是硬错误,使用 isError=True:
return [TextContent(type="text", text="Issue 'PROJ-999' not found", isError=True)]
LLM 收到 isError=true 后,会调整策略——更换 issue key 重试,或者直接告知用户。这种方式适用于资源不存在、参数非法、权限不足等场景。
方式 B 是软反馈,正常响应但不标记为错误:
return [TextContent(type="text", text=(
"Unknown project: 'INVALID'. A vailable projects: PROJ, MOBILE, INFRA"
))]
# 注意:没有 isError=True
LLM 认为工具执行成功了,只是结果中包含了纠错信息。它会读取建议后自动修正参数并重试。这种方式更适合搜索类操作——输入可能模糊,返回提示有助于 LLM 自我校正。
测试结果对比很能说明问题:
search 'anything' with project='INVALID'(无 isError):
✓ Unknown project: 'INVALID'. A vailable projects: PROJ, MOBILE, INFRA → LLM 读取后会用 PROJ/MOBILE/INFRA 重试
get_issue('PROJ-999')(有 isError=True):
✗ Issue 'PROJ-999' not found → LLM 知道这是失败,不会继续用这个 key
模式 4:日志必须输出到 stderr
这里有一个关键约束需要强调:stdout 是 JSON-RPC 的通信通道。任何写入 stdout 的内容都会破坏协议。
# ✅ 正确:日志写到 stderr
logging.basicConfig(
stream=sys.stderr, # ← 必须是 stderr
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logger = logging.getLogger("jira-mcp")
# 工具里的日志
logger.info("tools/call: %s args=%s", name, arguments)
logger.info("Created issue %s", key)
# ❌ 错误:任何 print() 都会破坏协议
print("Created issue PROJ-201") # ← 这会把文字注入 JSON-RPC 流,导致 Client 解析失败
调试时查看日志的方法也很简单:
# 日志输出到 stderr,不影响 stdout 的 JSON-RPC 流
python demo_jira_server.py 2>server.log &
# 后台运行,日志写文件
tail -f server.log
# 另开终端实时查看
模式 5:Resources 提供动态上下文
工具 schema 中虽然可以写 "A vailable: PROJ, MOBILE, INFRA",但这只是硬编码在描述文字里。更好的做法是暴露一个 Resource,让 LLM 能够主动读取最新的项目列表:
@server.list_resources()
async def list_resources() -> list[Resource]:
return [
Resource(
uri="jira://projects", # type: ignore[arg-type]
name="A vailable Projects",
description="List of Jira projects. Read this to know valid project keys.",
mimeType="application/json"
)
]
@server.read_resource() # type: ignore[arg-type]
async def read_resource(uri: str) -> str:
if str(uri) == "jira://projects":
data = [{"key": k, "name": v["name"], "lead": v["lead"]}
for k, v in PROJECTS.items()]
return json.dumps(data, indent=2)
这种方式的效果非常直接:LLM 在调用任何 Jira 工具之前,可以先读取 jira://projects 来了解合法的 project key,无需再去猜测。特别适合动态变化的枚举值——项目会增减,但 schema 中的硬编码不会自动更新。
完整测试结果汇总
所有测试结果汇总如下,覆盖了各个工具的正常流程和异常路径:
==================================== Jira MCP Server — Test Suite ====================================
[search_issues]
✓ search 'NPE' in PROJ
Found 1 issue(s): [PROJ-101] NullPointerException in parseInput()...
✓ search 'crash' with status=Open
Found 1 issue(s): [MOBILE-55] Crash on Android 14 when opening notification settings...
✓ search with invalid project (友好提示,无 isError)
Unknown project: 'INVALID'. A vailable projects: PROJ, MOBILE, INFRA
[get_issue]
✓ get PROJ-101 (P1 Bug)
Issue: PROJ-101 Type: Bug | Status: Open | Priority: P1 ...
✗ get PROJ-999 (not found → isError=true)
Issue 'PROJ-999' not found
✗ get malformed key 'not-a-key' (→ isError=true)
Unknown project: 'NOT'. A vailable projects: PROJ, MOBILE, INFRA
[create_issue]
✓ create valid Bug in PROJ
Created issue PROJ-201: Redis connection pool not releasing connections...
✗ create with empty summary (→ isError=true)
'summary' is required.
✗ create with invalid priority 'CRITICAL' (→ isError=true)
Invalid priority: 'CRITICAL'. Valid values: P0, P1, P2, P3
[update_issue]
✓ update PROJ-101 status + assignee
Updated PROJ-101: status: Open → In Progress, assignee: alice → bob
✗ update with no fields (→ isError=true)
No fields to update. Provide at least one of: status, assignee, priority.
✗ update with invalid status 'Shipped' (→ isError=true)
Invalid status: 'Shipped'. Valid values: Closed, Done, In Progress, In Review, Open
[resources]
A vailable resources: ['jira://projects']
Projects: ['PROJ', 'MOBILE', 'INFRA']
这里有一个值得注意的细节:get_issue('not-a-key') 返回了 "Unknown project: 'NOT'" 而不是 "invalid key format"。原因是验证逻辑先拆分 - 取 project 部分,not-a-key 被拆出了 NOT,进入了"project 不存在"的分支。对 LLM 来说这个错误信息仍然足够有用——它知道 NOT 不是合法 project。但如果需要更精确,可以在验证前先检查 key 是否匹配 [A-Z]+-\d+ 的格式。
Schema 设计要点解析
工具 schema 是 LLM 读取的文档,其重要性甚至超过人类编写的代码注释:
Tool(
name="search_issues",
description=(
"Search Jira issues by keyword, project, or status. "
"Use when the user asks about bugs, tasks, tickets, or issues. " # ← 触发时机
"Returns a list of matching issues with key, summary, status, priority." # ← 返回格式
),
inputSchema={
"properties": {
"project": {
"type": "string",
"description": f"Filter by project key. A vailable: {', '.join(PROJECTS)}"
# ↑ 把合法值内联在描述里,LLM 不需要另外问
},
"max_results": {
"type": "integer",
"description": "Maximum number of results (default: 10, max: 50)",
"default": 10
# ↑ 描述里写 max 值,schema 里写 default
}
}
}
)
接入 Claude Code 的配置方法
将这个 Server 接入 Claude Code 只需在配置文件中添加一个条目:
// .claude/settings.json
{
"mcpServers": {
"jira-demo": {
"command": "python",
"args": ["/path/to/mcp-04-first-server/demo_jira_server.py"]
}
}
}
重启 Claude Code 后,就可以直接提问:"帮我搜一下 PROJ 项目里所有 P1 的 Bug",Claude 会自动调用 search_issues 工具来完成搜索。
参考资料与延伸阅读
- MCP Python SDK 官方文档
- 本系列完整 Demo 代码:mcp-04-first-server
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
手写Mini Cursor:Agent文件读写、命令执行与项目创建
基于ReAct循环构建的MiniCursorAgent,通过Node jsRuntime提供读文件、写文件、查看目录、执行命令四个工具,在本地工作区自主完成创建ReactTodoList项目、实现功能、添加样式动画等编程任务,模型根据工具结果循环决策。
一条脚本结合多步流水线与HereDoc实现全自动内容生产
多步流水线将多个命令调用串联成自动化生产流程,HereDoc通过cat命令以标准输入方式传递长文本prompt,避免引号嵌套引发的混乱,显著提升脚本可读性与可维护性,是处理长文本提示的有效方法,广泛应用于自动化脚本编写。
iOS设备上PlayWright MCP服务器的安装配置与使用详细指南
基于Appium的iOS设备自动化测试MCPServer,提供屏幕截图、元素点击及UI树获取功能,支持Stdio与SSE两种运行模式,可无缝接入Trae等支持MCP协议的AI客户端,所有工具共享同一Appium会话,避免重复启动开销,显著提升测试效率。
MCP Server开发入门与协议调试生产部署
MCPServer开发需解决五个工程问题:多工具共享数据层;输入验证含枚举值、必填字段和业务规则;错误处理区分硬错误与软反馈;日志必须写入stderr避免破坏协议;Resources使LLM能动态获取上下文数据。以JiraServer为例,实现4个工具、1个资源和1个Prompt,覆盖完整测试用例。
机器学习基本术语详解与核心概念解析
机器学习以数据为基础,样本由属性构成特征向量。训练从数据中学习模型,有标记的样本用于监督学习(分类与回归),无标记的样本用于无监督学习(聚类)。泛化能力是衡量模型对新样本适用性的关键标准。
- 热门数据榜
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
相关攻略
2026-07-13 16:15
2026-07-13 16:14
2026-07-13 16:14
2026-07-13 16:14
2026-07-13 16:14
2026-07-13 16:13
2026-07-13 16:13
2026-07-13 16:13
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

