Skip to content

Commit 4141622

Browse files
Merge pull request #1 from ts-oas/feat/mcp
Add @nest-openapi/mcp
2 parents 641d769 + 28a1dbd commit 4141622

28 files changed

+3076
-72
lines changed

README.md

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,20 @@
2323

2424
## Features
2525

26-
- **🎯 Single Source of Truth** — Your OpenAPI spec drives validation, serialization, and mocking.
26+
- **🎯 Single Source of Truth** — Your OpenAPI spec drives validation, serialization, mocking, and MCP tools.
2727
- **⚡ Fast by Design** — AJV validation and `fast-json-stringify` serialization with caching and precompilation.
2828
- **🔌 Drop-in Integration** — Works with existing NestJS controllers and routes
2929
- **🎛️ Fine-Grained Control** — Per-route opt-out and custom schema overrides
3030
- **🚀 Platform Agnostic** — Supports both Express and Fastify adapters
3131

3232
## Packages
3333

34-
| Package | Description | Version | Docs |
35-
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
36-
| [`@nest-openapi/validator`](https://www.npmjs.com/package/@nest-openapi/validator) | Automatic request/response validation using your OpenAPI spec | [![npm](https://img.shields.io/npm/v/@nest-openapi/validator.svg)](https://www.npmjs.com/package/@nest-openapi/validator) | [📖 Docs](https://nest-openapi.github.io/validator/) |
37-
| [`@nest-openapi/serializer`](https://www.npmjs.com/package/@nest-openapi/serializer) | High-performance response serialization based on your OpenAPI spec | [![npm](https://img.shields.io/npm/v/@nest-openapi/serializer.svg)](https://www.npmjs.com/package/@nest-openapi/serializer) | [📖 Docs](https://nest-openapi.github.io/serializer/) |
38-
| [`@nest-openapi/mock`](https://www.npmjs.com/package/@nest-openapi/mock) | Spec-driven mock server for generating realistic mock responses | [![npm](https://img.shields.io/npm/v/@nest-openapi/mock.svg)](https://www.npmjs.com/package/@nest-openapi/mock) | [📖 Docs](https://nest-openapi.github.io/mock/) |
34+
| Package | Description | Version |
35+
| ------------------------------------------------------------------------ | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
36+
| [`@nest-openapi/validator`](https://nest-openapi.github.io/validator/) | Automatic request/response validation using your OpenAPI spec | [![npm](https://img.shields.io/npm/v/@nest-openapi/validator.svg)](https://www.npmjs.com/package/@nest-openapi/validator) |
37+
| [`@nest-openapi/serializer`](https://nest-openapi.github.io/serializer/) | High-performance response serialization based on your OpenAPI spec | [![npm](https://img.shields.io/npm/v/@nest-openapi/serializer.svg)](https://www.npmjs.com/package/@nest-openapi/serializer) |
38+
| [`@nest-openapi/mock`](https://nest-openapi.github.io/mock/) | Spec-driven mock server for generating realistic mock responses | [![npm](https://img.shields.io/npm/v/@nest-openapi/mock.svg)](https://www.npmjs.com/package/@nest-openapi/mock) |
39+
| [`@nest-openapi/mcp`](https://nest-openapi.github.io/mcp/) | Spec-driven MCP server for exposing OpenAPI operations as tools | [![npm](https://img.shields.io/npm/v/@nest-openapi/mcp.svg)](https://www.npmjs.com/package/@nest-openapi/mcp) |
3940

4041
## Quick Start
4142

@@ -111,6 +112,31 @@ export class AppModule {}
111112

112113
**Routes return mocked responses when enabled.** See [full documentation](https://nest-openapi.github.io/mock/) for advanced configuration.
113114

115+
### MCP
116+
117+
```bash
118+
npm i @nest-openapi/mcp
119+
```
120+
121+
```typescript
122+
import { Module } from "@nestjs/common";
123+
import { OpenAPIMcpModule } from "@nest-openapi/mcp";
124+
import * as openApiSpec from "./openapi.json";
125+
126+
@Module({
127+
imports: [
128+
OpenAPIMcpModule.forRoot({
129+
specSource: { type: "object", spec: openApiSpec },
130+
http: { path: "/mcp" },
131+
executor: { baseUrl: "http://127.0.0.1:3000" },
132+
}),
133+
],
134+
})
135+
export class AppModule {}
136+
```
137+
138+
**Expose OpenAPI operations as MCP tools.** See [full documentation](https://nest-openapi.github.io/mcp/) for advanced configuration.
139+
114140
## Usage Examples
115141

116142
### Manual Validation
@@ -126,7 +152,7 @@ import {
126152
export class MyService {
127153
constructor(
128154
@Inject(OPENAPI_VALIDATOR)
129-
private readonly validator: OpenAPIValidatorService
155+
private readonly validator: OpenAPIValidatorService,
130156
) {}
131157

132158
validate(ctx: HttpArgumentsHost) {
@@ -159,7 +185,7 @@ export class BooksController {
159185
## Compatibility
160186

161187
- Works with NestJS v9+
162-
- Supports Express and Fastify adopters
188+
- Supports Express and Fastify adapters
163189

164190
## Contributing
165191

docs/.vitepress/config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export default {
2323
{ text: "Validator", link: "/validator/" },
2424
{ text: "Serializer", link: "/serializer/" },
2525
{ text: "Mock", link: "/mock/" },
26+
{ text: "MCP", link: "/mcp/" },
2627
],
2728
socialLinks: [
2829
{ icon: "github", link: "https://github.com/ts-oas/nest-openapi" },
@@ -63,6 +64,17 @@ export default {
6364
],
6465
},
6566
],
67+
"/mcp/": [
68+
{
69+
text: "MCP",
70+
items: [
71+
{ text: "Overview", link: "/mcp/" },
72+
{ text: "Advanced setup", link: "/mcp/advanced-setup" },
73+
{ text: "Options", link: "/mcp/options" },
74+
{ text: "Manual Usage", link: "/mcp/manual" },
75+
],
76+
},
77+
],
6678
},
6779
outline: [2, 6],
6880
search: { provider: "local" },

docs/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ hero:
1818
- theme: brand
1919
text: Mock
2020
link: /mock/
21+
- theme: brand
22+
text: MCP
23+
link: /mcp/
2124
- theme: alt
2225
text: GitHub
2326
link: https://github.com/ts-oas/nest-openapi

docs/mcp/advanced-setup.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Advanced setup
2+
3+
## Extend the generated MCP server
4+
5+
Use `extendServer` when you want to register custom tools/resources/prompts in addition to OpenAPI-generated tools.
6+
7+
```ts
8+
OpenAPIMcpModule.forRoot({
9+
specSource: { type: "object", spec: openApiSpec },
10+
executor: { baseUrl: "http://127.0.0.1:3000" },
11+
extendServer: (server) => {
12+
server.registerTool(
13+
"health_check",
14+
{
15+
title: "Health Check",
16+
description: "Returns MCP server health status",
17+
inputSchema: { type: "object", properties: {} } as any,
18+
},
19+
async () => ({ content: [{ type: "text", text: "ok" }] }),
20+
);
21+
},
22+
});
23+
```
24+
25+
This hook runs after OpenAPI-derived tools are registered.
26+
27+
## HTTP transport modes
28+
29+
When HTTP transport is enabled, `OpenAPIMcpModule` exposes an MCP endpoint (default `/mcp`) and delegates each request to `OpenAPIMcpService.handleHttp()`.
30+
31+
### Stateless mode (default)
32+
33+
```ts
34+
http: {
35+
path: "/mcp",
36+
sessionMode: "stateless",
37+
}
38+
```
39+
40+
- A fresh MCP server/transport is created per request.
41+
- Simpler operational model.
42+
43+
### Stateful mode
44+
45+
```ts
46+
http: {
47+
path: "/mcp",
48+
sessionMode: "stateful",
49+
sessionTtlMs: 3600000,
50+
}
51+
```
52+
53+
- Server/transport instances are reused per `mcp-session-id`.
54+
- Session id is returned in response headers.
55+
- Sessions are cleaned automatically when TTL expires.
56+
57+
## Access control at the transport edge
58+
59+
Use host/origin allowlists to harden HTTP exposure:
60+
61+
```ts
62+
http: {
63+
path: "/mcp",
64+
allowedHosts: ["trusted-host.com"],
65+
allowedOrigins: ["https://trusted.app"],
66+
}
67+
```
68+
69+
Denied requests return `403`.
70+
71+
## Upstream execution policy
72+
73+
Generated tools execute upstream HTTP operations via `executor.baseUrl`.
74+
75+
### Forward selected headers
76+
77+
```ts
78+
executor: {
79+
baseUrl: "http://127.0.0.1:3000",
80+
forwardHeaders: ["authorization", "x-api-key"],
81+
}
82+
```
83+
84+
Only headers listed in `forwardHeaders` are propagated.
85+
86+
### Configure request timeout
87+
88+
```ts
89+
executor: {
90+
baseUrl: "http://127.0.0.1:3000",
91+
timeoutMs: 15000,
92+
}
93+
```
94+
95+
Timeout is enforced per tool execution request.
96+
97+
## Tool exposure strategy (`x-mcp`)
98+
99+
OpenAPI operations are included based on this precedence:
100+
101+
1. `operation.x-mcp`
102+
2. `pathItem.x-mcp`
103+
3. `root.x-mcp`
104+
4. `tools.defaultInclude`
105+
106+
This lets you define strict opt-in or broad include policies, then override at narrower scopes.

docs/mcp/index.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Overview
2+
3+
`@nest-openapi/mcp` turns your OpenAPI 3.x operations into MCP tools for NestJS applications.
4+
5+
- **Spec-driven** — Tools are generated from OpenAPI operations.
6+
- **API contract ↔ MCP contract** — Request/input and success response/output OpenAPI schemas are mapped into MCP tool schemas.
7+
- **Secure by default** — Tool exposure is opt-in by default (`x-mcp: true` per operation).
8+
- **NestJS-native** — HTTP transport integration plus programmatic server APIs.
9+
10+
### Install
11+
12+
```bash
13+
npm i @nest-openapi/mcp
14+
```
15+
16+
### Quick Start
17+
18+
```ts
19+
// app.module.ts
20+
import { Module } from "@nestjs/common";
21+
import { OpenAPIMcpModule } from "@nest-openapi/mcp";
22+
import * as openApiSpec from "./openapi.json";
23+
24+
@Module({
25+
imports: [
26+
OpenAPIMcpModule.forRoot({
27+
specSource: { type: "object", spec: openApiSpec },
28+
http: { path: "/mcp" },
29+
executor: { baseUrl: "http://127.0.0.1:3000" },
30+
}),
31+
],
32+
})
33+
export class AppModule {}
34+
```
35+
36+
That’s it. Your MCP endpoint is available at `/mcp`.
37+
38+
### Tool Exposure with `x-mcp`
39+
40+
By default, only operations explicitly marked with `x-mcp: true` are exposed as tools.
41+
42+
```yaml
43+
paths:
44+
/users/{id}:
45+
get:
46+
operationId: getUser
47+
x-mcp: true
48+
```
49+
50+
You can change this behavior using [`tools.defaultInclude`](/mcp/options#tools).
51+
52+
## Next Steps
53+
54+
- [Advanced setup](./advanced-setup.md) — Transport/session strategy and server extension patterns
55+
- [Options](./options.md) — Full configuration reference
56+
- [Manual Usage](./manual.md) — Programmatic MCP server usage

docs/mcp/manual.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Manual Usage
2+
3+
Inject the `OpenAPIMcpService` for programmatic MCP server usage (e.g., dedicated stdio process, custom transport handling, or advanced lifecycle control).
4+
5+
## Inject the service
6+
7+
```ts
8+
import { Inject, Injectable } from "@nestjs/common";
9+
import { OpenAPIMcpService, OPENAPI_MCP } from "@nest-openapi/mcp";
10+
11+
@Injectable()
12+
export class McpRunnerService {
13+
constructor(@Inject(OPENAPI_MCP) private readonly mcp: OpenAPIMcpService) {}
14+
}
15+
```
16+
17+
## Start stdio transport manually
18+
19+
Use this for CLI agents or sidecar MCP processes:
20+
21+
```ts
22+
await this.mcp.startStdio();
23+
```
24+
25+
This creates the MCP server from your OpenAPI spec and connects it to stdio transport.
26+
27+
## Create and extend server programmatically
28+
29+
You can create a fully configured MCP server instance and manage transport yourself:
30+
31+
```ts
32+
const server = await this.mcp.createServer();
33+
// connect your own transport here
34+
```
35+
36+
For non-manual patterns such as `extendServer`, HTTP session strategy, and header-forwarding policy, see [Advanced setup](./advanced-setup.md).

docs/mcp/options.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Options
2+
3+
## Configuration Options
4+
5+
| Option | Type | Default | Description |
6+
| --------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------- | ----------------------------------------------------------------------------------- |
7+
| [`specSource`](#specsource) | `{ type: "object"; spec: OpenAPISpec } \| { type: "url"; spec: string } \| { type: "file"; spec: string }` || Provide your OpenAPI 3.x spec as an object, or point to it via URL or file path. |
8+
| [`server`](#server) | `{ name?: string; version?: string }` | see [below](#server) | MCP server metadata exposed during initialize. |
9+
| [`tools`](#tools) | `OpenAPIMcpToolsOptions` | see [below](#tools) | Control tool inclusion and naming. |
10+
| [`http`](#http) | `OpenAPIMcpHttpOptions` | see [below](#http) | Configure built-in MCP HTTP endpoint behavior. |
11+
| [`executor`](#executor) | `OpenAPIMcpExecutorOptions` | see [below](#executor) | Configure how generated tools execute target HTTP operations. |
12+
| `extendServer` | `(server: McpServer) => void \| Promise<void>` || Hook to register custom tools/resources/prompts or modify the generated MCP server. |
13+
| `debug` | `boolean` | `false` | Verbose logs for troubleshooting. |
14+
15+
### `specSource`
16+
17+
| Type | Type | Typical use |
18+
| ---------- | ------------- | ------------------------------------------------ |
19+
| `"object"` | `OpenAPISpec` | Static spec object. |
20+
| `"url"` | `string` | Link to a centralized or externally hosted spec. |
21+
| `"file"` | `string` | Local file path to a json/yaml file. |
22+
23+
### `server`
24+
25+
| Option | Type | Default | Description |
26+
| --------- | -------- | ---------------------------------- | ------------------------------------------ |
27+
| `name` | `string` | `spec.info.title` or `openapi-mcp` | MCP server display name during initialize. |
28+
| `version` | `string` | `spec.info.version` or `0.0.0` | MCP server version during initialize. |
29+
30+
### `tools`
31+
32+
| Option | Type | Default | Description |
33+
| ---------------- | --------- | ------- | ------------------------------------------------------------------- |
34+
| `defaultInclude` | `boolean` | `false` | Include operations as tools by default when `x-mcp` is not defined. |
35+
| `namePrefix` | `string` || Optional prefix for generated tool names (e.g. `api_`). |
36+
37+
`x-mcp` precedence for operation inclusion is:
38+
39+
1. operation-level `x-mcp`
40+
2. path-level `x-mcp`
41+
3. root-level `x-mcp`
42+
4. fallback to `tools.defaultInclude`
43+
44+
### `http`
45+
46+
| Option | Type | Default | Description |
47+
| ---------------- | --------------------------- | ------------- | ---------------------------------------------------------------------------- |
48+
| `enabled` | `boolean` | `true` | Enable built-in MCP HTTP endpoint/controller. |
49+
| `path` | `string` | `"/mcp"` | Endpoint path for streamable HTTP MCP transport. |
50+
| `sessionMode` | `"stateless" \| "stateful"` | `"stateless"` | Transport lifecycle mode for HTTP requests. |
51+
| `sessionTtlMs` | `number` | `3600000` | Session TTL in stateful mode. Expired sessions are cleaned up automatically. |
52+
| `allowedOrigins` | `string[]` || Optional origin allowlist check (returns `403` when denied). |
53+
| `allowedHosts` | `string[]` || Optional host allowlist check (returns `403` when denied). |
54+
55+
### `executor`
56+
57+
| Option | Type | Default | Description |
58+
| ---------------- | ---------- | ------------------- | ---------------------------------------------------------------------- |
59+
| `baseUrl` | `string` || Base URL for upstream HTTP execution. Required. |
60+
| `forwardHeaders` | `string[]` | `["authorization"]` | Request headers to forward from MCP request context to upstream calls. |
61+
| `timeoutMs` | `number` | `30000` | Upstream HTTP timeout per tool call. |
62+
63+
## Async Configuration
64+
65+
Use `forRootAsync()` for dependency injection:
66+
67+
```ts
68+
OpenAPIMcpModule.forRootAsync({
69+
imports: [ConfigModule],
70+
useFactory: (config: ConfigService) => ({
71+
specSource: { type: "url", spec: config.getOrThrow("OPENAPI_SPEC_URL") },
72+
http: {
73+
path: "/mcp",
74+
sessionMode: "stateless",
75+
},
76+
executor: {
77+
baseUrl: config.getOrThrow("TARGET_API_BASE_URL"),
78+
forwardHeaders: ["authorization", "x-api-key"],
79+
timeoutMs: 15000,
80+
},
81+
}),
82+
inject: [ConfigService],
83+
});
84+
```

0 commit comments

Comments
 (0)