Полный код и деплой MCP-сервера с инструментами. Работает с Claude Desktop, Cursor, Continue.
MCP использует JSON-RPC 2.0 для обмена сообщениями. Четыре основных метода: initialize (установка соединения), tools/list (список инструментов), tools/call (вызов), resources/read (чтение ресурсов).
// Запрос: клиент запрашивает список инструментов { "jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {} } // Ответ: сервер возвращает описание инструментов { "jsonrpc": "2.0", "id": 1, "result": { "tools": [ { "name": "run_query", "description": "Выполняет SQL-запрос", "inputSchema": { "type": "object", "properties": { "sql": { "type": "string" } } } } ] } }
Для деплоя на сервер используйте Streamable HTTP вместо stdio. Это позволяет запускать MCP-сервер как веб-сервис и подключать несколько клиентов.
from mcp.server import Server from mcp.server.http import create_http_server server = Server("pg-mcp-server") # ... регистрация инструментов ... if __name__ == "__main__": app = create_http_server(server) app.run(host="0.0.0.0", port=8000)
HTTP-транспорт поддерживает server-sent events (SSE) для стриминга ответов. Клиент подключается по URL: http://your-server:8000/mcp. Claude Desktop тоже поддерживает HTTP-транспорт через поле "url" в конфиге.
Создадим 4 инструмента для работы с PostgreSQL: list_tables, describe_table, run_query, export_csv. Claude становится администратором базы данных.
import psycopg2 import csv, io CONN = "postgresql://user:pass@localhost:5432/mydb" @server.tool() def list_tables() -> str: """Показывает все таблицы в базе данных""" sql = "SELECT table_name FROM information_schema.tables WHERE table_schema='public'" with psycopg2.connect(CONN) as c: return str(c.cursor().execute(sql).fetchall()) @server.tool() def describe_table(table: str) -> str: """Показывает структуру таблицы: колонки и типы""" sql = f"SELECT column_name, data_type FROM information_schema.columns WHERE table_name='{table}'" with psycopg2.connect(CONN) as c: return str(c.cursor().execute(sql).fetchall()) @server.tool() def run_query(sql: str, limit: int = 100) -> str: """Выполняет SQL-запрос (только SELECT, лимит 100 строк)""" if not sql.strip().upper().startswith("SELECT"): return "Разрешены только SELECT-запросы" with psycopg2.connect(CONN) as c: return str(c.cursor().execute(sql).fetchmany(limit)) @server.tool() def export_csv(sql: str) -> str: """Экспортирует результат запроса в CSV""" with psycopg2.connect(CONN) as c: rows = c.cursor().execute(sql).fetchall() buf = io.StringIO() csv.writer(buf).writerows(rows) return buf.getvalue()
Запустите MCP-сервер как systemd-сервис за nginx reverse proxy с SSL. Сервер будет доступен по HTTPS и готов к продакшену.
# /etc/systemd/system/mcp-server.service [Unit] Description=MCP Server After=network.target [Service] Type=simple User=www-data WorkingDirectory=/opt/mcp-server ExecStart=/usr/bin/python3 server.py Restart=always RestartSec=10 [Install] WantedBy=multi-user.target # Запуск: sudo systemctl daemon-reload && sudo systemctl enable --now mcp-server # nginx reverse proxy + SSL location /mcp { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }
Настройте мониторинг через Prometheus метрики и просмотр логов через journalctl. Отслеживайте количество вызовов инструментов и время ответа.
# Просмотр логов MCP-сервера journalctl -u mcp-server -f # real-time логи journalctl -u mcp-server --since "1 hour ago" # за последний час # Prometheus метрики в server.py: from prometheus_client import Counter, Histogram tool_calls = Counter('mcp_tool_calls', 'Tool calls', ['tool']) tool_latency = Histogram('mcp_tool_latency', 'Latency', ['tool']) # В каждом инструменте: tool_calls.labels('run_query').inc() with tool_latency.labels('run_query').time(): # ... тело инструмента ...
Напишите тесты с pytest, мокируя stdio. Проверьте JSON-RPC ответы и корректность работы инструментов.
import pytest, json from mcp.client import ClientSession from mcp.testing import stdio_client @pytest.mark.asyncio async def test_tools_list(): """Проверка: tools/list возвращает список инструментов""" async with stdio_client("python3", ["server.py"]) as (read, write): async with ClientSession(read, write) as session: tools = await session.list_tools() assert len(tools) >= 4 assert "run_query" in [t.name for t in tools] @pytest.mark.asyncio async def test_run_query(): """Проверка: вызов run_query возвращает корректный JSON-RPC ответ""" async with stdio_client("python3", ["server.py"]) as (read, write): async with ClientSession(read, write) as session: result = await session.call_tool("run_query", {"sql": "SELECT 1"}) assert result is not None