Все уроки · 44
Путь 3 · Дорожная карта AI-инженера·Урок 25 из 33

Создайте свой первый MCP-сервер

Пошаговый MCP-сервер на Python с официальным SDK: инструменты, ресурсы, промпты, транспорт stdio и подключение к клиенту.

УровеньСреднийВремя~20 мин

Сначала решите, что предоставлять

MCP-сервер может предлагать три вида возможностей, и этот выбор важнее кода:

  • Инструменты (tools) — действия, которые модель сама решает вызвать: искать, записывать, отправлять, создавать. Используйте их для всего, что имеет побочные эффекты или требует вычислений.
  • Ресурсы (resources) — данные, которые приложение читает и подставляет в контекст: конфигурации, схемы, документы. Используйте их для справочных материалов.
  • Промпты (prompts) — готовые шаблоны, которые поставляет сервер; полезны, когда у вашей команды есть стандартный способ что-то делать (ревью кода, разбор инцидента).

Хороший первый сервер делает одну небольшую вещь хорошо. Мы построим сервер заметок с двумя инструментами: сохранить заметку и показать список заметок.

«Сервер» здесь — не стойка в дата-центре. Это небольшая программа, которая тихо сидит, отвечает на вопросы вроде «какие у вас есть инструменты?» и «запустите этот инструмент с такими аргументами» и отдаёт ответ. Вот и всё.

Чтобы повторять за уроком, нужны три вещи: Python 3.10 или новее, терминал и одна команда установки.

Сколько усилий это потребует. Код ниже занимает около сорока строк, и запустить его можно за десять минут. Но на первый сервер всё равно заложите час. Вот на что он уходит: настроить конфигурационный файл клиента так, чтобы он указывал на правильный абсолютный путь; перезапустить приложение, чтобы оно заметило изменение; научиться отлаживать программу, вывод которой вы не видите. Причина в том, что print stdio-сервера попадает прямо в поток протокола и ломает его. Код пишется легко, а трудности начинаются при подключении. Если ваш первый сервер не появляется в клиенте, это обычное дело.

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

Официальный Python SDK (pip install "mcp[cli]") даёт вам FastMCP, где инструмент — это просто функция Python с декоратором. Аннотации типов становятся JSON Schema, а докстринг — описанием, которое читает модель:

