Подключите к Claude Code собственный MCP-сервер с одной функцией: получить вымышленный товар по идентификатору. Проверим результат, отсутствие товара и недоступность записи. Так будет видно, что агент действительно вызвал программу и какие действия она ему предлагает.
Разберите, что будет запущено
MCP, или Model Context Protocol, описывает обмен между приложением с агентом и отдельной программой. Сервер сообщает, какие инструменты у него есть, а приложение может вызвать их с аргументами. В нашем упражнении Claude Code будет клиентом, Python-программа — сервером.
«Сервер» здесь не означает внешний сайт. Используем локальный транспорт stdio: Claude Code запускает дочерний процесс и общается с ним через стандартные ввод и вывод. Порт и публичный адрес не понадобятся. Такой запуск описан в документации официального Python SDK.
Понадобятся установленный Claude Code, рабочая сессия, Python 3.10 или новее и редактор. Для чтения примера достаточно понимать функцию, словарь и условие. Установку агента пройдите по официальному руководству. Если знакомство с агентами только начинается, сначала разберите учебный запуск Qwen Code.
Для первого подключения я рекомендую собственную функцию с вымышленными данными. Вы сможете прочитать весь код и проверить каждый ответ, не разбираясь одновременно в аккаунтах стороннего сервиса.
Подготовьте отдельную папку и SDK
Создайте папку mcp-catalog-demo и откройте её в терминале. В macOS, Linux или WSL выполните:
python3 -m venv .venv
.venv/bin/python -m pip install "mcp==2.3.0"
В PowerShell Windows:
py -m venv .venv
.venv\Scripts\python.exe -m pip install "mcp==2.3.0"
Команда py подходит, если установлена программа запуска Python py. Если у вас несколько Python, сначала проверьте, что выбранный интерпретатор удовлетворяет требованию SDK.
Виртуальное окружение .venv отделяет зависимости примера от других проектов. Используем его Python напрямую: активировать окружение и менять политику выполнения PowerShell не требуется.
На 9 октября 2026 года текущая стабильная линия SDK — v2. Для воспроизводимости зафиксирована официальная версия 2.3.0, опубликованная 2 октября. Требование Python 3.10+ и установка описаны в руководстве SDK. Примеры v1 могут использовать другие импорты: не смешивайте их с этим кодом.
Создайте сервер с одной операцией чтения
Сохраните в server.py:
import json
from mcp.server import MCPServer
mcp = MCPServer("Demo catalog")
CATALOG = {
"cup": {"name": "Учебная кружка", "price": 450},
"lamp": {"name": "Учебная лампа", "price": 1200},
}
@mcp.tool()
def get_product(product_id: str) -> str:
"""Возвращает вымышленный товар. Не изменяет каталог."""
product = CATALOG.get(product_id)
if product is None:
result = {"found": False, "id": product_id}
else:
result = {
"found": True,
"id": product_id,
"name": product["name"],
"price": product["price"],
}
return json.dumps(result, ensure_ascii=False)
if __name__ == "__main__":
mcp.run()
Декоратор @mcp.tool() делает функцию доступной через MCP. Имя, описание и тип аргумента дают клиенту сведения для вызова. Функция получает строку product_id и возвращает строку с JSON. Клиент видит, какой аргумент нужно передать, ещё до вызова.
Каталог встроен в программу. Функция не читает файлы, не обращается к сети и не записывает цену. Если товара нет, она возвращает found: false. Так клиент получает определённый результат и не должен дополнять каталог догадкой.
Вызов mcp.run() без аргументов запускает stdio. Условие if __name__ == "__main__" позволяет импортировать объект сервера для проверки, не запуская бесконечное ожидание входных сообщений.
Не добавляйте отладочный print() в сервер: стандартный вывод занят протоколом. Для диагностики используйте журнал в стандартном потоке ошибок, как рекомендует документация SDK.
Проверьте программу отдельно от агента
Сохраните рядом check_server.py:
import asyncio
import json
from mcp import Client
from mcp.types import TextContent
from server import CATALOG, mcp
async def check() -> None:
before = json.dumps(CATALOG, sort_keys=True)
async with Client(mcp, raise_exceptions=True) as client:
listed = await client.list_tools()
assert {tool.name for tool in listed.tools} == {"get_product"}
known = await client.call_tool(
"get_product", {"product_id": "cup"}
)
assert not known.is_error
assert isinstance(known.content[0], TextContent)
data = json.loads(known.content[0].text)
assert data == {
"found": True, "id": "cup",
"name": "Учебная кружка", "price": 450,
}
missing = await client.call_tool(
"get_product", {"product_id": "unknown"}
)
assert not missing.is_error
assert isinstance(missing.content[0], TextContent)
assert json.loads(missing.content[0].text) == {
"found": False, "id": "unknown",
}
absent = await client.call_tool(
"set_price", {"product_id": "cup", "price": 1}
)
assert absent.is_error
assert json.dumps(CATALOG, sort_keys=True) == before
print("Проверки учебного сервера прошли")
if __name__ == "__main__":
asyncio.run(check())
SDK позволяет подключить Client к объекту сервера в памяти. Проверка обращается к серверу в той же программе. Так мы сначала проверяем данные и список функций, а затем отдельно разбираемся с запуском сервера из Claude Code.
Вызов неизвестного инструмента возвращает результат с is_error, что описано в справочнике клиента. Проверка ожидает это поле, а не случайное исключение.
Выполните .venv/bin/python check_server.py; в Windows — .venv\Scripts\python.exe check_server.py. При успехе появится строка о пройденных проверках. Если возникло исключение, прочитайте его до подключения агента. Неверный импорт часто означает, что установилась другая линия SDK или запускался Python вне .venv.
Зарегистрируйте локальное подключение
Всё ещё находясь в mcp-catalog-demo, выполните в Bash:
demoRoot="$(pwd)"
claude mcp add --transport stdio --scope local demo-catalog -- "$demoRoot/.venv/bin/python" "$demoRoot/server.py"
В PowerShell:
$demoRoot = (Get-Location).Path
claude mcp add --transport stdio --scope local demo-catalog -- "$demoRoot\.venv\Scripts\python.exe" "$demoRoot\server.py"
Переменная получает абсолютный путь текущей папки. В команду попадают абсолютные пути к интерпретатору и файлу: запуск не зависит от того, где позже окажется рабочая папка дочернего процесса. Кавычки сохраняют путь с пробелами.
Разделитель -- отделяет параметры Claude Code от команды сервера. --scope local делает подключение личным для текущего проекта; запись хранится в ~/.claude.json под путём проекта. Это отличается от общей конфигурации в .mcp.json. Синтаксис и область действия приведены в документации Claude Code.
Команда добавления сохраняет настройки. Затем выполните:
claude mcp get demo-catalog
claude mcp list
Запустите claude из той же папки и откройте /mcp. Найдите demo-catalog, проверьте состояние соединения и инструмент get_product. Если возникнет запрос доверия или разрешения, прочитайте точную команду и путь. Для упражнения достаточно этого сервера.
Сверьте реальный вызов и результат
Отправьте агенту:
Через MCP-сервер demo-catalog вызовите get_product
с product_id="cup".
Назовите id, название и цену из результата инструмента.
Не читайте server.py вместо вызова и не меняйте файлы.
Ожидаемые данные заданы исходным кодом: cup, «Учебная кружка», 450. Смотрите журнал инструментов. Верный текст ответа сам по себе недостаточен: агент мог прочитать программу или вспомнить предыдущий запрос.
Затем запросите product_id="unknown". Агент должен сообщить, что товар не найден. Если он придумал название, верните его к полю found из реального ответа.
Наконец, предложите изменить цену через этот MCP-сервер. Операции set_price у него нет. Агент должен назвать ограничение, сохранив данные. Если вместо этого предложена правка server.py, остановите такое действие: оно меняет программу через другой инструмент и выходит за условия проверки подключения.
| Проверка | Что должно быть видно |
|---|---|
| Чтение cup | Вызов get_product и совпадение трёх полей |
| Чтение unknown | found=false без выдуманного товара |
| Запись цены | Нет доступной операции записи; каталог прежний |
Подключение к Claude Code в этом материале не выполнялось. Приведены код и ожидаемые свойства; фактические вызовы и ответы получите при прохождении упражнения.
Уточните границы доступа
«Только чтение» относится к интерфейсу нашей функции. Это не изоляция всего Python-процесса. Локальный сервер запускается как программа на вашей машине; другой код в нём мог бы читать файлы или отправлять запросы.
Разрешение MCP-инструмента тоже не делает встроенную песочницу Bash защитой этого процесса. Песочница Claude Code ограничивает соответствующие команды оболочки; MCP и другие инструменты имеют отдельные границы.
Перед чужим сервером изучайте код, команду запуска и предлагаемые операции. В рабочем подключении права должны ограничиваться и на стороне источника данных: например, отдельной учётной записью с доступом только к чтению. Поле в промпте «работайте осторожно» таких прав не создаёт.
Skills сохраняют процедуру работы, MCP предоставляет вызываемую функцию. В нашем примере процедуру можно написать одной фразой, а результат чтения всё равно приходит от отдельной программы.
Завершите упражнение
Чтобы убрать подключение, выйдите из сессии и выполните из учебной папки:
claude mcp remove demo-catalog --scope local
Проверьте список серверов. Удаление настройки не удаляет server.py или виртуальное окружение.
Когда чтение, неизвестный товар и отсутствие записи проверены, откройте бесплатный раздел курса «Разработка с AI-агентами: инженерный подход». Сохраните этот маленький сервер как пример, в котором можете объяснить каждую доступную операцию.