mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 06:56:35 +00:00
fix(mcp): answer the discovery documents under the server address too
A client handed https://host/mcp as the server address looks for the well-known documents below that path instead of asking the origin, and every one of those probes fell through to the web app: 200 with an HTML body, which reads to the client as "this server has no OAuth" (Open WebUI reports it as an unexpected mimetype). The three documents are served under /mcp/.well-known now, anything else there answers a JSON 404, and the RFC 8414 path-inserted form /.well-known/oauth-authorization-server/mcp serves the metadata instead of 404. The wiki says which redirect URI belongs to the client and that a plain-HTTP APP_URL leaves the discovery document pointing at localhost.
This commit is contained in:
@@ -103,6 +103,7 @@ export const PUBLIC_ROUTE_ALLOW_LIST: string[] = [
|
||||
'ConfigController.getConfig',
|
||||
// OAuth/OIDC discovery documents + the JSON 404 catchalls that keep
|
||||
// /.well-known probes from ever seeing SPA HTML.
|
||||
'DiscoveryController.authorizationServerForMcp',
|
||||
'DiscoveryController.openidConfiguration',
|
||||
'DiscoveryController.protectedResource',
|
||||
'DiscoveryController.wellKnownFallback',
|
||||
@@ -129,6 +130,12 @@ export const PUBLIC_ROUTE_ALLOW_LIST: string[] = [
|
||||
'JourneyPublicController.legacyPhoto',
|
||||
'JourneyPublicController.photo',
|
||||
// The MCP transport — bearer tokens are verified inside the handler.
|
||||
// The same documents under the resource URL, for clients that append the
|
||||
// well-known path to the server address they were handed.
|
||||
'McpResourceDiscoveryController.authorizationServer',
|
||||
'McpResourceDiscoveryController.fallback',
|
||||
'McpResourceDiscoveryController.openidConfiguration',
|
||||
'McpResourceDiscoveryController.protectedResource',
|
||||
'McpTransportController.delete',
|
||||
'McpTransportController.get',
|
||||
'McpTransportController.post',
|
||||
|
||||
@@ -73,6 +73,15 @@ export class DiscoveryController {
|
||||
throw new NotFoundException(`Cannot ${req.method} ${req.originalUrl}`);
|
||||
}
|
||||
|
||||
// RFC 8414 path insertion: a client that starts from the resource URL
|
||||
// (https://host/mcp) rather than the issuer looks for the AS metadata under
|
||||
// the resource's path. The SDK router only serves the flat form, and the
|
||||
// catchall below would answer 404 to a document we do have.
|
||||
@Get('oauth-authorization-server/mcp')
|
||||
authorizationServerForMcp(@Res() res: Response): void {
|
||||
res.json(this.meta.getOAuthMetadata());
|
||||
}
|
||||
|
||||
// Return 404 JSON for any /.well-known/* path the SDK metadata router doesn't
|
||||
// handle. Without this, the SPA catch-all serves HTML — clients probing
|
||||
// /.well-known/openid-configuration or the RFC 8414 path-suffixed AS metadata
|
||||
@@ -83,3 +92,37 @@ export class DiscoveryController {
|
||||
res.status(404).json({ error: 'not_found' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The same documents under the resource URL. A client handed https://host/mcp
|
||||
* as the server address and looking for discovery appends the well-known path
|
||||
* to it instead of asking the origin, and until now every one of those probes
|
||||
* fell through to the SPA: 200 with an HTML body, which reads to the client as
|
||||
* "this server has no OAuth" (Open WebUI reports it as an unexpected mimetype).
|
||||
* Answering here costs one route per document and a JSON 404 for the rest.
|
||||
*/
|
||||
@Public('OAuth/OIDC discovery metadata read by MCP clients before any session exists')
|
||||
@Controller('mcp/.well-known')
|
||||
export class McpResourceDiscoveryController {
|
||||
constructor(private readonly documents: DiscoveryController) {}
|
||||
|
||||
@Get('openid-configuration')
|
||||
openidConfiguration(@Res() res: Response): void {
|
||||
this.documents.openidConfiguration(res);
|
||||
}
|
||||
|
||||
@Get('oauth-authorization-server')
|
||||
authorizationServer(@Res() res: Response): void {
|
||||
this.documents.authorizationServerForMcp(res);
|
||||
}
|
||||
|
||||
@Get('oauth-protected-resource')
|
||||
protectedResource(@Res() res: Response): void {
|
||||
this.documents.protectedResource(res);
|
||||
}
|
||||
|
||||
@All('*path')
|
||||
fallback(@Res() res: Response): void {
|
||||
res.status(404).json({ error: 'not_found' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Module, type MiddlewareConsumer, type NestModule } from '@nestjs/common';
|
||||
import { AddonsModule } from '../addons/addons.module';
|
||||
import { DiscoveryController } from './discovery.controller';
|
||||
import { DiscoveryController, McpResourceDiscoveryController } from './discovery.controller';
|
||||
import { DiscoveryMetadataService } from './discovery-metadata.service';
|
||||
import { mcpMetadataMiddlewareProvider } from './mcp-metadata.middleware';
|
||||
import { ConsentCoopMiddleware } from './consent-coop.middleware';
|
||||
@@ -23,8 +23,8 @@ import { ConsentCoopMiddleware } from './consent-coop.middleware';
|
||||
*/
|
||||
@Module({
|
||||
imports: [AddonsModule],
|
||||
controllers: [DiscoveryController],
|
||||
providers: [DiscoveryMetadataService, mcpMetadataMiddlewareProvider, ConsentCoopMiddleware],
|
||||
controllers: [DiscoveryController, McpResourceDiscoveryController],
|
||||
providers: [DiscoveryMetadataService, DiscoveryController, mcpMetadataMiddlewareProvider, ConsentCoopMiddleware],
|
||||
})
|
||||
export class PlatformModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer): void {
|
||||
|
||||
Reference in New Issue
Block a user