Как собрать MCP-сервер на Python

В этом материале разберём, как собрать локальный MCP-сервер на Python с помощью официального SDK и FastMCP, подключить его к Cursor и Claude Desktop, а в качестве практического примера дадим модели доступ к небольшой SQLite-базе.

Что такое MCP и зачем он нужен

MCP расшифровывается как Model Context Protocol. Это открытый протокол, который стандартизирует связь между AI-host-приложениями и внешними источниками данных.

Простой пример: вы спрашиваете AI-помощника в Cursor: "Покажи пять самых активных участников в базе". Модель сама по себе не умеет читать вашу SQLite-базу. Но если Cursor видит подключённый MCP-сервер с инструментом get_top_chatters, он может предложить модели этот инструмент, запросить ваше подтверждение и передать вызов Python-серверу.

До MCP каждую интеграцию приходилось делать отдельно: один способ для Claude, другой для Cursor, третий для собственного агента. MCP предлагает общий контракт: host умеет обнаруживать capabilities сервера, модель получает описание доступных действий, а сервер выполняет только явно объявленные функции.

Где MCP особенно полезен

  • Работа с внутренними данными: CRM, база знаний, аналитика, SQLite/PostgreSQL, файлы проекта.

  • Автоматизация рутины: создание задач, запуск проверок, генерация отчётов, сбор метрик.

  • Инструменты для разработки: анализ репозитория, поиск по логам, работа с CI/CD, запросы к API.

  • Контролируемый доступ: модель не получает всё подряд, а видит только заранее описанные tools.

Как MCP-сервер, host и модель работают вместе

В локальном сценарии всё выглядит так:

  1. Пользователь пишет вопрос в Cursor или Claude Desktop.

  2. Host-приложение передаёт модели список доступных MCP-инструментов и их описания.

  3. Модель решает, что ей нужен конкретный tool, например get_top_chatters.

  4. Host показывает пользователю запрос на подтверждение вызова.

  5. После подтверждения host отправляет JSON-RPC-сообщение MCP-серверу.

  6. Python-сервер выполняет функцию, читает SQLite и возвращает структурированный результат.

  7. Модель превращает полученные данные в человеческий ответ.

Важно: в локальном варианте Cursor или Claude Desktop обычно запускает ваш MCP-сервер как дочерний процесс. Сервер общается с host через stdio, поэтому нельзя писать обычный вывод в stdout: он занят транспортом JSON-RPC. Для диагностики используйте логирование в stderr.

Три базовые сущности MCP

MCP-сервер может отдавать несколько типов возможностей. Для первого проекта достаточно понять три главные:

Сущность

Что делает

Пример

tools

Действия, которые модель может попросить выполнить

Получить топ пользователей из SQLite, создать задачу, вызвать API

resources

Данные только для чтения, которые host может показать модели

Файл конфигурации, документ базы знаний, справочник

prompts

Переиспользуемые шаблоны запросов

"Сделай code review", "Собери changelog", "Сформируй SQL-отчёт"

В этой статье сфокусируемся на tools, потому что именно они лучше всего показывают механику MCP: модель просит действие, пользователь подтверждает, сервер возвращает результат.

Подготовка Python-проекта

Нам понадобится Python 3.10 или новее. Создадим отдельную папку и виртуальное окружение:

mkdir mcp-sqlite-demo
cd mcp-sqlite-demo
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install "mcp>=1.27,<2"

Для Windows PowerShell активация окружения будет другой:

python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
pip install "mcp>=1.27,<2"

Проверить установку можно так:

pip show mcp

Если пакет найден, можно переходить к тестовым данным.

Создаём тестовую SQLite-базу

Создайте файл setup_demo_db.py:

from pathlib import Path
import sqlite3

DB_PATH = Path(__file__).resolve().parent / "community.db"

rows = [
    ("Анна", 184),
    ("Михаил", 156),
    ("Ирина", 143),
    ("Даниил", 118),
    ("Ольга", 97),
    ("Сергей", 82),
    ("Мария", 75),
    ("Кирилл", 61),
]

with sqlite3.connect(DB_PATH) as conn:
    conn.execute("DROP TABLE IF EXISTS chatters")
    conn.execute(
        """
        CREATE TABLE chatters (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            messages INTEGER NOT NULL
        )
        """
    )
    conn.executemany(
        "INSERT INTO chatters (name, messages) VALUES (?, ?)",
        rows,
    )
    conn.commit()

print(f"Database created: {DB_PATH}")

Запустите скрипт:

python setup_demo_db.py

Быстрая проверка:

python -c "import sqlite3; conn = sqlite3.connect('community.db'); print(conn.execute('SELECT COUNT(*) FROM chatters').fetchone()[0])"

