Базовая структура проекта MCP-сервера

Мы уже знаем, что такое Model Context Protocol, понимаем его архитектуру и определили необходимые эндпоинты для нашего MCP-сервера. Теперь пришло время перейти от теории к практике — и построить прочный фундамент для нашего агента.

Хорошая структура проекта — это как архитектура дома: если фундамент крепкий, можно строить сколько угодно этажей. Если нет — даже красивый фасад быстро рухнет. Сегодня мы создадим такую структуру, которая будет масштабируемой, понятной и готовой к интеграции с любыми LLM и инструментами.


Почему структура проекта так важна?

Без чёткой структуры код быстро превращается в "спагетти" — функции смешиваются, сложно найти нужное, а добавление нового функционала становится рискованным делом.

В MCP-сервере особенно важно разделять:

  • логику обработки запросов,
  • реализацию инструментов (tools),
  • настройки и конфигурацию.

Это позволяет:

  • легко тестировать отдельные компоненты,
  • быстро подключать новые инструменты,
  • безопасно масштабировать проект.

💡 Совет: даже если вы пока работаете в одиночку, пишите код так, будто над ним будет работать команда. Это сэкономит вам время, когда проект начнёт расти.


Ключевые модули MCP-сервера

На прошлом шаге мы выбрали FastAPI и определили эндпоинты: /prompt, /tool, /capabilities. Теперь нужно организовать код так, чтобы каждый компонент знал своё место.

Мы выделим три основных модуля:

МодульНазначение
handlersОбработка HTTP-запросов к эндпоинтам MCP
toolsРеализация функций, которые может вызывать LLM
configХранение настроек, API-ключей, параметров сервера

Также понадобится:

  • main.py — точка входа в приложение,
  • requirements.txt или pyproject.toml — управление зависимостями,
  • .env — хранение секретов (например, API-ключей).

Файловая структура проекта

Вот как будет выглядеть наша базовая структура:

my-mcp-server/
├── src/
│   ├── handlers/
│   │   └── __init__.py
│   ├── tools/
│   │   └── __init__.py
│   ├── config/
│   │   └── __init__.py
│   └── main.py
├── .env
├── .gitignore
├── requirements.txt
└── README.md

Разберём каждый элемент:

  • src/ — корневая папка с исходным кодом. Удобно выделять её, чтобы отделить код от конфигурации и документации.
  • handlers/ — здесь будут функции, отвечающие за обработку /prompt, /tool и других эндпоинтов.
  • tools/ — модуль для пользовательских инструментов. Каждый инструмент — это функция, которую LLM может вызвать.
  • config/ — настройки сервера, загрузка переменных окружения, конфигурация логирования.
  • main.py — инициализация FastAPI-приложения и подключение маршрутов.
  • .env — файл с переменными окружения (например, ANTHROPIC_API_KEY=...).
  • requirements.txt — список зависимостей, включая fastapi, uvicorn, python-dotenv.

Создаём структуру: пошагово

Откройте терминал и выполните:

mkdir my-mcp-server
cd my-mcp-server
python -m venv venv
source venv/bin/activate  # Linux/Mac
# или venv\Scripts\activate  # Windows

Создадим папки:

mkdir -p src/handlers src/tools src/config

Теперь добавим базовые файлы.

1. src/main.py — точка входа

from fastapi import FastAPI
from src.handlers import router as handlers_router

app = FastAPI(title="MCP Server", version="0.1.0")

# Подключаем маршруты
app.include_router(handlers_router)

@app.get("/")
def root():
    return {"message": "MCP Server is running"}

2. src/handlers/__init__.py — обработчики эндпоинтов

from fastapi import APIRouter

router = APIRouter(prefix="/mcp")

@router.get("/capabilities")
async def get_capabilities():
    return {
        "version": "0.1.0",
        "resources": ["/mcp/tool", "/mcp/prompt"],
        "tools": []
    }

@router.post("/prompt")
async def handle_prompt(data: dict):
    # Пока возвращаем заглушку
    return {"response": "Prompt received"}

@router.post("/tool")
async def handle_tool(data: dict):
    # Здесь будет вызов инструментов
    return {"result": "Tool executed"}

3. src/config/__init__.py — загрузка конфигурации

from dotenv import load_dotenv
import os

load_dotenv()

# Пример чтения переменной окружения
API_KEY = os.getenv("ANTHROPIC_API_KEY")
SERVER_PORT = int(os.getenv("SERVER_PORT", 8000))

4. .env — безопасное хранение данных

ANTHROPIC_API_KEY=your-key-here
SERVER_PORT=8000

🔐 Важно: никогда не коммитьте .env в Git. Добавьте его в .gitignore.


Принципы организации кода

✅ Типизация

Даже в Python важно использовать аннотации типов. Это делает код понятнее и помогает избежать ошибок.

Пример с типизацией:

from typing import Dict, Any

@router.post("/prompt")
async def handle_prompt(data: Dict[str, Any]) -> Dict[str, str]:
    return {"response": f"Received prompt: {data.get('prompt', '')}"}

✅ Асинхронность

Используем async/await, чтобы сервер мог обрабатывать несколько запросов одновременно — особенно важно при работе с внешними API.

import asyncio

@router.post("/tool")
async def handle_tool(data: dict):
    await asyncio.sleep(1)  # Имитация долгой операции
    return {"result": "Tool completed"}

✅ Читаемость

Каждая функция должна решать одну задачу. Название — понятное. Комментарии — только где необходимо.


Практическое задание

Создайте структуру проекта, как описано выше. Убедитесь, что:

  1. Виртуальное окружение активировано.
  2. Установлены зависимости:
    pip install fastapi uvicorn python-dotenv
    
  3. Сервер запускается:
    uvicorn src.main:app --reload
    
  4. Эндпоинт http://localhost:8000/mcp/capabilities возвращает JSON с возможностями.

Проверьте, что всё работает — это ваш первый рабочий MCP-сервер!


Что дальше?

Мы создали каркас, но пока наш агент "не помнит" предыдущих взаимодействий. А ведь именно контекст — сердце любого ИИ-агента.

В следующей теме мы изучим, как сохранять и передавать состояние между запросами. Вы узнаете, как использовать Redis и файловое хранилище, чтобы агент мог "помнить" диалог, настройки и промежуточные результаты.

Это ключевой шаг к созданию по-настоящему умного и полезного агента.

Готовы добавить память своему ИИ? Тогда — вперёд! 🚀