Skip to content

FastMCPProxy servers trigger Claude Desktop's "Authentication required to use this tool" prompt on first tool call, plain FastMCP servers don't #3996

@7robots

Description

@7robots

What happened?

Summary

Two FastMCP servers with otherwise-identical configuration behave differently under Claude Desktop's MCP OAuth flow. A server built with
create_proxy() (returning FastMCPProxy) triggers a per-first-tool-use "Authentication required to use this tool" confirmation dialog in Claude
Desktop after successful OAuth-at-connect. A plain FastMCP server with the same Okta OIDC auth wiring does not. I've isolated the trigger to the
FastMCPProxy / ProxyProvider shape by elimination.

Expected behavior

After a user authenticates once at connection time via OAuth, subsequent tool calls in that session should use the cached token silently — as plain
FastMCP servers do.

Actual behavior

On a FastMCPProxy server:

  1. User clicks "Connect" in Claude Desktop → browser OAuth flow → success.
  2. User sends a message that invokes any tool → Claude Desktop shows:
    ▎ Claude wants to use <tool_name> from
    ▎ Request {}
    ▎ Authentication required to use this tool
  3. User clicks through → tool runs.
  4. Subsequent tool calls in the same session do not re-prompt.

The per-first-tool prompt is persistent across disconnect/quit/reopen/reconnect cycles and is unaffected by Claude Desktop's "Always allow"
permission setting for that tool.

Reproduction

Two deployed servers on fastmcp.cloud, both behind the same Okta OIDC app using identical MultiAuth(server=OIDCProxy,
verifiers=[IntrospectionTokenVerifier]) wiring:

Server A — FastMCPProxy (reproduces the prompt):
mcp = create_proxy(
MCPConfigTransportSubclass(build_proxy_config(BACKENDS_PATH)),
name="mcp-docs-server",
instructions="...",
auth=_create_auth(),
transforms=[CodeMode(discovery_tools=[...])],
)

Server B — plain FastMCP (no prompt):
mcp = FastMCP(
"moon-d1-mcp",
instructions="...",
auth=_create_auth(),
)

@mcp.tool()
async def moon_list_features(...): ...

Both servers are connected to the same Claude Desktop, same account, same browser session. The tool call on A prompts; the tool call on B does not.

What I've ruled out

  • CodeMode transform — disabled it temporarily; prompt still fired on directly-invoked proxied backend tools (cloudflare_*).
  • fastmcp version — tested both directions. Upgrading Server B from 3.1.1 to 3.2.4 didn't introduce the prompt. Downgrading Server A from 3.2.4 to
    3.1.1 didn't remove it. Version is not the cause.
  • Tool metadata / annotations — fastmcp inspect --format mcp output for both servers shows annotations: null and _meta: { fastmcp: { tags: [] } } on
    every tool. No requiresAuth-style fields on either side.
  • Backend 401 + WWW-Authenticate propagation — I patched this separately by subclassing MCPConfigTransport to set forward_incoming_headers = False
    after _create_proxy(); that fixed a different-but-related issue where AWS/Cloudflare docs backends were 401'ing on the forwarded Okta token and
    triggering a separate per-backend OAuth dance. That fix is in place and the symptom under discussion remains.
  • Claude Desktop per-tool consent model — "Always allow" in the tool permissions UI does not suppress the prompt, so it isn't standard per-tool
    consent.

Diagnostic observation

fastmcp.cloud server logs show no incoming HTTP request at the moment the "Authentication required" dialog appears — the prompt is emitted
client-side, pre-emptively, before any tools/call reaches the server. This suggests Claude Desktop's MCP client is deciding tool-level auth is
required based on something it read from the server's initialize / tools/list response, rather than reacting to a runtime 401.

Environment

  • fastmcp: 3.2.4
  • mcp SDK: 1.27.0
  • Deploy target: fastmcp.cloud (streamable-http, stateless)
  • Client: Claude Desktop (macOS), current version
  • Auth: OIDCProxy wrapping Okta, MultiAuth(server=..., verifiers=[IntrospectionTokenVerifier])

Hypothesis

Something in FastMCPProxy / ProxyProvider's capability-advertisement or initialize-response shape is cueing Claude Desktop to treat tool calls as
needing their own auth step, even when the server's Okta gate already holds a valid session token. Since we've excluded version, metadata, transform,
and config-level differences, this points at the create_proxy() code path itself.

Example Code

Version Information

FastMCP version:                                                           3.2.4
MCP version:                                                              1.27.0
Python version:                                                           3.13.6

Metadata

Metadata

Assignees

No one assigned

    Labels

    authRelated to authentication (Bearer, JWT, OAuth, WorkOS) for client or server.bugSomething isn't working. Reports of errors, unexpected behavior, or broken functionality.serverRelated to FastMCP server implementation or server-side functionality.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions