Skip to content

fix(mcp): honour --url in stdio, make --mcp-authorizer-url inert - #768

Merged
lakhansamani merged 4 commits into
mainfrom
deprecate/mcp-authorizer-url
Aug 14, 2026
Merged

fix(mcp): honour --url in stdio, make --mcp-authorizer-url inert#768
lakhansamani merged 4 commits into
mainfrom
deprecate/mcp-authorizer-url

Conversation

@lakhansamani

@lakhansamani lakhansamani commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What

Three things, in the order that makes each one true:

  1. fix(mcp)authorizer mcp now honours --url.
  2. deprecate(mcp)--mcp-authorizer-url is now inert: still accepted, warns, read by nothing.
  3. Drops its usage from internal/e2e/smoke_test.go, which passes --url instead.

The bug that made this possible

authorizer mcp inherits the root flag set, so --url was always accepted there. But parsers.SetTrustedURL was only ever called from runRoot:

$ grep -rn "SetTrustedURL" --include=*.go . | grep -v _test
cmd/root.go:545:  parsers.SetTrustedURL(rootArgs.config.AuthorizerURL)

So inside the stdio subcommand trustedURL stayed empty, GetHostFromRequest fell through to header derivation, and --url did nothing. authorizer mcp --url=https://auth.example.com looked configured and wasn't — while --mcp-authorizer-url was the only mechanism that actually worked.

runMCP now pins the trusted URL exactly as the server does. That is what lets the old flag become inert without breaking the stdio path.

How the bug was found

I first assumed --mcp-authorizer-url was already redundant, reasoning from GetHostFromRequest's precedence, and deleted it from examples/with-agent-permissions/mcp-agent.mjs. The stdio probe failed immediately:

== Driving the real MCP server over stdio (delegated token) ==
rpc error: code = Unauthenticated desc = unauthorized

Correct about the function, wrong about the process: SetTrustedURL was never called there.

Verified inert, not just deprecated

With --url correct and a deliberately wrong --mcp-authorizer-url, all six assertions still pass against a live server:

$ node mcp-agent.mjs --verify      # args include --mcp-authorizer-url=https://WRONG.example
== Driving the real MCP server over stdio (delegated token) ==
  ✓ check_permissions q4-plan -> allowed
  ✓ check_permissions payroll -> DENIED
  ✓ list_permissions includes q4-plan
  ✓ list_permissions EXCLUDES payroll

== Control: the same tools with the USER's own token ==
  ✓ check_permissions q4-plan -> allowed
  ✓ check_permissions payroll -> allowed (the user CAN see it)

Before the runMCP fix, that wrong value would have broken issuer validation. It is now read by nothing.

$ authorizer mcp … --mcp-authorizer-url=…
Flag --mcp-authorizer-url has been deprecated, it has NO EFFECT as of 2.4.0 —
pass --url with the same value instead. …

Still parsed, not deleted, so an existing 2.3.x invocation keeps starting instead of dying on unknown flag. It shipped in 2.3.0, so deleting it would be a breaking change; it goes with the subcommand in 2.5.0.

Also

--mcp-bearer without --url is now refused at startup with an explanatory message. That combination previously failed later as a bare Unauthenticated on every tool call, which reads as a bad token rather than missing config — exactly the confusion that hid the bug above.

Verification

go build ./..., go vet ./cmd/, go vet -tags smoke ./internal/e2e/, gofmt -l, go test ./cmd/ all clean. End-to-end verified against a live server.

Related

  • authorizerdev/docs — flag table and runnable blocks now use --url
  • authorizerdev/examples — mcp-agent.mjs no longer passes it

Refs #764

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.
`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.
@lakhansamani lakhansamani changed the title deprecate(mcp): --mcp-authorizer-url in favour of --url fix(mcp): honour --url in stdio, deprecate --mcp-authorizer-url Aug 14, 2026
lakhansamani added a commit to authorizerdev/examples that referenced this pull request Aug 14, 2026
--url alone now suffices: authorizerdev/authorizer#768 makes
`authorizer mcp` honour it. Verified with `node mcp-agent.mjs --verify`
against a live server — all six assertions pass.

The example passed --url and --mcp-authorizer-url with the same value.
Removing the latter WITHOUT the server fix failed with
`rpc error: code = Unauthenticated`, which is how the underlying bug
was found.
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.
Reverts the deletion in 4a3b1af. 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.
@lakhansamani lakhansamani changed the title fix(mcp): honour --url in stdio, deprecate --mcp-authorizer-url fix(mcp): honour --url in stdio, make --mcp-authorizer-url inert Aug 14, 2026
@lakhansamani
lakhansamani merged commit a0e64eb into main Aug 14, 2026
4 checks passed
@lakhansamani
lakhansamani deleted the deprecate/mcp-authorizer-url branch August 14, 2026 06:19
lakhansamani added a commit to authorizerdev/examples that referenced this pull request Aug 14, 2026
* fix: document the flags the server actually requires

The setup line repeated in 11 READMEs was v1-era and missing FOUR flags
the 2.4.0 server refuses to start without. As written it fails on the
first one before ever reaching the others:

  $ ./authorizer --database-type sqlite --database-url authorizer.db \
      --admin-secret secret
  Error: --encryption-key is required: ...

Replaced with a command verified to boot against a build of authorizer
main: adds --url (authorizerdev/authorizer#764), --encryption-key,
--jwt-type/--jwt-secret and --client-id/--client-secret.

Also adds --url to the two org-SSO compose files and the k8s-tokenreview
manifest; all three map 8080:8080, so the container's own address and the
address the demo dials agree.

* fix(agent-permissions): drop redundant --mcp-authorizer-url

--url alone now suffices: authorizerdev/authorizer#768 makes
`authorizer mcp` honour it. Verified with `node mcp-agent.mjs --verify`
against a live server — all six assertions pass.

The example passed --url and --mcp-authorizer-url with the same value.
Removing the latter WITHOUT the server fix failed with
`rpc error: code = Unauthenticated`, which is how the underlying bug
was found.
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.

1 participant