Skip to content

feat(authorization): send the RFC 8707 resource parameter - #466

Open
tylerklose wants to merge 1 commit into
modelcontextprotocol:mainfrom
tylerklose:authorization-code-grant-resource-parameter
Open

feat(authorization): send the RFC 8707 resource parameter#466
tylerklose wants to merge 1 commit into
modelcontextprotocol:mainfrom
tylerklose:authorization-code-grant-resource-parameter

Conversation

@tylerklose

Copy link
Copy Markdown

Fixes #465.

The runner is the MCP client in the authorization direction. The spec requires
clients to send resource in both the authorization request and the token
request. It sent neither.

Changes

  • src/index.ts--resource <uri> on the authorization command.
  • src/schemas.tsresource on AuthorizationServerOptionsSchema, so
    --file supplies it too.
  • authorization-code-grant.ts — sends resource in the authorization request
    and the token request when it's set.

The value must be an absolute URI with no fragment — the spec's own two invalid
examples are mcp.example.com (no scheme) and https://mcp.example.com#fragment.

Two conditionals, one per request. It's only sent when the flag or file field is
present, so existing runs are unchanged.

No new check IDs

Deliberate. Here the runner is the client, so a check for "the runner sent
resource" is the harness grading its own homework — it can never fail. URI
validation happens in the schema, before scenarios run, so a check for that can't
fail either.

None is needed. The existing grant checks now reach a path they couldn't before:
an AS that mishandles resource fails them, where before it was never asked.

Tests

Six cases added (npm test: 530 passing).

Scenario:

  • resource appears in both the authorization URL and the token body when set
  • it appears in neither when unset — guards against it silently becoming
    unconditional
  • a mock authorization server that rejects resource makes the existing grant
    check fail, and passes when the parameter is absent

Schema:

  • accepts an absolute URI
  • rejects a fragment
  • rejects a relative URI

Revert the two conditionals and the first and third fail.

Validated against a real authorization server

Run against hitch-rails, my own Rails MCP server and its own authorization
server. It requires resource at both endpoints, and rejects a
token exchange whose resource doesn't match the one recorded at authorization
time. The public-client grant passes end to end — it couldn't have before this
change, because the parameter never arrived.

The confidential-client path wasn't exercised: selectTokenAuthMethod returns
none whenever an AS advertises none, even with a client secret supplied, so
the runner shows up as a public client against a client registered for
client_secret_basic. Unrelated to this change; worth raising separately.

Open question

See the issue: a run without --resource stays silent about the requirement.
Whether it should fail instead, or report untestable at WARNING per #248, is your
call.

Checklist

  • npm run build passes
  • npm test passes (530)
  • eslint + prettier clean
  • No scenario added or removed; no check IDs changed, so no traceability drift
  • Run against a real authorization server (above)

The runner is the MCP client in the `authorization` direction, and the
spec requires clients to send `resource` in both the authorization
request and the token request. It sent neither.

Adds `--resource <uri>` and a matching `resource` field on
AuthorizationServerOptionsSchema, so `--file` supplies it too. The value
must be an absolute URI with no fragment. The parameter is only sent
when set, so existing runs are unchanged.

No new check IDs: in this direction the runner is the client, so a check
that it sent `resource` could never fail. The existing grant checks now
reach a path they couldn't before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
tylerklose added a commit to tylerklose/hitch-rails that referenced this pull request Aug 21, 2026
Validating an upstream branch that would make our patch unnecessary means
running the harness against a working tree instead of the pinned clone plus
reviewed patch. There was no seam for that, so the choice was reimplement the
fixture, TLS, browser operator and credential canaries, or don't check.

HITCH_CONFORMANCE_LOCAL_RUNNER stages a working tree by rsync — not a clone,
so uncommitted edits are what gets exercised — and skips the patch step. Unset
by default; CI never sets it. Evidence produced this way stamps its version
and commit as "unpinned-local-checkout" so it can never be read as pinned
conformance evidence.

This is what proved modelcontextprotocol/conformance#466 works against a real
authorization server: the public-client grant passes end to end, which it
could not before, because Hitch rejects a missing resource indicator at both
the authorization and token endpoints.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018JmnbitJ3Gx4kRUhbMjktc
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.

authorization-code-grant never sends the RFC 8707 resource parameter

1 participant