нейропоток
ГайдыПрактик

MCP сервер своими руками: пишем, подключаем, используем

Пишем MCP сервер своими руками: FastMCP на Python, подключение к Claude Desktop и Cursor, отладка. Реальный кейс — мой сервер для генерации обложек.

Павел·14 мин чтения
MCP сервер своими руками: пишем, подключаем, используем
Поделиться:TelegramVK

Год назад я смотрел на MCP-серверы и думал — это для разработчиков уровня senior, с конфигами, протоколами, JSON-RPC. Сложно. Пусть кто-нибудь другой разберётся.

Потом я написал MCP сервер своими руками за вечер. На Python, с нуля, без особых мучений. Теперь Claude Code сам генерирует обложки для блога через мой сервер — я просто пишу промпт, остальное происходит автоматически.

В этом гайде — всё, что нужно, чтобы повторить это самому. Минимальный сервер на FastMCP, подключение к Claude Desktop и Cursor, отладка и типичные ошибки. И реальный кейс в конце — мой TypeScript сервер для kie.ai, который я использую каждый день.

Гайд для тех, кто уже работал с AI-инструментами (ChatGPT, Claude, Cursor) и хочет пойти дальше. Если ты впервые слышишь про API или CLI — начни с вайб-кодинга, там более мягкий вход.

Что понадобится

  • Python 3.10+ (для примера на FastMCP) или Node.js 18+ (для TypeScript)
  • Текстовый редактор или IDE
  • Claude Desktop — бесплатно, скачать с claude.ai/download
  • Опционально: Cursor, n8n для дополнительных подключений
  • Время: 1-2 часа на первый сервер
  • Стоимость: бесплатно (только Claude Desktop)

Что такое MCP и зачем вы захотите его сделать

MCP (Model Context Protocol) — это протокол, который позволяет AI-модели обращаться к внешним инструментам и данным. Стандарт выпустила Anthropic в конце 2024 года, и с тех пор под него написали тысячи серверов: для баз данных, GitHub, Slack, браузеров, файловых систем.

Простая аналогия: если обычный API — это телефон, через который ты сам звонишь сервису, то MCP — это когда AI звонит сам, когда ему нужно. Ты один раз описываешь инструмент («вот функция, вот что она делает»), и модель начинает использовать его в своих рассуждениях.

Зачем писать свой сервер, если готовых тысячи? Потому что твои данные — не чужие данные. База клиентов, внутренняя вики, корпоративная CRM, собственный API — это то, чего нет в публичном реестре. MCP-сервер даёт Claude прямой доступ к твоим данным, без копипасты и ручного контекста.

Я, например, написал сервер для генерации обложек через API kie.ai. Теперь в рамках одной сессии Claude Code запускает генерацию картинки, проверяет статус задачи и скачивает результат — всё само, без моего участия.

Архитектура за 5 минут: host, client, server — кто есть кто

Когда говорят «MCP-сервер», это немного сбивает с толку. На самом деле в системе три участника:

Host — приложение с AI-моделью. Claude Desktop, Cursor, n8n. То, чем пользуешься ты.

Client — встроенный в host компонент, который говорит по MCP-протоколу. Ты его не видишь, он внутри.

Server — твой код. Запускается как отдельный процесс, ждёт команд и возвращает результаты. Вот его и пишем.

Claude Desktop (host) └─ MCP Client (встроен) ←→ твой MCP Server (отдельный процесс)

Важный момент: сервер общается с клиентом через stdio (стандартный ввод-вывод) — stdin и stdout. Никаких портов, никакого HTTP. Просто процесс, который читает JSON из stdin и пишет JSON в stdout. Это важно: если твой сервер печатает что-то лишнее в stdout — он сломает протокол.

Инфо

Есть и HTTP-транспорт (SSE/Streamable HTTP) — для серверов, которые запущены удалённо. Но для начала stdio проще: никакой настройки сети, сервер запускается и останавливается вместе с host-приложением.

Tools, Resources, Prompts — в чём разница и что использовать

MCP предлагает три типа примитивов. Я сам путался поначалу — вот как это работает на практике.

Tools — функции, которые AI вызывает для совершения действий. Написать файл, запросить API, выполнить поиск. Это самое часто используемое. Если не знаешь, что выбрать — бери Tools.

Resources — данные, которые AI читает как контекст. Файл, страница базы данных, результат запроса. Не «сделай что-то», а «дай мне информацию». Думай про это как про системный промпт как архитектуру: ты кормишь модель контекстом через стандартный механизм.

Prompts — шаблоны для быстрого вызова сценариев. Например, готовый промпт «проведи code review вот этого файла». Реже используется, но удобно для повторяющихся сценариев.

Для большинства случаев — Tools. Resources стоит добавлять, когда нужно предоставить AI доступ к конкретным данным, которые меняются.

Python или TypeScript: выбираем стек для MCP сервера

Оба варианта рабочие. Вот честное сравнение.

Python + FastMCP:

  • Проще API. @mcp.tool — один декоратор, и функция стала инструментом
  • Меньше бойлерплейта
  • Хорошо, если уже знаешь Python
  • FastMCP — это враппер над официальным Python SDK от Anthropic, добавляющий удобный синтаксис

TypeScript + MCP SDK:

  • Официальный SDK Anthropic — @modelcontextprotocol/sdk
  • Хорошо, если проект уже в Node.js экосистеме
  • Чуть больше кода, зато типизация из коробки
  • Идеально при вайб-кодинге — AI хорошо знает TypeScript паттерны

Для гайда я выбрал Python + FastMCP. Меньше кода, быстрее старт. TypeScript покажу на реальном кейсе в конце.

Пишем первый MCP сервер на Python с FastMCP

Установка

bash
pip install fastmcp

Всё. Одна зависимость.

Минимальный сервер

Создаём файл server.py:

python
from fastmcp import FastMCP mcp = FastMCP("Мой первый сервер") @mcp.tool def get_weather(city: str) -> str: """Возвращает текущую погоду для города.""" # В реальном коде — вызов weather API return f{city} сейчас 18°C, облачно" if __name__ == "__main__": mcp.run()

Это рабочий MCP-сервер. Claude увидит инструмент get_weather, прочитает описание из docstring, и при необходимости вызовет его.

Запускаем:

bash
python server.py

Сервер стартует и ждёт соединения. Без вывода в консоль — это нормально, он общается через stdio.

Добавляем реальную логику

Сделаем что-то полезное — инструмент для поиска файлов:

python
from fastmcp import FastMCP import os from pathlib import Path mcp = FastMCP("File Tools") @mcp.tool def find_files(directory: str, extension: str = ".py") -> list[str]: """ Ищет файлы с заданным расширением в директории. Args: directory: Путь к директории для поиска extension: Расширение файлов (по умолчанию .py) Returns: Список путей к найденным файлам """ path = Path(directory) if not path.exists(): return [f"Директория {directory} не существует"] files = [str(f) for f in path.rglob(f"*{extension}")] return files[:50] # Не больше 50 результатов @mcp.tool def read_file(filepath: str) -> str: """Читает содержимое файла и возвращает текст.""" try: with open(filepath, 'r', encoding='utf-8') as f: return f.read() except FileNotFoundError: return f"Файл {filepath} не найден" except Exception as e: return f"Ошибка чтения: {str(e)}" if __name__ == "__main__": mcp.run()

Несколько моментов, которые не очевидны с первого раза. Docstring — это буквально документация для AI: Claude читает его, чтобы понять когда и как вызывать функцию. Пиши туда что-то внятное, не "делает штуку". Аннотации типов FastMCP превращает в JSON Schema — благодаря им модель знает, что directory это строка, а не список. И главное: обрабатывай ошибки через return, а не raise. Если бросить исключение — сервер упадёт. Верни строку с описанием что пошло не так.

Сервер с внешним API

Теперь что-то, что реально делает HTTP-запросы:

