MCP сервер на Python: создаём и деплоим собственные AI-инструменты
🔌

MCP сервер на Python: создаём и деплоим собственные AI-инструменты

Практический туториал по созданию MCP сервера с нуля на Python. Настройка окружения, инструменты (Tools), ресурсы (Resources), промпты (Prompts), транспорт stdio/HTTP и деплой на Railway. Реальный код без псевдокода — берите и используйте.

MCP Python ⏱ 25 минут чтения

Model Context Protocol (MCP) — открытый стандарт от Anthropic, который позволяет AI-агентам (Claude, Codex CLI, Continue, Zed) подключаться к внешним инструментам через единый интерфейс. Если вы когда-либо хотели, чтобы Claude мог выполнять SQL-запросы, читать файлы вашего проекта, отправлять HTTP-запросы или взаимодействовать с вашим API — MCP сервер на Python решает эту задачу.

Ключевое преимущество MCP в том, что вы пишете сервер один раз, а использовать его могут любые совместимые клиенты. Ваш коллега, работающий в Claude Desktop, видит те же инструменты, что и разработчик в Cursor или Continue. Это избавляет от фрагментации: раньше для каждого AI-агента приходилось писать отдельные плагины и интеграции. Теперь достаточно одного MCP сервера.

В этом руководстве мы с нуля создадим MCP сервер, который предоставляет AI-агенту инструменты для работы с файловой системой, API-запросами и базой данных, зарегистрируем ресурсы и промпты, а затем задеплоим его на Railway. Весь код — реальный и протестированный. Никакого псевдокода.

Почему именно Python? Экосистема MCP SDK для Python наиболее зрелая после TypeScript-версии. Декораторы, автоматическая генерация JSON Schema из type hints, поддержка async/await — всё это делает разработку быстрой и приятной. А богатая экосистема библиотек (httpx, psycopg2, sqlite3, pydantic) позволяет интегрировать сервер с чем угодно: от корпоративных баз данных до внешних REST API.

Для кого этот гайд: Python-разработчики, DevOps-инженеры, AI-энтузиасты и все, кто хочет расширить возможности AI-агентов собственными инструментами. Требуется базовое знание Python (декораторы, async/await, type hints) и JSON Schema.
Отличие от других гайдов: это практическое руководство с акцентом на код и деплой. Если вам нужна теория MCP (архитектура, JSON-RPC, жизненный цикл соединения) — смотрите гайд «MCP: Model Context Protocol — глубокое погружение». Здесь мы пишем и запускаем код.

📊 Архитектура MCP: как AI-агент общается с сервером

Диаграмма показывает поток данных: AI-агент инициализирует соединение, получает список доступных инструментов/ресурсов, вызывает их и получает результаты. Всё по протоколу JSON-RPC 2.0.

🤖 AI-Агент (Клиент) Claude Desktop / Codex CLI / Continue ① initialize Обмен capabilities ② tools/list Получить инструменты ③ tools/call Вызвать инструмент ④ resources/read Читать ресурсы 🖥 MCP Сервер (Python) mcp / fastmcp + JSON-RPC 2.0 📦 Resources URI-доступ к данным 🔧 Tools Функции с JSON Schema 📝 Prompts Шаблоны взаимодействия 🔄 Transports stdio | HTTP/SSE | WebSocket Ваша бизнес-логика: API, БД, файлы, веб-скрапинг...

1. Настройка окружения

Для создания MCP сервера на Python доступны две основные библиотеки: официальный mcp SDK от Anthropic и упрощённая обёртка fastmcp. Мы будем использовать mcp как основной SDK (он даёт полный контроль), но покажу и пример с fastmcp для быстрого старта.

1.1. Установка зависимостей

# Создаём виртуальное окружение
python -m venv .venv
source .venv/bin/activate  # Linux/macOS
.venv\Scripts\activate     # Windows

# Устанавливаем MCP SDK и вспомогательные библиотеки
pip install mcp uvicorn httpx pydantic python-dotenv

# Опционально: fastmcp для быстрого прототипирования
pip install fastmcp