В ответе должно быть 8. Значит, база создана и серверу будет что читать.

Пишем MCP-сервер на Python

Теперь создаём файл sqlite_server.py. Он объявит MCP-сервер, зарегистрирует два инструмента и запустит transport через mcp.run().

from pathlib import Path
import sqlite3

from mcp.server.fastmcp import FastMCP

DB_PATH = Path(__file__).resolve().parent / "community.db"

mcp = FastMCP("ByteStory SQLite Demo")


@mcp.tool()
def get_top_chatters(limit: int = 5) -> list[dict[str, str | int]]:
    """Вернуть участников с максимальным количеством сообщений."""
    safe_limit = min(max(limit, 1), 50)

    with sqlite3.connect(DB_PATH) as conn:
        cursor = conn.execute(
            """
            SELECT name, messages
            FROM chatters
            ORDER BY messages DESC
            LIMIT ?
            """,
            (safe_limit,),
        )
        rows = cursor.fetchall()

    return [
        {"name": name, "messages": messages}
        for name, messages in rows
    ]


@mcp.tool()
def get_chatter_stats() -> dict[str, int | float]:
    """Вернуть общую статистику по активности участников."""
    with sqlite3.connect(DB_PATH) as conn:
        total_users, total_messages, avg_messages = conn.execute(
            """
            SELECT COUNT(*), SUM(messages), AVG(messages)
            FROM chatters
            """
        ).fetchone()

    return {
        "total_users": total_users or 0,
        "total_messages": total_messages or 0,
        "avg_messages": round(avg_messages or 0, 2),
    }


if __name__ == "__main__":
    mcp.run()

Что здесь происходит

  • FastMCP("ByteStory SQLite Demo") создаёт сервер и задаёт ему имя.

  • @mcp.tool() регистрирует обычную Python-функцию как MCP-инструмент.

  • Type hints помогают SDK собрать схему входных параметров и результата.

  • Docstring становится описанием tool для host-приложения и модели.

  • mcp.run() запускает сервер в режиме stdio, который ожидают локальные MCP-hosts.

Почему мы ограничиваем limit

Даже в демо лучше показывать правильную привычку: параметры от модели не стоит принимать без ограничений. В примере safe_limit = min(max(limit, 1), 50) не даёт запросить отрицательное количество строк или случайно вытащить слишком большой объём данных.

Важное правило про stdout

В MCP-сервере, который работает через stdio, нельзя делать обычный print() для отладки. Host ждёт JSON-RPC-сообщения в стандартном выводе. Любой лишний текст может сломать обмен. Если нужны логи, используйте logging с выводом в stderr.

import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.info("MCP server started")

Такой лог не загрязнит transport, потому что стандартная конфигурация logging пишет в stderr.

Подключаем сервер к Cursor

Откройте Cursor, перейдите в Settings → MCP и выберите добавление нового глобального MCP-сервера. Cursor откроет или предложит создать файл ~/.cursor/mcp.json.

Сначала узнайте абсолютный путь к Python внутри виртуального окружения:

which python

Пример конфига:

{
  "mcpServers": {
    "bytestory-sqlite": {
      "command": "/home/user/mcp-sqlite-demo/.venv/bin/python",
      "args": [
        "/home/user/mcp-sqlite-demo/sqlite_server.py"
      ],
      "description": "SQLite MCP server with community activity demo data"
    }
  }
}

Замените пути на свои. Важно указывать именно абсолютные пути: host запускает процесс не из вашей текущей папки терминала, поэтому относительные пути часто приводят к ошибкам.

После сохранения Cursor должен показать сервер в списке. Если рядом с ним появился зелёный индикатор, процесс стартовал успешно.

Как проверить в Cursor

Откройте чат и спросите:

Покажи топ-5 самых активных участников из SQLite-базы.

Cursor должен предложить вызов get_top_chatters. Подтвердите его. После этого модель получит JSON-результат и оформит ответ обычным текстом.

Ещё один тест:

Сколько всего участников в базе и сколько сообщений они написали суммарно?

Здесь модель должна выбрать get_chatter_stats.

Подключаем сервер к Claude Desktop

Claude Desktop использует похожую структуру mcpServers. На macOS конфиг обычно находится здесь:

~/Library/Application Support/Claude/claude_desktop_config.json

В Linux и Windows путь зависит от установки, но открыть его можно через Settings → Developer → Edit Config.

Пример конфига:

{
  "mcpServers": {
    "bytestory-sqlite": {
      "command": "/home/user/mcp-sqlite-demo/.venv/bin/python",
      "args": [
        "/home/user/mcp-sqlite-demo/sqlite_server.py"
      ]
    }
  }
}