сервер заметок + конфигурация клиента
1# Разовая настройка, выполните в терминале:
2# pip install "mcp[cli]"
3# Это устанавливает официальный Python SDK для MCP.
4 
5# FastMCP — простой способ написать сервер: вы пишете обычные функции
6# Python, а он превращает их в инструменты MCP.
7from mcp.server.fastmcp import FastMCP
8 
9# У каждого сервера есть имя. Клиент показывает его пользователю.
10mcp = FastMCP(#98c379">"notes")
11 
12# Наша «база данных» — просто словарь Python: заголовок заметки -> текст заметки.
13# Он живёт в памяти, поэтому заметки пропадают, когда сервер останавливается.
14_notes: dict[str, str] = {}
15 
16 
17# @mcp.tool() регистрирует эту функцию как инструмент, который может вызвать модель.
18# Докстринг ниже читает МОДЕЛЬ — пишите его для модели,
19# а не для ревьюера-человека.
20# Аннотации типов (title: str, body: str) становятся JSON Schema, по которой
21# модель строит правильный вызов.
22@mcp.tool()
23def save_note(title: str, body: str) -> str:
24 #98c379">"""Save a note under the given title. Overwrites if the title exists."""
25 _notes[title] = body
26 # Всё, что вы вернёте, — единственное, что получит модель.
27 # Понятное сообщение лучше «сырой» выгрузки данных.
28 return #98c379">"Saved: " + title
29 
30 
31@mcp.tool()
32def list_notes() -> str:
33 #98c379">"""List all saved note titles."""
34 if not _notes:
35 # Полезное сообщение об ошибке позволяет модели исправиться самой.
36 return #98c379">"No notes yet."
37 return "\n".join(sorted(_notes))
38 
39 
40# Ресурс — это справочный материал, который ПРИЛОЖЕНИЕ может прочитать в контекст:
41# вызывать его решает не модель, а приложение.
42@mcp.resource(#98c379">"notes://help")
43def help_text() -> str:
44 #98c379">"""Short guide to the notes server."""
45 return #98c379">"Ask the model to save or list notes by title."
46 
47 
48# Транспорт stdio: клиент сам запускает этот скрипт и общается с ним
49# через стандартный ввод и вывод. Сетевые порты нигде не открываются.
50if __name__ == #98c379">"__main__":
51 mcp.run(transport=#98c379">"stdio")

Три вещи, на которые стоит обратить внимание:

  • Докстринг — это интерфейс. Модель никогда не видит ваш код — только имя функции, аннотации типов и докстринг. Пишите докстринг для модели.
  • Аннотации типов задают схему вызова. title: str, body: str становится JSON Schema, по которой модель строит правильный вызов инструмента.
  • Транспорт stdio означает, что клиент запускает ваш скрипт и общается с ним через стандартный ввод и вывод. Ничто не слушает сетевой порт.

Проверьте, прежде чем трогать клиент

Не пропускайте этот шаг. Он отделяет «сервер сломан» от «клиент не находит мой файл» — а без него эти две ситуации выглядят одинаково. В SDK есть отладчик, который запускает ваш сервер и позволяет вызывать инструменты вручную в браузере:

Запустите сервер в MCP Inspector
# установите официальный SDK (один раз)
pip install "mcp[cli]"

# запустите сервер под Inspector, в браузере
mcp dev server.py

# ...или вызовите его прямо из терминала, без всякого клиента
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | python server.py

# Если вы видите здесь список из двух своих инструментов, сервер работает.
# Если нет, ошибка в коде, а не в конфигурации клиента.

Две вещи, которые ломают каждый первый сервер, и ни одна не очевидна:

  • Отладка через print всё ломает. При транспорте stdio print() пишет в тот самый поток, который использует протокол, поэтому случайный print портит разговор. Вместо этого пишите логи в файл (logging с FileHandler) или в stderr.
  • Относительные пути не работают. Клиент запускает ваш скрипт из произвольного рабочего каталога. Используйте в конфигурации абсолютный путь, а пути к файлам стройте относительно __file__, а не текущего каталога.

Зарегистрируйте его в клиенте

Конфигурационный файл выше — для Claude Desktop (Settings, затем Developer, затем Edit Config). command вместе с args говорят: запусти этот скрипт как локальный процесс и подключись к нему по stdio. После перезапуска приложения оба инструмента автоматически появляются в контексте модели — попросите её «сохранить заметку с названием groceries: молоко и хлеб» и посмотрите, как происходит вызов инструмента.

Cursor, VS Code (агентный режим Copilot), Zed и Claude Code используют похожую JSON-структуру или эквивалентную команду claude mcp add. В этом и смысл протокола: один и тот же сервер работает везде. Но гладко бывает не всегда: клиенты различаются тем, где лежит конфигурационный файл, нужен ли полный перезапуск и насколько громко они сообщают, что сервер не запустился (часто — вообще никак). Сначала загляните в собственную документацию клиента по MCP, а затем возвращайтесь сюда за разбором серверной части.

Локально или удалённо

Пока что сервер работает на вашей машине и запускается клиентом (stdio). Для команды его размещают удалённо, по HTTP:

{
  "mcpServers": {
    "notes": {
      "url": "https://notes.example.com/mcp",
      "headers": { "Authorization": "Bearer <token>" }
    }
  }
}

Если в конфигурации указан URL, клиент подключается по Streamable HTTP, а не запускает процесс. Объём работы при этом вырастает скачком. Удалённый сервер — это публичная точка доступа, а значит, вам понадобятся полноценная аутентификация (в спецификации для этого предусмотрен OAuth), TLS, ограничение частоты запросов, логирование, управление секретами и кто-то, кто заметит, что сервер лёг. Этот список не короче, чем у любого API в реальной эксплуатации. Практическое правило: пока разрабатываете, начинайте со stdio, переходите на HTTP, когда сервер нужен более чем одному человеку или сервису, и считайте этот переход отдельным проектом.

Самые важные практики

  • Чёткие описания инструментов. Скажите, что делает инструмент, когда его использовать и когда не использовать. «Сохраняет заметку. Используйте для текста, созданного пользователем; не используйте для фрагментов кода» лучше, чем «сохраняет данные».
  • Небольшие наборы инструментов. Схема каждого инструмента занимает место в контексте, а слишком много похожих инструментов сбивает модель с толку. Десять целенаправленных инструментов лучше сорока пересекающихся.
  • Полезные сообщения об ошибках. Когда вызов не удался, верните сообщение, по которому модель поймёт, что делать дальше: «Заметка ‘groceries’ не найдена. Доступны: milk-list, ideas», а не «сырую» трассировку стека. С понятным сообщением об ошибке модель справляется без помощи человека.
  • Возвращайте структурированные, компактные результаты. Обрежьте данные до того, что нужно модели; огромные ответы сжигают окно контекста.
  • Делайте инструменты безопасными по умолчанию. Подтверждайте разрушительные операции, ограничивайте права учётных данных и никогда не позволяйте инструменту делать больше, чем обещано в его описании.

Проверьте себя

Проверьте себя

Что делает докстринг функции-инструмента в MCP-сервере?

Что на практике означает транспорт stdio?

Вы хотите предоставить справочный материал, который приложение читает в контекст, а не то, что модель решает вызвать. Какую возможность использовать?

Куда идти дальше

Теперь вы умеете создавать, подключать и доводить до надёжного состояния MCP-сервер — востребованный навык, ведь команды встраивают MCP во всё подряд. Если вы пропустили теорию, урок MCP: USB-порт для ИИ объясняет три роли и путь запроса, а Инструменты и вызов функций раскрывает механизм, на котором построен MCP.