From de91c813731e188fa5a82a736f93be763ff30805 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 14 Aug 2026 11:21:33 +0530 Subject: [PATCH 1/4] deprecate(mcp): --mcp-authorizer-url in favour of --url MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two feed different mechanisms. --url sets the trusted URL, which GetHostFromRequest returns before it looks at any header; --mcp-authorizer-url only stamps `x-authorizer-url` metadata. Now that --url is required (#764) and inherited by the subcommand, it always wins — so --mcp-authorizer-url is inert wherever --url is set. That is the reason to deprecate rather than leave it: passing both is not an error and warns about nothing, so a divergent value looks configured and silently does nothing. examples/with-agent-permissions already passes both, so the flag is dead there today. Not removed. It still reaches the header path when `authorizer mcp` runs without --url, and breaking a 2.3.x stdio setup in a minor release to delete a flag whose whole subcommand goes in 2.5.0 buys nothing. --- CHANGELOG.md | 1 + cmd/mcp.go | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0569eda2..aaebe607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ Targets the 2.4.0 release. Significant additions include enterprise SSO (SAML Id ### Deprecated +- **`--mcp-authorizer-url`** — superseded by `--url`, removed in 2.5.0 with the subcommand it belongs to. The two were never equal partners: `--url` sets the trusted URL and is consulted *before* any header, while `--mcp-authorizer-url` only stamps an `x-authorizer-url` header. Now that `--url` is required, it always wins — so `--mcp-authorizer-url` is **inert whenever `--url` is set**, which is the trap it was worth deprecating for: passing both is not an error and warns about nothing, so a divergent value looks configured and does nothing. It keeps working for `authorizer mcp` invoked without `--url` (the one case that still reaches the header path) and now prints a deprecation notice. Drop it and pass `--url`. - **`authorizer mcp` (stdio transport)** — superseded by `--mcp-enabled`, removed in 2.5.0. The stdio subcommand ran a second copy of every provider (storage, memory store, embedded FGA engine) alongside the real server, and its identity was a single process-wide `--mcp-bearer`, so one process could only ever serve one user. Both are gone with the HTTP transport: the MCP surface shares the running server's providers, and every request carries its own token. The subcommand keeps working and now prints a deprecation notice. ### Security diff --git a/cmd/mcp.go b/cmd/mcp.go index 85241421..92a77f6f 100644 --- a/cmd/mcp.go +++ b/cmd/mcp.go @@ -90,6 +90,26 @@ func init() { "(e.g. https://auth.example.com). Required with --mcp-bearer: "+ "JWT issuer validation compares the token's iss claim against "+ "this value.") + // Superseded by --url, which the root command makes REQUIRED as of 2.4.0 + // and which this subcommand inherits. + // + // The two feed different mechanisms and are not equal partners: --url sets + // the trusted URL, and GetHostFromRequest returns it before it ever looks + // at a header, while --mcp-authorizer-url only stamps `x-authorizer-url` + // metadata — a header. So once --url is set, this flag is INERT. Passing + // both is not an error and produces no warning, which is the trap: a + // divergent --mcp-authorizer-url looks configured and does nothing. + // + // Kept working for the one case that still reaches the header path — this + // subcommand invoked without --url — so a 2.3.x stdio setup is not broken + // by a minor release. The whole subcommand goes in 2.5.0 regardless. + if err := mcpCmd.Flags().MarkDeprecated("mcp-authorizer-url", + "use --url instead. --url is required as of 2.4.0 and takes precedence, "+ + "so this flag is ignored whenever --url is set."); err != nil { + // Only fails when the flag name does not exist, which is a + // programming error in the line directly above. + panic(err) + } RootCmd.AddCommand(mcpCmd) } From 9f336c875fbafed0501d12ef3b0b21e5e502c249 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 14 Aug 2026 11:26:05 +0530 Subject: [PATCH 2/4] fix(mcp): honour --url in the stdio subcommand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `authorizer mcp` inherits the root flag set, so --url was always accepted there — but parsers.SetTrustedURL was only ever called from runRoot. The flag therefore did nothing in this path: issuer validation stayed on header derivation, and --mcp-authorizer-url was the only mechanism that worked. Caught by deleting --mcp-authorizer-url from the example on the theory that it was already redundant. It was not; the stdio probe failed with `rpc error: code = Unauthenticated`. runMCP now pins the trusted URL as the server does, and the same probe passes with --url alone. This is what makes the deprecation in the previous commit true rather than assumed. --- CHANGELOG.md | 3 ++- cmd/mcp.go | 40 +++++++++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aaebe607..7075f318 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,7 +70,7 @@ Targets the 2.4.0 release. Significant additions include enterprise SSO (SAML Id ### Deprecated -- **`--mcp-authorizer-url`** — superseded by `--url`, removed in 2.5.0 with the subcommand it belongs to. The two were never equal partners: `--url` sets the trusted URL and is consulted *before* any header, while `--mcp-authorizer-url` only stamps an `x-authorizer-url` header. Now that `--url` is required, it always wins — so `--mcp-authorizer-url` is **inert whenever `--url` is set**, which is the trap it was worth deprecating for: passing both is not an error and warns about nothing, so a divergent value looks configured and does nothing. It keeps working for `authorizer mcp` invoked without `--url` (the one case that still reaches the header path) and now prints a deprecation notice. Drop it and pass `--url`. +- **`--mcp-authorizer-url`** — superseded by `--url`, removed in 2.5.0 with the subcommand it belongs to. The two feed different mechanisms: `--url` sets the trusted URL, which is consulted *before* any header, while `--mcp-authorizer-url` only stamps an `x-authorizer-url` header. `authorizer mcp` now honours `--url` (see Fixed), so the two agree by construction and the older flag is redundant. It still works when the subcommand runs without `--url`, and now prints a deprecation notice. Drop it and pass `--url`. - **`authorizer mcp` (stdio transport)** — superseded by `--mcp-enabled`, removed in 2.5.0. The stdio subcommand ran a second copy of every provider (storage, memory store, embedded FGA engine) alongside the real server, and its identity was a single process-wide `--mcp-bearer`, so one process could only ever serve one user. Both are gone with the HTTP transport: the MCP surface shares the running server's providers, and every request carries its own token. The subcommand keeps working and now prints a deprecation notice. ### Security @@ -95,6 +95,7 @@ Targets the 2.4.0 release. Significant additions include enterprise SSO (SAML Id ### Fixed +- **`authorizer mcp` silently ignored `--url`**: the subcommand inherits the root flag set, so `--url` was always *accepted* there, but `parsers.SetTrustedURL` was only called from the server's own startup path. Passing `--url` to the stdio MCP subcommand therefore looked configured and did nothing, leaving issuer validation on header derivation and making `--mcp-authorizer-url` the only mechanism that worked. `runMCP` now pins the trusted URL exactly as the server does. A flag that is accepted and ignored is worse than one that is rejected. - **Public client_id now exposed in Client API type**: `Client` GraphQL type and proto now include `client_id` (distinct from surrogate `id`). Dashboard Clients page displays correct "Client ID" (the configured client_id, not the internal id). Seeded interactive client has immutable `client_id` from `--client-id` flag ([#664](https://github.com/authorizerdev/authorizer/pull/664)). - **Nil-pointer panics in claim/header type assertions**: two unguarded type assertions on untrusted map values (email-verify redirect-uri and webhook-event headers) could panic and crash the process; now guarded with safe type coercion ([#701](https://github.com/authorizerdev/authorizer/pull/701)). - **Dashboard and login UI crashes**: CSV file import error handling in dashboard, non-null assertion guards in InputField component, logout button event handling, and WCAG label association for home realm discovery email input ([#702](https://github.com/authorizerdev/authorizer/pull/702)). diff --git a/cmd/mcp.go b/cmd/mcp.go index 92a77f6f..af8fab09 100644 --- a/cmd/mcp.go +++ b/cmd/mcp.go @@ -18,6 +18,7 @@ import ( "github.com/authorizerdev/authorizer/internal/grpcsrv" "github.com/authorizerdev/authorizer/internal/mcp" "github.com/authorizerdev/authorizer/internal/memory_store" + "github.com/authorizerdev/authorizer/internal/parsers" "github.com/authorizerdev/authorizer/internal/service" "github.com/authorizerdev/authorizer/internal/sms" "github.com/authorizerdev/authorizer/internal/storage" @@ -93,19 +94,21 @@ func init() { // Superseded by --url, which the root command makes REQUIRED as of 2.4.0 // and which this subcommand inherits. // - // The two feed different mechanisms and are not equal partners: --url sets - // the trusted URL, and GetHostFromRequest returns it before it ever looks - // at a header, while --mcp-authorizer-url only stamps `x-authorizer-url` - // metadata — a header. So once --url is set, this flag is INERT. Passing - // both is not an error and produces no warning, which is the trap: a - // divergent --mcp-authorizer-url looks configured and does nothing. + // The two feed different mechanisms: --url sets the trusted URL, which + // GetHostFromRequest returns before it reads any header, while + // --mcp-authorizer-url only stamps an `x-authorizer-url` header. Until + // runMCP started calling SetTrustedURL (same commit as this deprecation), + // --url was accepted here and silently did nothing, so this flag was the + // only mechanism that worked in the stdio path — which is why it cannot + // simply be deleted. // - // Kept working for the one case that still reaches the header path — this - // subcommand invoked without --url — so a 2.3.x stdio setup is not broken - // by a minor release. The whole subcommand goes in 2.5.0 regardless. + // Now that --url is honoured here, the two agree by construction and this + // flag is redundant whenever --url is set. It still reaches the header + // path when the subcommand runs without --url, so it keeps working; the + // whole subcommand goes in 2.5.0 regardless. if err := mcpCmd.Flags().MarkDeprecated("mcp-authorizer-url", - "use --url instead. --url is required as of 2.4.0 and takes precedence, "+ - "so this flag is ignored whenever --url is set."); err != nil { + "use --url instead. --url is required as of 2.4.0 and is honoured by "+ + "this subcommand, so it makes this flag redundant."); err != nil { // Only fails when the flag name does not exist, which is a // programming error in the line directly above. panic(err) @@ -123,6 +126,21 @@ func runMCP(_ *cobra.Command, _ []string) { log.Warn().Msg("`authorizer mcp` (stdio) is deprecated and will be removed in 2.5.0 — " + "run the server with --mcp-enabled and connect to POST /mcp instead") + // Honour --url here as the server does. This subcommand inherits the root + // flag set, so --url has always been ACCEPTED here — but SetTrustedURL was + // only ever called from runRoot, so it silently did nothing, and + // --mcp-authorizer-url (which stamps an `x-authorizer-url` header) was the + // only thing that worked. A flag that is accepted and ignored is worse than + // one that is rejected: `authorizer mcp --url=https://auth.example.com` + // looked configured and left issuer validation on header derivation. + // + // Setting it here is also what makes --mcp-authorizer-url genuinely + // redundant rather than redundant-in-theory: GetHostFromRequest returns the + // trusted URL before it reads any header, so with --url set the two agree + // by construction instead of by the operator passing the same value twice. + parsers.SetTrustedURL(rootArgs.config.AuthorizerURL) + parsers.SetLogger(&log) + // Wire all subsystems an MCP-exposed tool might need. As more ops // migrate into internal/service, this list stays the same — the // service-provider dependencies don't change per op, only the methods From 4a3b1af74839cb0536b07b963aee73d8dc2c924a Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 14 Aug 2026 11:33:41 +0530 Subject: [PATCH 3/4] feat(mcp)!: remove --mcp-authorizer-url in 2.4.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE: --mcp-authorizer-url is gone; pass --url instead. The flag shipped in 2.3.0, so this is breaking for stdio MCP users. It lands in 2.4.0 rather than 2.5.0 because 2.4.0 already forces every deployment to add --url (#764) — the invocation is being edited anyway, so this rides along instead of costing a second migration. --url now does the job here: runMCP pins the trusted URL, which GetHostFromRequest consults before any header, and mcp.Options takes its value. Verified end to end with a live server via `node mcp-agent.mjs --verify` — all six assertions pass with the flag absent. Also refuses --mcp-bearer without --url at startup. That combination previously failed later as a bare `Unauthenticated` on every tool call, which reads as a bad token rather than missing config. --- CHANGELOG.md | 2 +- cmd/mcp.go | 66 ++++++++++++++++++---------------------------------- 2 files changed, 24 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7075f318..754ae22c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,7 +70,6 @@ Targets the 2.4.0 release. Significant additions include enterprise SSO (SAML Id ### Deprecated -- **`--mcp-authorizer-url`** — superseded by `--url`, removed in 2.5.0 with the subcommand it belongs to. The two feed different mechanisms: `--url` sets the trusted URL, which is consulted *before* any header, while `--mcp-authorizer-url` only stamps an `x-authorizer-url` header. `authorizer mcp` now honours `--url` (see Fixed), so the two agree by construction and the older flag is redundant. It still works when the subcommand runs without `--url`, and now prints a deprecation notice. Drop it and pass `--url`. - **`authorizer mcp` (stdio transport)** — superseded by `--mcp-enabled`, removed in 2.5.0. The stdio subcommand ran a second copy of every provider (storage, memory store, embedded FGA engine) alongside the real server, and its identity was a single process-wide `--mcp-bearer`, so one process could only ever serve one user. Both are gone with the HTTP transport: the MCP surface shares the running server's providers, and every request carries its own token. The subcommand keeps working and now prints a deprecation notice. ### Security @@ -105,6 +104,7 @@ Targets the 2.4.0 release. Significant additions include enterprise SSO (SAML Id ### Removed +- **BREAKING — `--mcp-authorizer-url` removed.** Use `--url` instead. The two fed different mechanisms: `--url` sets the trusted URL, consulted *before* any header, while `--mcp-authorizer-url` only stamped an `x-authorizer-url` header. `authorizer mcp` now honours `--url` (see Fixed), so the older flag has no remaining job. Shipped in 2.3.0, so this is a breaking change for stdio MCP users — but 2.4.0 already requires every deployment to add `--url`, so the invocation is being edited anyway: drop `--mcp-authorizer-url` and keep the same value on `--url`. Passing it now fails with `unknown flag`. Setting `--mcp-bearer` without `--url` is refused at startup with an explanatory message, rather than failing later as a bare `Unauthenticated` on every tool call. - **`authorizer_client_id_not_found_total`**: replaced by **`authorizer_client_id_header_missing_total`**, which matches the actual behavior (header omitted, request still allowed). Update dashboards and alerts accordingly. - **OIDC Discovery — `registration_endpoint`**: previously pointed to the signup UI rather than an RFC 7591 dynamic client registration endpoint. (It returns in 2.4.0, pointing at a real RFC 7591 endpoint and only when `--enable-dynamic-client-registration` is set.) diff --git a/cmd/mcp.go b/cmd/mcp.go index af8fab09..5f4cbd99 100644 --- a/cmd/mcp.go +++ b/cmd/mcp.go @@ -4,6 +4,7 @@ import ( "context" "os" "os/signal" + "strings" "syscall" "github.com/rs/zerolog" @@ -35,10 +36,6 @@ var mcpArgs struct { // (`profile`, `check_permissions`, `list_permissions`) won't have a // caller to attribute to. bearer string - // authorizerURL is the public URL of the Authorizer instance that - // minted the bearer token; stamped as `x-authorizer-url` so JWT issuer - // validation passes for identity-bearing tools. - authorizerURL string } // mcpCmd serves Authorizer's MCP surface over stdio. Designed to be wired @@ -86,33 +83,6 @@ func init() { "user identity for tools like Profile / Permissions / Session). "+ "When unset the MCP server runs anonymously; public tools (Meta) "+ "still work but identity-bearing tools will fail authn.") - mcpCmd.Flags().StringVar(&mcpArgs.authorizerURL, "mcp-authorizer-url", "", - "Public URL of the Authorizer instance that issued --mcp-bearer "+ - "(e.g. https://auth.example.com). Required with --mcp-bearer: "+ - "JWT issuer validation compares the token's iss claim against "+ - "this value.") - // Superseded by --url, which the root command makes REQUIRED as of 2.4.0 - // and which this subcommand inherits. - // - // The two feed different mechanisms: --url sets the trusted URL, which - // GetHostFromRequest returns before it reads any header, while - // --mcp-authorizer-url only stamps an `x-authorizer-url` header. Until - // runMCP started calling SetTrustedURL (same commit as this deprecation), - // --url was accepted here and silently did nothing, so this flag was the - // only mechanism that worked in the stdio path — which is why it cannot - // simply be deleted. - // - // Now that --url is honoured here, the two agree by construction and this - // flag is redundant whenever --url is set. It still reaches the header - // path when the subcommand runs without --url, so it keeps working; the - // whole subcommand goes in 2.5.0 regardless. - if err := mcpCmd.Flags().MarkDeprecated("mcp-authorizer-url", - "use --url instead. --url is required as of 2.4.0 and is honoured by "+ - "this subcommand, so it makes this flag redundant."); err != nil { - // Only fails when the flag name does not exist, which is a - // programming error in the line directly above. - panic(err) - } RootCmd.AddCommand(mcpCmd) } @@ -126,21 +96,31 @@ func runMCP(_ *cobra.Command, _ []string) { log.Warn().Msg("`authorizer mcp` (stdio) is deprecated and will be removed in 2.5.0 — " + "run the server with --mcp-enabled and connect to POST /mcp instead") - // Honour --url here as the server does. This subcommand inherits the root - // flag set, so --url has always been ACCEPTED here — but SetTrustedURL was - // only ever called from runRoot, so it silently did nothing, and - // --mcp-authorizer-url (which stamps an `x-authorizer-url` header) was the - // only thing that worked. A flag that is accepted and ignored is worse than - // one that is rejected: `authorizer mcp --url=https://auth.example.com` - // looked configured and left issuer validation on header derivation. + // Honour --url here as the server does. // - // Setting it here is also what makes --mcp-authorizer-url genuinely - // redundant rather than redundant-in-theory: GetHostFromRequest returns the - // trusted URL before it reads any header, so with --url set the two agree - // by construction instead of by the operator passing the same value twice. + // This subcommand inherits the root flag set, so --url was always ACCEPTED + // here — but SetTrustedURL was only ever called from runRoot, so it silently + // did nothing, and the removed --mcp-authorizer-url (which stamped an + // `x-authorizer-url` header) was the only mechanism that worked. A flag that + // is accepted and ignored is worse than one that is rejected: + // `authorizer mcp --url=https://auth.example.com` looked configured and left + // issuer validation on header derivation. Wiring it here is what let the + // older flag go in 2.4.0 rather than lingering to 2.5.0. parsers.SetTrustedURL(rootArgs.config.AuthorizerURL) parsers.SetLogger(&log) + // Identity-bearing tools validate the bearer's `iss` against this server's + // own URL. With no --url there is nothing to compare against — and no HTTP + // request to derive a host from either, since tool calls arrive as gRPC + // metadata — so every such call fails with a bare `Unauthenticated` that + // looks like a bad token rather than missing config. Say so up front. + if mcpArgs.bearer != "" && strings.TrimSpace(rootArgs.config.AuthorizerURL) == "" { + log.Fatal().Msg("--url is required with --mcp-bearer: identity-bearing tools " + + "validate the token's iss claim against this server's own URL, and without " + + "it every tool call fails as Unauthenticated. Pass --url=, e.g. https://auth.example.com") + } + // Wire all subsystems an MCP-exposed tool might need. As more ops // migrate into internal/service, this list stays the same — the // service-provider dependencies don't change per op, only the methods @@ -244,7 +224,7 @@ func runMCP(_ *cobra.Command, _ []string) { Name: "authorizer", Version: constants.VERSION, Bearer: mcpArgs.bearer, - AuthorizerURL: mcpArgs.authorizerURL, + AuthorizerURL: rootArgs.config.AuthorizerURL, }) if err != nil { log.Fatal().Err(err).Msg("failed to create mcp server") From bed3b3e7eabd46a3fd9138e8c3f7d7788d117037 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 14 Aug 2026 11:36:22 +0530 Subject: [PATCH 4/4] deprecate(mcp): make --mcp-authorizer-url inert, not removed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts the deletion in 4a3b1af7. The flag stays parsed so a 2.3.x invocation keeps starting instead of dying on `unknown flag`, but nothing reads it — --url supplies the value now that runMCP pins the trusted URL. Verified inert end to end: with --url correct and --mcp-authorizer-url=https://WRONG.example, all six assertions of `node mcp-agent.mjs --verify` still pass against a live server. Before the runMCP fix that wrong value would have broken issuer validation. Drops the flag from internal/e2e/smoke_test.go, which now passes --url instead — the same migration the deprecation notice prescribes. --- CHANGELOG.md | 2 +- cmd/mcp.go | 39 +++++++++++++++++++++++++++++++++----- internal/e2e/smoke_test.go | 4 +++- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 754ae22c..e3b0ba25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ Targets the 2.4.0 release. Significant additions include enterprise SSO (SAML Id ### Deprecated +- **`--mcp-authorizer-url`** — **has no effect as of 2.4.0**; pass `--url` with the same value instead. The two fed different mechanisms: `--url` sets the trusted URL, consulted *before* any header, while this flag only stamped an `x-authorizer-url` header. `authorizer mcp` now honours `--url` (see Fixed), which supplies the value outright — so the flag is read by nothing and a stale or wrong value can no longer affect issuer validation. It is still **parsed**, so a 2.3.x invocation keeps starting rather than dying on `unknown flag`; it warns and is ignored. Removed in 2.5.0 with the subcommand. Setting `--mcp-bearer` without `--url` is now refused at startup with an explanatory message, rather than failing later as a bare `Unauthenticated` on every tool call. - **`authorizer mcp` (stdio transport)** — superseded by `--mcp-enabled`, removed in 2.5.0. The stdio subcommand ran a second copy of every provider (storage, memory store, embedded FGA engine) alongside the real server, and its identity was a single process-wide `--mcp-bearer`, so one process could only ever serve one user. Both are gone with the HTTP transport: the MCP surface shares the running server's providers, and every request carries its own token. The subcommand keeps working and now prints a deprecation notice. ### Security @@ -104,7 +105,6 @@ Targets the 2.4.0 release. Significant additions include enterprise SSO (SAML Id ### Removed -- **BREAKING — `--mcp-authorizer-url` removed.** Use `--url` instead. The two fed different mechanisms: `--url` sets the trusted URL, consulted *before* any header, while `--mcp-authorizer-url` only stamped an `x-authorizer-url` header. `authorizer mcp` now honours `--url` (see Fixed), so the older flag has no remaining job. Shipped in 2.3.0, so this is a breaking change for stdio MCP users — but 2.4.0 already requires every deployment to add `--url`, so the invocation is being edited anyway: drop `--mcp-authorizer-url` and keep the same value on `--url`. Passing it now fails with `unknown flag`. Setting `--mcp-bearer` without `--url` is refused at startup with an explanatory message, rather than failing later as a bare `Unauthenticated` on every tool call. - **`authorizer_client_id_not_found_total`**: replaced by **`authorizer_client_id_header_missing_total`**, which matches the actual behavior (header omitted, request still allowed). Update dashboards and alerts accordingly. - **OIDC Discovery — `registration_endpoint`**: previously pointed to the signup UI rather than an RFC 7591 dynamic client registration endpoint. (It returns in 2.4.0, pointing at a real RFC 7591 endpoint and only when `--enable-dynamic-client-registration` is set.) diff --git a/cmd/mcp.go b/cmd/mcp.go index 5f4cbd99..3b73d44f 100644 --- a/cmd/mcp.go +++ b/cmd/mcp.go @@ -36,6 +36,12 @@ var mcpArgs struct { // (`profile`, `check_permissions`, `list_permissions`) won't have a // caller to attribute to. bearer string + // authorizerURL is DEPRECATED and has NO EFFECT as of 2.4.0. It is still + // parsed so that an existing 2.3.x invocation keeps starting instead of + // dying on `unknown flag`, but nothing reads it — the value is supplied by + // --url, which runMCP now pins as the trusted URL. Kept as a named field + // rather than discarded so the flag registration below stays readable. + authorizerURL string } // mcpCmd serves Authorizer's MCP surface over stdio. Designed to be wired @@ -83,6 +89,29 @@ func init() { "user identity for tools like Profile / Permissions / Session). "+ "When unset the MCP server runs anonymously; public tools (Meta) "+ "still work but identity-bearing tools will fail authn.") + + // DEPRECATED and INERT as of 2.4.0. Superseded by --url. + // + // The two fed different mechanisms: --url sets the trusted URL, which + // GetHostFromRequest consults before it reads any header, while this flag + // only stamped an `x-authorizer-url` header. Until runMCP started calling + // SetTrustedURL, --url was accepted here and silently did nothing, so this + // was the only mechanism that worked in the stdio path. + // + // Now that --url is honoured here it supplies the value outright, and this + // flag is read by nothing. Still PARSED rather than deleted so a 2.3.x + // invocation keeps starting instead of dying on `unknown flag` — it warns + // and is ignored. Goes with the subcommand in 2.5.0. + mcpCmd.Flags().StringVar(&mcpArgs.authorizerURL, "mcp-authorizer-url", "", + "DEPRECATED and ignored — use --url instead.") + if err := mcpCmd.Flags().MarkDeprecated("mcp-authorizer-url", + "it has NO EFFECT as of 2.4.0 — pass --url with the same value instead. "+ + "--url is required for the server and is honoured by this subcommand, "+ + "and is what the token's iss claim is validated against."); err != nil { + // Only fails when the flag name does not exist, which is a + // programming error in the line directly above. + panic(err) + } RootCmd.AddCommand(mcpCmd) } @@ -100,12 +129,12 @@ func runMCP(_ *cobra.Command, _ []string) { // // This subcommand inherits the root flag set, so --url was always ACCEPTED // here — but SetTrustedURL was only ever called from runRoot, so it silently - // did nothing, and the removed --mcp-authorizer-url (which stamped an - // `x-authorizer-url` header) was the only mechanism that worked. A flag that - // is accepted and ignored is worse than one that is rejected: + // did nothing, and --mcp-authorizer-url (which stamped an `x-authorizer-url` + // header) was the only mechanism that worked. A flag that is accepted and + // ignored is worse than one that is rejected: // `authorizer mcp --url=https://auth.example.com` looked configured and left - // issuer validation on header derivation. Wiring it here is what let the - // older flag go in 2.4.0 rather than lingering to 2.5.0. + // issuer validation on header derivation. Wiring it here is what lets + // --mcp-authorizer-url become inert without breaking the stdio path. parsers.SetTrustedURL(rootArgs.config.AuthorizerURL) parsers.SetLogger(&log) diff --git a/internal/e2e/smoke_test.go b/internal/e2e/smoke_test.go index 2e0d3d7a..1f64bf05 100644 --- a/internal/e2e/smoke_test.go +++ b/internal/e2e/smoke_test.go @@ -493,8 +493,10 @@ func TestReleaseSmoke(t *testing.T) { "--jwt-type=HS256", "--jwt-secret=" + smokeJWTSecret, "--admin-secret=" + smokeAdminSecret, "--client-id=" + smokeClientID, "--client-secret=" + smokeClientSecret, + // Replaces --mcp-authorizer-url, inert as of 2.4.0. This is what the + // bearer's iss claim is validated against. + "--url=" + baseURL, "--mcp-bearer=" + token, - "--mcp-authorizer-url=" + baseURL, } mcp := startMCP(t, bin, mcpArgs)