docs(api): complete OpenAPI/Swagger documentation for all endpoints

- Add Swagger decorators (@ApiTags, @ApiOperation, @ApiResponse, @ApiParam,
  @ApiBearerAuth) to MCP transport controller — the only controller missing them
- Add reviews and mcp tags to DocumentBuilder config
- Enable JSON spec export at /api/v1/docs-json
- Update Helmet CSP to allow Swagger UI assets from cdn.jsdelivr.net

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-09 09:08:26 +07:00
parent 7f694e2e60
commit b23be886b1
2 changed files with 22 additions and 2 deletions

View File

@@ -43,10 +43,13 @@ async function bootstrap() {
.addTag('admin', 'Admin panel operations')
.addTag('notifications', 'Notification history & preferences')
.addTag('analytics', 'Market reports & price analytics')
.addTag('reviews', 'Property reviews & ratings')
.addTag('mcp', 'Model Context Protocol server transport')
.build();
const document = SwaggerModule.createDocument(app, swaggerConfig);
SwaggerModule.setup('api/v1/docs', app, document, {
swaggerOptions: { persistAuthorization: true },
jsonDocumentUrl: 'api/v1/docs-json',
});
// ── Security Headers (Helmet) ──
@@ -55,8 +58,8 @@ async function bootstrap() {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'", "'unsafe-inline'", 'https://cdn.jsdelivr.net'],
styleSrc: ["'self'", "'unsafe-inline'", 'https://cdn.jsdelivr.net'],
imgSrc: ["'self'", 'data:', 'https:', 'blob:'],
connectSrc: ["'self'"],
fontSrc: ["'self'", 'data:'],

View File

@@ -10,9 +10,12 @@ import {
HttpStatus,
UseGuards,
} from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import type { Request, Response } from 'express';
import { JwtAuthGuard, CurrentUser, type JwtPayload } from '@modules/auth';
@ApiTags('mcp')
@ApiBearerAuth('JWT')
@Controller('mcp')
@UseGuards(JwtAuthGuard)
export class McpTransportController {
@@ -21,11 +24,19 @@ export class McpTransportController {
constructor(private readonly registry: McpRegistryService) {}
@Get('servers')
@ApiOperation({ summary: 'List available MCP servers' })
@ApiResponse({ status: 200, description: 'List of registered MCP server names' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
listServers(): { servers: string[] } {
return { servers: this.registry.getServerNames() };
}
@Get(':serverName/sse')
@ApiOperation({ summary: 'Open SSE connection to an MCP server' })
@ApiParam({ name: 'serverName', description: 'Name of the MCP server to connect to' })
@ApiResponse({ status: 200, description: 'SSE stream established' })
@ApiResponse({ status: 404, description: 'MCP server not found' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async handleSse(
@Param('serverName') serverName: string,
@CurrentUser() _user: JwtPayload,
@@ -54,6 +65,12 @@ export class McpTransportController {
}
@Post(':serverName/messages')
@ApiOperation({ summary: 'Send a message to an MCP server session' })
@ApiParam({ name: 'serverName', description: 'Name of the MCP server' })
@ApiResponse({ status: 200, description: 'Message processed' })
@ApiResponse({ status: 400, description: 'Missing sessionId query parameter' })
@ApiResponse({ status: 404, description: 'Session not found or expired' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async handleMessage(
@Param('serverName') _serverName: string,
@CurrentUser() _user: JwtPayload,