Skip to content

Chore/mcp sdk v2 - #6787

Open
madhu-mohan-jaishankar wants to merge 3 commits into
mainfrom
chore/mcp-sdk-v2
Open

Chore/mcp sdk v2#6787
madhu-mohan-jaishankar wants to merge 3 commits into
mainfrom
chore/mcp-sdk-v2

Conversation

@madhu-mohan-jaishankar

Copy link
Copy Markdown
Collaborator

No description provided.

* chore(deps): drop fastmcp dev dependency and protocol-compliance make targets

Signed-off-by: Jonathan Springer <jps@s390x.com>

* test(live-gateway): rewrite MCP protocol E2E on the official mcp SDK

Signed-off-by: Jonathan Springer <jps@s390x.com>

* chore(tests): remove the compliance reference server and matrix tooling

The fastmcp-based protocol-compliance harness was the only consumer of the
compliance reference server and the compliance matrix generator, so both go
with it.

- delete mcp-servers/python/compliance_reference_server/ and
  scripts/compliance_matrix.py
- drop the compliance-reference-server dev dependency and its
  [tool.uv.sources] editable entry; regenerate uv.lock (also sheds fastmcp,
  fastmcp-slim, authlib and their transitives)
- drop the compliance-only pytest markers from pyproject.toml
- refresh the pip-audit skip-list note and the PYTEST_IGNORE comment

Signed-off-by: Jonathan Springer <jps@s390x.com>

* docs: drop protocol-compliance references and name the mcp SDK client

Follow-up to the harness removal: the PR-review validation gate, the
live-gateway README/target list, and the test docs still pointed at
`make test-protocol-compliance` and described the protocol E2E suites as
FastMCP-driven.

- AGENTS.md, tests/AGENTS.md, llms/NOTES.template.md,
  docs/docs/development/release-management.md,
  tests/live_gateway/README.md: retire the removed targets, name the
  official mcp SDK client
- tests/live_gateway/helpers/mcp_test_helpers.py: correct the module
  docstring's consumer list and point new tests at the SDK ClientSession
  pattern

Signed-off-by: Jonathan Springer <jps@s390x.com>

* test(sso): move Entra ID integration test to tests/integration

Ports the relocation from mcp-2-0-0-prerelease (d7c1f9a): the Entra ID
suite is fully in-process (httpx.ASGITransport) and self-skips when the
AZURE_* credentials are absent, so it belongs with the integration tests
that run under the default 'make test' rather than the live-gateway tree.

- tests/live_gateway/sso/test_entra_id_integration.py ->
  tests/integration/test_entra_id_integration.py (header updated)
- tests/live_gateway/sso/__init__.py: docstring now covers only the
  Keycloak suite and points at the new Entra ID location
- docs/docs/testing/entra-id-e2e.md: all pytest invocation paths updated
- Makefile: test-e2e-sso targets Keycloak only, notes the Entra ID move
  (help comment aligned as well)

Signed-off-by: Jonathan Springer <jps@s390x.com>

* chore(deps): remove fastmcp and rebuild live-gateway E2E on the official mcp SDK

Drops the fastmcp dev dependency and everything that depended on it,
replacing the live-gateway protocol and RBAC E2E suites with a client
built on the official mcp Python SDK.

- drop fastmcp dev dependency and protocol-compliance make targets
- remove the fastmcp-based protocol compliance harness and the
  fast-server go/rust parity test
- rewrite the MCP protocol and RBAC transport E2E suites on the
  official mcp SDK ClientSession
- remove the compliance reference server and compliance matrix tooling
  (the harness was their only consumer); regenerate uv.lock, which also
  sheds fastmcp-slim, authlib and their transitives
- update AGENTS.md, test docs, and the live-gateway README to retire
  the removed targets and name the mcp SDK client

Closes #5910

Signed-off-by: Jonathan Springer <jps@s390x.com>

* chore(deps): drop fastmcp dev dependency and protocol-compliance make targets

