You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
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)
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
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)
Real-client driver against Claude Desktop / Cursor (manual-only QA; the integration test uses mcp-remote if scriptable, else a hand-rolled HTTP client)
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/oauthwired 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
integrationTests/fixtures/mcp-oauth/with:config.yamlenabling the plugin with a test OAuth provider (stub IdP),mcp.enabled: true, configuredmcp.issuerandmcp.resource/mcproute wrapped bywithMCPAuth(handler)that returns the verifiedrequest.mcpclaims as JSON — useful for assertionspackage.jsonwith no deps (install-fixtures injects the OAuth tarball, per existing fixture convention)integrationTests/that:harper-integration-test-run/mcpwith no Authorization, asserts401+WWW-Authenticate: Bearer resource_metadata=...resource_parameter_supported: trueregistration_endpoint(DCR), capturesclient_idresourceparam (programmatic browser-like client ORmcp-remoteas subprocess if scriptable)token_endpoint, captures access token/mcpwithAuthorization: Bearer <token>, asserts 200 and verifies the response echoes the expectedsub,aud,client_idinvalid_grantplainPKCE at/authorizeresourceparameteraudat/mcpAuthorizationin query stringSpec 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
integrationTests/fixtures/mcp-oauth/boots cleannpm run test:integrationand passes on Node 22 and Node 24Dependencies
Out of scope
mcp-remoteif scriptable, else a hand-rolled HTTP client)🤖 Generated with Claude Code