# Проверяем версии
pip show mcp
Name: mcp
Version: 1.8.0
Summary: Model Context Protocol SDK for Python

1.2. Структура проекта

Рекомендованная структура директорий для MCP сервера:

my-mcp-server/
├── pyproject.toml          # метаданные и зависимости
├── .env                    # переменные окружения (API ключи)
├── .env.example            # шаблон .env для документации
├── server.py               # точка входа (stdio транспорт)
├── server_http.py          # точка входа (HTTP транспорт)
├── tools/
│   ├── __init__.py
│   ├── filesystem.py       # инструменты для работы с ФС
│   └── api_client.py       # инструменты для HTTP-запросов
├── resources/
│   ├── __init__.py
│   └── project_context.py  # ресурсы с контекстом проекта
└── prompts/
    ├── __init__.py
    └── templates.py        # шаблоны промптов
Совет: Разделение на tools, resources и prompts — не требование MCP, а best practice для поддерживаемости. Вы можете держать всё в одном файле server.py на старте.

2. Создание базового MCP сервера

Сердце любого MCP сервера — объект Server. Он регистрирует инструменты, ресурсы и промпты, обрабатывает входящие JSON-RPC запросы и управляет жизненным циклом соединения. Начнём с минимального рабочего сервера.

Объект Server из пакета mcp инкапсулирует всю логику протокола: инициализацию соединения (обмен capabilities), обработку JSON-RPC сообщений, маршрутизацию вызовов к зарегистрированным инструментам и ресурсам. Разработчику остаётся только описать функции и повесить декораторы — SDK берёт на себя сериализацию, валидацию и транспорт.

Транспорт stdio (стандартный ввод/вывод) — самый простой и надёжный вариант для локальной разработки. Процесс сервера запускается как дочерний процесс AI-агента, обмениваясь JSON-RPC сообщениями через stdin/stdout. Это не требует открытия сетевых портов, настройки CORS или SSL — всё работает из коробки.

2.1. Минимальный сервер (stdio)

import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server

# Создаём объект сервера с именем и версией
server = Server("my-first-mcp-server")

@server.tool()
async def hello(name: str) -> str:
    """Приветствует пользователя по имени — простейший тестовый инструмент."""
    return f"Привет, {name}! MCP сервер работает. 🚀"

async def main():
    async with stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            server.create_initialization_options()
        )

if __name__ == "__main__":
    asyncio.run(main())

Этот код создаёт MCP сервер с одним инструментом hello. Декоратор @server.tool() автоматически извлекает сигнатуру функции, type hints и docstring — и генерирует JSON Schema для параметров. Никакой ручной сериализации.

2.2. Подключение к Claude Desktop

Чтобы Claude увидел ваш сервер, добавьте его в конфигурацию Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json на macOS, %APPDATA%\Claude\claude_desktop_config.json на Windows):

{
  "mcpServers": {
    "my-first-server": {
      "command": "python",
      "args": ["/absolute/path/to/server.py"],
      "env": {
        "API_KEY": "your-secret-key"
      }
    }
  }
}

После перезапуска Claude Desktop вы увидите в интерфейсе иконку молотка (🔨) — это означает, что MCP инструменты подключены. Спросите Claude: «Поздоровайся с Дмитрием через hello tool» — и он вызовет ваш инструмент.

3. Добавление инструментов (Tools)

Инструменты — это основной способ расширения возможностей AI-агента. Каждый инструмент: функция Python с type hints, docstring на естественном языке и возвращаемым значением. MCP SDK автоматически генерирует JSON Schema из сигнатуры, а docstring используется агентом для понимания, когда вызывать инструмент.

Важно понимать, как AI-агент выбирает, какой инструмент вызвать. Когда вы пишете docstring «Читает содержимое файла. Используй для просмотра исходников, конфигов, логов» — это не просто документация для людей. Claude (и другие LLM) читает эти описания и сопоставляет с запросом пользователя. Если пользователь просит «покажи конфигурацию сервера», агент видит в описании слово «конфигов» и выбирает read_file. Поэтому хороший docstring — это половина успеха вашего MCP сервера.

