Python SDK v1 · mcp==1.30.0 · протокол 2025-11-25. Для SDK v2 требуется отдельная миграция.
Перед началом
Завершите первый сервер. Откройте PowerShell в его папке mcp-label-lab. В ней уже должны быть .venv, requirements-label.txt, label_server.py и check_stdio.py. Установка зависимостей заново не нужна. Проверьте интерпретатор:
.\.venv\Scripts\python.exe -c "from importlib.metadata import version; print(version('mcp'))"Ожидается 1.30.0. При ModuleNotFoundError вернитесь к установке в первом уроке. Этот материал не требует входа в редактор или ключа модели.
1. Создайте общий клиент лаборатории
Сохраните lab_session.py рядом с сервером. Этот файл используется и в следующих уроках. Он запускает только один из трёх явно разрешённых учебных серверов, открывает stdio, выполняет initialize и закрывает сессию вместе с дочерним процессом при выходе из контекста:
"""Open one supplied lab server using the pinned Python SDK's stdio client."""
from contextlib import asynccontextmanager
import os
from pathlib import Path
import sys
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
@asynccontextmanager
async def lab_session(filename: str = "label_server.py"):
if filename not in {"label_server.py", "validation_server.py", "context_server.py"}:
raise ValueError("Choose a supplied lab server")
server = Path(__file__).resolve().parent / filename
if not server.is_file():
raise FileNotFoundError("Save " + filename + " beside lab_session.py")
parameters = StdioServerParameters(
command=sys.executable,
args=["-u", str(server)],
env={"PYTHONPATH": os.environ.get("PYTHONPATH", "")},
)
async with stdio_client(parameters) as (reader, writer):
async with ClientSession(reader, writer) as session:
initialized = await session.initialize()
yield session, initializedstdio_client управляет транспортом, ClientSession — MCP-сообщениями. Через один транспорт могут идти вызовы разных методов; это не означает, что tool, resource и prompt являются одним объектом. В реальном приложении host управляет пользовательским интерфейсом и разрешениями, клиент ведёт соединение, а сервер предоставляет функции и контекст. Здесь host заменён небольшим проверочным скриптом, который ничего не поручает модели.
2. Сохраните полный инспектор
Сохраните inspect_contract.py:
"""Discover the actual MCP contract; no direct call to the server function."""
import asyncio
import json
from lab_session import lab_session
async def main():
async with lab_session() as (session, initialized):
listing = await session.list_tools()
assert initialized.protocolVersion == "2025-11-25"
assert initialized.capabilities.tools is not None
assert [tool.name for tool in listing.tools] == ["normalize_label"]
tool = listing.tools[0]
assert tool.inputSchema["properties"]["text"]["type"] == "string"
assert "text" in tool.inputSchema["required"]
result = await session.call_tool("normalize_label", {"text": " two words "})
assert result.isError is False
assert result.structuredContent == {"label": "two words"}
unknown = await session.call_tool("missing_tool", {"text": "Draft"})
assert unknown.isError is True
print("PASS: discovered schema and preserved internal spaces")
print(json.dumps({"protocol": initialized.protocolVersion, "server": initialized.serverInfo.name, "tool": tool.model_dump(mode="json", exclude_none=True), "success": result.model_dump(mode="json", exclude_none=True), "unknown_tool": unknown.model_dump(mode="json", exclude_none=True)}, ensure_ascii=False, indent=2))
if __name__ == "__main__":
asyncio.run(main())В tools/list вы увидите строковое обязательное поле text. Схема выхода dict[str, str] описывает объект со строковыми значениями, но сама по себе не закрепляет единственный ключ label. Поэтому клиент дополнительно сравнивает конкретное structuredContent. Правило длины 1–80 пока находится в коде функции и описании, а не в JSON Schema. Следующий урок покажет эту границу на дополнительном аргументе.
3. Запустите и прочитайте результат
.\.venv\Scripts\python.exe inspect_contract.pyПервая строка должна быть:
PASS: discovered schema and preserved internal spacesНиже будет JSON с protocol: "2025-11-25", server: "label-lab" и полной схемой инструмента. success.structuredContent должен совпасть с:
{
"label": "two words"
}Два пробела внутри сохранились, крайние исчезли. В unknown_tool ожидаются isError: true и текст Unknown tool: missing_tool. Для этого SDK ошибка неизвестного tool приходит как результат вызова с флагом ошибки; клиент не должен принимать его за успешные данные.
Если проверка остановилась
Choose a supplied lab serverозначает неподдерживаемое имя вlab_session; используйте приведённый код целиком.Save label_server.py beside lab_session.pyозначает, что файл сервера лежит в другой папке или имеет расширение.py.txt.AssertionErrorна версии или схеме — сигнал изменения контракта. Сверьте версию пакета и восстановите учебный сервер из первого урока; не удаляйте assert ради зелёного результата.- Прямой запуск сервера ждёт клиента. Запускайте инспектор, который сам создаёт процесс.
Проверьте себя
В копии инспектора замените ожидаемый результат на {"label":"two words"}. Проверка должна завершиться ненулевым кодом, поскольку инструмент не склеивает внутренние пробелы. Верните точное ожидаемое значение и получите PASS. Так вы отличите работающий тест контракта от скрипта, который просто печатает произвольный ответ.
Версия и источники
Упражнение закреплено за Python 3.11 и mcp==1.30.0, SDK v1, протоколом 2025-11-25. SDK v2 требует отдельной миграции. Примеры используют синтетические подписи, без внешнего API и модели. Документация сверена 8 октября 2026 года.
Проверены реальный initialize, tools/list, tools/call и отказ неизвестного инструмента на установленном SDK 1.30.0. Интерфейс редактора здесь не проверяется.
Источники: архитектура MCP (в новой вкладке), клиент SDK v1 (в новой вкладке), контракт tools 2025-11-25 (в новой вкладке), жизненный цикл (в новой вкладке).
Вопросы по материалу
Зачем сравнивать ответ, если схема уже прочитана?
Схема dict[str, str] разрешает объект со строковыми значениями, но сама по себе не обещает поле label. Инспектор дополнительно сравнивает structuredContent с ожидаемой подписью и двумя внутренними пробелами.
Неизвестный инструмент — это успешный пустой ответ?
В примере на SDK 1.30.0 вызов missing_tool возвращает результат с isError: true. Проверка ожидает этот отказ. Не используйте его как успешные данные и не переносите это поведение на другие методы или версии без проверки.
Продукты ATOMNO
Другие задачи для AI-помощника
Это отдельные продукты ATOMNO для задач с данными. Они не входят в учебные примеры.
Узнать курс и ставку ЦБ
Помощник получает курсы валют и ключевую ставку.
Курсы и ставка Банка России
Опубликованный клиент; свежесть конкретного ответа сверяйте с источником.
Проверить организацию
ИНН или ОГРН → ограниченная проверка организации.
Проверка контрагента
Состав проверки и доступ уточняйте в документации.
Найти госзакупку
Поиск извещения → чтение карточки закупки.
Поиск госзакупок
Полнота архива и доставка уведомлений не обещаются.
Открыть карточку товарного знака
Номер свидетельства → карточка российского знака.
Карточка товарного знака
Поиск по названию и оценка сходства не входят.