Создание первого сервера MCP
Используйте Python MCP SDK, чтобы создать сервер, предоставляющий ресурсы, инструменты и запросы, а затем подключите его к Claude Desktop и проверьте работу всего решения от начала до конца.
«Создание первого сервера MCP» — бесплатный урок AI Engineering Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Engineering Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Engineering Academy содержит 4 уроков всего.
Настройка проекта для сервера MCP
Для создания сервера MCP на Python нужны SDK mcp и среда 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 ресурсов и реализуйте средство чтения ресурсов. В клиенте ресурсы отображаются как элементы, на которые может ссылаться ИИ; они полезны для конфигурации, документации или часто используемых снимков данных.
@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 exchangeОбработка ошибок на серверах MCP
Серверы 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 взаимодействуют с клиентом через stdio, операторы печати нарушат работу протокола. Всегда используйте 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
Чтобы поделиться сервером MCP, упакуйте его с помощью pyproject.toml и опубликуйте в PyPI либо распространяйте как контейнер Docker для команд. Используйте переменные окружения для всех секретов — ключей API, URL баз данных, — чтобы конфигурация сервера оставалась отделённой от кода. Чётко задокументируйте обязательные переменные окружения и пример конфигурации в README.
Быстрая проверка
Проверьте, насколько хорошо Вы поняли создание сервера MCP на Python.
Итоги урока
В этом уроке Вы узнали, что: серверы MCP предоставляют инструменты с помощью декораторов @app.list_tools() и @app.call_tool(), ресурсы и подсказки расширяют сервер, добавляя доступные для чтения данные и повторно используемые шаблоны, а для ведения журналов необходимо использовать stderr, чтобы не повреждать канал протокола MCP через stdio. Далее Вы подключите сервер MCP к базе данных и предоставите динамические ресурсы с постраничной выдачей.
Часто задаваемые вопросы
Урок «Создание первого сервера MCP» бесплатный?
Да — полный текст урока «Создание первого сервера MCP» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Engineering Academy, подпишись на CoddyKit PRO. Курс AI Engineering Academy содержит 4 уроков всего.
Чему я научусь в уроке «Создание первого сервера MCP»?
Используйте Python MCP SDK, чтобы создать сервер, предоставляющий ресурсы, инструменты и запросы, а затем подключите его к Claude Desktop и проверьте работу всего решения от начала до конца. Ты практикуешь AI Engineering Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Engineering Academy?
Предыдущий опыт не требуется. AI Engineering Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Создание первого сервера MCP»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Engineering Academy?
Да. Каждый урок AI Engineering Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Что такое MCP и почему это важно
- Создание первого сервера MCP
- Предоставление ресурсов базы данных через MCP
- Безопасность и аутентификация MCP