Signed-off-by: Jonathan Springer <jps@s390x.com>

* chore(tests): remove the compliance reference server and matrix tooling

The fastmcp-based protocol-compliance harness was the only consumer of the
compliance reference server and the compliance matrix generator, so both go
with it.

- delete mcp-servers/python/compliance_reference_server/ and
  scripts/compliance_matrix.py
- drop the compliance-reference-server dev dependency and its
  [tool.uv.sources] editable entry; regenerate uv.lock (also sheds fastmcp,
  fastmcp-slim, authlib and their transitives)
- drop the compliance-only pytest markers from pyproject.toml
- refresh the pip-audit skip-list note and the PYTEST_IGNORE comment

Signed-off-by: Jonathan Springer <jps@s390x.com>

* docs: drop protocol-compliance references and name the mcp SDK client

Follow-up to the harness removal: the PR-review validation gate, the
live-gateway README/target list, and the test docs still pointed at
`make test-protocol-compliance` and described the protocol E2E suites as
FastMCP-driven.

- AGENTS.md, tests/AGENTS.md, llms/NOTES.template.md,
  docs/docs/development/release-management.md,
  tests/live_gateway/README.md: retire the removed targets, name the
  official mcp SDK client
- tests/live_gateway/helpers/mcp_test_helpers.py: correct the module
  docstring's consumer list and point new tests at the SDK ClientSession
  pattern

Signed-off-by: Jonathan Springer <jps@s390x.com>

* feat: migrate to MCP SDK 2.0.0b2 with modern 2026-07-28 upstream negotiation

Squashed arc:

- MCP SDK migration 2.0.0a2 -> 2.0.0b1 -> 2.0.0b2 (Python 3.14 support added; floor stays >=3.11 — mcp 2.0.0b2 needs only >=3.10, cpex >=3.11, CI is py3.12, MCPError rename, transport/Client compatibility fixes)
- Upstream federation path migrated from raw mcp.ClientSession to mcp.client.Client: mode=auto probes server/discover and adopts modern 2026-07-28, with transparent legacy fallback; MCP_CLIENT_CONNECT_MODE=legacy rollback flag
- Pooled upstream session registry on the high-level Client with typed SessionLifecycle handle (replaces _cf_* attribute smuggling), cache=False to preserve ADR-052 notification wiring, dispatcher-aware transport health check
- mcp_proxy_client gained transport= (streamablehttp/sse) and mode= options; all five ad-hoc SSE fallback sites routed through it
- Compose test stack: single cfex-mcp-fast-time-server image (stale Go-era flags fixed), strict modern-only 2026-07-28 fast_time_2026 variant added, five register_* one-shots consolidated into a single 'register' container running scripts/register_mcp_servers.py 'stack'
- Tests: SDK invariant spike suite, config validators, reworked registry/service suites, E2E negotiation suite (11 tests) proving auto/legacy modes against strict and legacy upstreams
- Docs: MCP_CLIENT_CONNECT_MODE and legacy rollback documented
- Fixes: four-parameter httpx2.Timeout in proxy-client default path, token-exchange integration test isolation from shared Redis, pre-existing get_httpx_client_factory dedent repair in gateway_service

Packaging — the SDK pin lands in core dependencies, not in extras:

- An earlier revision of this branch split the `mcp` pin into mutually
  exclusive `runtime` / `live-tests` extras backed by a `[tool.uv] conflicts`
  block, so the gateway could stay on `mcp` 2.x while the fastmcp-based
  protocol-compliance harness stayed on `mcp` < 2.0. That harness is gone, so
  the partition is removed: `mcp==2.0.0b2` and `mcp-types==2.0.0b2` sit in core
  `dependencies` alongside `cpex`, and `pip install mcp-contextforge-gateway`
  once again resolves a working install.
- Every `--extra runtime` / `--extra live-tests` flag is dropped from the Make
  targets, the pytest/pytest-rust/wrapper workflows and both Containerfiles,
  restoring the install targets to their pre-extras form. The `granian` extra
  is dropped from the Containerfile install lists as well; no such extra has
  ever been defined.