Входные параметры валидируются автоматически: если вы указали limit: int = 25, SDK не пропустит вызов с limit: "abc". Type hints в Python (включая Optional, Literal, Union) транслируются в соответствующие конструкции JSON Schema — проверка типов работает на уровне протокола, до того как ваш код начнёт выполняться.

3.1. Инструменты для работы с файловой системой

import os
import pathlib
from typing import Optional

@server.tool()
async def read_file(path: str, encoding: str = "utf-8") -> str:
    """Читает содержимое файла. Используй для просмотра исходников,
    конфигов, логов. Возвращает текст файла."""
    p = pathlib.Path(path).resolve()
    if not p.exists():
        return f"Ошибка: файл {path} не найден"
    if p.stat().st_size > 1_000_000:
        return "Ошибка: файл слишком большой (>1 MB)"
    return p.read_text(encoding=encoding)

@server.tool()
async def list_directory(path: str = ".",
                          pattern: Optional[str] = None) -> str:
    """Показывает содержимое директории. Можно фильтровать по glob-паттерну.
    Пример: pattern='*.py' покажет только Python-файлы."""
    p = pathlib.Path(path).resolve()
    if not p.is_dir():
        return f"Ошибка: {path} не является директорией"

    if pattern:
        items = sorted(p.glob(pattern))
    else:
        items = sorted(p.iterdir())

    lines = []
    for item in items:
        suffix = "/" if item.is_dir() else ""
        size = item.stat().st_size if item.is_file() else 0
        lines.append(f"{item.name}{suffix}{size} байт")

    return "\n".join(lines[:50])  # Лимит для безопасности

3.2. Инструмент для HTTP-запросов

import httpx
from typing import Literal

@server.tool()
async def http_request(
    url: str,
    method: Literal["GET", "POST"] = "GET",
    body: Optional[str] = None,
) -> str:
    """Выполняет HTTP-запрос к указанному URL.
    Используй для вызова внешних API, проверки доступности сервисов,
    получения данных из REST-эндпоинтов."""
    async with httpx.AsyncClient(timeout=15.0) as client:
        try:
            if method == "GET":
                resp = await client.get(url)
            else:
                resp = await client.post(url, content=body)

            resp.raise_for_status()
            return f"Status {resp.status_code}\n{resp.text[:2000]}"
        except httpx.HTTPStatusError as e:
            return f"HTTP ошибка: {e.response.status_code}"
        except Exception as e:
            return f"Ошибка запроса: {type(e).__name__}: {e}"

3.3. Инструмент для SQL-запросов (SQLite)

import sqlite3
import json

@server.tool()
async def query_database(
    sql: str,
    db_path: str = ":memory:",
    limit: int = 25
) -> str:
    """Выполняет SQL-запрос к базе данных SQLite.
    Только SELECT-запросы (безопасность!). Возвращает результат в JSON."""
    sql_upper = sql.strip().upper()
    if not sql_upper.startswith("SELECT"):
        return "Ошибка: разрешены только SELECT-запросы"

    try:
        conn = sqlite3.connect(db_path)
        conn.row_factory = sqlite3.Row
        cursor = conn.execute(sql)
        rows = cursor.fetchmany(limit)
        conn.close()

        result = [dict(row) for row in rows]
        return json.dumps(result, ensure_ascii=False, indent=2)
    except Exception as e:
        return f"Ошибка SQL: {e}"
Безопасность: всегда валидируйте входные параметры инструментов. Ограничивайте размер возвращаемых данных (лимиты, пагинация), проверяйте типы SQL-запросов (только SELECT), используйте resolve() для путей. AI-агент не должен иметь доступ к произвольным системным вызовам без ограничений.

4. Ресурсы (Resources) и промпты (Prompts)

Помимо инструментов, MCP сервер может предоставлять ресурсы (данные для чтения) и промпты (шаблоны взаимодействия). Это делает сервер не просто набором функций, а полноценным источником контекста для AI-агента.

