AmigaОбучение ИИ
Модуль 4 · Практика: пишем сервер · урок 6 из 14

Настройка проекта

9 мин чтения▶ есть видеоверсия

Начинаем практику. В этом уроке мы создадим проект, установим Python SDK для MCP и напишем каркас сервера: пока без инструментов, но уже запускающийся и отвечающий клиенту. Инструменты добавим в следующем уроке, ресурсы и промпты — в модуле про клиент.

Установка uv

Официальная документация MCP использует uv — быстрый менеджер пакетов и окружений для Python. Установка одной командой:

bash
curl -LsSf https://astral.sh/uv/install.sh | sh

На Windows — команда из документации на docs.astral.sh/uv. После установки перезапустите терминал и проверьте: uv --version.

Если uv не хочется, всё в курсе работает и с обычным pip. Мы будем давать команды для uv, а в конце раздела — их аналог для pip.

Создаём проект

bash
uv init project-docs
cd project-docs
rm main.py
uv add "mcp[cli]"

uv init создаёт папку с pyproject.toml и заглушкой main.py, которая нам не нужна. uv add "mcp[cli]" ставит SDK; часть [cli] добавляет утилиту mcp с командами mcp dev, mcp run и mcp install — они пригодятся в следующих уроках. Кавычки нужны, чтобы оболочка не пыталась раскрыть квадратные скобки.

Виртуальное окружение uv создаст сам в папке .venv, активировать его не нужно: команды запускаем через uv run.

Аналог для pip:

bash
mkdir project-docs && cd project-docs
python3 -m venv .venv
source .venv/bin/activate
pip install "mcp[cli]"

Дальше везде, где написано uv run docs_server.py, читайте python docs_server.py в активированном окружении.

Проверьте, что установилась вторая мажорная версия SDK: uv run mcp version. Курс написан под SDK 2.x. Если вы найдёте в интернете примеры с from mcp.server.fastmcp import FastMCP — это первая версия; класс переименован в MCPServer, и старый импорт в 2.x не работает. Остальные отличия перечислены в руководстве по миграции.

Каркас сервера

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

python
import logging

from mcp.server import MCPServer

logger = logging.getLogger(__name__)

mcp = MCPServer("project-docs")

DOCS: dict[str, str] = {
    "brief.md": (
        "# Бриф\n\nКлиент: сеть кофеен. Задача: мобильное приложение лояльности "
        "с картой заведений и предзаказом."
    ),
    "estimate.md": (
        "# Оценка\n\nАналитика — 2 недели, дизайн — 3 недели, "
        "разработка iOS и Android — 10 недель, тестирование — 3 недели."
    ),
    "risks.md": (
        "# Риски\n\n1. Интеграция с кассовой системой клиента не описана.\n"
        "2. У клиента нет аккаунта разработчика в App Store."
    ),
}

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    mcp.run(transport="stdio")

Разберём по частям.

MCPServer("project-docs") — объект сервера. Имя увидит клиент при подключении; в Claude Code оно будет фигурировать в списке /mcp. Через этот объект мы будем регистрировать инструменты, ресурсы и промпты декораторами.

DOCS — наше хранилище. Словарь «имя файла → содержимое», три учебных документа. Он живёт в памяти процесса: правки будут действовать, пока сервер запущен, и пропадут после перезапуска. Для курса этого достаточно. Если захотите настоящие файлы — замените словарь на чтение папки docs/, и остальной код не изменится: мы везде будем обращаться к документам через две-три функции.

mcp.run(transport="stdio") — запуск. Сервер начинает читать JSON-RPC из стандартного ввода и писать ответы в стандартный вывод. Другие транспорты — "streamable-http" и устаревший "sse" — включаются тем же параметром.

logging.basicConfig — единственный допустимый способ что-то вывести из stdio-сервера. Модуль logging пишет в стандартный поток ошибок, а он не мешает протоколу. Правило из урока про архитектуру: ни одного print() в stdio-сервере. Заведите себе привычку сразу, пока сервер маленький.

Запуск и первая проверка

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

bash
uv run docs_server.py

Ничего не произойдёт: сервер ждёт сообщений на стандартном вводе, а мы ничего не пишем. Это правильное поведение. Остановите его через Ctrl+C.

Чтобы убедиться, что сервер живой, напишем крошечного клиента. SDK умеет подключаться к серверу прямо в том же процессе, без запуска подпроцесса — так удобно проверять и писать тесты. Создайте check.py:

python
import asyncio

from mcp import Client

from docs_server import mcp


async def main() -> None:
    async with Client(mcp) as client:
        print("Сервер:", client.server_info.name)
        print("Возможности:", client.server_capabilities.model_dump(exclude_none=True))
        tools = await client.list_tools()
        print("Инструменты:", [t.name for t in tools.tools])


asyncio.run(main())

Запустите: uv run check.py. Вы увидите имя сервера, его возможности и пустой список инструментов. Здесь print допустим: это клиент, а не сервер, и его стандартный вывод ничем не занят.