python
from fastmcp import FastMCP import httpx mcp = FastMCP("GitHub Tools") @mcp.tool async def get_repo_info(owner: str, repo: str) -> dict: """ Получает информацию о GitHub репозитории. Args: owner: Владелец репозитория (логин пользователя или организации) repo: Название репозитория """ async with httpx.AsyncClient() as client: response = await client.get( f"https://api.github.com/repos/{owner}/{repo}", headers={"Accept": "application/vnd.github.v3+json"} ) if response.status_code == 404: return {"error": f"Репозиторий {owner}/{repo} не найден"} data = response.json() return { "name": data["name"], "description": data.get("description", ""), "stars": data["stargazers_count"], "language": data.get("language", ""), "url": data["html_url"], } if __name__ == "__main__": mcp.run()

FastMCP поддерживает и async-функции — без дополнительных настроек.

Совет

Секреты (API-ключи, токены) передавай через переменные окружения, не хардкодь в коде. В сервере читай через os.environ.get("MY_API_KEY"). Как именно передать переменные в сервер — покажу в разделе про подключение.

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

Claude Desktop — это десктопное приложение от Anthropic. Бесплатно, macOS и Windows. Скачать: claude.ai/download.

Конфиг

Конфиг MCP-серверов лежит в одном файле:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Открываем и добавляем:

json
{ "mcpServers": { "my-tools": { "command": "python", "args": ["/абсолютный/путь/к/server.py"], "env": { "MY_API_KEY": "sk-ваш-ключ-здесь" } } } }

Важно: путь должен быть абсолютным. Относительные пути не работают — Claude Desktop запускается из другой директории.

После сохранения конфига — перезапускаем Claude Desktop. Без перезапуска изменения не применятся.

Если всё правильно, в новом чате появится иконка инструментов (молоток) рядом с полем ввода. Кликни — увидишь список доступных серверов и их инструментов.

Тест

Пишем в Claude Desktop:

Найди все .py файлы в /Users/pavel/projects

Claude должен вызвать find_files автоматически, если ты добавил сервер из предыдущего раздела.

Virtualenv

Если используешь виртуальное окружение (venv — изолированный Python со своими пакетами, чтобы зависимости разных проектов не конфликтовали) — указывай путь к интерпретатору внутри него:

json
{ "mcpServers": { "my-tools": { "command": "/путь/к/проекту/.venv/bin/python", "args": ["/путь/к/проекту/server.py"] } } }

Подключаем к Cursor и n8n

Cursor

Cursor (AI-редактор кода) поддерживает MCP с версии 0.43. Настройка через Settings → MCP:

json
{ "mcpServers": { "my-tools": { "command": "python", "args": ["/абсолютный/путь/к/server.py"] } } }

Синтаксис тот же, что и для Claude Desktop. После добавления в Cursor Agent Mode инструменты становятся доступны автоматически.

n8n

n8n — платформа автоматизации, о которой я уже писал — n8n AI агент без кода. Там есть нода MCP Client. Подключение немного другое — n8n общается с сервером через SSE (Server-Sent Events), а не stdio.

Для поддержки SSE нужно запустить сервер с HTTP-транспортом:

python
if __name__ == "__main__": mcp.run(transport="streamable-http", port=8000)

Затем в n8n указываешь URL сервера. Это уже для продвинутого сценария — если хочешь встроить MCP в автоматизированный пайплайн без десктопного приложения.

Отладка: MCP Inspector и типичные ошибки

Без этого раздела первые шаги — чистая рулетка. Пишешь код, всё выглядит правильно, а Claude молчит.

MCP Inspector

Официальный инструмент для тестирования серверов. Не нужно каждый раз запускать Claude Desktop.

bash
npx @modelcontextprotocol/inspector python server.py

Откроется веб-интерфейс на http://localhost:5173. Там можно вызывать инструменты руками, смотреть запросы и ответы, проверять схему.

Запусти Inspector первым делом — убедись, что сервер вообще стартует и инструменты видны, прежде чем лезть в конфиг Claude Desktop.

Типичные ошибки

Сервер не появляется в Claude Desktop. Проверь: путь в конфиге абсолютный? Сделал перезапуск? Python доступен по тому пути, что указан?

Для диагностики смотри логи:

  • macOS: ~/Library/Logs/Claude/mcp*.log
  • Windows: %APPDATA%\Claude\logs\mcp*.log

Инструмент вызывается, но возвращает пустой результат. FastMCP ожидает конкретные возвращаемые типы. Если функция вернула None — это ошибка. Всегда возвращай строку, число, список или словарь.

Сервер падает при запуске с ошибкой json decode error. Что-то печатает в stdout при старте. Импорт библиотеки, отладочный print — всё, что попадает в stdout, ломает протокол. Убери все print() из кода сервера. Для логирования используй stderr:

python
import sys print("Debug info", file=sys.stderr) # Это безопасно

ModuleNotFoundError при запуске из Claude Desktop. Claude Desktop запускает сервер в своём окружении. Если FastMCP установлен в virtualenv, а в конфиге указан системный python — ничего не найдёт. Решение: указывай полный путь к python внутри venv.

Внимание

Никогда не используй print() для вывода в stdout внутри MCP-сервера. Stdout — это канал протокола. Любой лишний текст там сломает JSON-RPC и сервер перестанет работать. Только sys.stderr для логов.

Инструмент вызывается бесконечно. Иногда Claude пытается вызвать инструмент снова и снова, если не получает понятного ответа. Убедись, что возвращаемые данные информативны. Пустая строка или {} — плохой ответ. Верни что-то осмысленное, даже если операция не дала результатов: "Файлы не найдены" лучше, чем [].

Включаем verbose логирование

В FastMCP можно включить детальное логирование:

python
import logging logging.basicConfig(level=logging.DEBUG, stream=sys.stderr) mcp = FastMCP("My Server")

Все внутренние события FastMCP пойдут в stderr, и ты увидишь, что именно происходит при каждом запросе.

Реальный кейс: мой MCP сервер для генерации обложек

Ладно, теперь реальный случай — то, что я использую каждый день.

Для блога нейропоток нужны обложки к каждой статье. Раньше это был ручной процесс: открыть сайт, написать промпт, подождать, скачать. Теперь это делает Claude Code, пока я занимаюсь чем-то полезным.

Я написал MCP-сервер на TypeScript, который общается с API kie.ai (агрегатор AI-моделей для генерации изображений). Сервер называется kieai, вот упрощённая версия его ключевой части:

typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; const server = new McpServer({ name: "kieai", version: "3.0.0", }); server.tool( "generate_cover", "Start image generation via kie.ai API. Returns taskId — use check_status to poll.", { prompt: z.string().describe("Image generation prompt in English"), model: z.enum(["imagen4", "flux2-pro", "seedream"]).default("imagen4"), aspectRatio: z.enum(["1:1", "16:9", "9:16"]).default("16:9"), }, async ({ prompt, model, aspectRatio }) => { const modelMap: Record<string, string> = { "imagen4": "google/imagen4", "flux2-pro": "flux2/pro-text-to-image", "seedream": "seedream/seedream-v4-text-to-image", }; const response = await apiRequest("/api/v1/jobs/createTask", { method: "POST", body: JSON.stringify({ model: modelMap[model], input: { prompt, aspect_ratio: aspectRatio }, }), }); const taskId = response?.data?.taskId; return { content: [{ type: "text", text: `Task ID: ${taskId}\nNext: call check_status with taskId="${taskId}"`, }], }; } ); const transport = new StdioServerTransport(); await server.connect(transport);

Обрати внимание на API сервера TypeScript SDK: server.tool(name, description, schema, handler). Схема — это Zod-объект (библиотека для описания и валидации типов в TypeScript) с описанием полей. Структурно похоже на FastMCP, только без декораторов (декоратор — это @что-то перед функцией, как @mcp.tool в Python).

