Skip to content

Publish the OAuth identifiers the operator configured, not a normalised copy of them - #157

Merged
Cloto-dev merged 1 commit into
masterfrom
fix/prm-preserves-the-issuer-as-written
Aug 28, 2026
Merged

Publish the OAuth identifiers the operator configured, not a normalised copy of them#157
Cloto-dev merged 1 commit into
masterfrom
fix/prm-preserves-the-issuer-as-written

Conversation

@Cloto-dev

Copy link
Copy Markdown
Owner

The discovery document published an authorization server identifier the
operator had not configured. Measured against the live deployment: the
configured value carries no trailing slash, the published one did, and the
authorization server itself reports the configured form.

Why this is a defect rather than a spelling difference

RFC 8414 §3.3, verbatim:

The "issuer" value returned MUST be identical to the authorization server's
issuer identifier value into which the well-known URI string was inserted to
create the URL used to retrieve the metadata. If these values are not
identical, the data contained in the response MUST NOT be used.

The comparison is against the string the client started from — the one this
document handed it. An authorization server whose issuer has no path returns
it without a slash forever, so a conformant client must refuse the metadata it
just fetched. RFC 9728 §3.3 places the same requirement on resource; that
half stayed invisible because the deployed resource carries a path, and the
normalisation only rewrites identifiers that have none.

Two causes, and why neither fix works alone

Both were measured, not reasoned about:

Change Published
Neither half https://as.example/
Preserve the operator's string only https://as.example/
Set the model flag only https://as.example/
Both https://as.example

This server converted the strings to the normalising URL type before handing
them over, and the SDK's metadata models are themselves typed with it. The
second is not ours: upstream fixed it in modelcontextprotocol/python-sdk#2925
(closing #2883) with a single url_preserve_empty_path flag, and it ships in
SDK 2.0 — a major upgrade this server has not taken. Rather than hand-write a
document whose whole value is that clients already parse it, the same flag is
set on the same models at startup. It is skipped where the SDK already carries
it, so it retires itself when that upgrade lands.

Verification

  • 1,307 passed / 5 skipped; issue registry clean; docs facts and mutation waivers OK
  • New tests configure four spellings (with and without a path, with and
    without a terminating slash) and assert the served document reproduces each,
    plus the multi-server ordering case and resource
  • Mutation-tested per half: removing either the preserved strings or the
    model flag turns 5 tests red — the same 5 either way, which is the point
  • Validation is unchanged: a malformed issuer still disables discovery with a
    warning, pinned by a test, since publishing verbatim means nothing
    downstream would catch a typo
  • Two existing tests had encoded the normalised form. Their subjects were the
    public route and the list separators; the expectations were written around
    what the code did (AUTH_SERVER + "/"), and are corrected with a pointer to
    the reason.

An issuer written without a path was published with a trailing slash. That
is not a spelling difference: RFC 8414 requires the issuer a client reads
back from an authorization server to be identical to the value it started
from, and says the response MUST NOT be used otherwise. An authorization
server whose issuer has no path returns it without one forever, so the
document described a server no client could validate. RFC 9728 states the
same requirement for the resource identifier, which stayed invisible only
because the deployed resource carries a path.

Two causes had to go together, and either alone leaves the output
unchanged: this server converted the strings before handing them over, and
the SDK's metadata models are themselves typed with the normalising URL
type. The second is fixed upstream and ships in the next major SDK, which
this server has not taken yet — so the same flag is set on the same models
here, where it becomes a no-op once that upgrade lands.

Measured against the live deployment: the configured issuer has no slash,
the published one had it, and the authorization server reports the former.
Two existing tests had encoded the normalised form; their subjects were the
public route and the list separators, not the spelling.
@Cloto-dev
Cloto-dev merged commit 524d6da into master Aug 28, 2026
8 checks passed
@Cloto-dev
Cloto-dev deleted the fix/prm-preserves-the-issuer-as-written branch August 28, 2026 16:41
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