Skip to content

Commit b5971e7

Browse files
authored
support for custom controllers (#109)
* Adds support for custom controllers * Adds example using custom controllers with the advanced server * Adds support for async configuration * bump version to 1.8.1
1 parent f55113c commit b5971e7

16 files changed

+1104
-557
lines changed

CLAUDE.md

Lines changed: 144 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
55
# NestJS MCP Server Module
66

77
## Project Overview
8-
This is `@rekog/mcp-nest`, a NestJS module that transforms NestJS applications into Model Context Protocol (MCP) servers. It exposes tools, resources, and prompts for AI consumption via decorators, supporting multiple transport protocols (HTTP+SSE, Streamable HTTP, STDIO).
8+
This is `@rekog/mcp-nest`, a NestJS module that transforms NestJS applications into Model Context Protocol (MCP) servers. It exposes tools, resources, and prompts for AI consumption via decorators, supporting multiple transport protocols (HTTP+SSE, Streamable HTTP, STDIO) with optional OAuth 2.1 authentication.
99

1010
## Essential Development Commands
1111

@@ -34,10 +34,51 @@ npx --node-options=--experimental-vm-modules jest tests/mcp-tool.e2e.spec.ts
3434
npx --node-options=--experimental-vm-modules jest --testNamePattern="auth"
3535
```
3636

37-
## Core Architecture Patterns
37+
## Core Components
3838

39-
### 1. Decorator-Based Registration System
40-
Tools, resources, and prompts are discovered through decorators using NestJS's reflection capabilities:
39+
### 1. McpModule - The Primary MCP Server Module
40+
Located at `src/mcp/mcp.module.ts:18`. This is the main module for creating MCP servers.
41+
42+
**Key Features**:
43+
- Decorator-based tool/resource/prompt discovery via `McpRegistryService`
44+
- Multi-transport support (HTTP+SSE, Streamable HTTP, STDIO)
45+
- Dynamic controller generation for different transport types
46+
- Module instance isolation with unique `moduleId` per `forRoot()` call
47+
48+
**Basic Usage**:
49+
```typescript
50+
McpModule.forRoot({
51+
name: 'my-mcp-server',
52+
version: '1.0.0',
53+
transport: [McpTransportType.SSE, McpTransportType.STREAMABLE_HTTP],
54+
guards: [SomeGuard], // Optional authentication
55+
})
56+
```
57+
58+
### 2. McpAuthModule - OAuth 2.1 Authorization Server
59+
Located at `src/authz/mcp-oauth.module.ts:76`. Provides complete OAuth 2.1 compliant Identity Provider implementation.
60+
61+
**Key Features**:
62+
- Built-in GitHub and Google OAuth providers
63+
- Multiple storage backends (memory, TypeORM, custom)
64+
- MCP Authorization specification compliance (2025-06-18)
65+
- Dynamic client registration (RFC 7591)
66+
- PKCE support and comprehensive token validation
67+
68+
**Basic Usage**:
69+
```typescript
70+
McpAuthModule.forRoot({
71+
provider: GitHubOAuthProvider,
72+
clientId: process.env.GITHUB_CLIENT_ID!,
73+
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
74+
jwtSecret: process.env.JWT_SECRET!,
75+
serverUrl: 'http://localhost:3030',
76+
apiPrefix: 'auth',
77+
})
78+
```
79+
80+
### 3. Tool Definition Pattern
81+
Tools are defined using decorators with three-parameter method signature:
4182

4283
```typescript
4384
@Injectable()
@@ -56,130 +97,146 @@ export class MyService {
5697
}
5798
```
5899

59-
**Key Pattern**: Method signature is `(args, context, httpRequest)` where:
60-
- `args`: Zod-validated parameters
100+
**Method Parameters**:
101+
- `args`: Zod-validated parameters from tool call
61102
- `context`: MCP context with `reportProgress`, `mcpServer`, `mcpRequest`, logging
62-
- `httpRequest`: HTTP request object (undefined for STDIO)
103+
- `request`: HTTP request object (undefined for STDIO transport)
63104

64-
### 2. Multi-Transport Architecture
65-
Three transport types with different controllers:
105+
### 4. Transport Architecture
106+
Three transport types with dynamic controller creation:
66107
- **SSE**: `createSseController()` - GET `/sse` + POST `/messages`
67108
- **Streamable HTTP**: `createStreamableHttpController()` - POST `/mcp` (+ GET/DELETE for stateful)
68-
- **STDIO**: `StdioService` - No HTTP endpoints
109+
- **STDIO**: `StdioService` - No HTTP endpoints, uses standard input/output
69110

70-
Each transport creates dynamic controllers via factory functions in `McpModule.forRoot()`.
111+
Controllers are generated dynamically in `McpModule.forRoot()` based on transport configuration.
112+
113+
### 5. Module Integration Pattern
114+
Both modules work together for authenticated MCP servers:
71115

72-
### 3. Request Scoping & Dependency Injection
73-
Uses NestJS's request scoping for per-request instances:
74116
```typescript
75-
@Injectable({ scope: Scope.REQUEST })
76-
export class RequestScopedService {
77-
constructor(@Inject(REQUEST) private request: Request) {}
78-
}
117+
@Module({
118+
imports: [
119+
McpAuthModule.forRoot({ /* OAuth config */ }),
120+
McpModule.forRoot({
121+
guards: [McpAuthJwtGuard], // Links auth to MCP
122+
/* other MCP config */
123+
}),
124+
],
125+
providers: [McpAuthJwtGuard],
126+
})
127+
class AppModule {}
79128
```
80129

81-
The `McpExecutorService` (REQUEST-scoped) orchestrates handler registration per request.
130+
## Documentation Structure
131+
The project maintains comprehensive documentation in the `docs/` directory:
82132

83-
### 4. Module Instance Isolation
84-
Each `McpModule.forRoot()` call creates a unique module instance with its own `moduleId`:
85-
```typescript
86-
const moduleId = `mcp-module-${instanceIdCounter++}`;
87-
```
88-
This enables multiple MCP servers in one application with different endpoints/capabilities.
133+
### Core Guides
134+
- `docs/tools.md` - Tool creation, parameters, progress reporting, elicitation
135+
- `docs/resources.md` - Static and dynamic content serving
136+
- `docs/resource-templates.md` - Parameterized resource URIs
137+
- `docs/prompts.md` - Reusable prompt templates
138+
- `docs/server-examples.md` - Complete server configurations and transport examples
139+
- `docs/dependency-injection.md` - NestJS DI patterns within MCP context
89140

90-
## Testing Patterns
141+
### Authorization Documentation
142+
- `docs/built-in-authorization-server.md` - Complete McpAuthModule usage and configuration
143+
- `docs/external-authorization-server/README.md` - External OAuth server integration
91144

92-
### E2E Test Structure
93-
The test suite comprehensively covers all transport types:
94-
- E2E tests create actual NestJS apps with different transport configurations
95-
- Tests run the same scenarios across HTTP+SSE, Streamable HTTP (stateful/stateless), and STDIO
96-
- Use `createSseClient()`, `createStreamableClient()`, `createStdioClient()` helpers
145+
## Key Implementation Details
97146

98-
### Test File Patterns
99-
- `*.e2e.spec.ts` - End-to-end integration tests
100-
- `*.spec.ts` - Unit tests
101-
- All tests require `--node-options=--experimental-vm-modules` flag
147+
### Module Instance Isolation
148+
Each `McpModule.forRoot()` creates isolated instances with unique `moduleId`:
149+
```typescript
150+
const moduleId = `mcp-module-${instanceIdCounter++}`;
151+
```
152+
This enables multiple MCP servers in one application with different capabilities.
102153

103-
## Critical Implementation Details
154+
### Request Scoping & Discovery
155+
- `McpRegistryService` discovers decorated methods at bootstrap using `DiscoveryService`
156+
- `McpExecutorService` (REQUEST-scoped) handles per-request tool execution
157+
- Registry maintains maps by `mcpModuleId` for isolation
104158

105159
### Output Schema Validation
106-
Tools with `outputSchema` get validated results. Failed validation throws `McpError`:
160+
Tools with `outputSchema` validate results, throwing `McpError` on failure:
107161
```typescript
108162
if (outputSchema) {
109163
const validation = outputSchema.safeParse(result);
110164
if (!validation.success) {
111165
throw new McpError(ErrorCode.InternalError, `Tool result does not match outputSchema`);
112166
}
113-
return { structuredContent: result, content: this.buildDefaultContentBlock(result) };
114167
}
115168
```
116169

117-
### Resource URI Matching
118-
Resources use `path-to-regexp` for dynamic URI matching:
170+
### Resource URI Templates
171+
Resources use `path-to-regexp` for dynamic URIs:
119172
- Static: `uri: 'mcp://hello-world'`
120173
- Template: `uriTemplate: 'mcp://hello-world/{userId}/{userName}'`
121174

122-
Templates extract parameters using `match()` function with URL decoding.
175+
### OAuth Store Configuration
176+
McpAuthModule supports multiple storage backends:
177+
- Memory store (default, testing)
178+
- TypeORM store (production, with unique connection name to avoid clashes)
179+
- Custom store implementation via `IOAuthStore` interface
123180

124-
### Authentication Integration
125-
Guards apply to all MCP endpoints:
126-
```typescript
127-
McpModule.forRoot({
128-
guards: [AuthGuard], // Applied to SSE/messages/mcp endpoints
129-
})
130-
```
131-
Request context flows through to tools via dependency injection.
181+
## Testing Patterns
182+
- E2E tests create actual NestJS apps with different transport configurations
183+
- Tests run scenarios across HTTP+SSE, Streamable HTTP (stateful/stateless), and STDIO
184+
- All tests require `--node-options=--experimental-vm-modules` flag
185+
- Use client helpers: `createSseClient()`, `createStreamableClient()`, `createStdioClient()`
132186

133-
## Project-Specific Conventions
187+
## Project Structure
134188

135189
### File Organization
136-
- `src/mcp/` - Core MCP functionality
137-
- `src/authz/` - OAuth authentication module
190+
- `src/mcp/` - Core MCP functionality (McpModule, transports, services)
191+
- `src/authz/` - OAuth authentication module (McpAuthModule)
138192
- `src/mcp/decorators/` - Tool/Resource/Prompt decorators
139-
- `src/mcp/services/handlers/` - Protocol request handlers
140-
- `src/mcp/transport/` - Transport implementations
141-
- `playground/` - Working examples
142-
- `tests/` - Comprehensive E2E test suite
143-
144-
### Error Handling Patterns
145-
MCP tools should return standardized error format:
146-
```typescript
147-
return {
148-
content: [{ type: 'text', text: error.message }],
149-
isError: true,
150-
};
151-
```
152-
153-
### Registry Service Pattern
154-
`McpRegistryService` discovers decorated methods at bootstrap using `DiscoveryService` and `MetadataScanner`. It maintains maps by `mcpModuleId` for isolation.
193+
- `src/mcp/services/handlers/` - MCP protocol request handlers
194+
- `src/mcp/transport/` - Transport implementations (SSE, Streamable HTTP, STDIO)
195+
- `src/authz/providers/` - OAuth providers (GitHub, Google, custom interface)
196+
- `src/authz/stores/` - Storage backends (memory, TypeORM, custom interface)
197+
- `playground/` - Working examples and demo servers
198+
- `tests/` - Comprehensive E2E test suite covering all transports
199+
- `docs/` - Complete documentation for all features
155200

156201
### HTTP Adapter Abstraction
157-
`HttpAdapterFactory` provides framework-agnostic request/response handling for Express/Fastify compatibility.
202+
`HttpAdapterFactory` at `src/mcp/adapters/` provides framework-agnostic request/response handling for Express/Fastify compatibility.
158203

159-
## Key Integration Points
204+
## Integration Points
160205

161206
### With NestJS Ecosystem
162-
- Full DI container integration
163-
- Guard/Interceptor support
164-
- Request scoping
165-
- Module system
166-
- Versioning compatibility (VERSION_NEUTRAL)
207+
- Full dependency injection container integration
208+
- Guard/Interceptor support for authentication
209+
- Request scoping for per-request instances
210+
- Module system with dynamic module configuration
211+
- Compatibility with NestJS versioning (VERSION_NEUTRAL)
167212

168213
### With MCP SDK
169-
- Wraps `@modelcontextprotocol/sdk` transports
170-
- Handles MCP protocol schemas
171-
- Progress reporting via context
172-
- Elicitation support for interactive tools
214+
- Wraps `@modelcontextprotocol/sdk` for transport layer
215+
- Handles MCP protocol message schemas
216+
- Progress reporting via context object
217+
- Elicitation support for interactive tool calls
173218

174219
### External Dependencies
175-
- `zod` for parameter validation
176-
- `path-to-regexp` for URI matching
177-
- `zod-to-json-schema` for OpenAPI-style schemas
220+
- `@modelcontextprotocol/sdk` - Core MCP protocol implementation
221+
- `zod` - Parameter validation and schema definition
222+
- `path-to-regexp` - Dynamic URI matching for resource templates
223+
- `zod-to-json-schema` - Schema conversion for tool parameters
224+
- `passport` + provider strategies - OAuth authentication
225+
- `@nestjs/jwt` - JWT token management
226+
- `typeorm` (optional) - Database storage for OAuth data
227+
228+
## Key Architecture Principles
229+
230+
### Transform Pattern
231+
The module transforms existing NestJS services into MCP servers through decorators, making business logic available to AI systems without modification.
232+
233+
### Transport Agnostic
234+
Each `McpModule.forRoot()` can serve multiple transport protocols simultaneously, with clients choosing their preferred connection method.
235+
236+
### Security Integration
237+
Authentication is handled at the transport level via NestJS Guards, ensuring all MCP endpoints respect the same security policies as the rest of the application.
178238

179-
## Important Notes from Copilot Instructions
239+
### Stateful vs Stateless
240+
Supports both stateful (session-based) and stateless operation modes, with configurable session ID generation for multi-user scenarios.
180241

181-
- This module transforms NestJS services into MCP servers through decorators, not the other way around
182-
- The NestJS application hosts the MCP server, making existing business logic available to AI systems
183-
- Each `McpModule.forRoot()` creates an isolated instance with unique endpoints
184-
- Authentication uses standard NestJS Guards applied at the transport level
185-
- Progress reporting is handled through the MCP context object passed to tools
242+
- don't run linting, I don't care about it or formatting

docs/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
- [Resources Guide](./resources.md) — Defining and exposing resources.
88
- [Dependency Injection](docs/dependency-injection.md) — Leverage NestJS DI system throughout MCP components.
99

10+
### Advanced Usage
11+
12+
- [Advanced Server Pattern](../playground/servers/advanced/README.md) — Direct service usage with custom controllers for maximum control over interceptors, guards, middleware, and endpoint configuration.
13+
1014
### OAuth & Authorization
1115

1216
- [Built-in Authorization Server](./built-in-authorization-server.md) — Using the built-in Authorization Server for simpler setups.

docs/server-examples.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,52 @@ curl -X POST http://localhost:3030/mcp \
316316
}'
317317
```
318318

319+
## Advanced Server Pattern
320+
321+
For maximum control over your MCP endpoints, you can disable automatic controller generation and use the MCP services directly in custom controllers:
322+
323+
```typescript
324+
@Module({
325+
imports: [
326+
McpModule.forRoot({
327+
name: 'advanced-server',
328+
version: '1.0.0',
329+
transport: [], // Disable automatic controllers
330+
}),
331+
],
332+
controllers: [CustomSseController, CustomStreamableController],
333+
providers: [GreetingTool],
334+
})
335+
class AppModule {}
336+
```
337+
338+
And the controller would be similar to:
339+
340+
```typescript
341+
@Controller()
342+
export class CustomStreamableController {
343+
constructor(private readonly mcpStreamableHttpService: McpStreamableHttpService) {}
344+
345+
@Post('/mcp')
346+
async handlePostRequest(
347+
@Req() req: any,
348+
@Res() res: any,
349+
@Body() body: unknown,
350+
): Promise<void> {
351+
await this.mcpStreamableHttpService.handlePostRequest(req, res, body);
352+
}
353+
354+
// additional endpoints ...
355+
}
356+
```
357+
358+
This pattern allows you to:
359+
- Apply custom guards, interceptors, and middleware
360+
- Define custom endpoint paths and routing
361+
- Have fine-grained control over request/response handling
362+
363+
**See:** [Advanced Server Pattern Guide](../playground/servers/advanced/README.md) for a full implementation.
364+
319365
## Example Locations
320366

321367
Complete examples can be found in:
@@ -325,6 +371,7 @@ Complete examples can be found in:
325371
- `playground/servers/stdio.ts` - STDIO server
326372
- `playground/servers/server-stateful-fastify.ts` - Fastify server
327373
- `playground/servers/server-stateful-oauth.ts` - Server with OAuth
374+
- `playground/servers/advanced/` - Advanced pattern with custom controllers
328375

329376
## Related
330377

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rekog/mcp-nest",
3-
"version": "1.8.1-alpha.1",
3+
"version": "1.8.1",
44
"description": "NestJS module for creating Model Context Protocol (MCP) servers",
55
"main": "dist/index.js",
66
"license": "MIT",

0 commit comments

Comments
 (0)