mcp-server-template
mcp-server-template
这是一个面向生产环境(production-shaped)的 MCP 服务器起点。
MCP 文档里的快速入门只需十行代码就能给你一个可用的工具。而下面这些,是这个工具一旦被某个你无法控制的东西调用之后,在接下来的三个星期里你需要补上的东西。
@mcp.tool()
def add(a: int, b: int) -> int:
return a + b # fine on a laptop那里缺的并不是功能,而是当工具挂起、抛错、返回一个奇怪的输出,或者被同一时刻调用四十次时会发生什么——以及在这种情况下,模型被允许看到什么。
它解决的问题
MCP 工具的调用方是一个语言模型,这改变了工程上的取舍。
模型读不懂堆栈跟踪,但它会乐呵呵地把堆栈复述给你的业务用户。所以泄漏的 traceback 既毫无用处,又是一种信息泄露。
模型有自己的截止时间。一个挂起的工具不会换来一个慢一点的回答,它只会带了一个已经死掉的对话。
模型分不清被截断的结果和完整的结果。上下文悄悄地溢出并不会抛错——它只会让回答质量下降,而你往往是从用户那里才知道出了问题。
只要你允许,模型就会重试。所以「not found」和「upstream is down」必须是两种绝对不同的回答,否则它会为一个根本不存在的记录反复锤打某个服务。
以上每一种情况,都在同一点只处理一次,所以周五下午新加的工具,和第一天小心翼翼写出来的那个工具,享有着同等的保护。
Related MCP server: mcp-starter-kit
你得到了什么
每次工具超时 | 真实截止,而不是事后才收到警告。返回一个模型可以据此行动的 |
并发上限 | 受控的并行执行,突发流量不会冲击的工具调用的下游对象 |
错误边界 | 声明的错误原样传给调用方;未预期的错误变成无任何细节的 |
密钥脱敏 | 同时作用于日志 和 出站消息,因为密钥更多是通过插值格式化后的异常字符串泄漏出去的,而不是在代码里直接泄漏 |
可见截断 | 过长的输出会被截断,并且一定保留标记,绝不静默 |
关联 ID | 每次调用、每条日志、每个错误都是同一个 ID,方便用户原样反馈回你 |
结构化日志到 stderr | stdout 是协议专用的;一行多余的 |
快速失败的配置 | 糟糕的配置在启动即停止服务器,而不是等第一个请求来了才报错 |
离线测试 | 测试套件在高铁上都能跑。不需要真实密钥,也不依赖网络 |
快速开始
git clone https://github.com/muhammadwaqasmbd/mcp-server-template
cd mcp-server-template
make install
make test
make run # stdio, ready for a desktop MCP client改为通过线上提供服务:
TRANSPORT=streamable-http PORT=8000 python -m mcp_server_template让桌面客户端接入它
{
"mcpServers": {
"template": {
"command": "python",
"args": ["-m", "mcp_server_template"],
"cwd": "/absolute/path/to/mcp-server-template"
}
}
}添加你自己的工具
只需要写这个接口。其他什么都不用。
# src/mcp_server_template/tools/orders.py
from ..errors import InvalidInput, UpstreamUnavailable
async def cancel_order(order_id: str) -> dict:
"""Cancel an order. Returns the order's new state."""
if not order_id.strip():
raise InvalidInput("order_id must not be empty") # model can fix this
...
raise UpstreamUnavailable("order service timed out") # model may retry在守卫(guard)后面注册它:
mcp.tool(name="cancel_order", description="Cancel an order by id.")(
guard.wrap(orders.cancel_order)
)它现在就有了超时、并发上限、错误边界、截断和日志。你一行都没写。
当模型可以纠正输入时,抛出 InvalidInput;当重试可能有效时,抛出 UpstreamUnavailable;对于单纯是「失败」的结果,直接正常返回即可——查无此事是一种答案,而不是失败。
架构
server.py the ONLY module that imports the MCP SDK
│
├── guard.py timeout · concurrency · error boundary · truncation · timing
├── errors.py what a model is allowed to see, and secret redaction
├── observability.py JSON logs on stderr, correlation ids
├── config.py validated once at boot, immutable thereafter
└── tools/ plain functions. No protocol knowledge. No decorators依赖关系只有单向:工具对 MCP 一无所知,守卫(guard)对你的工具也一无所知。正因如此,测试才能在不启动服务器的情况下以毫秒级跑完,才让 SDK 升级仅仅只改一个文件。
本模板刻意没有做的事情
坦诚边界比罗列更多特性更有用。
无鉴权认证。 在 stdio 之上,操作系统边界就是安全边界。如果用 HTTP 暴露它,就必须自己在前端增加真正的 auth——SDK 是支持的;把它在这里替换,甚至暗示了一个你自己还没选定的威胁模型。
工具内没有重试逻辑。 guard 只报告是否 retryable;是否重试由调用方决定,因为只有调用对方拥有上下文和预算。
没有调用方的限速区分。 并发上限约束的是总工程量,而不属于单用户身份的公平。你如果需要公平,你得先有身份。
没有持久化、没有队列、没有调度器。 一个悄悄变成任务执行器的工具服务器,就是一套没人设计过的分布式系统。
没有流式部分结果。 对长时间运行的工具有价值;这里没做,因为它会弄复杂多错误边界,而多数工具用不到。
测试
make test这套测试是刻意用来测「失败」的,而不是测「覆盖率」的。它断言了:会挂起的工具已被取消;意外的异常不会泄漏自己消息;超大输出被是可见地截断;并发上限在十万个同时调用时仍然成立;以及阻塞的同步工具不会让事件循环挨无休止地饥饿。
许可证
MIT,参见 LICENSE。
由 Muhammad Waqas 构建,他大部分时间都消耗在受监管行业中的 agent 系统上——在那些行业里,一个自信且错误答案错误答案就是一起事故。
Available Tools
3 toolsfetch_recordA
Look up a record by key. Returns found=false when absent; that is not an error.
| Name | Required | Description | Default |
|---|---|---|---|
| key | Yes | ||
| simulate_outage | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden of behavioral disclosure. It does add one valuable behavioral detail: a missing record is reported via 'found=false' and is not treated as an error. This is useful context. However, it does not state that the operation is read-only, nor does it explain the simulate_outage parameter's behavior. The description provides some behavioral clarity but leaves notable gaps.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences, direct, and front-loaded with the primary action. It avoids verbosity and includes only the most essential information. The key behavioral note about 'found=false' is presented efficiently. No wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple fetch tool, the description covers the main action and the most important edge case (absent record). However, it omits any explanation of the 'simulate_outage' parameter, which is part of the tool's interface. While an output schema exists and relieves the description of detailing return values, the parameter gap remains. The description is adequate for basic usage but incomplete for full correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It implicitly clarifies that 'key' is the lookup identifier, but it offers no additional detail about the parameter's format or acceptable values. The 'simulate_outage' parameter is entirely unexplained—an agent cannot infer its purpose or effect from the description. Given the low coverage, this is a significant shortfall.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the primary action: 'Look up a record by key.' This is a specific verb (look up) with a clear resource (record) and method (by key). It is immediately distinct from sibling tools like summarise_lengths and slow_operation, as those suggest different operations. No ambiguity.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies when to use the tool (when you need to retrieve a record by its key) but provides no explicit guidance on alternatives or exclusion conditions. It does not mention any context where this tool should be avoided or mention sibling tools. This qualifies as implied usage, which meets the minimum viable threshold.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
slow_operationA
Sleeps, to demonstrate the server's timeout behaviour.
| Name | Required | Description | Default |
|---|---|---|---|
| seconds | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description must carry the full burden of behavioral disclosure. It states the core behavior (sleeping) and the intended effect (timeout demonstration), but does not mention the adjustable 'seconds' parameter, what happens after the sleep (return value or error), or any side effects. This is partially transparent but incomplete for a tool with no annotation coverage.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, short sentence that front-loads the core action ('Sleeps') and purpose. There is no wasted wording, making it highly efficient and easy to parse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple tool with one optional parameter and an output schema, the description is fairly complete. It conveys the tool's purpose and behavior, and the output schema covers return values. However, it could explicitly mention that the sleep duration is configurable via the 'seconds' parameter, which is currently only in the schema. Overall, it is adequate for a demo tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has one parameter 'seconds' with default 60 and 0% description coverage. The description does not mention this parameter at all, so it fails to connect the sleep duration to the parameter. The parameter name is somewhat self-explanatory, but the description provides no added meaning, leaving the agent to guess how the parameter influences the operation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb 'Sleeps' and the resource 'server's timeout behaviour', making the tool's purpose unmistakable. It distinguishes itself from siblings 'summarise_lengths' and 'fetch_record' which clearly serve different functions.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage for demonstrating timeout behaviour, which gives context, but it does not explicitly mention when to use this tool versus alternatives. No exclusions or conditions are provided, leaving the agent to infer that it is for testing timeouts only.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
summarise_lengthsA
Return per-item character counts and a total for a list of strings.
| Name | Required | Description | Default |
|---|---|---|---|
| items | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It states the behavior accurately but does not discuss edge cases (e.g., empty list, null elements) or any side effects. For a pure computation tool, this is adequate but minimal disclosure.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single, front-loaded sentence with no wasted words. The core functionality is stated immediately, making it quick for an agent to parse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is simple, and an output schema exists, so the description does not need to explain return values. Combined with the clear single parameter and the presence of the output schema, the description is complete for an agent to invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate for the undocumented 'items' parameter. It clarifies that the parameter is a list of strings, which aligns with the schema. However, it does not add details about constraints (e.g., whether null is allowed) or the order of results. The description adds minimal value beyond the schema's type declaration.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool returns per-item character counts and a total for a list of strings, specifying a concrete verb and resource. It does not explicitly differentiate from siblings, but the siblings (fetch_record, slow_operation) are obviously distinct in purpose, so the lack of explicit differentiation isn't a major gap.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The purpose implies when to use it (whenever you need length summaries), but no explicit guidance is given about when not to use it or which alternatives to prefer. Since the alternatives are clearly unrelated, the implied usage is sufficient, though not explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v0.1.0- First observed
fetch_record - First observed
slow_operation - First observed
summarise_lengths
TDQS
Scored across 3 tools
Each tool addresses a separate concern: one computes string lengths, one looks up records, and one simulates a timeout. There is no overlap or realistic chance of choosing the wrong tool.
Two tools follow a clear snake_case verb_noun pattern (summarise_lengths, fetch_record), but slow_operation reads as adjective_noun. The naming is still readable and predictable overall.
Three tools is a reasonable size for a template server. Each tool exists to demonstrate a distinct capability, so none feel redundant or excessive.
As a template, the set covers the demonstrated scenarios: a utility operation, a record lookup with a defined absent-case, and a timeout example. There are no obvious dead ends or missing pieces for that purpose.
Maintenance
Related MCP Connectors
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
Security & DLP proxy for MCP: tool-poisoning scans, PII redaction on tool args/results. Beta.
MCP server for progressive tool usage at any scale (see https://klavis.ai)
AgentGuard — 20-tool AI safety MCP: policy preflight, risk scoring, audit logging, rate limits.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceA production-grade MCP server designed for multi-tenant, authenticated, and observable AI agent systems, enabling secure tool execution across heterogeneous data sources.67MIT
- AlicenseAqualityCmaintenanceA production-ready foundation for building secure, observable MCP servers with built-in authentication, rate limiting, and reference tools like database-query and semantic-search.1517 npmMIT
- FlicenseNot gradedqualityDmaintenanceA production-ready, security-first starting point for building Model Context Protocol servers, enforcing safe defaults like dry-run and tenant isolation.-
- AlicenseNot gradedqualityDmaintenanceAn enterprise MCP server scaffold that provides secure, governed access to internal tools through RBAC, audit logging, rate limiting, and prompt-injection boundaries, with a FastAPI control plane and OpenTelemetry observability.MIT