- All install documentation, the four affected ADRs and the agent-facing
  dependency-layout guidance revert to their extras-free wording.
- Fixes a duplicate `cpex-secrets-detection` key in
  `[tool.uv.exclude-newer-package]` that left pyproject.toml invalid TOML.

Also realigns two upstream session-factory tests that were left on the
pre-migration SDK shape: they patched `streamablehttp_client` / `ClientSession`
and reached for the retired `_cf_shutdown_event` / `_cf_owner_task` attributes,
so they errored during setup instead of exercising the post-ready WARNING path
and the timeout structured-logging fallback.

Signed-off-by: Jonathan Springer <jps@s390x.com>

* feat: upgrade MCP SDK from 2.0.0b2 to 2.0.0 final

The 2.0.0 final release removes mcp.shared.session entirely (the b2
typing-only RequestResponder stub is gone) and changes Client(cache=)
from a bool flag to CacheConfig | None, where None disables the response
cache so message_handler passes through unwrapped.

- pyproject/uv.lock: mcp/mcp-types >=2.0.0, cpex tracked from the
  0.1.x-mcp-2.0.0 branch, drop stale exclude-newer pins
- mcpgateway/utils/session_compat.py: new shim re-creating the
  RequestResponder typing stub so existing annotations and isinstance
  guards keep working (never instantiated, matching b2 semantics)
- notification_service/upstream_session_registry: import from the shim;
  pass cache=None (was cache=False) in the pooled upstream session
  factory so the response cache does not wrap message_handler
- tests: repoint RequestResponder imports to the shim, assert
  cache is None, update gateway refresh test to the mcp_proxy_client
  seam, refresh upstream session error category expectations, regenerate
  .secrets.baseline

Signed-off-by: Jonathan Springer <jps@s390x.com>

* fix(services): align MCP httpx client factories on httpx2 types

Signed-off-by: Jonathan Springer <jps@s390x.com>

* test(live-gateway): migrate MCP e2e suites to SDK 2.0 final client API

Signed-off-by: Jonathan Springer <jps@s390x.com>

* chore(compose): restore per-service REST auto-registration, drop strict 2026-07-28 probe

Signed-off-by: Jonathan Springer <jps@s390x.com>

* chore(testing): drop stale Fast Time 2026 strict-probe references

The fast_time_2026_server service was removed from docker-compose.yml in
5e54707, but its advertising and test wiring lingered:

- scripts/register_mcp_servers.py: delete (unused anywhere in the repo)
- Makefile: drop the :8887 banner and auto-registration echo from testing-up
- tests/e2e/test_upstream_connect_mode_e2e.py: strip the strict-upstream
  behavior matrix, wire-capture proofs, and strict registration tests;
  keep the legacy connect-mode proofs against fast_time_server (:8888)

Signed-off-by: Jonathan Springer <jps@s390x.com>

* Restore Containerfile and Makefile to main state

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* fix(mcp-2.0): address SDK 2.0 review findings and live-stack build issues

