Skip to content

MCP OAuth Stage 7: end-to-end integration fixture #97

@heskew

Description

@heskew

MCP OAuth Stage 7: end-to-end integration fixture

Sub-issue of #86. The spec-conformance proof: a real MCP client driving the full PRM → AS metadata → DCR → authorize → upstream IdP → token → bearer-authenticated MCP call round-trip against a Harper fixture with @harperfast/oauth wired up.

Context

Stages 1–5 each ship unit tests, but unit tests can lie about contract — they exercise modules in isolation against mocks. The integration test is what proves an unmodified MCP client can actually authenticate. This is the v1 acceptance gate from #86: "An unmodified MCP client (e.g., Claude Desktop) can complete the OAuth flow against a Harper app configured with @harperfast/oauth + this issue's changes."

What this adds

  1. New fixture under integrationTests/fixtures/mcp-oauth/ with:
    • config.yaml enabling the plugin with a test OAuth provider (stub IdP), mcp.enabled: true, configured mcp.issuer and mcp.resource
    • A stub /mcp route wrapped by withMCPAuth(handler) that returns the verified request.mcp claims as JSON — useful for assertions
    • A stub upstream IdP (mocked OAuth provider) so tests don't depend on real GitHub/Google
    • package.json with no deps (install-fixtures injects the OAuth tarball, per existing fixture convention)
  2. End-to-end test in integrationTests/ that:
    • Boots Harper via harper-integration-test-run
    • GETs /mcp with no Authorization, asserts 401 + WWW-Authenticate: Bearer resource_metadata=...
    • Fetches the PRM URL from the header, validates RFC 9728 shape
    • Fetches AS metadata, validates RFC 8414 shape including S256 + resource_parameter_supported: true
    • POSTs to registration_endpoint (DCR), captures client_id
    • Drives the authorize flow with PKCE-S256 + resource param (programmatic browser-like client OR mcp-remote as subprocess if scriptable)
    • Completes the stub upstream IdP login
    • Receives the auth code, exchanges via token_endpoint, captures access token
    • GETs /mcp with Authorization: Bearer <token>, asserts 200 and verifies the response echoes the expected sub, aud, client_id
    • Asserts refresh-token rotation: presenting the same refresh token twice returns invalid_grant
  3. Negative-case coverage in the same fixture:
    • Reject plain PKCE at /authorize
    • Reject missing resource parameter
    • Reject token with wrong aud at /mcp
    • Reject Authorization in query string
    • Confirm upstream IdP token is NEVER returned to the client at any stage

Spec requirements satisfied (cumulative across all prior stages)

This stage doesn't add new spec requirements — it validates that the cumulative implementation actually satisfies the MUST list from MCP 2025-06-18.

Acceptance

  • Fixture under integrationTests/fixtures/mcp-oauth/ boots clean
  • Test asserts every step of the discovery → DCR → authorize → token → bearer-auth round-trip
  • Each MUST from the MCP spec 2025-06-18 has at least one positive and one negative assertion
  • Test runs under npm run test:integration and passes on Node 22 and Node 24
  • No upstream-provider token leaks into MCP client at any step (explicit assertion)
  • Documented: how to add an MCP route to a Harper app, exact 10-line snippet referenced in Stage 8 docs

Dependencies

  • Depends on: Stages 1–5 (all the endpoints and the wrapper must exist)
  • Blocks: parent Add MCP OAuth flow support #86 closes when this stage merges (the v1 conformance bar)

Out of scope

  • Real-client driver against Claude Desktop / Cursor (manual-only QA; the integration test uses mcp-remote if scriptable, else a hand-rolled HTTP client)
  • Multi-instance / clustered Harper MCP token federation (out of scope per Add MCP OAuth flow support #86)

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions