Создание Hello World сервера. Запуск и отладка через stdio транспорт.
От теории к практике. В этой теме создадим первый работающий MCP сервер с нуля, запустим и протестируем через Claude Desktop.
Начнём с простейшего сервера, который предоставляет один инструмент.
#!/usr/bin/env python3
"""Простейший MCP сервер с одним инструментом."""
from mcp.server.fastmcp import FastMCP
# Создаём сервер
mcp = FastMCP("Hello MCP")
# Регистрируем инструмент
@mcp.tool()
def hello(name: str) -> str:
"""Приветствие пользователя."""
return f"Hello, {name}! Welcome to MCP."
# Запуск сервера
if __name__ == "__main__":
mcp.run()# Сохраните как hello_server.py
poetry run python hello_server.pyСервер запущен и ждёт подключений через stdio!
FastMCP — это упрощённый интерфейс для создания MCP серверов. Он автоматически:
@mcp.tool()@mcp.tool()
def hello(name: str) -> str:
"""Приветствие пользователя."""
return f"Hello, {name}! Welcome to MCP."Что происходит:
hello)"Приветствие пользователя.")Сгенерированная JSON Schema:
{
"name": "hello",
"description": "Приветствие пользователя.",
"inputSchema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": ""
}
},
"required": ["name"]
}
}# Установка (если не установлен)
npm install -g @modelcontextprotocol/inspector
# Запуск с вашим сервером
npx @modelcontextprotocol/inspector python hello_server.pyhttp://localhost:5173helloname: "World""Hello, World! Welcome to MCP."Интерфейс Inspector:
┌─────────────────────────────────────────┐
│ MCP Inspector │
├─────────────────────────────────────────┤
│ Tools │ Resources │ Prompts │
├─────────────────────────────────────────┤
│ hello │
│ Приветствие пользователя. │
│ │
│ Parameters: │
│ name: [World________] │
│ │
│ [Call Tool] │
│ │
│ Result: │
│ "Hello, World! Welcome to MCP." │
└─────────────────────────────────────────┘
Добавьте сервер в claude_desktop_config.json:
{
"mcpServers": {
"hello-mcp": {
"command": "poetry",
"args": ["run", "python", "hello_server.py"],
"cwd": "/path/to/your/project"
}
}
}hello-mcp в списке и статус "Connected"Напишите в чате:
Привет! Можешь поздравить меня с днём рождения?
Claude увидит доступный инструмент hello и может его использовать:
[Использует инструмент hello с name="User"]
Hello, User! Welcome to MCP.
Поздравляю с днём рождения! 🎉
Добавим ещё инструментов для демонстрации:
#!/usr/bin/env python3
"""MCP сервер с несколькими инструментами."""
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Calculator MCP")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Сложение двух чисел."""
return a + b
@mcp.tool()
def subtract(a: int, b: int) -> int:
"""Вычитание b из a."""
return a - b
@mcp.tool()
def multiply(a: int, b: int) -> int:
"""Умножение двух чисел."""
return a * b
@mcp.tool()
def divide(a: float, b: float) -> float:
"""Деление a на b."""
if b == 0:
raise ValueError("Деление на ноль невозможно")
return a / b
if __name__ == "__main__":
mcp.run()Запрос через Claude:
Сколько будет 123 * 456 - 789?
Claude использует инструменты:
[Вызывает multiply(123, 456) → 56088]
[Вызывает subtract(56088, 789) → 55299]
Ответ: 55299
@mcp.tool()
def greet(
name: str,
greeting: str = "Hello",
punctuation: str = "!"
) -> str:
"""
Персональное приветствие.
Args:
name: Имя человека
greeting: Слово приветствия (по умолчанию "Hello")
punctuation: Знак препинания (по умолчанию "!")
"""
return f"{greeting}, {name}{punctuation}"JSON Schema:
{
"name": "greet",
"description": "Персональное приветствие.",
"inputSchema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Имя человека"
},
"greeting": {
"type": "string",
"description": "Слово приветствия",
"default": "Hello"
},
"punctuation": {
"type": "string",
"description": "Знак препинания",
"default": "!"
}
},
"required": ["name"]
}
}Важно: Только name обязательный — у остальных есть значения по умолчанию.
@mcp.tool()
def safe_divide(a: float, b: float) -> float:
"""Безопасное деление с обработкой ошибок."""
if b == 0:
raise ValueError("Деление на ноль невозможно")
return a / bЧто видит LLM при ошибке:
{
"content": [
{
"type": "text",
"text": "Error: Деление на ноль невозможно"
}
],
"isError": true
}@mcp.tool()
def get_user_data(user_id: int) -> dict:
"""Получение данных пользователя."""
if user_id < 0:
raise ValueError("user_id должен быть положительным")
try:
# Попытка получить данные
data = database.get_user(user_id)
if data is None:
raise ValueError(f"Пользователь {user_id} не найден")
return data
except DatabaseError as e:
# Логируем ошибку, но не показываем детали LLM
logger.exception(f"Database error for user {user_id}")
raise ValueError("Ошибка при получении данных")Принципы:
@mcp.tool()
def get_name() -> str:
return "Alice"
@mcp.tool()
def get_age() -> int:
return 30
@mcp.tool()
def get_price() -> float:
return 19.99
@mcp.tool()
def is_active() -> bool:
return Truefrom typing import List, Dict
@mcp.tool()
def get_user(user_id: int) -> Dict:
"""Данные пользователя."""
return {
"id": user_id,
"name": "Alice",
"email": "alice@example.com"
}
@mcp.tool()
def list_users() -> List[Dict]:
"""Список всех пользователей."""
return [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
]@mcp.tool()
def get_report() -> str:
"""Отчёт в читаемом формате."""
data = {"sales": 1000, "profit": 200}
# Форматируем как текст
return f"""
Отчёт по продажам:
────────────────────
Продажи: {data['sales']}$
Прибыль: {data['profit']}$
Маржа: {data['profit'] / data['sales'] * 100:.1f}%
"""Кроме инструментов, MCP серверы могут предоставлять ресурсы — данные для чтения.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("File MCP")
@mcp.resource("config://app")
def get_config() -> str:
"""Конфигурация приложения."""
return """
APP_NAME=MyApp
VERSION=1.0.0
DEBUG=true
"""
@mcp.resource("file:///tmp/hello.txt")
def read_hello_file() -> str:
"""Содержимое файла hello.txt."""
with open("/tmp/hello.txt", "r") as f:
return f.read()| Схема | Пример | Описание |
|---|---|---|
file:// | file:///etc/hosts | Файл в файловой системе |
config:// | config://app | Конфигурация (кастомная схема) |
db:// | db://users/123 | Запись в базе данных |
http:// | http://api.example.com/data | Внешний API |
file:///tmp/hello.txtPrompts — предустановленные шаблоны для частых сценариев.
@mcp.prompt()
def code_review(code: str) -> str:
"""Шаблон для код-ревью."""
return f"""
Пожалуйста, проверь этот код:
```python
{code}Обрати внимание на:
### Промпт с несколькими аргументами
```python
@mcp.prompt()
def write_test(function_name: str, function_code: str, test_framework: str = "pytest") -> str:
"""Шаблон для написания тестов."""
return f"""
Напиши тесты для функции {function_name} используя {test_framework}:
```python
{function_code}
Тесты должны покрывать:
---
## 11. Полный пример: Weather MCP Server
Создадим сервер с инструментами, ресурсами и промптами:
```python
#!/usr/bin/env python3
"""MCP сервер с прогнозом погоды."""
from mcp.server.fastmcp import FastMCP
from datetime import datetime
import random
mcp = FastMCP("Weather MCP")
# === TOOLS ===
@mcp.tool()
def get_current_weather(city: str) -> str:
"""
Получить текущую погоду в городе.
Args:
city: Название города
"""
# Имитация API погоды
conditions = ["Sunny", "Cloudy", "Rainy", "Partly Cloudy"]
temp = random.randint(15, 30)
condition = random.choice(conditions)
return f"""
Погода в городе {city}:
🌡️ Температура: {temp}°C
☁️ Состояние: {condition}
💨 Ветер: {random.randint(0, 20)} км/ч
"""
@mcp.tool()
def get_forecast(city: str, days: int = 3) -> str:
"""
Получить прогноз погоды на несколько дней.
Args:
city: Название города
days: Количество дней (1-7)
"""
if days > 7:
raise ValueError("Максимум 7 дней")
forecast = f"Прогноз для {city}:\n"
conditions = ["☀️ Sunny", "☁️ Cloudy", "🌧️ Rainy", "⛅ Partly Cloudy"]
for i in range(days):
date = datetime.now().strftime(f"%d.%m")
temp = random.randint(15, 30)
condition = random.choice(conditions)
forecast += f" {date}: {temp}°C {condition}\n"
return forecast
# === RESOURCES ===
@mcp.resource("weather://cities")
def get_supported_cities() -> str:
"""Список поддерживаемых городов."""
return """
Поддерживаемые города:
- Moscow
- Saint Petersburg
- London
- New York
- Tokyo
"""
# === PROMPTS ===
@mcp.prompt()
def weather_advice(activity: str, city: str = "Moscow") -> str:
"""
Получить рекомендации по активности с учётом погоды.
Args:
activity: Планируемая активность
city: Город
"""
return f"""
Я планирую {activity} в городе {city}.
Пожалуйста:
1. Проверь текущую погоду и прогноз на сегодня
2. Посоветуй, подходит ли погода для этой активности
3. Если погода плохая, предложи альтернативы
4. Рекомендуй, что взять с собой
"""
if __name__ == "__main__":
mcp.run()
Запрос через Claude:
Хочу пойти на пикник в Москве. Стоит ли?
Claude использует инструменты:
[Вызывает get_current_weather("Moscow")]
[Вызывает get_forecast("Moscow", 3)]
Судя по погоде (22°C, солнечно), сегодня отличный день для пикника!
Рекомендую взять: солнцезащитный крем, воду, плед...
import logging
import sys
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
stream=sys.stderr
)
logger = logging.getLogger(__name__)
@mcp.tool()
def debug_tool(value: str) -> str:
logger.info(f"debug_tool called with: {value}")
logger.debug("Processing...")
return f"Processed: {value}"Запустите сервер вручную и отправьте запрос:
# Терминал 1: запуск сервера
poetry run python hello_server.py
# Терминал 2: отправка запроса
echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}' | \
poetry run python hello_server.py| Концепт | Описание |
|---|---|
| FastMCP | Высокоуровневый API для быстрого создания серверов |
| @mcp.tool() | Декоратор для регистрации инструментов |
| @mcp.resource() | Декоратор для регистрации ресурсов |
| @mcp.prompt() | Декоратор для регистрации промптов |
| Type hints | Используются для генерации JSON Schema |
| Docstrings | Используются для описания инструментов |
| MCP Inspector | Инструмент для тестирования серверов |
Следующая тема: Resources API — детально работа с ресурсами: шаблоны, подписки, бинарные данные.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.