После изменения файла полностью закройте Claude Desktop и откройте заново. Простого обновления окна часто недостаточно: приложение должно перечитать конфиг и заново поднять MCP-процессы.

Проверочный запрос:

Используй доступный инструмент и покажи статистику активности участников.

Если всё настроено правильно, Claude покажет запрос на разрешение tool call, выполнит его и вернёт ответ на основе данных SQLite.

Типичные ошибки и диагностика

Симптом

Вероятная причина

Что проверить

ModuleNotFoundError: No module named 'mcp'

Host запускает не тот Python

Проверьте command: он должен вести к Python внутри .venv

Сервер не появляется в Cursor

Ошибка в mcp.json или неверный путь к скрипту

Проверьте JSON-синтаксис, абсолютные пути и права на файл

Tool есть, но база не находится

community.db лежит не рядом со скриптом

Убедитесь, что community.db и sqlite_server.py находятся в одной папке

Host пишет про повреждённые JSON-RPC-сообщения

Сервер печатает отладочный текст в stdout

Уберите print() из MCP-сервера или перенесите логи в stderr

Claude не видит новый сервер

Приложение не перечитало конфиг

Полностью закройте Claude Desktop и запустите снова

Минимальный чек-лист

  • pip show mcp выполняется в активном виртуальном окружении.

  • command указывает на .venv/bin/python или Windows-аналог внутри проекта.

  • args содержит абсолютный путь к sqlite_server.py.

  • community.db создан и лежит рядом со скриптом сервера.

  • В MCP-сервере нет print(), который пишет в stdout.

FAQ

Что такое MCP в Python?

Это реализация MCP-сервера на Python с помощью пакета mcp. Вы пишете обычные функции, помечаете их декоратором @mcp.tool(), а SDK превращает их в инструменты, доступные Cursor, Claude Desktop или другому MCP-host.

Почему используется FastMCP?

FastMCP упрощает старт: не нужно вручную описывать JSON-RPC, схемы параметров и регистрацию tools. Имя функции, type hints и docstring становятся частью контракта, который host показывает модели.

MCP — это то же самое, что REST API?

Нет. REST API обычно строится вокруг HTTP-ресурсов и CRUD-операций. MCP описывает capabilities для AI-host: tools, resources и prompts. Под капотом локальный сервер часто общается через stdio, а не через HTTP. При этом внутри tool можно вызвать REST API, базу данных или любую другую интеграцию.

Можно ли подключить к MCP реальную базу данных?

Можно, но лучше начинать с read-only сценариев. Для PostgreSQL или MySQL заведите отдельного пользователя с минимальными правами, ограничьте SQL-запросы заранее написанными функциями и не давайте модели выполнять произвольный SQL без валидации.

Можно ли запустить MCP-сервер не локально, а удалённо?

Да. Для локальных desktop-hosts чаще всего используют stdio, но спецификация MCP поддерживает удалённые сценарии через Streamable HTTP. Для production-режима уже появляются дополнительные вопросы: аутентификация, rate limits, аудит tool calls, секреты и сетевой доступ.

Насколько это безопасно?

MCP сам по себе не делает интеграцию безопасной автоматически. Он задаёт понятный контракт, но безопасность зависит от реализации tools. Хорошее правило: каждый tool должен делать одно ограниченное действие, принимать минимальный набор параметров и не иметь лишних прав.

Что делать дальше

После первого SQLite-примера можно развивать сервер в нескольких направлениях:

  • Добавить resources: например, отдавать read-only справочник проекта или документацию.

  • Добавить prompts: подготовить шаблоны для анализа данных, ревью или генерации отчётов.

  • Подключить реальный источник: PostgreSQL, GitHub API, Jira, Notion, внутреннюю админку.

  • Ввести права доступа: ограничить tools по окружению, роли пользователя или типу операции.

  • Перейти к удалённому серверу: изучить Streamable HTTP, авторизацию и production-логирование.

Главная идея MCP проста: модель становится полезнее не потому, что знает всё, а потому что получает контролируемый способ обратиться к актуальным данным и действиям. Python хорошо подходит для первого сервера: минимум кода, понятный SDK и большой выбор библиотек для работы с базами, API и файлами.

Похожие статьи

Рекомендательные технологии Подробнее

Распространённые ошибки производительности в Laravel и простые способы их устранения

Практическое руководство по распространённым ошибкам производительности в Laravel и простым способам их устранения. Разбор N+1-запросов, кэширования, оптимизации выборок и других типичных проблем.

AI 4 месяца назад

Reddit считает AI-поиск своей следующей большой возможностью

Reddit рассматривает AI-поиск как ключевой элемент своего развития, объединяя традиционный поиск и AI-ответы для улучшения пользовательского опыта и создания новых источников роста. Статья объясняет стратегию компании и планы на будущее.