Python SDK v1 · mcp==1.30.0 · протокол 2025-11-25. Для SDK v2 требуется отдельная миграция.
Перед началом
В папке mcp-label-lab должны быть .venv с SDK 1.30.0 и lab_session.py из контракта. Исходный сервер и его конфигурация остаются в лаборатории. Новая версия будет отдельным context_server.py.
1. Сохраните сервер контекста
Создайте context_server.py:
"""Add a fixed resource and prompt to the local label workshop."""
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("label-context")
RULES = "A label contains 1 to 80 characters after trimming. Internal spaces are preserved."
@mcp.tool()
def normalize_label(text: str) -> dict[str, str]:
"""Trim a label; reject empty labels or more than 80 characters."""
label = text.strip()
if not 1 <= len(label) <= 80:
raise ValueError("Expected 1 to 80 characters after trimming")
return {"label": label}
@mcp.resource("workshop://rules")
def label_rules() -> str:
"""Read the fixed lab rules."""
return RULES
@mcp.prompt()
def review_label(text: str) -> str:
"""Prepare a message explaining a label under the lab rules."""
return f"Explain this label using these rules: {RULES}\nLabel: {text}"
if __name__ == "__main__":
mcp.run(transport="stdio")URI workshop://rules — идентификатор ресурса, не веб-адрес. Его читает MCP-клиент через resources/read; браузер и HTTP-сервер не участвуют. Текст фиксирован, не содержит пользовательских файлов или секретов. Шаблон review_label принимает text и возвращает сообщение с этими правилами.
Prompt в этом упражнении не является результатом работы модели и не запускает инструмент самостоятельно. В частности, get_prompt не обязан проверить подпись по правилам normalize_label. Клиент отдельно выбирает контекст, получает шаблон и решает, какие сообщения передать модели.
2. Сохраните проверку
Создайте check_context.py:
"""Read a resource and obtain a prompt without calling a language model."""
import asyncio
import json
from mcp.shared.exceptions import McpError
from lab_session import lab_session
RULES = "A label contains 1 to 80 characters after trimming. Internal spaces are preserved."
async def main():
async with lab_session("context_server.py") as (session, _):
resources = await session.list_resources()
assert [str(resource.uri) for resource in resources.resources] == ["workshop://rules"]
rules = await session.read_resource("workshop://rules")
assert rules.contents[0].text == RULES
prompts = await session.list_prompts()
assert [prompt.name for prompt in prompts.prompts] == ["review_label"]
prompt = await session.get_prompt("review_label", {"text": "Draft"})
assert prompt.messages[0].role == "user"
assert prompt.messages[0].content.text == f"Explain this label using these rules: {RULES}\nLabel: Draft"
errors = {}
for name, action in [("missing_resource", lambda: session.read_resource("workshop://missing")), ("missing_prompt_argument", lambda: session.get_prompt("review_label", {}))]:
try:
await action()
except McpError as error:
errors[name] = {"code": error.error.code, "message": error.error.message}
else:
raise AssertionError("Expected an MCP error for " + name)
print("PASS: resource read, prompt get, two expected errors")
print(json.dumps({"resource": rules.model_dump(mode="json", exclude_none=True), "prompt": prompt.model_dump(mode="json", exclude_none=True), "known_errors": errors}, ensure_ascii=False, indent=2))
if __name__ == "__main__":
asyncio.run(main())Тест проверяет оба списка, читает ресурс, сверяет содержимое сообщения и получает два ожидаемых исключения McpError. Это отличается от isError: true в результате tools/call: методы ресурсов и prompts возвращают протокольную ошибку, которую клиент должен обработать.
3. Получите контекст без модели
.\.venv\Scripts\python.exe check_context.pyОжидается:
PASS: resource read, prompt get, two expected errorsВ resource.contents[0] ожидаются URI workshop://rules, mimeType text/plain и полный RULES. В prompt.messages[0] роль будет user, а текст:
Explain this label using these rules: A label contains 1 to 80 characters after trimming. Internal spaces are preserved.
Label: DraftЭтот текст подтверждает сборку шаблона. Он ничего не доказывает о качестве будущего ответа модели. Проверка не оплачивает генерацию и не требует API-ключа.
Два известных отказа
workshop://missing должен дать Unknown resource. Вызов review_label без text должен дать Missing required arguments. В проверенной версии SDK оба диагностических объекта имеют код 0; не используйте этот код как универсальную классификацию всех ошибок MCP. Здесь тест утверждает именно факт исключения и сохраняет сообщение.
Если ресурс не найден, сравните URI полностью, включая scheme и имя. Если список prompts пуст, убедитесь, что клиент запустил context_server.py и декоратор @mcp.prompt() сохранён. При ошибке импорта восстановите lab_session.py. Обычные отладочные сообщения сервера отправляйте в stderr: stdout занят протоколом.
Проверьте себя
В отдельной копии сервера замените RULES и получите обновлённый ресурс. Затем обновите ожидаемый текст в копии теста. Оба результата — read_resource и get_prompt — должны измениться согласованно, поскольку шаблон использует тот же RULES. Не приписывайте этому автоматическое разрешение внешнего доступа: наш ресурс фиксированный и локальный.
Версия и источники
Упражнение закреплено за Python 3.11 и mcp==1.30.0, SDK v1, протоколом 2025-11-25. SDK v2 требует отдельной миграции. Примеры используют синтетические подписи, без внешнего API и модели. Документация сверена 8 октября 2026 года.
На реальном локальном stdio проверены resources/list, resources/read, prompts/list, prompts/get и два ожидаемых отказа. Модель и HTTP здесь не запускались.
Источники: ресурсы 2025-11-25 (в новой вкладке), prompts 2025-11-25 (в новой вкладке), клиент SDK v1 (в новой вкладке), FastMCP v1 (в новой вкладке).
Вопросы по материалу
Вызов review_label уже проверяет подпись с помощью модели?
Нет. Этот prompt собирает сообщение из правил и переданного text. Получение сообщения подтверждает работу шаблона; вызов модели и оценка её ответа в упражнение не входят.
Почему workshop://rules не открывается как сайт?
Это URI ресурса MCP, а не HTTP-адрес. В упражнении клиент читает его через resources/read и получает фиксированный текст правил. Веб-сервер для этого не нужен.
Продукты ATOMNO
Другие задачи для AI-помощника
Это отдельные продукты ATOMNO для задач с данными. Они не входят в учебные примеры.
Узнать курс и ставку ЦБ
Помощник получает курсы валют и ключевую ставку.
Курсы и ставка Банка России
Опубликованный клиент; свежесть конкретного ответа сверяйте с источником.
Проверить организацию
ИНН или ОГРН → ограниченная проверка организации.
Проверка контрагента
Состав проверки и доступ уточняйте в документации.
Найти госзакупку
Поиск извещения → чтение карточки закупки.
Поиск госзакупок
Полнота архива и доставка уведомлений не обещаются.
Открыть карточку товарного знака
Номер свидетельства → карточка российского знака.
Карточка товарного знака
Поиск по названию и оценка сходства не входят.