async with Client(mcp) — вся жизнь соединения. Вход в блок устанавливает соединение и договаривается о версии протокола, выход закрывает. Асинхронность (async, await) здесь обязательна: SDK построен на ней, потому что клиент одновременно ждёт ответов и может получать уведомления. Если вы не писали асинхронный код раньше, правило простое: вызовы методов клиента предваряются await, а всё вместе запускается через asyncio.run.

Структура проекта

К концу курса в папке будет:

text
project-docs/
  pyproject.toml     — зависимости
  docs_server.py     — сервер: инструменты, ресурсы, промпты
  check.py           — быстрая проверка в том же процессе
  client.py          — клиент с Claude (модуль «Подключаем клиент»)
  .env               — ключ API для клиента, в git не попадает

Добавьте .env и .venv в .gitignore прямо сейчас.

Попробуйте сами

10–15 мин на рабочем месте
  1. Добавьте в DOCS четвёртый документ — например, team.md с составом команды. Перезапустите check.py и убедитесь, что сервер по-прежнему стартует.
  2. Вставьте в docs_server.py строку print("старт") перед mcp.run(...), запустите uv run docs_server.py и посмотрите, куда уходит вывод. Потом замените на logger.info("старт") и сравните: сообщение появится в потоке ошибок с префиксом INFO.
  3. Если вы хотите работать с настоящими файлами, напишите функцию load_docs(folder: str) -> dict[str, str], которая читает все .md из папки, и заполните ею DOCS. Дальше в курсе ничего менять не придётся.

Коротко

  • uv init, uv add "mcp[cli]" — проект и SDK; аналог для pip работает так же.
  • Курс написан под SDK 2.x: класс сервера называется MCPServer, FastMCP — из первой версии.
  • Сервер — объект MCPServer, хранилище — словарь в памяти, запуск — mcp.run(transport="stdio").
  • В stdio-сервере ни одного print(): только logging, он пишет в поток ошибок.
  • Client(mcp) подключается к серверу в том же процессе — быстрый способ проверить, что он отвечает.

Видеоверсия

Сценарий озвучки · 470 слов, ≈ 4 мин

Начинаем практику. Сегодня создадим проект, поставим Python SDK и напишем каркас сервера: пока без инструментов, но уже запускающийся и отвечающий клиенту.

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

Создаём проект: команда «ю-ви инит» с именем папки, потом добавляем пакет эм-си-пи с дополнением «си-эл-ай». Это дополнение ставит утилиту для запуска и отладки сервера, она пригодится в следующих уроках. Виртуальное окружение ю-ви создаст сам, активировать его не нужно.

Важная деталь: курс написан под вторую версию SDK. Если вы найдёте в интернете примеры с классом FastMCP — это первая версия. Класс переименован в MCPServer, и старый импорт во второй версии не работает.

Теперь каркас сервера. В файле три части. Первая — объект сервера с именем «project-docs». Это имя увидит клиент при подключении. Через этот объект мы будем регистрировать инструменты, ресурсы и промпты. Вторая — хранилище: словарь «имя файла — содержимое» с тремя учебными документами: бриф, оценка, риски. Он живёт в памяти процесса, правки пропадут после перезапуска, и для курса этого достаточно. Захотите настоящие файлы — замените словарь на чтение папки, остальной код не изменится. Третья часть — запуск с транспортом «стандартный ввод-вывод». Сервер начинает читать джейсон из стандартного ввода и писать ответы в стандартный вывод.

И правило, которое мы уже проговаривали: ни одной отладочной печати в таком сервере. Единственный способ что-то вывести — модуль логирования, он пишет в поток ошибок и протоколу не мешает. Заведите привычку сразу, пока сервер маленький.

Запускаем. Ничего не происходит: сервер ждёт сообщений, а мы ничего не пишем. Это правильно. Чтобы убедиться, что он живой, пишем крошечного клиента. SDK умеет подключаться к серверу прямо в том же процессе, без подпроцесса, — так удобно проверять и писать тесты. Клиент печатает имя сервера, его возможности и список инструментов — пока пустой.

Обратите внимание на асинхронность: клиент написан через «эйсинк» и «эвейт». Это обязательно — SDK построен на ней, потому что клиент одновременно ждёт ответов и может получать уведомления. Правило простое: вызовы методов клиента предваряются словом «эвейт», а всё вместе запускается через «эйсинкио ран».

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

И задания для самостоятельной работы. Добавьте в хранилище четвёртый документ — например, состав команды — и убедитесь, что сервер по-прежнему стартует. Вставьте отладочную печать перед запуском и посмотрите, куда уходит вывод, потом замените её на логирование и сравните. А если хотите работать с настоящими файлами, напишите функцию, которая читает все маркдаун-файлы из папки, и заполните ею словарь. Дальше в курсе ничего менять не придётся.

В следующем уроке добавим серверу первые инструменты — прочитать и отредактировать документ — и посмотрим, как SDK превращает функцию на Python в описание для модели.

Отметка хранится только в вашем браузере