Review findings (PR #5493):
- transports/streamablehttp: ctx.meta is a TypedDict (RequestParamsMeta) in
  MCP 2.0 with no model_dump(); extract via dict() with a legacy-model
  fallback at the call_tool/get_prompt/read_resource sites
- transports/streamablehttp: complete() now returns types.CompleteResult
  wrapping types.Completion so the wire shape carries the required
  'completion' nesting; update test assertions to the corrected contract
- utils/streamable_http_compat: build httpx2.AsyncClient (not v0 httpx) in
  the no-factory fallback, matching what the MCP 2.0 SDK transports expect;
  same-class fixes in resource_service factory and the upstream session
  registry (HttpxClientFactory alias + httpx/httpx2 exception matching)
- services/http_client_service: add get_httpx2_timeout(); the v0
  httpx.Timeout from get_http_timeout() is rejected by httpx2.AsyncClient,
  so point the gateway/tool/resource factory fallbacks at the new helper
- transports/streamablehttp: capture server-emitted session IDs whenever
  stateful sessions are enabled, not only when multi-worker affinity is on,
  so the logical-owner claim fires on single-node deployments; unskip the
  affinity-disabled regression test

Live stack:
- Containerfile: add git to the builder stage (uv needs it to fetch the
  git-pinned cpex dependency) and split the monolithic venv RUN into
  discrete steps so build failures name the exact stage
- docker-compose: repin cfex-mcp-fast-time-server to the current digest;
  the previous pin predates the 2026-07-28 caching directives and its
  tools/list responses failed ListToolsResult validation during federation

Also rewrite test_invoke_tool_mcp_refuses_stored_env_filter to the v2
mcp_proxy_client mock pattern (pre-existing stale migration leftover).

Signed-off-by: Jonathan Springer <jps@s390x.com>

* fix: direct_proxy resource reads — SSE transport, final content shape, blob decoding

Three defects left direct_proxy federated resource reads broken or
silently corrupting content:

1. mcp_proxy_client was called without a transport argument, so SSE
   gateways were dialed with streamable-HTTP framing and the connect
   failed. Select the transport from the gateway's registered type.

2. The proxied read already returns final content, but execution fell
   through into the cache-mode pointer-resolution machinery, which
   assumes a DB-shaped object and crashed on content.id (AttributeError,
   masked to "" by the ingress). Build the final ResourceContent (with
   id and mime_type) in the branch and skip the machinery via a flag
   initialized at function scope alongside the other locals.

3. MCP transports carry blobs as base64 strings while
   ResourceContent.blob holds raw bytes; passing the string through
   meant the ingress re-encoded it, double-encoding every binary
   resource. Decode to raw bytes at the seam.

Tests: TestReadResourceDirectProxy updated to assert the final content
shape (ResourceContent) — the previously asserted intermediate shape is
exactly what crashed the pointer-resolution machinery in production —
and the blob test now uses valid base64 and asserts decoded raw bytes.

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* fix: let 2026-era requests through the session-less peeks so the ingress can serve modern sessions

The streamable ingress wrapper peeks at session-less POSTs to give
unknown-method probes a truthful -32601 (instead of the SDK's
'Missing session ID'). But 2026-era requests are self-describing and
session-less BY DESIGN, and server/discover is not in the gateway's
known-method list — so every modern client's discover probe was
answered -32601 here, before the SDK's era routing ever ran, and the
client silently fell back to the legacy handshake. The gateway could
consume modern upstream sessions but never serve one.

Mirror the SDK's own era-dispatch rule (MCP-Protocol-Version header
present and not a handshake version => modern) and skip both
session-less peeks for such requests: the SDK's modern classifier owns
their validation and rejections. Also add server/discover to the
known-method list.

With this, a v2 client negotiates 2026-07-28 through the gateway
(tools, prompts, and resources verified against both a 1.x-era SSE
downstream and a 2026-era streamable downstream); legacy clients are
unaffected (no header / handshake header keeps the old peek behavior,
pinned by test).

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* refactor(tests): group era-aware peek-bypass tests under TestModernEraPeekBypass

Groups the three behaviors pinning the modern-era peek bypass (modern
header passes through, handshake header still peeked, server/discover
is a known method) under one class with a shared docstring telling the
era-boundary story — easier to locate and read as a unit in this large
test module. No behavior change.

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* rename era peek-bypass group to TestDualEra

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* feat: standing listen streams for modern servers feeding debounced refresh

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* refactor: module-level listener imports, tighter notify_list_changed docstring

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* fix: prune stale catalog rows created via notification-driven refresh

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* fix: answer unknown tool/prompt/resource requests in proper error shapes for both eras

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* feat: relay upstream tool progress to downstream callers

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* fix: surface tool invocation failures as isError results for both eras

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* feat: pass through 2026 MRTR elicitation for modern clients on modern servers

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* port: move #5934 MCP handshake probe onto the SDK 2.0 client