Ресурсы идеальны для данных, которые агенту нужно видеть, но не менять: схема базы данных, документация API, конфигурация проекта, текущие метрики. Агент сам решает, когда запросить ресурс — например, перед написанием SQL-запроса он может прочитать ресурс со схемой БД, чтобы понять структуру таблиц. Ресурсы кэшируются на стороне клиента, что снижает нагрузку на сервер.

Промпты — это «подсказки» сервера агенту о том, как эффективно с ним взаимодействовать. Когда агент вызывает prompts/list и видит шаблон explore_project с описанием «для исследования структуры проекта», он понимает, что для анализа кодовой базы нужно использовать именно этот промпт. Промпты могут содержать переменные для подстановки — например, путь к проекту или текст ошибки.

Комбинация инструментов, ресурсов и промптов создаёт полноценный контекстный интерфейс: ресурсы дают данные, промпты учат агента правильно их использовать, а инструменты позволяют выполнять действия. Это и есть главная сила MCP — не просто набор функций, а семантически связанная экосистема для AI-взаимодействия.

4.1. Статические и динамические ресурсы

from mcp.server import Resource
import datetime

# Статический ресурс: схема базы данных
@server.resource("schema://database")
async def database_schema() -> str:
    """Текущая схема базы данных с описанием таблиц и колонок."""
    conn = sqlite3.connect("app.db")
    cursor = conn.execute(
        "SELECT sql FROM sqlite_master WHERE type='table'"
    )
    schemas = [row[0] for row in cursor if row[0]]
    conn.close()
    return "\n\n".join(schemas)

# Динамический ресурс: статус сервера (обновляется каждый вызов)
@server.resource("monitor://status")
async def server_status() -> str:
    """Текущий статус сервера: uptime, использование памяти, активные соединения."""
    import psutil  # pip install psutil
    now = datetime.datetime.now().isoformat()
    mem = psutil.virtual_memory()
    return f"""Статус на {now}
ОЗУ: {mem.percent}% использовано ({mem.used // 1024**2} MB из {mem.total // 1024**2} MB)
CPU: {psutil.cpu_percent()}%"""

4.2. Промпты: учим агента правильно использовать сервер

@server.prompt()
async def explore_project(project_path: str) -> str:
    """Генерирует промпт для исследования структуры проекта."""
    return f"""Ты анализируешь проект, расположенный в {project_path}.

Инструкции:
1. Сначала используй list_directory для обзора структуры.
2. Найди файлы конфигурации (pyproject.toml, package.json, Cargo.toml).
3. Прочитай readme и основные исходные файлы.
4. Опиши архитектуру проекта: язык, фреймворк, зависимости.
5. Выдели потенциальные проблемы и предложи улучшения.

Доступные инструменты: list_directory, read_file, http_request."""

@server.prompt()
async def debug_error(error_message: str, context_file: str = "") -> str:
    """Генерирует промпт для отладки ошибки с контекстом файла."""
    base = f"""Произошла ошибка: {error_message}

Твоя задача:
1. Проанализируй ошибку и определи возможные причины.
2. Если указан файл — прочитай его через read_file.
3. Предложи исправление с конкретным кодом.
4. Объясни, почему возникла ошибка и как избежать её в будущем."""
    if context_file:
        return base + f"\n\nКонтекстный файл: {context_file} (прочитай его через read_file)"
    return base
Промпты vs Системные инструкции: промпты в MCP — это шаблоны, которые агент запрашивает у сервера для самоконфигурации. Это мощный паттерн: сервер может дать агенту инструкции, оптимизированные под конкретный контекст (структуру БД, формат API, правила безопасности).

5. Деплой: от localhost до продакшена

MCP сервер по умолчанию работает через stdio (стандартный ввод/вывод), что идеально для локального использования с Claude Desktop. Но для командной работы, облачных агентов и масштабирования нужен HTTP-транспорт. Рассмотрим оба варианта.

