Skip to content

Tighten the public API surface: root exports, listen types, dead-code removal - #3203

Open
maxisbey wants to merge 1 commit into
mainfrom
api-surface-cleanup
Open

Tighten the public API surface: root exports, listen types, dead-code removal#3203
maxisbey wants to merge 1 commit into
mainfrom
api-surface-cleanup

Conversation

@maxisbey

Copy link
Copy Markdown
Contributor

Cleanup pass over the v2 public surface: a few missing exports, a few dead ones, and some no-op aliases.

Motivation and Context

  • Root mcp: exports MCPServer next to the existing Client, so from mcp import MCPServer, Client works (the first-steps.md tip claiming otherwise is removed). Also exports NoBackChannelError, which every server-initiated request path can raise on a 2026-07-28 connection. Drops the v1-carryover SamplingRole alias (use mcp.types.Role).
  • mcp.client: re-exports the types Client.listen() yields and raises — Subscription, SubscriptionLost, ListenNotSupportedError — which previously required reaching into mcp.client.subscriptions.
  • Dead surface removed: StreamableHTTPError / ResumptionError (never raised from a code path a caller could catch) and the unused StreamableHTTPTransport.get_session_id(); the no-op RegistrationRequest / TokenSuccessResponse auth-handler aliases; the validate_extension_identifier re-export alias in mcp.server.extension; the unreachable __main__ guard in mcp/cli/__init__.py.
  • The lowlevel server Context is not constructed by ServerRunner yet, so it moves to the private mcp.server._context module until it's wired back in.
  • _handle_resumption_request now takes the resumption token as a required argument instead of re-deriving it, which is what let the unreachable no-token branch (and the two exception classes) go away.
  • Enables ruff RUF022 and sorts every __all__; mcp_types/__init__.py is exempted since its __all__ is grouped by section comments on purpose.

How Has This Been Tested?

Full ./scripts/test (100% coverage held, strict-no-cover clean), plus a smoke test of the public boundary in a fresh interpreter: root/mcp.client imports resolve, removed names raise ImportError, and from mcp import MCPServer, Client round-trips a tool call both in-memory and over Streamable HTTP.

Breaking Changes

Yes, all documented in docs/migration.md: SamplingRole is gone from the root package, StreamableHTTPError/ResumptionError/get_session_id() are gone from mcp.client.streamable_http, and mcp.server.context.Context is now private (ServerRequestContext and the middleware types are unchanged).

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

AI Disclaimer

… removal

Root `mcp` package: export `MCPServer` (alongside the existing `Client`,
so `from mcp import MCPServer, Client` works) and `NoBackChannelError`;
drop the v1-carryover `SamplingRole` alias (`mcp.types.Role`). `mcp.client`
now re-exports the types `Client.listen()` yields and raises: `Subscription`,
`SubscriptionLost`, `ListenNotSupportedError`.

Remove dead surface: `StreamableHTTPError`/`ResumptionError` (never raised
from a reachable path) and `StreamableHTTPTransport.get_session_id()` on the
client transport; the no-op `RegistrationRequest`/`TokenSuccessResponse`
auth-handler aliases; the `validate_extension_identifier` re-export alias;
the unreachable `__main__` guard in `mcp/cli/__init__.py`. The unused
lowlevel server `Context` moves to the private `mcp.server._context`
module.

`_handle_resumption_request` now takes the resumption token as a required
argument instead of re-deriving it and carrying an unreachable no-token
branch.

Enable ruff `RUF022` and sort every `__all__`; `mcp_types/__init__.py` is
exempted since its `__all__` is grouped by section comments on purpose.
@maxisbey
maxisbey marked this pull request as ready for review July 28, 2026 11:27
@github-actions

Copy link
Copy Markdown
Contributor

📚 Documentation preview

Preview https://pr-3203.mcp-python-docs.pages.dev
Deployment https://34f99564.mcp-python-docs.pages.dev
Commit 8c88d27
Triggered by @maxisbey
Updated 2026-07-28 11:28:06 UTC

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 26 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/mcp/server/extension.py">

<violation number="1" location="src/mcp/server/extension.py:31">
P2: The server-extension validator is still publicly importable, so this removal is not actually effective. This direct import creates the same module attribute as the removed alias, and `MCPServer` still imports it through `mcp.server.extension`. Consider importing it privately here and changing `mcp.server.mcpserver.server` to import the shared helper directly, so the old import path raises `ImportError` as intended.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


# Re-exported from `mcp.shared.extension` (shared with the client surface) for existing importers.
from mcp.shared.extension import validate_extension_identifier as validate_extension_identifier
from mcp.shared.extension import validate_extension_identifier

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The server-extension validator is still publicly importable, so this removal is not actually effective. This direct import creates the same module attribute as the removed alias, and MCPServer still imports it through mcp.server.extension. Consider importing it privately here and changing mcp.server.mcpserver.server to import the shared helper directly, so the old import path raises ImportError as intended.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/mcp/server/extension.py, line 31:

<comment>The server-extension validator is still publicly importable, so this removal is not actually effective. This direct import creates the same module attribute as the removed alias, and `MCPServer` still imports it through `mcp.server.extension`. Consider importing it privately here and changing `mcp.server.mcpserver.server` to import the shared helper directly, so the old import path raises `ImportError` as intended.</comment>

<file context>
@@ -28,9 +28,7 @@
-
-# Re-exported from `mcp.shared.extension` (shared with the client surface) for existing importers.
-from mcp.shared.extension import validate_extension_identifier as validate_extension_identifier
+from mcp.shared.extension import validate_extension_identifier
 
 if TYPE_CHECKING:
