Skip to content

feat(client): support HTTP auth in ClientSessionGroup - #3516

Open
musi22 wants to merge 5 commits into
modelcontextprotocol:mainfrom
musi22:feat/session-group-httpx-auth
Open

musi22 wants to merge 5 commits into
modelcontextprotocol:mainfrom
musi22:feat/session-group-httpx-auth

Conversation

@musi22

@musi22 musi22 commented Sep 17, 2026

Copy link
Copy Markdown

Closes #1723

Summary

ClientSessionGroup server parameters (SseServerParameters and StreamableHttpParameters) lacked an auth field, requiring users to manually manage bearer tokens via the headers parameter and preventing automated OAuth flows (like OAuthClientProvider) with dynamic client registration and token refresh.

This change adds an optional auth: httpx2.Auth | None = None parameter to both HTTP server parameter models and propagates it into sse_client() and create_mcp_http_client().

Changes

  • Added auth: httpx2.Auth | None = Field(default=None, description="Optional HTTPX authentication handler.", exclude=True) to SseServerParameters and StreamableHttpParameters.
  • Added ConfigDict(arbitrary_types_allowed=True) to both parameter models.
  • Propagated auth to sse_client() and create_mcp_http_client() within ClientSessionGroup._establish_session().
  • Added unit tests in tests/client/test_session_group.py verifying:
    • auth is passed to sse_client() in SSE transport.
    • auth is passed to create_mcp_http_client() in Streamable HTTP transport.
    • Pydantic serialization (model_dump, model_dump_json) safely excludes auth.
    • Independent auth state is maintained when multiple servers are connected to the same session group.
    • Existing custom headers and auth handler cleanly coexist.
  • Updated docs/client/session-groups.md with an authentication usage guide and example.

Backward Compatibility

100% backward compatible:

  • auth defaults to None.
  • Existing static headers configurations remain fully supported.
  • StdioServerParameters is unaffected.

Test Plan

  • uv run --frozen pytest tests/client/test_session_group.py (17 passed, 100% coverage on session_group.py).
  • uv run --frozen pytest tests/client/ (778 passed, 0 regressions).
  • uv run --frozen pyright src/mcp/client/session_group.py tests/client/test_session_group.py (0 errors).
  • uv run --frozen ruff check . and uv run --frozen ruff format --check . (passed).

@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.

2 issues found across 3 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="docs/client/session-groups.md">

<violation number="1" location="docs/client/session-groups.md:83">
P2: Copying this example raises `NameError` for `client_metadata`, `token_storage`, `redirect_handler`, and `callback_handler`; define these values or link a complete runnable OAuth example.</violation>

<violation number="2" location="docs/client/session-groups.md:98">
P2: When the same auth object is assigned to two parameter instances, `ClientSessionGroup` shares its mutable OAuth context and tokens. Tell users to construct a distinct auth instance per server before claiming these lifecycles are independent.</violation>
</file>

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

Re-trigger cubic

Comment thread docs/client/session-groups.md Outdated

server_auth = OAuthClientProvider(
server_url="https://api.example.com",
client_metadata=client_metadata,

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: Copying this example raises NameError for client_metadata, token_storage, redirect_handler, and callback_handler; define these values or link a complete runnable OAuth example.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/client/session-groups.md, line 83:

<comment>Copying this example raises `NameError` for `client_metadata`, `token_storage`, `redirect_handler`, and `callback_handler`; define these values or link a complete runnable OAuth example.</comment>

<file context>
@@ -70,6 +70,33 @@ If you already hold a connected `ClientSession` (`Client.session` is one), hand
+
+server_auth = OAuthClientProvider(
+    server_url="https://api.example.com",
+    client_metadata=client_metadata,
+    storage=token_storage,
+    redirect_handler=redirect_handler,
</file context>

await group.connect_to_server(server_params)
```

Because `auth` is configured per `ServerParameters` instance, each server in the session group maintains independent authentication context, scopes, and token-refresh lifecycle. Custom headers can still be supplied alongside `auth` via `headers=`.

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: When the same auth object is assigned to two parameter instances, ClientSessionGroup shares its mutable OAuth context and tokens. Tell users to construct a distinct auth instance per server before claiming these lifecycles are independent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/client/session-groups.md, line 98:

<comment>When the same auth object is assigned to two parameter instances, `ClientSessionGroup` shares its mutable OAuth context and tokens. Tell users to construct a distinct auth instance per server before claiming these lifecycles are independent.</comment>

<file context>
@@ -70,6 +70,33 @@ If you already hold a connected `ClientSession` (`Client.session` is one), hand
+    await group.connect_to_server(server_params)
+```
+
+Because `auth` is configured per `ServerParameters` instance, each server in the session group maintains independent authentication context, scopes, and token-refresh lifecycle. Custom headers can still be supplied alongside `auth` via `headers=`.
+
 ## Recap
</file context>
Suggested change
Because `auth` is configured per `ServerParameters` instance, each server in the session group maintains independent authentication context, scopes, and token-refresh lifecycle. Custom headers can still be supplied alongside `auth` via `headers=`.
Because `auth` is configured per `ServerParameters` instance, give each server a distinct auth instance to maintain independent authentication context, scopes, and token-refresh lifecycle. Custom headers can still be supplied alongside `auth` via `headers=`.

@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 1 file (changes from recent commits).

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="docs/client/session-groups.md">

<violation number="1" location="docs/client/session-groups.md:82">
P2: The new `main()` is never invoked, so copying this authentication example only defines the function and never connects to the server. Add an async runner such as `asyncio.run(main())` and its import.</violation>
</file>

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

Re-trigger cubic

from mcp.client.session_group import ClientSessionGroup, StreamableHttpParameters


async def main() -> None:

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 new main() is never invoked, so copying this authentication example only defines the function and never connects to the server. Add an async runner such as asyncio.run(main()) and its import.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/client/session-groups.md, line 82:

<comment>The new `main()` is never invoked, so copying this authentication example only defines the function and never connects to the server. Add an async runner such as `asyncio.run(main())` and its import.</comment>

<file context>
@@ -78,21 +78,23 @@ When connecting to HTTP servers using `StreamableHttpParameters` or `SseServerPa
-async with ClientSessionGroup() as group:
-    await group.connect_to_server(server_params)
+
+async def main() -> None:
+    server_auth = OAuthClientProvider(
+        server_url="https://api.example.com",
</file context>

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.

auth specification in ClientSessionGroup

1 participant