From 20828491311941869d8808c74285c93b139eb27a Mon Sep 17 00:00:00 2001 From: Max Isbey <224885523+maxisbey@users.noreply.github.com> Date: Sat, 25 Jul 2026 17:20:56 +0000 Subject: [PATCH] Remove Context.client_id The property echoed a non-standard `client_id` key from the request's `_meta`, which nothing in the SDK or the spec populates, so it was `None` unless a caller injected the meta key by hand. The name also collided with the OAuth `client_id` that callers usually mean, and the line carried a `# pragma: no cover` because it had never been exercised. The migration guide covers both replacements: reading the raw `_meta` key from `ctx.request_context.meta`, and the OAuth client via `get_access_token().client_id`. --- docs/migration.md | 21 +++++++++++++++++++++ src/mcp/server/mcpserver/context.py | 11 ----------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/docs/migration.md b/docs/migration.md index afe095363..62d68465c 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -916,6 +916,27 @@ await ctx.log(level="info", data="hello") Positional calls (`await ctx.info("hello")`) are unaffected. +### `Context.client_id` removed + +`Context.client_id` has been removed. It never returned an authenticated client identity: it echoed a non-standard `client_id` key from the request's `_meta`, which nothing in the SDK or the MCP spec populates, so it was `None` unless a caller injected `meta={"client_id": ...}` by hand. The name also collided with the OAuth `client_id`, which is what callers usually mean by "the client". + +If you were reading a custom `_meta` key, read it from the meta dict directly. If you want the authenticated OAuth client, use the access token: + +```python +# Before (v1) +client_id = ctx.client_id + +# After (v2) — the raw _meta key, if you were setting it yourself +meta = ctx.request_context.meta +client_id = meta.get("client_id") if meta else None + +# After (v2) — the authenticated OAuth client (usually what you want) +from mcp.server.auth.middleware.auth_context import get_access_token + +token = get_access_token() +client_id = token.client_id if token else None +``` + ### `ProgressContext` and `progress()` context manager removed The `mcp.shared.progress` module (`ProgressContext`, `Progress`, and the `progress()` context manager) has been removed. This module had no real-world adoption — all users send progress notifications via `Context.report_progress()` or `session.send_progress_notification()` directly. diff --git a/src/mcp/server/mcpserver/context.py b/src/mcp/server/mcpserver/context.py index 97ac905bd..bf4c26a24 100644 --- a/src/mcp/server/mcpserver/context.py +++ b/src/mcp/server/mcpserver/context.py @@ -54,7 +54,6 @@ async def my_tool(x: int, ctx: Context) -> str: # Get request info request_id = ctx.request_id - client_id = ctx.client_id return str(x) ``` @@ -275,16 +274,6 @@ async def log( related_request_id=self.request_id, ) - # TODO(maxisbey): see if this is needed otherwise remove - @property - def client_id(self) -> str | None: - """Get the client ID if available. - - Note: this reads from the MCP request's `_meta` params, not the OAuth - bearer token. For that, use `get_access_token().client_id`. - """ - return self.request_context.meta.get("client_id") if self.request_context.meta else None # pragma: no cover - @property def headers(self) -> Mapping[str, str] | None: """Request headers carried by this message, when the transport has them.