The Test Connection endpoint's initialize-path probe was written against
the SDK 1.x client API. Port it to SDK 2.0: _SniPinningTransport is now
built on httpx2 (the stack the v2 client transports run on) with
identical pinning logic, the local pinned-client factory returns
httpx2.AsyncClient, _probe_session reads the v2 result field names, and
the probe connects via the v2 sse_client / streamable_http_client. The
raw server/discover probe path is unchanged. Tests updated to the v2
shapes; every pinning assertion (sni_hostname, pinned_host, TLS verify
context) still asserts.

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

---------

Signed-off-by: Jonathan Springer <jps@s390x.com>
Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>
Co-authored-by: Jonathan Springer <jps@s390x.com>
* ci: run PR checks on pull requests targeting chore/mcp-sdk-v2

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* fix: relay plugin-blocked tool calls as isError results on the /mcp path

A PluginViolationError raised on the SDK-served /servers/{id}/mcp path fell
through to the generic handler and was wrapped as a bare code-0 JSON-RPC
error. Catch it in call_tool and return an isError CallToolResult, matching
the contract the plugin e2e suites assert.

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* fix: port inherited unit tests to SDK v2 call shapes

The tests still targeted the SDK 1.x API: the streamablehttp_client patch
target and 3-tuple transport, camelCase result fields, session.call_tool
mocks where v2 calls client.session.call_tool, and call_tool assertions
without the v2 keyword arguments. Header assertions now inspect the
http_client passed to the SDK instead of a headers kwarg.

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* fix: satisfy pylint and pre-commit on migration files

Disable no-member on the mcp_app.request_context reads (the property is
injected at runtime for the v1 handler adapters) and try-except-raise on
the listener's CancelledError guard; strip a trailing space flagged by
pre-commit.

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* fix: satisfy pre-commit and diff coverage for the plugin-block change

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

* fix: remove unwanted commentsn from tests

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

---------

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>
…release verification (#6716)

* feat(compose): add 'ui' profile for contextforge-web-ui and document release verification

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* fix: lint issue

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* docs: fix forbid-source-placeholders hook failure in ui.md

Replace '(source: ...)' pattern on line 130 with an inline link so the
pre-commit forbid-source-placeholders hook no longer rejects the file.
Also refresh .secrets.baseline line numbers after the one-line change.

Closes #6716

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* chore: refresh .secrets.baseline line numbers after rebase onto chore/mcp-sdk-v2

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* fix(compose): pin web_ui image to a released version and clarify BFF UI docs

Address PR review feedback: pin WEB_UI_IMAGE to a specific released
tag instead of `latest`, add a release-checklist step to bump and
verify that version, and clarify that the built-in UI and the BFF-style
contextforge-web-ui are both supported side by side.

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* fix(compose): pin web_ui image by digest and add ui profile config check

Pin WEB_UI_IMAGE to tag@digest (docker-compose.yml, .env.example) so the
supported ui profile's session/auth-handling BFF service can't be silently
swapped by a retargeted registry tag. Document the resolve-and-bump-digest
step in release-management.md #6.4, add `make compose-ui-config-check`
(docker compose --profile ui config --quiet) plus a matching CI workflow
to catch profile/schema regressions on PRs, and drop the duplicated
WEB_UI_IMAGE version literal from AGENTS.md so there's one source of truth.

Addresses review feedback from vishu-bh and gcgoncalves on #6716.

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* fix: refresh .secrets.baseline line numbers for docker-compose.yml

The baseline committed in ef347b9 was generated before the last edit
to docker-compose.yml's web_ui image comment block, leaving it 5 lines
stale and failing detect-secrets in CI (Pre-commit Checks, run
34468285949) even though it passed locally against the pre-edit state.

Signed-off-by: Marek Dano <mk.dano@gmail.com>

---------

Signed-off-by: Marek Dano <mk.dano@gmail.com>
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.

2 participants