HTTP-транспорт через SSE (Server-Sent Events) позволяет MCP серверу принимать соединения от удалённых клиентов. В отличие от WebSocket, SSE работает поверх обычного HTTP, что упрощает настройку reverse proxy (nginx, Caddy), балансировщиков нагрузки и firewall. Клиент подписывается на поток событий через GET-запрос, а отправляет сообщения серверу через POST.

Платформы для деплоя Python-приложений отлично подходят для MCP серверов. Railway автоматически определяет Python-проект по наличию requirements.txt или pyproject.toml, устанавливает зависимости и запускает приложение. Fly.io даёт глобальное распределение (ваш сервер физически ближе к пользователям) и поддерживает автомасштабирование. Для максимального контроля можно использовать обычный VPS с systemd и nginx.

5.1. HTTP-транспорт через SSE (Server-Sent Events)

# server_http.py — HTTP версия MCP сервера
import asyncio
from mcp.server import Server
from mcp.server.sse import SseServerTransport
from starlette.applications import Starlette
from starlette.routing import Mount, Route
from starlette.responses import JSONResponse
import uvicorn

server = Server("my-mcp-http-server")
sse = SseServerTransport("/messages/")

# ... здесь регистрация инструментов, ресурсов, промптов ...

async def handle_sse(request):
    async with sse.connect_sse(
        request.scope, request.receive, request._send
    ) as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            server.create_initialization_options()
        )

async def health_check(request):
    return JSONResponse({"status": "ok", "server": "mcp-http"})

app = Starlette(
    routes=[
        Route("/sse", endpoint=handle_sse),
        Route("/health", endpoint=health_check),
        Mount("/messages/", app=sse.handle_post_message),
    ]
)

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

5.2. Деплой на Railway

Railway — отличная платформа для деплоя MCP серверов: поддержка Python из коробки, автоматический HTTPS, масштабирование и бесплатный тир для старта.

# railway.json — конфигурация деплоя
{
  "build": {
    "builder": "NIXPACKS"
  },
  "deploy": {
    "startCommand": "python server_http.py",
    "healthcheckPath": "/health",
    "restartPolicyType": "ON_FAILURE"
  }
}

Шаги деплоя:

  1. Установите Railway CLI: npm i -g @railway/cli
  2. Авторизуйтесь: railway login
  3. Инициализируйте проект: railway init
  4. Добавьте переменные окружения: railway variables set API_KEY=your-key
  5. Задеплойте: railway up

После деплоя ваш MCP сервер будет доступен по URL вида https://your-project.up.railway.app. Подключите его к Claude Desktop, указав URL вместо команды:

{
  "mcpServers": {
    "my-http-server": {
      "url": "https://your-project.up.railway.app/sse",
      "transport": "sse"
    }
  }
}

5.3. Альтернатива: Docker-деплой

# Dockerfile
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["python", "server_http.py"]
# requirements.txt
mcp>=1.8.0
uvicorn>=0.30.0
httpx>=0.27.0
pydantic>=2.0
python-dotenv>=1.0
starlette>=0.38.0
Railway vs Fly.io vs VPS: Railway — самый простой старт (автоопределение Python, HTTPS из коробки). Fly.io даёт больше контроля и глобальный edge. VPS (Hetzner, DigitalOcean) — максимальный контроль и фиксированная цена, но требует ручной настройки nginx + SSL через Certbot.

6. Продвинутые примеры и best practices

6.1. FastMCP — быстрый старт в 10 строк

fastmcp — обёртка над mcp SDK, которая убирает бойлерплейт и добавляет удобные фичи: автоматическую документацию OpenAPI, веб-интерфейс для тестирования, CLI-инструменты.

from fastmcp import FastMCP

mcp = FastMCP("My Tools 🚀")

@mcp.tool()
def add(a: int, b: int) -> int:
    """Складывает два числа."""
    return a + b

@mcp.tool()
def get_weather(city: str) -> str:
    """Получает текущую погоду для города (мок-версия)."""
    return f"В городе {city} сейчас солнечно, +22°C"

if __name__ == "__main__":
    # Запуск с HTTP-транспортом одной командой
    mcp.run(transport="sse", port=8000)

6.2. Композитные инструменты с контекстом

