Skip to content

MCP tool discovery and invocation apply opposite precedence to auth and provider headers #7548

Closed

Description

@CorgiBoyG

Describe the bug

When an McpToolset has both an auth credential and a header_provider, the tool-discovery and tool-invocation paths merge those headers in opposite orders.

On current main (377306e43c5914de3f64f72526173b0732b0c7d8):

  • McpToolset._build_headers() adds provider headers first and auth headers second, so the auth credential wins on duplicate keys.
  • McpTool._run_async_impl() adds auth headers first and provider headers second, so the provider wins on duplicate keys.

For example, if both sources provide Authorization, tools/list and tools/call can run under different credentials.

This is distinct from #2168, which ensured that the auth credential participates in tool discovery, and from #6349, which concerned ambient mTLS credential injection overriding an existing header.

Why this matters

The MCP session pool key is derived from the merged headers. Opposite precedence therefore produces different session keys and different pooled ClientSession instances for discovery and invocation.

A tool may be listed and cached under principal A, then invoked under principal B. Depending on the server, this can cause a successful discovery followed by a 401/403, or expose a tool catalog that does not match the permissions used for the call.

The lifecycle invariant should be that one McpToolset computes the same effective credential identity for tools/list and tools/call from the same runtime context.

Relevant code

Discovery (mcp_toolset.py):

headers.update(provider_headers)
...
headers.update(auth_headers)  # auth wins

Invocation (mcp_tool.py):

headers.update(auth_headers)
...
headers.update(dynamic_headers)  # provider wins

MCPSessionManager._generate_session_key() hashes the merged headers, so the differing effective value is observable in pooling behavior as well as on the wire.

Reproduction

  1. Configure a streamable HTTP McpToolset with an auth credential that resolves to:

    {"Authorization": "Bearer AUTH"}
  2. Configure its header_provider to return:

    {"Authorization": "Bearer PROVIDER"}
  3. Call get_tools() and capture the headers passed to create_session(): discovery uses Bearer AUTH.

  4. Invoke one of the returned tools and capture the headers passed to create_session(): invocation uses Bearer PROVIDER.

  5. The two header dictionaries also produce different _session_key_for() values.

Existing tests cover provider and auth headers on disjoint keys, but not this duplicate-key case.

Expected behavior

Discovery and invocation should share one header-merge implementation and one documented precedence rule.

My initial preference is for the explicit auth credential to win over a duplicate provider key, matching the discovery path and keeping the credential resolved for the toolset authoritative across both tools/list and tools/call. The core correctness requirement is consistency; I am happy to follow maintainer guidance if provider precedence is intended instead.

Proposed test coverage

  • A duplicate-key test proving discovery and invocation produce the same effective headers and session key.
  • A disjoint-key test preserving the current behavior where both sources are merged.
  • Coverage for both synchronous and asynchronous header_provider callbacks.

Activity

  1. CorgiBoyG commented on Oct 15, 2026

    @CorgiBoyG
    ContributorAuthor

    I'd like to work on this. I've reproduced the discovery/invocation header divergence on current main and will prepare focused regression coverage plus a minimal fix. My proposed starting point is to make the explicit auth credential win consistently, but I'll follow maintainer guidance if provider precedence is intended.

  2. added
    mcp[Component] This issues is related to MCP support
    on Oct 15, 2026
  3. sanketpatil06 commented on Oct 15, 2026

    @sanketpatil06

    Thanks @CorgiBoyG. Confirmed the issue, and #7550 fixes it. All MCP unit tests pass, and discovery and invocation now use the same headers. The PR is under review and will be merged once approved.

  4. CorgiBoyG commented on Oct 15, 2026

    @CorgiBoyG
    ContributorAuthor

    Thank you, @sanketpatil06, for confirming the issue and reviewing the fix. I appreciate you taking the time to validate it.

    I did one more careful pass before updating #7550 and caught an OAuth2 edge case: a credential object without an access token could produce Authorization: Bearer None and override a valid provider header once auth became authoritative. The PR now treats that as unusable auth, preserves the provider header, and includes a regression test for the path.

    The updated verification is:

    • MCP suite: 441 passed
    • tools + auth suites: 3,208 passed, 1 skipped
    • formatting and static checks: passed

    Thanks again for the confirmation and for helping move this forward.

  5. added a commit that references this issue on Oct 16, 2026
    f82aa22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

mcp[Component] This issues is related to MCP support

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions