В этом материале разберём, как собрать локальный 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 и модель работают вместе
В локальном сценарии всё выглядит так:
Пользователь пишет вопрос в Cursor или Claude Desktop.
Host-приложение передаёт модели список доступных MCP-инструментов и их описания.
Модель решает, что ей нужен конкретный tool, например
get_top_chatters.Host показывает пользователю запрос на подтверждение вызова.
После подтверждения host отправляет JSON-RPC-сообщение MCP-серверу.
Python-сервер выполняет функцию, читает SQLite и возвращает структурированный результат.
Модель превращает полученные данные в человеческий ответ.
Важно: в локальном варианте Cursor или Claude Desktop обычно запускает ваш MCP-сервер как дочерний процесс. Сервер общается с host через stdio, поэтому нельзя писать обычный вывод в stdout: он занят транспортом JSON-RPC. Для диагностики используйте логирование в stderr.
Три базовые сущности MCP
MCP-сервер может отдавать несколько типов возможностей. Для первого проекта достаточно понять три главные:
Сущность | Что делает | Пример |
|---|---|---|
| Действия, которые модель может попросить выполнить | Получить топ пользователей из SQLite, создать задачу, вызвать API |
| Данные только для чтения, которые host может показать модели | Файл конфигурации, документ базы знаний, справочник |
| Переиспользуемые шаблоны запросов | "Сделай 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.
Типичные ошибки и диагностика
Симптом | Вероятная причина | Что проверить |
|---|---|---|
| Host запускает не тот Python | Проверьте |
Сервер не появляется в Cursor | Ошибка в | Проверьте JSON-синтаксис, абсолютные пути и права на файл |
Tool есть, но база не находится |
| Убедитесь, что |
Host пишет про повреждённые JSON-RPC-сообщения | Сервер печатает отладочный текст в | Уберите |
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 и файлами.