Summary
The MCP specification includes resources/subscribe and resources/unsubscribe methods for clients to receive notifications when resources change. These are currently not implemented.
Additionally, resources/read uses exact URI matching only. When a resource is registered with a uriSchema for query parameters, reading resource://base?id=123 fails because the Map lookup is literal.
Current Behavior
// Client calls resources/subscribe
// Server returns: METHOD_NOT_FOUND (-32601)
For URI with query params:
app.mcpAddResource({
uri: 'aip://findings',
name: 'Findings',
uriSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] }
}, async (uri, params) => {
// params.id should contain the query parameter value
const finding = await db.getFinding(params.id);
return { contents: [{ uri, text: JSON.stringify(finding), mimeType: 'application/json' }] };
});
// Reading 'aip://findings?id=abc123' fails - exact match only
Proposed Solution
1. Custom Subscription Handlers
Rather than building subscription tracking into the library, provide hooks for applications to implement their own storage:
// Application creates its own subscription store (memory, Redis, etc.)
const subscriptionStore = createSubscriptionStore();
app.mcpSetResourcesSubscribeHandler(async (params, context) => {
await subscriptionStore.subscribe(context.sessionId, params.uri);
return {};
});
app.mcpSetResourcesUnsubscribeHandler(async (params, context) => {
await subscriptionStore.unsubscribe(context.sessionId, params.uri);
return {};
});
// When resource changes, notify via existing mcpSendToSession
const subscribers = await subscriptionStore.getSubscribers(changedUri);
for (const sessionId of subscribers) {
await app.mcpSendToSession(sessionId, {
jsonrpc: '2.0',
method: 'notifications/resources/updated',
params: { uri: changedUri }
});
}
2. Query Parameter URI Matching
When exact match fails in resources/read:
let resource = resources.get(uri);
// If not found and URI has query params, try base URI
if (!resource && uri.includes('?')) {
const baseUri = uri.split('?')[0];
const baseResource = resources.get(baseUri);
// Only use if resource has uriSchema (expects query params)
if (baseResource?.definition?.uriSchema) {
resource = baseResource;
}
}
3. Handler Types
interface ResourceHandlerContext {
sessionId?: string;
request: FastifyRequest;
reply: FastifyReply;
authContext?: AuthorizationContext;
}
type ResourceSubscribeHandler = (
params: { uri: string },
context: ResourceHandlerContext
) => Promise<Record<string, unknown>>;
type ResourceUnsubscribeHandler = (
params: { uri: string },
context: ResourceHandlerContext
) => Promise<Record<string, unknown>>;
Files to Modify
src/handlers.ts - Add subscription handlers, query param fallback
src/decorators/meta.ts - Add setter decorators
src/types.ts - Add handler types, Fastify declaration merging
src/index.ts - Create resourceHandlers object, pass through
src/routes/mcp.ts - Include resourceHandlers in dependencies
Design Decisions
-
Custom handlers, not built-in tracking: Applications manage their own storage (memory, Redis, etc.). This follows the library's pattern of delegating application-specific logic.
-
METHOD_NOT_FOUND when not configured: Clear signal that feature isn't enabled, rather than silent success.
-
uriSchema as query param indicator: Only falls back to base URI if resource explicitly expects query params via uriSchema.
-
Dependency injection: resourceHandlers object passed through plugin chain, avoiding module-level state.
Backwards Compatibility
- Existing
mcpAddResource registrations unchanged
- Default
resources/read behavior unchanged for exact matches
- Subscribe/unsubscribe return errors (not silent success) when not configured
- No changes to existing decorators or APIs
I'm happy to submit a PR with these changes. I have a working implementation.
Summary
The MCP specification includes
resources/subscribeandresources/unsubscribemethods for clients to receive notifications when resources change. These are currently not implemented.Additionally,
resources/readuses exact URI matching only. When a resource is registered with auriSchemafor query parameters, readingresource://base?id=123fails because the Map lookup is literal.Current Behavior
For URI with query params:
Proposed Solution
1. Custom Subscription Handlers
Rather than building subscription tracking into the library, provide hooks for applications to implement their own storage:
2. Query Parameter URI Matching
When exact match fails in
resources/read:3. Handler Types
Files to Modify
src/handlers.ts- Add subscription handlers, query param fallbacksrc/decorators/meta.ts- Add setter decoratorssrc/types.ts- Add handler types, Fastify declaration mergingsrc/index.ts- Create resourceHandlers object, pass throughsrc/routes/mcp.ts- Include resourceHandlers in dependenciesDesign Decisions
Custom handlers, not built-in tracking: Applications manage their own storage (memory, Redis, etc.). This follows the library's pattern of delegating application-specific logic.
METHOD_NOT_FOUND when not configured: Clear signal that feature isn't enabled, rather than silent success.
uriSchema as query param indicator: Only falls back to base URI if resource explicitly expects query params via
uriSchema.Dependency injection:
resourceHandlersobject passed through plugin chain, avoiding module-level state.Backwards Compatibility
mcpAddResourceregistrations unchangedresources/readbehavior unchanged for exact matchesI'm happy to submit a PR with these changes. I have a working implementation.