Skip to content

MCP OAuth Stage 8: user-facing docs (#98) - #144

Merged
heskew merged 9 commits into
mainfrom
feat/mcp-stage8-docs
Jul 1, 2026
Merged

MCP OAuth Stage 8: user-facing docs (#98)#144
heskew merged 9 commits into
mainfrom
feat/mcp-stage8-docs

Conversation

@heskew

@heskew heskew commented Jun 30, 2026

Copy link
Copy Markdown
Member

Closes #98 — Stage 8 of the MCP OAuth epic (#86). App-author documentation for MCP OAuth.

Its dependencies — #134 (withMCPAuth) and #141 (audit + onMCPTokenIssued) — are both merged, so this is unblocked. Docs-only; merges cleanly into current main (#141 touched no docs).

What's added / changed

  • docs/mcp-oauth.md (new) — the single deep guide: end-to-end flow diagram, endpoint reference (discovery / authorize / token / JWKS), the withMCPAuth wrapper (both registration models, options, cross-component use via getConfig), the onMCPTokenIssued hook, audit events (all three types, incl. oauth.mcp.token.rejected), a production-deployment checklist, troubleshooting, and a migration guide from a hand-rolled MCP authorization server. Links the MCP authorization spec (2025-06-18) and RFCs 6749/6750/7591/7636/8252/8414/8707/9728.
  • README.md — MCP OAuth section trimmed to a quickstart (config + withMCPAuth) plus a pointer to docs/mcp-oauth.md; added the doc to the index.
  • docs/configuration.md — dropped the "(work in progress)" marker; documented signingKeyPem, signingAlgorithm, accessTokenTtl, refreshTokenTtl; fixed the JWKS note; trimmed the inline withMCPAuth how-to to a pointer + a security note.
  • docs/lifecycle-hooks.md — documented onMCPTokenIssued (the MCP-client analog of onLogin), reflecting MCP OAuth Stage 6: audit logging + onMCPTokenIssued hook (#96) #141's fire-and-forget / not-awaited behavior.

Design: single source of truth

#134 had already landed withMCPAuth walkthroughs in both README.md and docs/configuration.md. To avoid three copies that drift, the deep wrapper/flow/hook material now lives once in docs/mcp-oauth.md; README.md and configuration.md carry short pointers into it (so those two files are net-trimmed here).

Notes

  • Docs-only; no code changes.
  • prettier --check clean; all internal anchors resolve.
  • No v1.1 features advertised — per-tool scopes and transitive revocation are explicitly listed as not-yet-supported.

🤖 Generated with Claude Code

@heskew
heskew requested a review from kriszyp June 30, 2026 05:20

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces comprehensive documentation for the experimental MCP OAuth feature, enabling the plugin to act as an OAuth 2.1 authorization server for Model Context Protocol clients. The updates span the README, configuration reference, lifecycle hooks, and a new dedicated guide detailing the flow, endpoints, and the withMCPAuth wrapper. Feedback suggests adding the package property to the README's configuration snippet to ensure consistency and correct plugin resolution.

Comment thread README.md
@claude

This comment has been minimized.

@github-actions

This comment has been minimized.

@heskew
heskew force-pushed the feat/mcp-stage8-docs branch from 4011523 to aaeb8df Compare June 30, 2026 16:55
Comment thread docs/lifecycle-hooks.md
@heskew
heskew force-pushed the feat/mcp-stage8-docs branch from 0e6303e to 55095af Compare June 30, 2026 23:03
heskew added a commit that referenced this pull request Jun 30, 2026
gemini review on #144: add `package: '@harperfast/oauth'` to the MCP quickstart's
config.yaml so it matches the main Quick Start / configuration.md examples and
loads correctly when copy-pasted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread docs/mcp-oauth.md
@heskew
heskew marked this pull request as ready for review June 30, 2026 23:13
@heskew

heskew commented Jun 30, 2026

Copy link
Copy Markdown
Member Author

Developer Experience (DevExp) Review

This documentation PR is outstanding and extremely thorough. It anticipates real-world integration hurdles (such as multi-component getConfig static-scope resolution, cluster PEM key-drift, and middleware Basic/Bearer header conflicts) and handles them proactively.

Here are a few highly focused, non-blocking suggestions to refine the developer experience and achieve absolute documentation perfection:

1. Harmonize timing/execution language for onMCPTokenIssued

  • Files: docs/lifecycle-hooks.md:332 and docs/mcp-oauth.md:321
  • What: Wording in lifecycle-hooks.md says the hook is called "before the response returns", whereas mcp-oauth.md says it "runs detached — it is not awaited... Because it isn't awaited, its side effects may complete after the client already has the token".
  • Why it matters: If developers read "before the response returns," they might expect to block/delay the response or expect their side effects to finish synchronously before the client receives the token.
  • Suggested Fix: Harmonize the language in lifecycle-hooks.md to make the async/detached execution clear:
    - Called after an MCP access or refresh token is minted, before the response returns.
    + Invoked after an MCP access or refresh token is minted. Because it runs detached and is not awaited (fire-and-forget), it never delays the token response, and its side effects may complete after the client has already received the token.

2. Remove unused next parameter from Quickstart handler

  • File: docs/mcp-oauth.md:54
  • What: The quickstart handler definition includes an unused next argument:
    function mcpHandler(request, next) { ... }
  • Why it matters: Since the endpoint is registered as a leaf handler on a urlPath subroute (server.http(withMCPAuth(mcpHandler), { urlPath: '/mcp' })), it doesn't utilize or need next. Including next adds unnecessary cognitive load and suggests middleware-like chaining behavior.
  • Suggested Fix: Simplify the quickstart signature:
    function mcpHandler(request) {
    	const { sub, client_id, scope } = request.mcp; // verified token claims
    	return { jsonrpc: '2.0', result: { hello: sub } };
    }

3. Add explicit context for JSON-RPC response format

  • File: docs/mcp-oauth.md:56
  • What: The quickstart example returns { jsonrpc: '2.0', result: { hello: sub } }.
  • Why it matters: For developers unfamiliar with MCP internals, returning a JSON-RPC payload instead of standard REST JSON could look confusing or arbitrary.
  • Suggested Fix: Add a brief code comment explaining that MCP communicates using the JSON-RPC 2.0 protocol:
    function mcpHandler(request) {
    	const { sub, client_id, scope } = request.mcp; // verified token claims
    	// MCP endpoints communicate using JSON-RPC 2.0 (carried over SSE or stdio)
    	return { jsonrpc: '2.0', result: { hello: sub } };
    }

Reviewed by Antigravity (Advanced Agentic Coding, Google DeepMind) 🚀

heskew added a commit that referenced this pull request Jun 30, 2026
…uage

DevExp review on #144 (Antigravity/Gemini), non-blocking:
1. lifecycle-hooks.md said the hook fires "before the response returns" —
   contradicting mcp-oauth.md's not-awaited/detached wording. Harmonized to the
   fire-and-forget language (it runs detached; side effects may complete after the
   client has the token).
2. Dropped the unused `next` param from the mcp-oauth.md quickstart handler (a
   leaf urlPath route doesn't use it).
3. Added a comment noting MCP messages are JSON-RPC 2.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@heskew

heskew commented Jun 30, 2026

Copy link
Copy Markdown
Member Author

All three addressed in e5c586b: (1) harmonized the onMCPTokenIssued timing language in lifecycle-hooks.md to the not-awaited/detached wording (it contradicted mcp-oauth.md); (2) dropped the unused next param from the quickstart handler; (3) added a JSON-RPC 2.0 note on the quickstart return shape.

Comment thread docs/mcp-oauth.md
heskew added a commit that referenced this pull request Jul 1, 2026
gemini review on #144: the quickstart handler returned a bare { jsonrpc, result }
object, but Harper HTTP listeners return { status, body, headers? } (as the README
and the integration fixture do). Wrap the JSON-RPC response as the body.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread docs/mcp-oauth.md
heskew added a commit that referenced this pull request Jul 1, 2026
claude review on #144: the quickstart block (labeled resources.ts) calls
server.http() but imported only withMCPAuth — copy-pasting it verbatim would throw
ReferenceError: server is not defined. Add `import { server } from 'harper'`,
matching the README quickstart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread README.md
Comment thread docs/mcp-oauth.md
heskew added a commit that referenced this pull request Jul 1, 2026
…handler

gemini review on #144 (non-blocking): make explicit that request.mcp is defined
inside a withMCPAuth-guarded handler (the guard rejects missing/invalid tokens
first), so strict-TS users don't need optional chaining. Clarified the quickstart
comments in mcp-oauth.md + README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@heskew heskew left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docs/mcp-oauth.md:161 has the path-appended Protected Resource Metadata URL backwards. For a resource like https://host/mcp, the implementation builds and serves /.well-known/oauth-protected-resource/mcp (PRM_PATH + resource path), and withMCPAuth advertises that form in WWW-Authenticate. The current text says /mcp/.well-known/oauth-protected-resource, which sends users/proxies to the wrong route. Please flip the example to /.well-known/oauth-protected-resource/mcp.


🤖 Posted by Codex (gpt-5.5) on Nathan's behalf

heskew added a commit that referenced this pull request Jul 1, 2026
Codex review on #144: the RFC 9728 path-appended Protected Resource Metadata URL
was written `/mcp/.well-known/oauth-protected-resource`, but the implementation
(wellKnown.ts `protectedResourceMetadataUrl` = origin + PRM_PATH + resourcePath)
serves `/.well-known/oauth-protected-resource/mcp` — the well-known segment sits
between the origin and the resource path. Flipped both occurrences (the discovery
note + the WWW-Authenticate example) to the correct form.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@heskew

heskew commented Jul 1, 2026

Copy link
Copy Markdown
Member Author

Fixed in 3972763 — flipped both occurrences to the correct RFC 9728 form /.well-known/oauth-protected-resource/mcp (matching wellKnown.ts protectedResourceMetadataUrl = origin + PRM_PATH + resource path). The bare root-resource mentions were already correct.

Comment thread docs/lifecycle-hooks.md
Comment thread docs/mcp-oauth.md
heskew added a commit that referenced this pull request Jul 1, 2026
…ples

gemini review on #144 (non-blocking, devexp): the onMCPTokenIssued examples use
`tables.McpClient` — note that `tables` is a Harper global (so it isn't imported)
and `McpClient` is an illustrative app-owned table the plugin doesn't provide.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
heskew and others added 4 commits June 30, 2026 22:39
Adds the app-author documentation for MCP OAuth, with docs/mcp-oauth.md as
the single deep guide (flow diagram, endpoint reference, the withMCPAuth
wrapper + both registration models + options + cross-component use, the
onMCPTokenIssued hook, audit events, production-deployment checklist,
troubleshooting, and a hand-rolled-server migration guide). Links the MCP
spec (2025-06-18) and RFCs 6749/6750/7591/7636/8252/8414/8707/9728.

Rebased onto main after #134 merged. #134 had already added withMCPAuth
sections to README and configuration.md; those are trimmed here to short
pointers into docs/mcp-oauth.md so the wrapper isn't documented in three
places. configuration.md also: drops the "(work in progress)" marker,
documents signingKeyPem/signingAlgorithm/accessTokenTtl/refreshTokenTtl,
and fixes the JWKS note. docs/lifecycle-hooks.md documents onMCPTokenIssued.

Documents onMCPTokenIssued (#141), so merge this after #141.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match the hook's behavior after #141: it runs detached and is not awaited, so it
never delays/blocks the token response — and its side effects may complete after
the client already has the token.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gemini review on #144: add `package: '@harperfast/oauth'` to the MCP quickstart's
config.yaml so it matches the main Quick Start / configuration.md examples and
loads correctly when copy-pasted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…uage

DevExp review on #144 (Antigravity/Gemini), non-blocking:
1. lifecycle-hooks.md said the hook fires "before the response returns" —
   contradicting mcp-oauth.md's not-awaited/detached wording. Harmonized to the
   fire-and-forget language (it runs detached; side effects may complete after the
   client has the token).
2. Dropped the unused `next` param from the mcp-oauth.md quickstart handler (a
   leaf urlPath route doesn't use it).
3. Added a comment noting MCP messages are JSON-RPC 2.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
heskew and others added 5 commits June 30, 2026 22:39
gemini review on #144: the quickstart handler returned a bare { jsonrpc, result }
object, but Harper HTTP listeners return { status, body, headers? } (as the README
and the integration fixture do). Wrap the JSON-RPC response as the body.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
claude review on #144: the quickstart block (labeled resources.ts) calls
server.http() but imported only withMCPAuth — copy-pasting it verbatim would throw
ReferenceError: server is not defined. Add `import { server } from 'harper'`,
matching the README quickstart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…handler

gemini review on #144 (non-blocking): make explicit that request.mcp is defined
inside a withMCPAuth-guarded handler (the guard rejects missing/invalid tokens
first), so strict-TS users don't need optional chaining. Clarified the quickstart
comments in mcp-oauth.md + README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codex review on #144: the RFC 9728 path-appended Protected Resource Metadata URL
was written `/mcp/.well-known/oauth-protected-resource`, but the implementation
(wellKnown.ts `protectedResourceMetadataUrl` = origin + PRM_PATH + resourcePath)
serves `/.well-known/oauth-protected-resource/mcp` — the well-known segment sits
between the origin and the resource path. Flipped both occurrences (the discovery
note + the WWW-Authenticate example) to the correct form.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ples

gemini review on #144 (non-blocking, devexp): the onMCPTokenIssued examples use
`tables.McpClient` — note that `tables` is a Harper global (so it isn't imported)
and `McpClient` is an illustrative app-owned table the plugin doesn't provide.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@heskew
heskew force-pushed the feat/mcp-stage8-docs branch from b42e504 to 83c8442 Compare July 1, 2026 04:39
@heskew
heskew merged commit d185b6b into main Jul 1, 2026
10 checks passed
@heskew
heskew deleted the feat/mcp-stage8-docs branch July 1, 2026 04:48
This was referenced Jul 1, 2026
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.

MCP OAuth Stage 8: user-facing docs

1 participant