</file context>

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beyond the inline finding, two other candidate concerns were examined and ruled out: (1) the removed RegistrationRequest/TokenSuccessResponse auth-handler aliases have no references anywhere in docs/ and were internal no-op aliases, so their removal needs no migration entry; (2) the _handle_resumption_request refactor preserves behavior — the old code routed on bool(metadata and metadata.resumption_token) and the new code on the same truthiness of the extracted token, so the deleted no-token branch was genuinely unreachable.

Extended reasoning...

The single reported bug (the docs/migration.md entry for the mcp.server.context.Context privatization that the PR description claims but never adds) was verified against the post-PR migration guide: no matching entry exists, so the inline comment stands. Beyond that, I checked the two most plausible additional risks. The auth-handler alias removal is not a documented breaking change — neither name appears in docs/, both lived in internal handler modules with explicit no-op-alias comments. The resumption-token refactor in streamable_http.py is behavior-preserving under truthiness (None and empty string both route to the plain POST path before and after), confirming the removed ResumptionError branch was dead code. The remaining diff is mechanical RUF022 all sorting, export additions with matching docs updates, and test relocation — none of which changes runtime behavior.

Comment thread docs/migration.md
Comment on lines 2780 to +2783

- Sampling: `ServerSession.create_message()`, `ClientPeer.sample()`
- Roots: `ServerSession.list_roots()`, `ClientPeer.list_roots()`, `ClientSession.send_roots_list_changed()`, `Client.send_roots_list_changed()`
- Logging: `ServerSession.send_log_message()`, `Connection.log()`, `ClientSession.set_logging_level()`, `Client.set_logging_level()`, `mcp.server.context.Context.log()` (the lowlevel `Context`), and the `MCPServer` `Context` helpers `log()`, `debug()`, `info()`, `warning()`, `error()`
- Logging: `ServerSession.send_log_message()`, `Connection.log()`, `ClientSession.set_logging_level()`, `Client.set_logging_level()`, and the `MCPServer` `Context` helpers `log()`, `debug()`, `info()`, `warning()`, `error()`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 The breaking change moving the lowlevel Context class from the public mcp.server.context module to the private mcp.server._context module is not documented in docs/migration.md, even though the PR description states it is. AGENTS.md requires all breaking changes to be documented there — please add a short migration entry (grouped with the existing lowlevel-server/context sections) noting the class is now private and that from mcp.server.context import Context raises ImportError.

Extended reasoning...

What the bug is. This PR moves the lowlevel server Context class out of the public mcp.server.context module into a new private mcp.server._context module. That is a breaking change: any code doing from mcp.server.context import Context now raises ImportError. The PR description explicitly claims this is covered: "Yes, all documented in docs/migration.md: ... mcp.server.context.Context is now private" — but no such entry exists in the guide.

Verification. Grepping the post-PR docs/migration.md for _context, Context is now private, and related phrases finds nothing. The PR's migration.md edits are only: the SamplingRole table row, the MCPServer top-level export note, the new StreamableHTTPError/ResumptionError/get_session_id() section, the NoBackChannelError export note, and — at lines 2780–2783 — the removal of the mcp.server.context.Context.log() mention from the SEP-2577 deprecated-logging list. That removed mention also confirms the class was documented public surface before this PR: the guide referenced it as live API, and the PR deleted the reference rather than documenting the move.

Why this matters. AGENTS.md states: "Breaking changes (including those softened by a backwards-compatibility shim) must be documented in docs/migration.md." This is an explicit repo-mandated requirement, and the guide already carries intra-v2 pre-release notes (since 2.0.0b2, since 2.0.0rc1), so "Context never existed in v1" does not excuse the omission. Notably, the three other removals in this same PR (SamplingRole; StreamableHTTPError/ResumptionError/get_session_id()) each received a dedicated migration section — the omission looks like an oversight, not a policy decision, and the author's own PR description shows the entry was intended.

Concrete walk-through. (1) A v2-RC user has middleware or tests importing from mcp.server.context import Context (a public, documented path before this PR — the migration guide itself referenced mcp.server.context.Context.log()). (2) They upgrade past this PR. (3) The import raises ImportError. (4) They open docs/migration.md — the canonical record of every breaking change per AGENTS.md — and search for Context or mcp.server.context: no entry explains the class moved to mcp.server._context or that it is now private. They are left to bisect the SDK source.

Mitigating factors considered. One verifier noted the class's own new docstring says "Private for now: ServerRunner does not construct it", so real-world importers are likely rare, and the fix is docs-only. That argues for lower impact, but the repo's own instructions make documenting breaking changes in migration.md a hard requirement, and the PR affirmatively (and incorrectly) claims compliance — this is not a stale-description case, since the entry was clearly intended and simply never written.

How to fix. Add a short section to docs/migration.md (grouped near the existing lowlevel-Server/context sections, e.g. after the RequestContext split section) stating: the lowlevel Context class moved from mcp.server.context to the private mcp.server._context module and is no longer public API; from mcp.server.context import Context now raises ImportError; ServerRequestContext and the middleware types (CallNext, HandlerResult, ServerMiddleware) in mcp.server.context are unchanged; handlers receive a ServerRequestContext, which is the supported surface.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

migration.md is for breaking changes from v1 to v2. this class wasn't in v1 so there's no reason for it to be in migration.md

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