{"slug": "the-well-known-openid-configuration-alias-that-makes-mcp-connectors-just-work", "title": "The /.well-known/openid-configuration Alias That Makes MCP Connectors Just Work", "summary": "A developer working on the cleaniquecoders/laravel-mcp-kit package fixed an issue where remote MCP connectors failed to discover an OAuth server because they probed the OpenID Connect discovery endpoint /.well-known/openid-configuration instead of the OAuth metadata endpoints. The solution adds a 308 redirect from the OIDC endpoint to the authorization-server metadata, ensuring clients can authenticate without relying on reverse-proxy configuration.", "body_md": "Here's a small thing that cost a disproportionate amount of head-scratching this week: a remote MCP connector kept failing to discover my OAuth server, even though the OAuth server was *right there* and working. The fix turned out to be one route and a 308 redirect — but the *why* behind it is a nice little lesson in how discovery specs drift apart in practice. So let me write it up.\n\nThis is in `cleaniquecoders/laravel-mcp-kit`\n\n, a public package, so all the code below is the real thing.\n\nWhen you expose an MCP server over streamable HTTP and want remote clients (like claude.ai) to authenticate, you turn on OAuth. `laravel/mcp`\n\ngives you a one-liner for the discovery documents:\n\n```\nMcp::oauthRoutes();\n```\n\nThat registers the two documents the OAuth 2.1 world expects:\n\n`/.well-known/oauth-authorization-server`\n\n— RFC 8414, authorization-server metadata.`/.well-known/oauth-protected-resource`\n\n— RFC 9728, protected-resource metadata.A header-less connector hits those, learns where your `authorization_endpoint`\n\n, `token_endpoint`\n\n, and `registration_endpoint`\n\nlive, self-registers via Dynamic Client Registration (RFC 7591), and away it goes. Clean.\n\nExcept some clients — and `laravel/mcp`\n\n's own client, in certain paths — don't probe the OAuth metadata. They probe `/.well-known/openid-configuration`\n\ninstead. That's the **OpenID Connect** discovery document. Different spec, same neighbourhood. And `oauthRoutes()`\n\ndoesn't register it, because OIDC is a superset of OAuth that the package isn't claiming to implement.\n\nSo you get this maddening situation: your OAuth server is correct and complete, but the client is knocking on a door you never built, getting a 404, and giving up.\n\nThe lazy fix is a reverse-proxy redirect — tell nginx to rewrite `/.well-known/openid-configuration`\n\nto the authorization-server doc. It works, but now your auth discovery depends on infra config that lives outside the app, isn't tested, and silently breaks the moment someone deploys to a host that doesn't have that rule. That's exactly the kind of \"works on my machine\" landmine I try to keep out of a package.\n\nThe right fix is to make the app itself answer the probe. Since the OIDC document these clients actually want overlaps almost entirely with the authorization-server metadata, I just alias one to the other:\n\n```\nif ($oauth) {\n    Mcp::oauthRoutes();\n\n    // oauthRoutes() registers the two OAuth discovery documents but not\n    // OpenID Connect discovery. Some connectors (and laravel/mcp's own\n    // client) still probe /.well-known/openid-configuration; alias it to\n    // the authorization-server metadata so hosts don't need a reverse-proxy\n    // redirect. 308 preserves the request for clients that follow it.\n    if (config('mcp-kit.web.oauth.openid_configuration', true)) {\n        Route::get(\n            '/.well-known/openid-configuration',\n            fn () => redirect()->route('mcp.oauth.authorization-server', [], 308)\n        )->name('mcp-kit.openid-configuration');\n    }\n}\n```\n\nTwo design decisions worth pausing on.\n\n**Why 308 and not 302.** A 302 invites a client to switch a POST into a GET on the redirect (historically that's exactly what 302 implementations did). A `308 Permanent Redirect`\n\nis the strict one: it tells the client \"go here instead, and keep your method and body exactly as they were.\" For a discovery probe it's GET-on-GET either way, but 308 is the honest semantic — the resource genuinely lives at the other URL, permanently, and I don't want any client quietly mangling the request. Use the redirect code that says what you actually mean.\n\n**Why it's behind a config flag.** The alias only makes sense when OAuth is on, so it's nested inside the `if ($oauth)`\n\nblock — no point aliasing to an authorization-server route that isn't registered. And it's *also* guarded by `config('mcp-kit.web.oauth.openid_configuration', true)`\n\n, defaulting to on, so a host that genuinely implements its own OIDC discovery can switch the alias off and not collide. Default to the behaviour that makes the common case work; leave an escape hatch for the host that knows better.\n\nThe whole point of doing this in-app instead of in nginx is that I can *test* it. Two Pest tests: one asserts the redirect itself, one follows the redirect and asserts the metadata that comes back actually has the shape a connector needs.\n\n```\nit('aliases openid-configuration to the authorization-server discovery', function () {\n    $this->get('/.well-known/openid-configuration')\n        ->assertStatus(308)\n        ->assertRedirect(route('mcp.oauth.authorization-server'));\n});\n\nit('serves the same metadata once the openid-configuration alias is followed', function () {\n    $this->followingRedirects()\n        ->get('/.well-known/openid-configuration')\n        ->assertOk()\n        ->assertJsonStructure([\n            'issuer',\n            'authorization_endpoint',\n            'token_endpoint',\n            'registration_endpoint',\n            'code_challenge_methods_supported',\n            'grant_types_supported',\n        ]);\n});\n```\n\nThe first test pins the *mechanism* (the 308 to the right named route). The second pins the *contract* — `followingRedirects()`\n\nwalks the alias all the way through, and the `assertJsonStructure`\n\nchecks that a real connector following this path lands on a document with the keys it needs to self-register: where to authorize, where to get a token, where to register, and crucially `code_challenge_methods_supported`\n\n(PKCE) and `grant_types_supported`\n\n. If `laravel/mcp`\n\never changes the metadata shape under me, that second test goes red and I find out at CI, not when claude.ai can't connect.\n\nThat split — one test for the wiring, one test for the contract — is a habit I keep coming back to. The mechanism test stops *me* from breaking the route. The contract test stops a *dependency* from breaking the consumer.\n\nDiscovery specs that \"should\" be interchangeable rarely are in practice. OAuth 2.1 and OpenID Connect share a `/.well-known/`\n\nneighbourhood and a lot of vocabulary, but clients pick whichever door they were built to knock on. When you're the server, the friendliest thing you can do is answer *both* doors and point them at the same room — in the app, with a redirect code that means what it says, behind a flag, and pinned with a test. No reverse-proxy magic, no host-specific config, nothing that breaks on the next deploy.\n\nWhat's next: the same treatment for `/.well-known/oauth-protected-resource`\n\nedge cases that a couple of stricter clients probe with query parameters. Same idea, slightly more fiddly.", "url": "https://wpnews.pro/news/the-well-known-openid-configuration-alias-that-makes-mcp-connectors-just-work", "canonical_source": "https://dev.to/nasrulhazim/the-well-knownopenid-configuration-alias-that-makes-mcp-connectors-just-work-27j2", "published_at": "2026-06-18 12:09:27+00:00", "updated_at": "2026-06-18 12:21:38.551958+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["cleaniquecoders/laravel-mcp-kit", "laravel/mcp", "OAuth", "OpenID Connect", "RFC 8414", "RFC 9728", "RFC 7591", "Claude.ai"], "alternates": {"html": "https://wpnews.pro/news/the-well-known-openid-configuration-alias-that-makes-mcp-connectors-just-work", "markdown": "https://wpnews.pro/news/the-well-known-openid-configuration-alias-that-makes-mcp-connectors-just-work.md", "text": "https://wpnews.pro/news/the-well-known-openid-configuration-alias-that-makes-mcp-connectors-just-work.txt", "jsonld": "https://wpnews.pro/news/the-well-known-openid-configuration-alias-that-makes-mcp-connectors-just-work.jsonld"}}