Skip to content

chore(mcp): migrate to MCP Python SDK 2.3 (protocol 2025-11-25, v2 API) - #139

Open
ManSio wants to merge 16 commits into
mainfrom
chore/mcp-sdk-v2
Open

ManSio wants to merge 16 commits into
mainfrom
chore/mcp-sdk-v2

Conversation

@ManSio

@ManSio ManSio commented Nov 15, 2026 •

Copy link
Copy Markdown
Owner

Что и зачем

Переход на MCP Python SDK 2.3.0 (протокол до 2026-07-28, новый API). Причина: линия 1.x — maintenance mode, а наш пин стоял на mcp==1.30. План, решения и принятые деградации — docs/adr/0008-mcp-sdk-v2.md.

Что сделано

Фаза Изменение
1 FastMCP → MCPServer (импорт с fallback на 1.x), serverInfo.version = версия проекта, транспортные параметры через streamable_http_app(...)
1.1 конструктор version= вызывается только на 2.x (на 1.x FastMCP его не принимает — иначе падал дочерний процесс)
2 isError для JSON-конверта ошибок — через middleware (в 2.x request_handlers удалён); v1-путь сохранён
2 тесты портированы на Client(server) и версионно-независимый _is_error (is_error на 2.x, isError на 1.x); один шим mcp.server.fastmcp в tests/conftest.py
3 пины: mcp==2.3.0, mcp-types==2.3.0, httpx2==2.16.0, opentelemetry-api==1.45.2; sse-starlette остаётся транзитивной (3.4.5); httpx остаётся (наш код)
4 деградации на 2.x сделаны явными: брокер ставится в error_handler до ветвления по версии, кастомные методы скипаются с debug-логом, каждый пункт — строка в ADR и guard-тест

Решения (по фактам, не по вкусу)

Вопрос Решение Почему
heartbeat / dashboard / force-reindex / clear_memory не переносить в инструменты, регистрация под 2.x снята; поверхность остаётся 69 grep по src/ tests/ scripts/ adapters/ docs/ — ни одного потребителя; смысл покрыт intel_trigger_reindex, intel_reset_index, codebase(action="index", path="status")
OpenTelemetry (в 2.x по умолчанию) оставлен дефолт SDK без экспортёра спаны no-op; глобальное отключение ломает тем, кому нужно
Streamable HTTP idle 1800 с оставлен дефолт single-user remote: клиент после 404 переинициализируется, бесконечные сессии — утечка
isError middleware с меткой в wire-dict isError публичная точка, одна на 69 инструментов, payload не меняется
NotificationBroker (push в Zed) брокер установлен, push-канал выключен и объяснён одним WARNING публичного API для кастомных серверных уведомлений в 2.x нет; лезть в session._connection.outbound — та же приватная поверхность, из-за которой идёт миграция. progress.json (файл-контракт для агента) пишется без сессии
текст ошибки инструмента с исключением принят generic-текст 2.x, деталь — в лог решение SDK; вернуть деталь = ToolError в каждом из 69 инструментов

Эпоха 2026-07-28 — как есть (проверено заново)

Запрос Результат под 2.3.0
initialize(2026-07-28) counter-offer 2025-11-25 — handshake modern не умеет
server/discover с per-request _meta первым сообщением supportedVersions: ["2026-07-28"] (+capabilities), tools/list = 17, tools/call работает; serverInfo в ответе нет — он приходит на initialize (3.5.0)
enveloped-запрос на legacy-соединении -32601 / INVALID_REQUEST — ожидаемо

Мой первый probe не отправлял конверт, поэтому ADR раньше утверждал обратное — исправлено.

Проверка

suite под mcp 2.3.0:  2643 passed, 11 skipped, rc=0
suite под mcp 1.x:    2643 passed, 11 skipped, rc=0   (откатная линия)
ruff src/ tests/:     All checks passed
гейты:                check_known_issues / check_agents_md / check_tool_names / stale_detector — 0 проблем
мутации guard'ов:     broker не установлен -> 2 failed; _is_error только snake_case -> 5 failed;
                      инверсия ветки текста ошибки -> 1 failed;
                      transport_security=None -> Host-тест краснеет (421), на 1.x зелёный
e07_equiv.py:         rc=0, E-07 VERDICT: PASSED (3/3) — и на 2.3, и на 1.x
remote_main (2.3):    POST /mcp/ с Host: 192.0.2.55:8097 -> 200 (было 421)
приёмка (реальный stdio):
  2025-06-18 -> negotiated 2025-06-18 | server v3.5.0 | tools 17 | isError по конверту
  2025-11-25 -> negotiated 2025-11-25 | v3.5.0 | tools 17
  2026-07-28 -> modern envelope -> 2026-07-28 | tools 17

После второго ревью (закрыто)

Находка Что сделано
CI красный: e07_equiv.py звал streamablehttp_client двойной импорт (streamable_http_client / старое имя) + распаковка 2-tuple/3-tuple; локально E-07 PASSED (3/3) на обеих линиях
2.x отбивал не-localhost Host (421): transport_security=None включает DNS-rebinding allowlist явный TransportSecuritySettings(enable_dns_rebinding_protection=False) на 2.x, 1.x-путь не изменён; регресс-тест tests/test_streamable_http_host.py строит настоящий ASGI-app
httpx2>=, opentelemetry-api>= вместо точных пинов httpx2==2.16.0, opentelemetry-api==1.45.2 в pyproject.toml и requirements.txt
nits путь в KNOWN_ISSUES.md исправлен; ADR: serverInfo реально лежит в _meta, mcp.server.fastmcp — заглушка, риск «откат потребует ветки v1.x» снят (обе линии зелёные); докстринг error_results; except ModuleNotFoundError не маскирует поломку внутри пакета

Осталось (осознанно, не «доделаю потом молча»)

  1. Push-канал NotificationBroker на 2.x — отдельная задача (типизированные нотификации через Context.session или публичный API). Сейчас: один WARNING в лог, progress.json работает, guard-тест пиннит состояние.
  2. CI не проверяет линию 1.x (ставит lock = 2.3.0) — записано в KNOWN_ISSUES.md; либо отдельная job, либо отказ от 1.x после стабилизации.
  3. scripts/verify_client_snippets.py (5 клиентов) и transport-equiv-e2e — в CI этого PR.

Откат

mcp>=1.28,<2 в lock + pip install -r — код держит fallback, ревёрт не нужен (при условии, что откат делается до удаления поддержки 1.x).

Риски на шаг вперёд

  • httpx и httpx2 сосуществуют: наш код на httpx, объекты внутрь SDK — только httpx2 (иначе тихая деградация).
  • Strict-валидация результатов в 2.x может проявиться на редких инструментах вне smoke.
  • tests/conftest.py шим исчезнет вместе с линией 1.x — тогда удалить и оставить прямые импорты MCPServer.

@coderabbitai

coderabbitai Bot commented Nov 15, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 41a3819a-a23b-4764-83df-4575ca45450a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant