Python SDK v1 · mcp==1.30.0 · протокол 2025-11-25. Для SDK v2 требуется отдельная миграция.
Перед началом
Пройдите предыдущие пять шагов маршрута. В одной папке должны лежать три сервера label_server.py, validation_server.py, context_server.py; общий lab_session.py; check_stdio.py, inspect_contract.py, check_validation.py, check_context.py, check_transport.py; configure_client.py и созданная .mcp.json. Используется .venv из первого урока с mcp 1.30.0. Если чего-то нет, скачайте полный файл из соответствующего урока, а не дописывайте отсутствующие функции по догадке.
1. Сохраните последовательный запуск
Создайте check_suite.py:
"""Run the lab's independent checks sequentially and stop on a failure."""
from pathlib import Path
import subprocess
import sys
root = Path(__file__).resolve().parent
checks = ["check_stdio.py", "inspect_contract.py", "check_validation.py", "check_context.py", "check_transport.py"]
for filename in checks:
result = subprocess.run([sys.executable, str(root / filename)], cwd=root, capture_output=True, text=True, encoding="utf-8", timeout=30)
if result.returncode:
print("FAIL:", filename)
print(result.stdout)
print(result.stderr, file=sys.stderr)
raise SystemExit(result.returncode)
print("PASS:", filename)
print("PASS: all five lab checks")Каждая проверка запускается через тот же sys.executable, что и suite. Это защищает от случайного Python из PATH с другой версией SDK. У дочерней проверки есть предел 30 секунд. При ненулевом коде показываются её stdout и stderr, затем suite возвращает этот код. Это не тест одного большого постоянно работающего сервера: у каждой проверки собственный короткий жизненный цикл.
2. Проверьте все слои
.\.venv\Scripts\python.exe check_suite.py
$LASTEXITCODEОжидаемый вывод:
PASS: check_stdio.py
PASS: inspect_contract.py
PASS: check_validation.py
PASS: check_context.py
PASS: check_transport.py
PASS: all five lab checks
0| Проверка | Какой результат она подтверждает |
|---|---|
| check_stdio | initialize, схема, Draft и три отказа первого сервера |
| inspect_contract | контракт SDK-клиента, внутренние пробелы, неизвестный tool |
| check_validation | необязательный integer/default, два успеха, четыре отказа |
| check_context | ресурс, шаблон сообщения, два протокольных исключения |
| check_transport | настоящий stdio и чистая логика конфигурации HTTP |
Чтение этих результатов не заменяет проверку интерфейса редактора. Проверка HTTP здесь не обращается в сеть и не утверждает, что создан удалённый сервер. Ожидаемые отказы — часть успеха теста: инструмент должен отвергать плохие данные, а не всегда возвращать объект label.
3. Проверяйте функцию и протокол раздельно
Сохраните test_lab.py. Здесь стандартный unittest проверяет прямую функцию на таблицах входов, всю stdio-suite и намеренно испорченное ожидание в отдельной временной копии. Временная папка принадлежит тесту и удаляется его контекстом. Исходные серверы и проверки не перезаписываются:
"""Direct fixtures, a real stdio suite, and a failing isolated expectation."""
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import unittest
from validation_server import normalize_label
ROOT = Path(__file__).resolve().parent
class FunctionTests(unittest.TestCase):
def test_success_fixtures(self):
cases = [(" Draft ", 5, {"label": "Draft"}), (" two words ", 80, {"label": "two words"}), ("x", 1, {"label": "x"}), ("x" * 80, 80, {"label": "x" * 80})]
for text, limit, expected in cases:
with self.subTest(text=text, limit=limit):
self.assertEqual(normalize_label(text, limit), expected)
def test_invalid_text_fixtures(self):
for text, limit in [(" ", 80), ("Draft!", 5), ("xx", 1)]:
with self.subTest(text=text, limit=limit):
with self.assertRaisesRegex(ValueError, "after trimming"):
normalize_label(text, limit)
def test_invalid_limit_fixtures(self):
for limit in [0, -1, 81]:
with self.subTest(limit=limit):
with self.assertRaisesRegex(ValueError, "between 1 and 80"):
normalize_label("Draft", limit)
class ProtocolTests(unittest.TestCase):
def test_all_stdio_checks(self):
result = subprocess.run([sys.executable, str(ROOT / "check_suite.py")], cwd=ROOT, capture_output=True, text=True, encoding="utf-8", timeout=150)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn("PASS: all five lab checks", result.stdout)
def test_wrong_expectation_really_fails(self):
with tempfile.TemporaryDirectory(prefix="label-lab-negative-") as temporary:
work = Path(temporary)
for name in ["label_server.py", "lab_session.py", "check_suite.py", "check_stdio.py"]:
shutil.copyfile(ROOT / name, work / name)
original = (ROOT / "inspect_contract.py").read_text(encoding="utf-8")
expectation = 'assert result.structuredContent == {"label": "two words"}'
self.assertEqual(original.count(expectation), 1)
wrong = original.replace(expectation, 'assert result.structuredContent == {"label": "two words"}', 1)
(work / "inspect_contract.py").write_text(wrong, encoding="utf-8")
result = subprocess.run([sys.executable, str(work / "check_suite.py")], cwd=work, capture_output=True, text=True, encoding="utf-8", timeout=60)
self.assertNotEqual(result.returncode, 0)
self.assertIn("PASS: check_stdio.py", result.stdout)
self.assertIn("FAIL: inspect_contract.py", result.stdout)
self.assertIn("AssertionError", result.stderr)
self.assertNotIn("PASS: check_validation.py", result.stdout)
if __name__ == "__main__":
unittest.main(verbosity=2)Выполните:
.\.venv\Scripts\python.exe -m unittest -v test_lab
$LASTEXITCODEОжидаются пять тестов с ok, строка Ran 5 tests, OK и код 0. test_success_fixtures проверяет крайние и внутренние пробелы, минимальную и максимальную длину. Два следующих теста проверяют ошибки прямой функции. ProtocolTests запускает настоящий обмен и проверяет, что изменение ожидания вызывает падение и останавливает suite. Прямая функция не подтверждает схему или MCP-регистрацию; протокольный тест не заменяет таблицу предметных правил. Оба уровня нужны по своим причинам.
4. Убедитесь, что suite замечает падение
Сделайте отдельную копию лаборатории для опыта. В копии inspect_contract.py замените ожидаемый результат two words на two words, сохранив вызов с двойным внутренним пробелом. Запустите suite. Он должен напечатать PASS только для первого файла, затем FAIL: inspect_contract.py, traceback утверждения и ненулевой $LASTEXITCODE. Остальные три проверки не должны выполняться после этого падения.
Верните точное ожидаемое значение и повторите запуск: все пять должны снова пройти. Не проводите этот опыт в исходном готовом проекте без копии. Смысл опыта — проверить поведение управляющего скрипта, а не ослабить проверку инструмента.
Как разбирать ошибку
При FAIL сначала запустите отдельно указанный файл тем же интерпретатором. ModuleNotFoundError означает проблему среды или отсутствующий общий файл. AssertionError схемы означает изменение контракта. TimeoutExpired означает превышение предела проверки; изучите зависший шаг и stdout/stderr, а не просто увеличивайте таймаут. Если сервер печатает обычные строки в stdout, восстановите разделение протокола и диагностики.
Данные ответа и ошибки проверяются независимо. Для tools/call нужно рассматривать isError; для чтения ресурса или получения prompt — исключения McpError. Полный stderr при отрицательных сценариях может содержать ожидаемые диагностические записи сервера. Пустой stderr сам по себе не является доказательством исправности.
Что проверять при подключении внешнего API
Следующий отдельный этап — изолированный клиент API с синтетическими ответами: успех, неверный вход, 4xx, 5xx, timeout, ограничение частоты и повторные запросы. Здесь такого клиента нет. Также нет операций записи и доступа к персональным данным. Для удалённого HTTP-сервера нужны отдельные проверки авторизации и отказа доступа; наша локальная suite не подтверждает эти свойства.
Проверьте себя
Составьте таблицу «изменение → ожидаемое падение». Например, переименование tool должно падать на tools/list, изменение strip — на structuredContent, удаление prompt — на prompts/list. Если ни один тест не замечает важное изменение, добавьте проверку поведения до расширения возможностей сервера. Сохранённый first_tool.py из дополнительного разбора остаётся отдельным примером и не включён в эту лабораторию автоматически.
Версия и источники
Упражнение закреплено за Python 3.11 и mcp==1.30.0, SDK v1, протоколом 2025-11-25. SDK v2 требует отдельной миграции. Примеры используют синтетические подписи, без внешнего API и модели. Документация сверена 8 октября 2026 года.
Пять сценариев запускаются последовательно на SDK 1.30.0. Проверки среды используют уже установленный пакет; установка в новой пользовательской среде и интерфейс редактора — отдельные шаги первого урока. HTTP и права доступа не выдаются за проверенные.
Источники: testing SDK v1 (в новой вкладке), tools 2025-11-25 (в новой вкладке), клиент SDK v1 (в новой вкладке), HTTP-авторизация (в новой вкладке).
Вопросы по материалу
Почему после одной ошибки остальные проверки не запускаются?
Так устроен check_suite.py: при первом ненулевом коде он печатает вывод упавшей проверки и завершает запуск. Начните с указанного файла в том же окружении, затем повторите всю последовательность.
Если тесты функции прошли, зачем ещё проверять stdio?
Прямой вызов проверяет правила обработки подписи. Он не проверяет регистрацию инструмента, опубликованную схему и обмен MCP. Для этих условий лаборатория запускает отдельные протокольные проверки.
Продукты ATOMNO
Другие задачи для AI-помощника
Это отдельные продукты ATOMNO для задач с данными. Они не входят в учебные примеры.
Узнать курс и ставку ЦБ
Помощник получает курсы валют и ключевую ставку.
Курсы и ставка Банка России
Опубликованный клиент; свежесть конкретного ответа сверяйте с источником.
Проверить организацию
ИНН или ОГРН → ограниченная проверка организации.
Проверка контрагента
Состав проверки и доступ уточняйте в документации.
Найти госзакупку
Поиск извещения → чтение карточки закупки.
Поиск госзакупок
Полнота архива и доставка уведомлений не обещаются.
Открыть карточку товарного знака
Номер свидетельства → карточка российского знака.
Карточка товарного знака
Поиск по названию и оценка сходства не входят.