Полный сервер сложнее — там ещё check_status (проверить статус асинхронной задачи) и download_image (скачать результат локально). Всё вместе даёт Claude Code полный цикл: запустить генерацию → подождать → скачать файл. Я просто пишу «сгенерируй обложку к статье про MCP», и через минуту файл лежит на диске.

Не «AI помогает» — а «AI делает».

Чем MCP отличается от обычного API?

Меня спрашивают об этом часто. Чем MCP отличается от просто REST API?

Главная разница — кто решает когда вызывать. С обычным API ты сам вызываешь нужный endpoint в нужный момент. С MCP это решение принимает модель: она видит задачу, выбирает инструмент и вызывает сама, без твоей команды.

Ещё: REST API нужно знать заранее — URL, параметры, документация. MCP-инструменты модель "видит" в начале сессии и сама понимает, что применимо к текущей задаче.

Результат: ты один раз описываешь инструмент — и он работает. Во всех сценариях, где модель решит, что он нужен. Не пишешь каждый раз "вызови вот этот API с вот этими параметрами".

Обычный API — это калькулятор. Ты нажимаешь кнопки сам, когда решил считать. MCP — это когда коллега сам достаёт калькулятор, когда видит что надо посчитать, и приносит тебе ответ.

Что дальше: публикация и реестр серверов

Написал полезный сервер? Можно поделиться.

Официальный реестр: github.com/modelcontextprotocol/servers — там репозитории с reference-серверами от Anthropic и официально поддерживаемые интеграции.

mcp.so — каталог сообщества с тысячами серверов. Там можно найти готовое и туда же опубликовать своё.

Smithery (smithery.ai) — ещё один каталог, с возможностью деплоить серверы как managed-сервис.

Если хочешь опубликовать свой сервер как пакет:

bash
# Python pip install build twine python -m build twine upload dist/* # TypeScript — публикуем на npm npm publish

После публикации на npm (реестр пакетов для JavaScript/TypeScript) или PyPI (аналог для Python) другие пользователи смогут установить сервер одной командой.

Частые вопросы

Что такое MCP сервер простыми словами? MCP сервер — это отдельная программа, которая даёт AI-модели доступ к инструментам и данным. Ты описываешь функции (что они делают, какие принимают параметры), и модель начинает вызывать их самостоятельно, когда решает, что они нужны для задачи. Не ты управляешь вызовами — AI сам решает когда и что использовать.

Как написать MCP сервер на Python? Установи FastMCP (pip install fastmcp), создай объект FastMCP, добавь функции с декоратором @mcp.tool и понятными docstring, вызови mcp.run() в конце. Минимальный рабочий сервер — 10-15 строк кода. Всё описано в разделе выше с готовыми примерами.

Как проверить, что MCP сервер работает? Используй MCP Inspector: npx @modelcontextprotocol/inspector python server.py. Откроется веб-интерфейс, где можно вручную вызывать инструменты и видеть ответы. Не нужно подключать Claude Desktop — это первый шаг для диагностики.

Можно ли использовать MCP без навыков программирования? Для написания своего сервера — нужен базовый Python или TypeScript. Но использовать готовые MCP-серверы (а их тысячи) можно без кода: просто добавить конфиг в Claude Desktop. Если хочешь автоматизацию без кода совсем — n8n AI агент без кода — там уже есть MCP Client нода.


Начни с малого: одна функция, которую ты сам делаешь руками каждый день. Поиск по файлам, запрос к внутреннему API, парсинг какого-нибудь сайта. Один инструмент, двадцать строк кода. Дальше само разрастётся.

Если хочешь прокачать Claude Code дальше — читай про кастомные навыки и агентов Claude Code или про реальный рабочий пайплайн. А если нужно встроить MCP в автоматизацию без кода — n8n уже поддерживает MCP Client.

MCP — это не магия. Просто процесс, который читает JSON и пишет JSON. Двадцать строк кода — и Claude сам генерирует обложки, сам проверяет статус задачи, сам скачивает файл. Я всё ещё немного удивляюсь каждый раз, когда это работает.