Паттерн «инструмент-оркестратор»: один инструмент вызывает несколько внутренних и возвращает агрегированный результат.

@server.tool()
async def analyze_repository(repo_path: str) -> str:
    """Анализирует Git-репозиторий и возвращает сводку:
    структура, языки, последние коммиты, размер."""
    import subprocess

    p = pathlib.Path(repo_path)
    if not (p / ".git").exists():
        return "Ошибка: не Git-репозиторий"

    def run_git(*args):
        return subprocess.check_output(
            ["git", "-C", repo_path] + list(args),
            text=True, stderr=subprocess.STDOUT
        )

    commits = run_git("log", "--oneline", "-10")
    branches = run_git("branch", "-a")

    # Подсчёт файлов по расширениям
    extensions = {}
    for file in p.rglob("*"):
        if file.is_file() and ".git" not in str(file):
            ext = file.suffix or "(no ext)"
            extensions[ext] = extensions.get(ext, 0) + 1

    top_exts = sorted(extensions.items(), key=lambda x: x[1], reverse=True)[:5]

    return f"""📊 Анализ репозитория: {p.name}

📁 Расширения: {', '.join(f'{e}({c})' for e,c in top_exts)}

🌿 Ветки:
{branches}
📝 Последние коммиты:
{commits}"""

6.3. Best practices для production MCP серверов

ПрактикаОписание
Валидация входовПроверяйте все параметры инструментов: типы, диапазоны, допустимые значения. Используйте Pydantic-модели для сложной валидации.
ТаймаутыКаждый инструмент должен иметь разумный таймаут (asyncio.wait_for). Агент не должен висеть 60 секунд на одном вызове.
Лимиты данныхОграничивайте размер возвращаемых данных. Никаких 10 MB JSON-ответов — агент не сможет их обработать.
ЛогированиеЛогируйте все вызовы инструментов: имя, параметры, результат, время выполнения. Используйте structlog или стандартный logging.
АутентификацияДля HTTP-серверов добавьте API-ключ или JWT. MCP не имеет встроенной аутентификации — это зона ответственности разработчика.
Graceful shutdownКорректно завершайте соединения с БД, закрывайте файловые дескрипторы при остановке сервера.
Документация docstringПишите подробные docstring для инструментов — AI-агент использует их для принятия решений. Чем лучше описание, тем точнее вызовы.
ТестированиеТестируйте инструменты через MCP-клиент в pytest. Проверяйте, что JSON Schema генерируется корректно.

6.4. Тестирование MCP сервера с pytest

# test_server.py
import pytest
from mcp.client.session import ClientSession
from mcp.client.stdio import stdio_client

@pytest.mark.asyncio
async def test_hello_tool():
    """Проверяем, что инструмент hello работает корректно."""
    async with stdio_client("python", ["server.py"]) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            # Проверяем список инструментов
            tools = await session.list_tools()
            assert any(t.name == "hello" for t in tools)

            # Вызываем инструмент
            result = await session.call_tool("hello", {"name": "Тест"})
            assert "Тест" in result.content[0].text
            assert "MCP сервер работает" in result.content[0].text
Про отладку: запустите сервер с MCP_INSPECTOR=1 python server.py — встроенный инспектор от Anthropic покажет все зарегистрированные инструменты, ресурсы и позволит тестировать вызовы в реальном времени через веб-интерфейс на localhost:5173.

✅ Итог

Мы прошли полный цикл создания MCP сервера на Python — от установки пакетов до деплоя на Railway. Давайте зафиксируем главное:

Что дальше? Изучите официальный репозиторий MCP-серверов — там десятки примеров: от PostgreSQL и GitHub до Slack и Brave Search. Интегрируйте свой сервер с Claude Code, Cursor или Continue. Создайте MCP-сервер для своего продукта и опубликуйте в каталоге — экосистема растёт взрывными темпами, и ваш инструмент может стать стандартом для тысяч AI-агентов по всему миру. В 2026 году MCP — это то, чем HTTP был в 1995: фундаментальный протокол, на котором строится следующее поколение интернета. 🔌