内容
# mcp-py-server-template
一个用于构建 [Model Context Protocol (MCP)](https://modelcontextprotocol.io) 服务器的干净、完整的样板,使用 Python 和 [FastMCP](https://gofastmcp.com)。
## 特性
- 标准 `src/` 项目布局
- 示例工具,包含完整的类型提示和 Google 风格的文档字符串
- 示例资源(静态和参数化 URI 模板)
- 示例提示(可重用的单消息和多消息模板)
- 环境感知工具(客户端日志记录、进度报告、资源访问)
- 基于环境的配置(`pydantic-settings`)
- 结构化 JSON 日志记录(生产环境)或控制台日志记录(开发环境)
- 可选的传输方式:`stdio`(本地)或 `streamable-http`(网络)
- 可通过 `uvx` 运行(无需手动环境设置)
- 可选的 JWT 持有者令牌身份验证,用于 HTTP 传输
- 每个工具的超时和安全的错误处理
- 可选的 OpenTelemetry 跟踪和指标
- 多阶段 `Dockerfile`(非 root)+ `.dockerignore`
- GitHub Actions CI(lint、格式检查、类型检查、测试),支持 Python 3.10-3.12
- 固定的 `requirements.lock`,用于可复制的安装
- 即刻可运行的测试套件(`pytest` + `pytest-asyncio`)
## 项目结构
```
mcp-py-server-template/
├── pyproject.toml # 构建配置、依赖项、工具链
├── requirements.lock # 固定的版本,用于可复制的安装
├── Makefile # 常用开发任务(make help)
├── Dockerfile # 多阶段、非 root 生产镜像
├── .dockerignore
├── .env.example # 示例环境配置
├── README.md
├── .github/workflows/ci.yml # Lint、格式检查、类型检查、测试管道
├── src/
│ └── mcp_server_template/
│ ├── __init__.py # 包导出和版本
│ ├── config.py # 基于环境的设置(pydantic-settings)
│ ├── logging_config.py # JSON / 控制台日志设置
│ ├── auth.py # 可选的 JWT 持有者令牌验证器
│ ├── telemetry.py # 可选的 OpenTelemetry 设置
│ ├── server.py # FastMCP 应用实例 + 入口点
│ ├── tools.py # 示例工具(greet, add, slow_double, process_items)
│ ├── resources.py # 示例资源(静态 + 模板)
│ └── prompts.py # 示例提示(可重用的模板)
└── tests/
├── __init__.py
├── test_tools.py # 工具测试,通过内存客户端
├── test_resources.py # 资源测试,通过内存客户端
└── test_prompts.py # 提示测试,通过内存客户端
```
## 配置
所有设置均从环境变量(前缀 `MCP_`)或 `.env` 文件中读取。复制 [.env.example](.env.example) 到 `.env` 并进行调整。关键变量:
| 变量 | 默认值 | 描述 |
| --- | --- | --- |
| `MCP_TRANSPORT` | `stdio` | `stdio` 或 `streamable-http` |
| `MCP_HOST` | `127.0.0.1` | 绑定主机(HTTP 传输) |
| `MCP_PORT` | `8000` | 绑定端口(HTTP 传输) |
| `MCP_LOG_LEVEL` | `INFO` | 日志级别 |
| `MCP_JSON_LOGS` | `false` | 发出结构化 JSON 日志 |
| `MCP_TOOL_TIMEOUT_SECONDS` | `30` | 每个工具的超时 |
| `MCP_AUTH_JWKS_URI` | _(未设置)_ | JWKS 端点;设置后启用身份验证 |
| `MCP_AUTH_ISSUER` | _(未设置)_ | 预期的令牌 `iss` 声明 |
| `MCP_AUTH_AUDIENCE` | _(未设置)_ | 预期的令牌 `aud` 声明 |
| `MCP_OTEL_ENABLED` | `false` | 启用 OpenTelemetry 导出 |
| `MCP_OTEL_SERVICE_NAME` | `mcp-py-server-template` | 遥测服务名称 |
## 快速入门
创建虚拟环境并在可编辑模式下安装带有开发依赖的包:
```bash
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```
对于固定版本的复制安装,请使用 lockfile:
```bash
pip install -r requirements.lock
```
## 运行服务器
通过 stdio(默认 MCP 传输)启动服务器:
```bash
mcp-py-server-template
```
或直接运行模块:
```bash
python -m mcp_server_template.server
```
作为网络化的 HTTP 服务运行,并带有结构化日志:
```bash
MCP_TRANSPORT=streamable-http MCP_HOST=0.0.0.0 MCP_PORT=8000 MCP_JSON_LOGS=true \
mcp-py-server-template
```
## 身份验证
对于 HTTP 传输,设置 JWKS 变量以需要有效的 JWT 持有者令牌(验证签名、颁发者和受众)。当 `MCP_AUTH_JWKS_URI` 未设置时,身份验证被禁用 - 在本地_stdio` 使用时是安全的,但 **不要** 在没有它的情况下公开暴露服务器:
```bash
MCP_TRANSPORT=streamable-http \
MCP_AUTH_JWKS_URI=https://auth.example.com/.well-known/jwks.json \
MCP_AUTH_ISSUER=https://auth.example.com \
MCP_AUTH_AUDIENCE=mcp-py-server-template \
mcp-py-server-template
```
客户端随后在每个请求上发送 `Authorization: Bearer <token>`。
## 可观察性
启用 OpenTelemetry 跟踪和指标(首先安装额外的依赖):
```bash
pip install -e ".[otel]"
MCP_OTEL_ENABLED=true OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
mcp-py-server-template
```
## Docker
```bash
docker build -t mcp-py-server-template .
docker run -p 8000:8000 mcp-py-server-template
```
镜像默认为 `0.0.0.0:8000` 上的 HTTP 传输,带有 JSON 日志,并以非 root 用户身份运行。使用 `-e` 重写任意 `MCP_*` 变量。
### 从 MCP 客户端连接
将以下内容添加到客户端的 MCP 配置中(例如 Claude Desktop、VS Code)。使用 **绝对路径** 到虚拟环境中的解释器 - MCP 客户端不继承 shell 的 `PATH`,因此仅使用命令名称将失败,出现 `spawn ... ENOENT`:
```json
{
"mcpServers": {
"mcp-py-server-template": {
"command": "/absolute/path/to/mcp-py-server-template/.venv/bin/python",
"args": ["-m", "mcp_server_template.server"]
}
}
}
```
或者,直接指向生成的控制台脚本:
```json
{
"mcpServers": {
"mcp-py-server-template": {
"command": "/absolute/path/to/mcp-py-server-template/.venv/bin/mcp-py-server-template"
}
}
}
```
### 使用 uvx 运行(推荐)
[`uvx`](https://docs.astral.sh/uv/guides/tools/) (来自 [uv](https://docs.astral.sh/uv/))在临时、隔离的环境中获取并运行包。这样可以避免管理虚拟环境或担心 `PATH`,因为客户端只需要 `uvx` 即可在其 `PATH` 上运行。
在项目目录中本地运行(无需发布):
```bash
uvx --from . mcp-py-server-template
```
或直接从 Git 仓库运行:
```bash
uvx --from git+https://github.com/Omar-Emam-99/mcp-py-server-template mcp-py-server-template
```
一旦发布到 PyPI,就可以完全省略源:
```bash
uvx mcp-py-server-template
```
相应的 MCP 客户端配置:
```json
{
"mcpServers": {
"mcp-py-server-template": {
"command": "uvx",
"args": ["--from", "git+https://github.com/Omar-Emam-99/mcp-py-server-template", "mcp-py-server-template"]
}
}
}
```
发布到 PyPI 后,简化参数为仅包名:
```json
{
"mcpServers": {
"mcp-py-server-template": {
"command": "uvx",
"args": ["mcp-py-server-template"]
}
}
}
```
## 发布
使用 [uv](https://docs.astral.sh/uv/) (由 [Makefile](Makefile) 提供目标)构建可发布文件并发布到包索引:
```bash
make build # 构建 sdist + wheel 到 dist/
make publish-test # 上传到 TestPyPI
make publish # 上传到 PyPI
```
通过 `UV_PUBLISH_TOKEN` 环境变量(或 `uv publish --token ...`)提供凭证。一旦发布到 PyPI,客户端就可以使用上述 `uvx mcp-py-server-template` 形式启动服务器。
## 添加您自己的工具
在 [src/mcp_server_template/tools.py](src/mcp_server_template/tools.py) 中注册新工具,方法是将函数装饰为 `@mcp.tool`。类型提示定义输入模式,文档字符串成为工具描述:
```python
@mcp.tool
def multiply(a: float, b: float) -> float:
"""将两个数字相乘。
参数:
a: 第一个因子。
b: 第二个因子。
返回:
``a`` 和 ``b`` 的乘积。
"""
return a * b
```
工具可以是 `async`,并且可以使用 `@with_timeout` 包装(参见 [tools.py](src/mcp_server_template/tools.py)),以强制执行配置的每个工具超时,并将故障转换为安全的 `ToolError` 消息。在工具顶部验证输入并引发 `ToolError` 以处理错误请求。
## 添加您自己的资源
资源向客户端公开只读数据,通过 URI 地址。 在 [src/mcp_server_template/resources.py](src/mcp_server_template/resources.py) 中注册新资源,方法是将函数装饰为 `@mcp.resource`。 包含 `{placeholders}` 的 URI 成为 *资源模板*,请求的 URI 会填充匹配的函数参数:
```python
@mcp.resource(
"users://{user_id}/profile",
name="user-profile",
description="按用户 ID 键入的合成用户配置文件。",
mime_type="application/json",
)
def user_profile(user_id: str) -> str:
"""返回给定用户 ID 的 JSON 配置文件。"""
return json.dumps({"id": user_id, "display_name": f"User {user_id}"})
```
模板附带三个示例:静态资源 (`config://server-info`) 加两个模板 (`greeting://{name}` 和 `users://{user_id}/profile`)。客户端通过 `list_resources` / `list_resource_templates` 发现它们,并使用 `read_resource` 获取内容。
## 添加您自己的提示
提示是可重用的消息模板。 在 [src/mcp_server_template/prompts.py](src/mcp_server_template/prompts.py) 中注册新提示,方法是将函数装饰为 `@mcp.prompt`。 函数参数成为提示的参数,文档字符串成为其描述。 返回纯字符串以获取单个 `user` 消息,或返回 `Message` 对象列表以获取多轮模板:
```python
from fastmcp.prompts import Message
@mcp.prompt(name="summarize-text", description="总结提供的文本。")
def summarize_text(text: str) -> list[Message]:
"""引导模型总结给定的文本。"""
return [
Message("您是一个简洁的总结者。", role="assistant"),
Message(f"总结以下文本:\n\n{text}"),
]
```
模板附带两个示例:`explain-topic`(带有可选 `level` 参数的单个消息)和 `summarize-text`(多消息)。客户端通过 `list_prompts` 发现它们,并使用 `get_prompt` 渲染它们。
## 使用请求上下文
工具、资源和提示可以通过 FastMCP 的 `Context` 对象访问每请求的 MCP 功能。 声明一个注释为 `Context` 的参数,FastMCP 会自动注入它 - 客户端永远不会提供它,它不会出现在工具的输入模式中。 上下文提供结构化日志记录返回给客户端、增量进度报告、资源访问、采样、引发和每请求元数据:
```python
from fastmcp import Context
@mcp.tool
async def process_items(count: int, ctx: Context) -> dict[str, object]:
"""处理项目同时向客户端报告进度和日志记录。"""
await ctx.info(f"处理 {count} 个项目,请求 ID 为 {ctx.request_id}")
for index in range(1, count + 1):
await ctx.report_progress(progress=index, total=count)
result = await ctx.read_resource("config://server-info")
return {"processed": count, "request_id": ctx.request_id}
```
[src/mcp_server_template/tools.py](src/mcp_server_template/tools.py) 中的 `process_items` 工具演示了客户端日志记录 (`ctx.info` / `ctx.debug`)、进度报告 (`ctx.report_progress`) 和读取服务器资源 (`ctx.read_resource`)。 客户端通过 `log_handler` 和 `progress_handler` 回调接收日志和进度通知。
## 开发
[Makefile](Makefile) 包装了常见任务。 使用以下命令列出所有目标:
```bash
make help
```
运行检查:
```bash
make check # 格式检查、lint、类型检查、测试
make ci # 与 GitHub Actions CI 相同的检查
```
其他有用目标:`make install` / `make install-all`,`make sync`(从 lockfile 安装),`make lock`(重新生成),`make format`,`make lint-fix`,`make run` / `make run-http`,`make docker-build` / `make docker-run`,以及 `make clean`。
等效的原始命令是:
```bash
ruff format --check . # 格式化
ruff check . # lint
mypy src # 类型检查
pytest -q # 测试
```
CI 在每次推送和拉取请求时运行相同的检查,涵盖 Python 3.10、3.11 和 3.12 - 请参阅 [.github/workflows/ci.yml](.github/workflows/ci.yml)。
## 许可证
MIT - 请参阅 [LICENSE](LICENSE)。