构建您的第一个 MCP 服务器
使用 Python MCP SDK 创建一个公开资源、工具和提示的服务器,然后将其连接到 Claude Desktop,完整查看其运行效果。
构建您的第一个 MCP 服务器 是 CoddyKit 上的免费 AI Engineering Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Engineering Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Engineering Academy 课程共包含 4 节课。
MCP 服务器项目设置
在 Python 中构建 MCP 服务器需要 mcp SDK 和 Python 环境。您将创建一个服务器,使 Claude Desktop 或任何 MCP 客户端都能通过 stdio 连接。首先安装软件包并创建服务器文件。
# Create a project directory
# mkdir my_mcp_server && cd my_mcp_server
# Create a virtual environment
# python -m venv venv && source venv/bin/activate
# Install the MCP SDK
# pip install mcp httpx
# Project structure:
# my_mcp_server/
# server.py <- Your MCP server
# requirements.txt
# README.md创建服务器对象
从 mcp 软件包导入内容,并使用服务器名称创建一个 Server 实例。客户端会在 MCP 服务器列表中显示该名称,因此请选择一个具有描述性的名称。服务器对象是注册所有工具、资源和提示的入口。
# server.py
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp import types
import asyncio
import httpx
# Create the server — name is shown in Claude Desktop
app = Server('weather-server')
# --- Tool registrations go here ---
# Entry point
if __name__ == '__main__':
asyncio.run(stdio_server(app))使用 @app.list_tools() 注册工具
@app.list_tools() 装饰器会注册一个处理程序,当客户端发出请求时,该处理程序会返回可用工具列表。每个工具都由名称、描述和 inputSchema 定义,后者是描述其参数的 JSON 架构。客户端会将此列表发送给 LLM,使其了解可以调用哪些工具。
@app.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name='get_weather',
description='Get current weather for a city. Use when the user asks about weather in a specific location.',
inputSchema={
'type': 'object',
'properties': {
'city': {
'type': 'string',
'description': 'City name, e.g. London or New York'
},
'units': {
'type': 'string',
'enum': ['metric', 'imperial'],
'description': 'Temperature units. Default is metric.'
}
},
'required': ['city']
}
),
types.Tool(
name='list_cities',
description='Return a list of major cities the user can query weather for.',
inputSchema={'type': 'object', 'properties': {}, 'required': []}
)
]实现工具执行
@app.call_tool() 装饰器负责处理工具执行。当客户端调用工具时,此处理器会接收工具名称和参数。执行相应的逻辑,并返回一个包含结果字符串的 TextContent 对象列表。
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
if name == 'list_cities':
cities = ['London', 'New York', 'Tokyo', 'Paris', 'Sydney']
return [types.TextContent(type='text', text=', '.join(cities))]
if name == 'get_weather':
city = arguments['city']
units = arguments.get('units', 'metric')
unit_symbol = 'C' if units == 'metric' else 'F'
# Call real weather API (stub here)
async with httpx.AsyncClient() as client:
# Replace with actual API call
result = f'{city}: 18{chr(176)}{unit_symbol}, partly cloudy, humidity 65%'
return [types.TextContent(type='text', text=result)]
raise ValueError(f'Unknown tool: {name}')公开资源
资源是模型可以读取的静态或动态数据——您可以把它们理解为文件。定义资源 URI 并实现资源读取器。资源会作为客户端中的条目出现,供 AI 引用,适用于配置、文档或经常访问的数据快照。
@app.list_resources()
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri='weather://supported-cities',
name='Supported Cities List',
description='Complete list of cities available in this weather server.',
mimeType='text/plain'
)
]
@app.read_resource()
async def read_resource(uri: str) -> str:
if uri == 'weather://supported-cities':
cities = ['London', 'New York', 'Tokyo', 'Paris', 'Sydney', 'Dubai', 'Singapore']
return '\n'.join(cities)
raise ValueError(f'Unknown resource URI: {uri}')注册提示模板
提示是可重复使用的消息模板,客户端会将其作为斜杠命令或快捷操作呈现给用户。它们接受参数,并返回一个消息列表,作为对话的初始上下文。提示非常适合封装复杂指令,让用户通过一条命令即可触发。
@app.list_prompts()
async def list_prompts() -> list[types.Prompt]:
return [
types.Prompt(
name='weather-report',
description='Generate a formatted weather report for a city.',
arguments=[
types.PromptArgument(name='city', description='City name', required=True)
]
)
]
@app.get_prompt()
async def get_prompt(name: str, arguments: dict) -> types.GetPromptResult:
if name == 'weather-report':
city = arguments.get('city', 'London')
return types.GetPromptResult(
description=f'Weather report for {city}',
messages=[
types.PromptMessage(
role='user',
content=types.TextContent(
type='text',
text=f'Use the get_weather tool to look up {city} and give me a detailed weather report including what clothing I should wear.'
)
)
]
)
raise ValueError(f'Unknown prompt: {name}')连接到 Claude Desktop
要将您的 MCP 服务器与 Claude Desktop 搭配使用,请将其添加到 Claude Desktop 配置文件中。在 macOS 上,该文件位于 ~/Library/Application Support/Claude/claude_desktop_config.json。指定启动服务器的命令,以及服务器所需的环境变量。
# ~/Library/Application Support/Claude/claude_desktop_config.json
# Add this JSON configuration:
# {
# "mcpServers": {
# "weather-server": {
# "command": "/path/to/venv/bin/python",
# "args": ["/path/to/my_mcp_server/server.py"],
# "env": {
# "WEATHER_API_KEY": "your_api_key_here"
# }
# }
# }
# }
# After saving, restart Claude Desktop.
# Your server's tools will appear in Claude's tool list.使用 MCP CLI 测试您的服务器
在连接到 Claude Desktop 之前,请使用 MCP 检查器或 CLI 工具测试您的服务器。mcp dev 命令会启动服务器,并打开一个基于浏览器的检查器,您可以在其中手动调用工具并查看原始协议消息,从而轻松调试问题。
# Install the MCP development tools
# pip install 'mcp[cli]'
# Start the inspector with your server
# mcp dev server.py
# The inspector opens at http://localhost:5173
# You can:
# - See all registered tools and their schemas
# - Call tools with custom arguments
# - Browse available resources
# - Test prompt templates
# - View the full JSON-RPC message exchangeMCP 服务器中的错误处理
MCP 服务器绝不能因输入错误而崩溃。请将所有工具执行过程包装在 try/except 中,并将错误消息作为 TextContent 返回,而不是抛出异常。对于致命错误(例如配置问题或缺少 API 密钥),请在启动时记录日志,并在服务器进入主循环前抛出异常。
@app.call_tool()
async def call_tool_safe(name: str, arguments: dict) -> list[types.TextContent]:
try:
if name == 'get_weather':
city = arguments.get('city')
if not city:
return [types.TextContent(type='text', text='Error: city argument is required.')]
result = await fetch_weather(city, arguments.get('units', 'metric'))
return [types.TextContent(type='text', text=result)]
raise ValueError(f'Unknown tool: {name}')
except httpx.TimeoutException:
return [types.TextContent(type='text', text='Error: Weather API timed out. Try again.')]
except Exception as e:
return [types.TextContent(type='text', text=f'Error: {str(e)}')]MCP 服务器的日志记录
由于 MCP 服务器通过标准输入输出与客户端通信,打印语句会破坏协议。请始终使用 stderr 进行日志记录——它会写入独立的数据流,不会干扰 MCP 消息交换。请配置 Python 的日志模块,将日志写入 stderr。
import logging
import sys
# Configure logging to stderr (NOT stdout — that's the MCP channel)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
stream=sys.stderr
)
logger = logging.getLogger('weather-server')
# In your tool handler:
# logger.info(f'Getting weather for {city}')
# logger.error(f'API call failed: {e}')
# Claude Desktop captures stderr to a log file:
# ~/Library/Logs/Claude/mcp-server-weather-server.log打包您的 MCP 服务器
您可以使用 pyproject.toml 打包 MCP 服务器并发布到 PyPI,也可以将其作为 Docker 容器分发给团队。请使用环境变量保存所有机密信息——例如 API 密钥和数据库 URL——使服务器配置与代码分离。请在清晰易懂的 README 中记录所需的环境变量和示例配置。
快速检查
测试您对使用 Python 构建 MCP 服务器的理解。
课程回顾
在本课中,您学到了:MCP 服务器通过 @app.list_tools() 和 @app.call_tool() 装饰器公开工具;资源和提示通过可读取的数据与可重复使用的模板扩展服务器功能;以及日志记录必须使用 stderr,以避免破坏标准输入输出上的 MCP 协议通道。接下来,我们将把 MCP 服务器连接到数据库,并通过分页公开动态资源。
常见问题解答
「构建您的第一个 MCP 服务器」课时是免费的吗?
是的 — 「构建您的第一个 MCP 服务器」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Engineering Academy 课程的其余内容,请升级到 CoddyKit PRO。 AI Engineering Academy 课程共包含 4 节课。
「构建您的第一个 MCP 服务器」这节课中我会学到什么?
使用 Python MCP SDK 创建一个公开资源、工具和提示的服务器,然后将其连接到 Claude Desktop,完整查看其运行效果。 你通过在浏览器中直接运行的动手代码来练习 AI Engineering Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 AI Engineering Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 AI Engineering Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「构建您的第一个 MCP 服务器」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 AI Engineering Academy 课中编写并运行代码吗?
能。每节 AI Engineering Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 什么是 MCP,以及它为何重要
- 构建您的第一个 MCP 服务器
- 通过 MCP 暴露数据库资源
- MCP 安全与身份验证