Skip to content

feat: OIDC discovery, resource subscriptions, DCR hooks, and introspection auth#1

Open
getlarge wants to merge 26 commits intomainfrom
dev
Open

feat: OIDC discovery, resource subscriptions, DCR hooks, and introspection auth#1
getlarge wants to merge 26 commits intomainfrom
dev

Conversation

@getlarge
Copy link
Copy Markdown
Owner

@getlarge getlarge commented Jan 28, 2026

Summary

This PR combines all fork changes for publishing @getlarge/fastify-mcp with Ory Hydra compatibility.

Changes

1. OIDC Discovery and redirect_uri support (upstream PR platformatic#97)

  • Fetch endpoints from /.well-known/openid-configuration with 5-min cache
  • Add redirect_uri to authorization and token exchange requests
  • Add excludedPaths configuration option
  • Skip /oauth/callback in auth prehandler

2. Resource subscriptions support (upstream PR platformatic#98)

  • Add mcpSetResourcesSubscribeHandler and mcpSetResourcesUnsubscribeHandler
  • Query parameter URI matching for resources with uriSchema
  • Applications manage their own subscription storage

3. DCR hooks and introspection auth (upstream PR platformatic#100)

Skip /oauth/register in auth prehandler

DCR endpoint must be accessible before client has credentials.

Add introspectionAuth configuration

Supports bearer (API key), basic (client credentials), or none for token introspection.

tokenValidation: {
  introspectionEndpoint: 'https://ory.example.com/admin/oauth2/introspect',
  introspectionAuth: { type: 'bearer', token: 'api-key' }
}

Add dcrHooks for DCR proxy pattern

Enables MCP server to act as a DCR proxy with request/response transformation.

dcrHooks: {
  upstreamEndpoint: 'https://ory.example.com/oauth2/register',
  onResponse: async (response) => {
    // Clean empty fields that break Claude Code's Zod validation
    if (response.client_uri === '') delete response.client_uri;
    return response;
  }
}

4. Package rename

  • Renamed to @getlarge/fastify-mcp for fork publishing

Breaking Changes

  • /oauth/register returns 501 unless dcrHooks is configured (prevents infinite loops)

Tests

  • ✅ 247/247 non-Redis tests pass

Upstream PRs

🤖 Generated with Claude Code

@getlarge getlarge changed the title feat(auth): DCR hooks, introspection auth, and prehandler fixes feat: OIDC discovery, resource subscriptions, DCR hooks, and introspection auth Jan 28, 2026
getlarge and others added 26 commits April 4, 2026 15:24
…on tests

The MCP SDK types use discriminated unions (e.g., text | image content).
TypeScript requires type narrowing before accessing type-specific properties.

This fixes typecheck failures introduced by SDK type changes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add OIDC discovery to fetch endpoints from /.well-known/openid-configuration
  with 5-minute caching and fallback to default /oauth/* paths
- Include redirect_uri in authorization request (required for OIDC 1.0)
- Pass redirect_uri to token exchange (must match authorization request)
- Skip /oauth/callback in auth prehandler
- Add excludedPaths option for custom routes to bypass authorization
  (e.g., health checks)

This enables compatibility with OAuth providers like Ory Hydra that use
non-standard endpoint paths (e.g., /oauth2/auth instead of /oauth/authorize).

Closes platformatic#95

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…pport

- Add mcpSetResourceSubscribeHandler and mcpSetResourceUnsubscribeHandler
  decorators for custom subscription management
- Implement resources/subscribe and resources/unsubscribe JSON-RPC methods
- Add query parameter URI fallback: when exact match fails and URI has
  query params, try base URI for resources with uriSchema
- Return METHOD_NOT_FOUND when handlers not configured (clear signal)

This enables MCP clients to subscribe/unsubscribe to resource changes
while keeping subscription storage application-managed.

Closes platformatic#96

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fork version 1.2.2-getlarge.1 with:
- OIDC discovery and redirect_uri support
- Resource subscription handlers
- Query parameter URI matching

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit adds several OAuth/DCR improvements:

Patch 3.1: Skip /oauth/register in auth prehandler
- DCR endpoint must be accessible before client has credentials
- Fixes chicken-and-egg problem with dynamic registration

Patch 3.2: Add introspectionAuth configuration
- Supports bearer (API key), basic (client credentials), or none
- Required for Ory Hydra admin introspection endpoint
- Backwards compatible - defaults to no auth header

Patch 3.3: Pass DCR request body through to dynamicClientRegistration
- Client metadata is now merged with defaults (client wins)
- Allows clients to specify their own redirect_uris, client_name, etc.

Patch 4: Add dcrHooks for DCR proxy pattern
- upstreamEndpoint: Required, bypasses OIDC discovery to avoid loops
- onRequest: Hook to transform/enrich DCR request
- onResponse: Hook to clean/transform DCR response (e.g., remove empty fields)
- Returns 501 when not configured (prevents infinite loop)

Breaking change: /oauth/register now returns 501 unless dcrHooks is configured.
This prevents infinite loops when OIDC discovery points back to the MCP server.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Parameterized resources (URIs with RFC 6570 {param} syntax) are now
served via resources/templates/list instead of resources/list, per the
MCP spec. Template detection happens at query time using a simple regex.
The ResourceTemplate response shape maps uri to uriTemplate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Clients can now explicitly terminate sessions via DELETE with the
Mcp-Session-Id header, per the MCP transport spec. The handler
force-closes active SSE streams, unsubscribes from the message broker,
and deletes the session from the store. Returns 204 on success.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
11 new tests covering:
- resources/templates/list shape, filtering, empty list, read, mixed split
- DELETE /mcp: 204 success, 400 missing header, 404 not found, SSE
  stream closure, store cleanup, disabled SSE guard

Also fixes auth-compatibility test to use a concrete URI so it stays
in resources/list after the template filtering change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- withSpan() helper wraps handler execution in active OTel spans
- buildSpanAttributes() resolves MCP semconv keys from @opentelemetry/semantic-conventions/incubating when available, falling back to local constants
- Both @opentelemetry/api and @opentelemetry/semantic-conventions loaded dynamically - zero cost for users without telemetry configured
- Add @opentelemetry/semantic-conventions as optional peer dep

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- handleToolsCall, handleResourcesRead, handlePromptsGet each wrapped in withSpan when tracer is present
- telemetry module loaded lazily via dynamic import - zero cost without tracer
- tracer wired from opts.telemetry.tracer through routes into HandlerDependencies
- Span attributes use resolved semconv keys (mcp.method.name, mcp.session.id, mcp.resource.uri, mcp.tool.name)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
buildSpanAttributes is now synchronous using local MCP_ATTR constants.
The semconv fallback abstraction was over-engineering - if @opentelemetry/api
is installed, @opentelemetry/semantic-conventions is too, and a missing
dep should fail loudly rather than silently fall back.
Remove @opentelemetry/semantic-conventions from peer deps accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses a span() helper built inline in handleRequest so all methods
share a single span-name = request.method pattern without repetition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…EADME

- Export MCP_ATTR, withSpan, buildSpanAttributes from package root
- Export HandlerDependencies type for advanced consumers
- Add Telemetry section to README with usage example and span attribute table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Centralise all span creation in handleRequest - consistent single span per method, no asymmetry
- Remove per-handler withSpan dispatchers and *Core split - simpler code
- Cache SpanStatusCode after first dynamic import - not re-imported per call
- Add mcp.prompt.name attribute to prompts/get spans
- Spread instead of Object.assign in buildSpanAttributes
- Add integration test covering full plugin → tracer wiring end-to-end

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…licates

MCP_ATTR is a plain object with no OTel runtime dependency, so importing
telemetry.ts statically for its constants is safe. Removes the redundant
local MCP_ATTR_TOOL_NAME/RESOURCE_URI/PROMPT_NAME constants.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract MCP_ATTR and buildSpanAttributes to telemetry-constants.ts (no OTel dep)
  so handlers.ts can import them statically without the lazy-load contradiction
- Define TracerLike interface locally in types.ts so consumers don't need
  @opentelemetry/api installed just to import plugin types
- Fix _SpanStatusCode type to use typeof import(...).SpanStatusCode (avoids TS1361)
- Use TracerLike in withSpan and HandlerDependencies instead of Tracer
- Export TracerLike from package root
- Fix README: add mcp.prompt.name to prompts/get table row, clarify OTel cost wording

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Update fastify to ^5.8.4 (DoS, host spoofing CVEs)
- Update undici to ^7.24.7
- Update @modelcontextprotocol/sdk to ^1.29.0
- Update eslint to ^9.39.4
- Add npm overrides for transitive vulns: flatted, brace-expansion, diff, lodash, js-yaml, bn.js
- Fix pino logger call signatures broken by updated types
- Rebase on origin/main

Remaining unfixable: fast-jwt/elliptic (no upstream patch available)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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