这篇适合谁,读完能做到什么
适合已经写过一个 MCP 工具、准备把服务器交给别人用或放到生产环境的开发者。读完你应该能按清单检查自己的服务器:工具是否职责单一,参数是否校验,错误信息是否可操作,权限和限流是否到位,以及有副作用的操作是否做好了重试设计。
先定几条原则
这一课归纳了几条核心原则:用 JSON-RPC 2.0 做标准化通信;以用户为中心,优先考虑用户的同意、控制和知情;安全第一;采用模块化设计,每个工具和资源都有清晰、单一的用途;状态要显式管理[1]。
其中最后一条需要特别留意。MCP 2026-07-28 版在协议层是无状态的。如果一个流程需要跨多次调用保持状态,就要用显式的句柄,或者用普通的工具参数,背后由可靠的应用层状态支撑[1]。不要指望协议替你记住上一次调用的结果。
安全:先做这几件事
官方的安全最佳实践列表以用户同意为起点:访问数据或执行操作之前,要获得用户的明确同意;调用任何工具之前,也要获得明确同意[1][2]。
接下来是几项具体要求[1]:
- 认证:访问工具、资源或敏感操作之前,要先完成认证,可以使用 API 密钥、OAuth 令牌或其他安全的方式。
- 参数校验:对每一次工具调用都做校验,防止畸形或恶意的输入进入工具实现。
- 限流:限制调用频率,防止滥用,并保证服务器资源被公平使用。
- 工具权限控制:为每个请求和授权上下文配置模型可以使用哪些工具,只允许明确授权的工具被访问。
这一课还列出了 OWASP MCP Top 10,作为安全风险和缓解措施的参考[1]。
工具设计:一个工具只做一件事
工具应该职责单一,每个工具专注做好一件具体的事。把所有功能塞进一个“万能”工具,会让参数变得复杂,模型也更难判断该调用哪一个[1]。这和模块化的原则是一致的。
参数校验:按顺序检查
参数校验要彻底。下面的 JavaScript 示例在检查参数值的阶段,先确认操作类型属于允许的集合,然后再继续检查其他条件[1]:
// 3. Validate parameter values
const validOperations = ["read", "write", "delete"];
if (!validOperations.includes(parameters.operation)) {
throw new ToolError(`Invalid operation. Must be one of: ${validOperations.join(", ")}`);
}完整的顺序是:先检查参数是否存在,再检查类型,然后检查取值是否合法,接着检查 write 操作是否带了内容,最后检查路径是否在允许的目录之内[1]。文件操作示例里,operation 参数是 read、write、delete 三者之一的枚举,path 必须位于允许的目录中[1]。
错误处理:分清类型,给出可操作的信息
标准化的错误信息和错误码,能帮助人快速定位问题,也能让失败被优雅地处理,并给出可操作的反馈[1]。下面的 Python 示例把超时、数据库连接错误和查询错误分开处理,每一类抛出不同的工具错误,数据库调用还设置了 10 秒超时[1]:
except asyncio.TimeoutError:
raise ToolExecutionError("Database query timed out after 10 seconds")
except DatabaseConnectionError as e:
# Connection errors might be transient
self._log_error("Database connection error", e)
raise ToolExecutionError(f"Database connection error: {str(e)}")
except DatabaseQueryError as e:
# Query errors are likely client errors
self._log_error("Database query error", e)
raise ToolExecutionError(f"Invalid query: {str(e)}")示例里的注释说明了分类的理由:连接错误可能是暂时的,查询错误多半来自调用方的输入。分类之后,调用方才知道该重试还是该修改请求。
限流和认证的写法
这一课给了一个 C# 限流中间件的例子。它从配置项 RateLimit:MaxRequestsPerMinute 读取每分钟的上限,默认值为 60[1]。它按客户端标识(API 密钥或用户 ID)和当前分钟统计请求数,超过上限时返回 HTTP 429,并带上值为 60 的 Retry-After 头;正常响应则附带 X-RateLimit-Limit 和 X-RateLimit-Remaining 头[1]。
认证的例子用 Java 写成。它先校验认证令牌,再针对具体的数据 ID 和操作检查授权,最后才执行读取或更新[1]。注意顺序:认证在前,授权紧随其后,二者缺一不可。
可观测性:日志写到 stderr
在 stdio 方式下,标准输出承载协议消息,诊断信息应当写到 stderr。结构化的可观测性可以使用 OpenTelemetry。规范 2026-07-28 版已把 MCP 自带的日志功能标记为弃用[1]。
有副作用的工具不要盲目重试
通用的重试循环对创建工单、支付、发送消息、部署之类会产生真实影响的工具并不安全。原因是响应可能在操作已经提交之后丢失,客户端以为失败了,于是重试,结果就是重复执行[1]。
这类工具需要更仔细的设计,比如为每次操作设置稳定的操作键,防止重复受理;用检查点记录进度;定期与外部系统对账。这一课还推荐了一篇配套课程,专门讲这些内容[1]。
长任务:报告进度,允许取消
对耗时较长的操作,服务器应当报告进度,让界面保持响应;客户端也应当能取消已经不再需要、或者耗时过长的请求[1]。
限制和常见问题
- 这些示例大多是片段,省略了实现细节,例如
_log_error没有给出实现。直接复制到项目里会跑不起来,应该理解其中的思路再写。 - 最佳实践会随规范变化。这一课以 2026-07-28 版为基准,旧版本的握手和日志方式不同,迁移时要以当前规范为准[1][2]。
- 限流和重试的参数(如 60 次每分钟、10 秒超时)都是示例取值,需要根据服务的实际负载和后端能力调整。
下一步
读完这篇,可以接着看同一系列的测试和工作流设计章节,把这里的原则落实到测试用例上。若服务器会执行有副作用的操作,优先去读关于安全重试的配套课程[1]。
本文改编自 Microsoft 开源课程《MCP for Beginners》的开发最佳实践一课[1],原文以 MIT 许可证发布。
来源
- 1MCP Development Best PracticesMicrosoft mcp-for-beginners (MIT)
- 2Security Best PracticesModel Context Protocol
引用本页: MCP 服务器开发最佳实践. TellWorld, https://www.tellworld.net/zh/practice/practice-mcp-best-practices. 更新于 2026-11-14