游乐游手机版
首页/AI教程/文章详情

MCP Server开发入门与协议调试生产部署

时间:2026-07-13 16:14
MCPServer开发需解决五个工程问题:多工具共享数据层;输入验证含枚举值、必填字段和业务规则;错误处理区分硬错误与软反馈;日志必须写入stderr避免破坏协议;Resources使LLM能动态获取上下文数据。以JiraServer为例,实现4个工具、1个资源和1个Prompt,覆盖完整测试用例。

从 MCP Server 入门到生产级部署实战

上一篇文章中的 demo server 虽然能够正常运行,但实际上只提供了 echoadd 两个基础工具。想要将一个 Server 真正部署到生产环境,还需要解决以下 5 个关键工程问题:

MCP Server 入门开发 + 协议调试 + 生产级部署

  1. 多个工具之间如何协同工作,数据如何实现共享
  2. 用户输入如何进行验证——不要以为 LLM 就不会传递错误参数
  3. 工具执行失败时,如何告知客户端,采用什么格式返回错误信息
  4. 日志应该输出到哪里——写错位置会直接破坏协议
  5. 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_issueupdate_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
来源:https://juejin.cn/post/7660896500881801268
上一篇机器学习基本术语详解与核心概念解析 下一篇iOS设备上PlayWright MCP服务器的安装配置与使用详细指南
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
手写Mini Cursor:Agent文件读写、命令执行与项目创建
AI教程 · 2026-07-13

手写Mini Cursor:Agent文件读写、命令执行与项目创建

基于ReAct循环构建的MiniCursorAgent,通过Node jsRuntime提供读文件、写文件、查看目录、执行命令四个工具,在本地工作区自主完成创建ReactTodoList项目、实现功能、添加样式动画等编程任务,模型根据工具结果循环决策。

一条脚本结合多步流水线与HereDoc实现全自动内容生产
AI教程 · 2026-07-13

一条脚本结合多步流水线与HereDoc实现全自动内容生产

多步流水线将多个命令调用串联成自动化生产流程,HereDoc通过cat命令以标准输入方式传递长文本prompt,避免引号嵌套引发的混乱,显著提升脚本可读性与可维护性,是处理长文本提示的有效方法,广泛应用于自动化脚本编写。

iOS设备上PlayWright MCP服务器的安装配置与使用详细指南
AI教程 · 2026-07-13

iOS设备上PlayWright MCP服务器的安装配置与使用详细指南

基于Appium的iOS设备自动化测试MCPServer,提供屏幕截图、元素点击及UI树获取功能,支持Stdio与SSE两种运行模式,可无缝接入Trae等支持MCP协议的AI客户端,所有工具共享同一Appium会话,避免重复启动开销,显著提升测试效率。

机器学习基本术语详解与核心概念解析
AI教程 · 2026-07-13

机器学习基本术语详解与核心概念解析

机器学习以数据为基础,样本由属性构成特征向量。训练从数据中学习模型,有标记的样本用于监督学习(分类与回归),无标记的样本用于无监督学习(聚类)。泛化能力是衡量模型对新样本适用性的关键标准。