mcp-server-template
A production-ready starting point for MCP servers, adding per-tool timeouts, concurrency ceilings, error boundaries, secret redaction, correlation IDs, and structured logging so tools are safe and observable for LLM callers.
README
mcp-server-template
A production-shaped starting point for an MCP server.
The quickstart in the MCP docs gets you a working tool in ten lines. This is what you end up adding over the following three weeks, once that tool is being called by something you do not control.
@mcp.tool()
def add(a: int, b: int) -> int:
return a + b # fine on a laptop
What is missing there is not features. It is what happens when the tool hangs, raises, returns a novel, or gets called forty times at once — and what the model is allowed to see when it does.
The problem this solves
An MCP tool's caller is a language model, which changes the engineering.
- A model cannot read a stack trace, but it will cheerfully repeat one to your user. So a leaked traceback is both useless and a disclosure.
- A model has its own deadline. A tool that hangs does not produce a slow answer; it produces a dead conversation.
- A model cannot tell a truncated result from a complete one. Silently overflowing its context does not raise an error — it degrades the answer, and you find out from a customer.
- A model will retry if you let it. So "not found" and "upstream is down" have to be different answers, or it will hammer a service over a record that was never there.
Every one of those is handled once, in one place, so that a tool added on a Friday afternoon inherits the same protections as the one written carefully on day one.
What you get
| Per-tool timeout | Real cancellation, not a warning afterwards. Returns a retryable error the model can act on |
| Concurrency ceiling | Bounded parallel execution, so a burst cannot stampede whatever your tools call |
| Error boundary | Declared errors reach the caller; unexpected ones become internal_error with no detail, and the traceback goes to the log |
| Secret redaction | Applied to logs and outbound messages, because keys escape through interpolated exception strings more often than through code |
| Visible truncation | Oversized results are cut with a marker, never silently |
| Correlation ids | One id per call, in the log and in the error the user can quote back to you |
| Structured logs on stderr | stdout belongs to the protocol — a stray print() corrupts the stream |
| Fail-fast config | Bad settings stop the server at boot, not on the first request |
| Offline tests | The suite runs on a train. No live keys, no network |
Quickstart
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
Serve it over the network instead:
TRANSPORT=streamable-http PORT=8000 python -m mcp_server_template
Point a desktop client at it
{
"mcpServers": {
"template": {
"command": "python",
"args": ["-m", "mcp_server_template"],
"cwd": "/absolute/path/to/mcp-server-template"
}
}
}
Adding your own tool
Write the function. Nothing else.
# 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
Register it behind the guard:
mcp.tool(name="cancel_order", description="Cancel an order by id.")(
guard.wrap(orders.cancel_order)
)
It now has the timeout, the ceiling, the error boundary, the truncation and the logging. You wrote none of that.
Raise InvalidInput when the model can fix it. Raise UpstreamUnavailable
when retrying might work. Return normally for outcomes that are simply false —
a missing record is an answer, not a failure.
Architecture
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
The dependency arrow points one way: tools know nothing about MCP, and the guard knows nothing about your tools. That is why the tests run in milliseconds without a server, and why an SDK change touches exactly one file.
What this deliberately does not do
Being honest about the edges is more useful than a longer feature list.
- No authentication. Over stdio the OS boundary is the security boundary. If you expose it over HTTP, put real auth in front — the SDK supports it, and wiring it here would imply a threat model you have not chosen yet.
- No retry logic inside tools. The guard reports whether a failure is retryable; deciding to retry belongs to the caller, which has the context and the budget.
- No rate limiting per caller. The concurrency ceiling bounds total work, not per-identity fairness. If you need that, you need identity first.
- No persistence, queue or scheduler. A tool server that quietly became a job runner is a distributed system nobody designed.
- No streaming partial results. Worth adding for long-running tools; left out because it complicates the error boundary and most tools do not need it.
Testing
make test
The suite is deliberately about failure, not coverage. It asserts that a hung tool is cancelled, that an unexpected exception cannot leak its message, that oversized output is truncated visibly, that the concurrency ceiling holds under ten simultaneous calls, and that a blocking sync tool does not starve the event loop.
Licence
MIT — see LICENSE.
Built by Muhammad Waqas, who spends most of his time on agent systems in regulated industries, where a confident wrong answer is a reportable incident.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。