@silkweave/nestjs 1.11.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +16 -17
- package/.turbo/turbo-prepack.log +7 -8
- package/README.md +98 -135
- package/build/index.d.mts +222 -197
- package/build/index.d.mts.map +1 -1
- package/build/index.mjs +698 -352
- package/build/index.mjs.map +1 -1
- package/package.json +16 -10
- package/src/decorator/mcp.ts +39 -0
- package/src/index.ts +5 -7
- package/src/lib/controllerDiscovery.ts +201 -0
- package/src/lib/metadata.ts +32 -46
- package/src/lib/rebind.ts +117 -0
- package/src/lib/reflect/classValidator.ts +56 -0
- package/src/lib/reflect/openapi.ts +87 -0
- package/src/lib/reflect/params.ts +63 -0
- package/src/lib/reflect/route.ts +58 -0
- package/src/lib/reflect/schema.ts +270 -0
- package/src/lib/reflect/swagger.ts +39 -0
- package/src/lib/silkweave.module.ts +17 -17
- package/src/lib/types.ts +14 -5
- package/tsconfig.tsbuildinfo +1 -1
- package/src/adapter/rest.ts +0 -161
- package/src/adapter/trpc.ts +0 -58
- package/src/adapter/typegen.ts +0 -55
- package/src/decorator/action.ts +0 -38
- package/src/decorator/actions.ts +0 -25
- package/src/lib/discovery.ts +0 -125
- package/src/lib/filter.ts +0 -26
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
$ tsdown
|
|
2
|
-
[34mℹ[39m [34mtsdown v0.21.10[39m powered by [38;2;255;126;23mrolldown v1.0.0-rc.17[39m
|
|
3
|
-
[34mℹ[39m config file: [4m/Users/atomic/projects/silkweave/silkweave/packages/nestjs/tsdown.config.ts[24m
|
|
4
|
-
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
5
|
-
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
6
|
-
[34mℹ[39m Build start
|
|
7
|
-
[34mℹ[39m
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
[34mℹ[39m [2mbuild/[
|
|
13
|
-
[34mℹ[39m [2mbuild/[22mindex.
|
|
14
|
-
[34mℹ[39m [2mbuild/[
|
|
15
|
-
[34mℹ[39m
|
|
16
|
-
[
|
|
17
|
-
[32m✔[39m Build complete in [32m742ms[39m
|
|
1
|
+
[2m$ tsdown[22m
|
|
2
|
+
[34mℹ[39m [34mtsdown v0.21.10[39m powered by [38;2;255;126;23mrolldown v1.0.0-rc.17[39m
|
|
3
|
+
[34mℹ[39m config file: [4m/Users/atomic/projects/silkweave/silkweave/packages/nestjs/tsdown.config.ts[24m
|
|
4
|
+
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
5
|
+
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
6
|
+
[34mℹ[39m Build start
|
|
7
|
+
[34mℹ[39m Hint: consider adding [34mdeps.onlyBundle[39m option to avoid unintended bundling of dependencies, or set [34mdeps.onlyBundle: false[39m to disable this hint.
|
|
8
|
+
See more at [4mhttps://tsdown.dev/options/dependencies#deps-onlybundle[24m
|
|
9
|
+
Detected dependencies in bundle:
|
|
10
|
+
- [34mrxjs[39m
|
|
11
|
+
[34mℹ[39m [2mbuild/[22m[1mindex.mjs[22m [2m339.14 kB[22m [2m│ gzip: 49.99 kB[22m
|
|
12
|
+
[34mℹ[39m [2mbuild/[22mindex.mjs.map [2m579.22 kB[22m [2m│ gzip: 88.28 kB[22m
|
|
13
|
+
[34mℹ[39m [2mbuild/[22mindex.d.mts.map [2m 3.06 kB[22m [2m│ gzip: 1.10 kB[22m
|
|
14
|
+
[34mℹ[39m [2mbuild/[22m[32m[1mindex.d.mts[22m[39m [2m 17.08 kB[22m [2m│ gzip: 6.02 kB[22m
|
|
15
|
+
[34mℹ[39m 4 files, total: 938.50 kB
|
|
16
|
+
[32m✔[39m Build complete in [32m1034ms[39m
|
package/.turbo/turbo-prepack.log
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
[2m$ pnpm clean && pnpm build[22m
|
|
1
|
+
[2m$ pnpm clean && pnpm build[22m
|
|
3
2
|
[2m$ rimraf build[22m
|
|
4
3
|
[2m$ tsdown[22m
|
|
5
4
|
[34mℹ[39m [34mtsdown v0.21.10[39m powered by [38;2;255;126;23mrolldown v1.0.0-rc.17[39m
|
|
@@ -11,9 +10,9 @@
|
|
|
11
10
|
See more at [4mhttps://tsdown.dev/options/dependencies#deps-onlybundle[24m
|
|
12
11
|
Detected dependencies in bundle:
|
|
13
12
|
- [34mrxjs[39m
|
|
14
|
-
[34mℹ[39m [2mbuild/[22m[1mindex.mjs[22m [
|
|
15
|
-
[34mℹ[39m [2mbuild/[22mindex.mjs.map [
|
|
16
|
-
[34mℹ[39m [2mbuild/[22mindex.d.mts.map [2m
|
|
17
|
-
[34mℹ[39m [2mbuild/[22m[32m[1mindex.d.mts[22m[39m [2m
|
|
18
|
-
[34mℹ[39m 4 files, total:
|
|
19
|
-
[32m✔[39m Build complete in [
|
|
13
|
+
[34mℹ[39m [2mbuild/[22m[1mindex.mjs[22m [2m344.18 kB[22m [2m│ gzip: 51.78 kB[22m
|
|
14
|
+
[34mℹ[39m [2mbuild/[22mindex.mjs.map [2m589.05 kB[22m [2m│ gzip: 91.22 kB[22m
|
|
15
|
+
[34mℹ[39m [2mbuild/[22mindex.d.mts.map [2m 3.45 kB[22m [2m│ gzip: 1.22 kB[22m
|
|
16
|
+
[34mℹ[39m [2mbuild/[22m[32m[1mindex.d.mts[22m[39m [2m 20.10 kB[22m [2m│ gzip: 6.89 kB[22m
|
|
17
|
+
[34mℹ[39m 4 files, total: 956.79 kB
|
|
18
|
+
[32m✔[39m Build complete in [32m822ms[39m
|
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @silkweave/nestjs
|
|
2
2
|
|
|
3
|
-
NestJS adapter for [Silkweave](https://github.com/silkweave/silkweave).
|
|
3
|
+
NestJS adapter for [Silkweave](https://github.com/silkweave/silkweave). Expose your **existing NestJS controllers** as MCP (Model Context Protocol) tools by adding a single `@Mcp()` decorator to a route handler. The tool's name, description, and input schema are **reflected** from the route, the `@Param`/`@Query`/`@Body` decorators, and any `@nestjs/swagger` / `class-validator` metadata the method already carries - nothing is re-declared. On a tool call the validated input is split back into the method's positional arguments and the handler is invoked directly, with `@UseGuards()` guards applied first.
|
|
4
|
+
|
|
5
|
+
It is **additive**: controllers keep serving HTTP exactly as before, and removing `@Mcp()` fully reverts a method.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
@@ -8,52 +10,59 @@ NestJS adapter for [Silkweave](https://github.com/silkweave/silkweave). Define a
|
|
|
8
10
|
pnpm add @silkweave/core @silkweave/nestjs @nestjs/common @nestjs/core @nestjs/platform-express reflect-metadata
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
`@nestjs/swagger` and `class-validator` are **optional** peers - install whichever your DTOs already use; the reflector reads both and falls back to TypeScript `design:type` when neither is present.
|
|
14
|
+
|
|
15
|
+
> NestJS needs decorator-aware transforms with `emitDecoratorMetadata`. Use `@swc-node/register` (`node --import @swc-node/register/esm-register src/main.ts`) or the `nest start` CLI; plain `tsx` does not emit decorator metadata.
|
|
12
16
|
|
|
13
17
|
## Usage
|
|
14
18
|
|
|
15
19
|
```ts
|
|
16
|
-
// users.
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import
|
|
20
|
+
// users.controller.ts
|
|
21
|
+
import { Body, Controller, Get, NotFoundException, Param, Post, Query, UseGuards } from '@nestjs/common'
|
|
22
|
+
import { ApiOperation, ApiParam, ApiProperty, ApiQuery } from '@nestjs/swagger'
|
|
23
|
+
import { Mcp } from '@silkweave/nestjs'
|
|
24
|
+
import { IsBoolean, IsOptional, IsString, MinLength } from 'class-validator'
|
|
21
25
|
import { AdminGuard } from './admin.guard.js'
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
class BanUserDto {
|
|
28
|
+
@ApiProperty({ description: 'Reason for the ban' })
|
|
29
|
+
@IsString() @MinLength(3)
|
|
30
|
+
reason!: string
|
|
31
|
+
|
|
32
|
+
@ApiProperty({ description: 'Whether the ban is permanent', required: false })
|
|
33
|
+
@IsOptional() @IsBoolean()
|
|
34
|
+
permanent?: boolean
|
|
35
|
+
}
|
|
26
36
|
|
|
27
|
-
@
|
|
28
|
-
|
|
29
|
-
export class UserActions {
|
|
37
|
+
@Controller('users')
|
|
38
|
+
export class UsersController {
|
|
30
39
|
constructor(private readonly db: DbService) {}
|
|
31
40
|
|
|
32
|
-
@
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return this.db.listUsers(input.activeOnly)
|
|
41
|
+
@Get()
|
|
42
|
+
@ApiOperation({ summary: 'List users' })
|
|
43
|
+
@ApiQuery({ name: 'activeOnly', required: false, type: Boolean, description: 'Only active users' })
|
|
44
|
+
@Mcp()
|
|
45
|
+
list(@Query('activeOnly') activeOnly?: boolean) {
|
|
46
|
+
return this.db.listUsers(activeOnly)
|
|
39
47
|
}
|
|
40
48
|
|
|
41
|
-
@
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
@Get(':id')
|
|
50
|
+
@ApiOperation({ summary: 'Get a single user by ID' })
|
|
51
|
+
@ApiParam({ name: 'id', description: 'User ID' })
|
|
52
|
+
@Mcp()
|
|
53
|
+
get(@Param('id') id: string) {
|
|
54
|
+
const user = this.db.getUser(id)
|
|
55
|
+
if (!user) { throw new NotFoundException('user not found') }
|
|
56
|
+
return user
|
|
48
57
|
}
|
|
49
58
|
|
|
50
|
-
@
|
|
51
|
-
@
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
})
|
|
55
|
-
ban(
|
|
56
|
-
return this.db.banUser(
|
|
59
|
+
@Post(':id/ban')
|
|
60
|
+
@ApiOperation({ summary: 'Ban a user' })
|
|
61
|
+
@ApiParam({ name: 'id', description: 'User ID' })
|
|
62
|
+
@UseGuards(AdminGuard) // runs on every transport, MCP included
|
|
63
|
+
@Mcp({ description: 'Ban a user (admin only).' })
|
|
64
|
+
ban(@Param('id') id: string, @Body() body: BanUserDto) {
|
|
65
|
+
return this.db.banUser(id, body.reason, body.permanent ?? false)
|
|
57
66
|
}
|
|
58
67
|
}
|
|
59
68
|
```
|
|
@@ -61,22 +70,19 @@ export class UserActions {
|
|
|
61
70
|
```ts
|
|
62
71
|
// app.module.ts
|
|
63
72
|
import { Module } from '@nestjs/common'
|
|
64
|
-
import { mcp,
|
|
73
|
+
import { mcp, SilkweaveModule } from '@silkweave/nestjs'
|
|
65
74
|
import { AdminGuard } from './admin.guard.js'
|
|
66
|
-
import {
|
|
75
|
+
import { UsersController } from './users.controller.js'
|
|
67
76
|
|
|
68
77
|
@Module({
|
|
69
78
|
imports: [
|
|
70
79
|
SilkweaveModule.forRoot({
|
|
71
80
|
silkweave: { name: 'my-app', description: 'My App', version: '1.0.0' },
|
|
72
|
-
adapters: [
|
|
73
|
-
rest({ basePath: '/api' }),
|
|
74
|
-
trpc({ basePath: '/trpc' }),
|
|
75
|
-
mcp({ basePath: '/mcp' })
|
|
76
|
-
]
|
|
81
|
+
adapters: [mcp({ basePath: '/mcp' })]
|
|
77
82
|
})
|
|
78
83
|
],
|
|
79
|
-
|
|
84
|
+
controllers: [UsersController],
|
|
85
|
+
providers: [AdminGuard]
|
|
80
86
|
})
|
|
81
87
|
export class AppModule {}
|
|
82
88
|
```
|
|
@@ -91,90 +97,64 @@ const app = await NestFactory.create(AppModule)
|
|
|
91
97
|
await app.listen(8080)
|
|
92
98
|
```
|
|
93
99
|
|
|
94
|
-
The `
|
|
95
|
-
|
|
96
|
-
- **REST:** `GET /api/users/list?activeOnly=true` and `GET /api/users/get?id=1`
|
|
97
|
-
- **tRPC:** `client.usersList.query({ activeOnly: true })` and `client.usersGet.query({ id: '1' })`
|
|
98
|
-
- **MCP:** tools `UsersList` and `UsersGet`
|
|
99
|
-
|
|
100
|
-
`users.ban` is guarded by `@UseGuards(AdminGuard)` on **every** transport - REST, tRPC, **and** MCP. The guard reads its credential from the request header (`switchToHttp().getRequest().headers`); over MCP the inbound tool-call headers are surfaced the same way (see [Guards & DI](#guards--di)).
|
|
101
|
-
|
|
102
|
-
## Decorators
|
|
103
|
-
|
|
104
|
-
### `@Action(options)` (method decorator)
|
|
105
|
-
|
|
106
|
-
| Option | Type | Default | Description |
|
|
107
|
-
|--------|------|---------|-------------|
|
|
108
|
-
| `name` | `string` | kebab-cased method name | Override the action name. Joined with the class prefix via `.` |
|
|
109
|
-
| `description` | `string` | *required* | Human-readable summary. Used as MCP tool description |
|
|
110
|
-
| `input` | `z.ZodObject` | *required* | Zod object schema for the action's input |
|
|
111
|
-
| `output` | `z.ZodObject` | - | Optional output schema (used by tRPC type inference) |
|
|
112
|
-
| `chunk` | `z.ZodType` | - | Schema for chunks yielded by a streaming (`async function*`) method. **Required** when the method is an async generator |
|
|
113
|
-
| `kind` | `'query' \| 'mutation'` | `'mutation'` | `'query'` → GET in REST, `.query()` in tRPC. `'mutation'` → POST / `.mutation()` |
|
|
114
|
-
| `transports` | `('rest' \| 'trpc' \| 'mcp')[]` | all | Allowlist of transports that expose this action |
|
|
115
|
-
| `isEnabled` | `(ctx) => boolean` | - | Dynamic gate (AND-combined with `transports`) |
|
|
116
|
-
| `toolResult` | `(response, ctx) => CallToolResult` | - | Custom MCP `CallToolResult` formatter |
|
|
117
|
-
|
|
118
|
-
### `@Actions(prefix?)` (class decorator)
|
|
100
|
+
The MCP endpoint at `/mcp` now exposes three tools - `UsersList`, `UsersGet`, `UsersBan` - whose input schemas are reflected from the controllers:
|
|
119
101
|
|
|
120
|
-
|
|
102
|
+
- `UsersGet` → `{ id: string }` (`id` required, described "User ID" from `@ApiParam`)
|
|
103
|
+
- `UsersList` → `{ activeOnly?: boolean }` (optional, typed/described from `@ApiQuery`)
|
|
104
|
+
- `UsersBan` → `{ id: string, reason: string (minLength 3), permanent?: boolean }` (flattened from the path param + `BanUserDto`, with `@ApiProperty` + `class-validator` merged)
|
|
121
105
|
|
|
122
|
-
|
|
123
|
-
@Actions('users')
|
|
124
|
-
class UserActions {
|
|
125
|
-
@Action({ ... }) list(...) {} // action name: 'users.list'
|
|
126
|
-
@Action({ name: 'top' }) ... {} // action name: 'users.top'
|
|
127
|
-
}
|
|
128
|
-
```
|
|
106
|
+
`UsersBan` is guarded by `@UseGuards(AdminGuard)`; over MCP the guard reads the inbound tool-call headers (see [Guards & DI](#guards--di)).
|
|
129
107
|
|
|
130
|
-
|
|
108
|
+
## Decorator
|
|
131
109
|
|
|
132
|
-
|
|
110
|
+
### `@Mcp(options?)` (method decorator)
|
|
133
111
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
Maps actions to REST routes on the Nest HTTP server.
|
|
112
|
+
Exposes the decorated controller route as an MCP tool. Every option is optional.
|
|
137
113
|
|
|
138
114
|
| Option | Type | Default | Description |
|
|
139
115
|
|--------|------|---------|-------------|
|
|
140
|
-
| `
|
|
141
|
-
| `
|
|
116
|
+
| `name` | `string` | `${ControllerBase}${MethodName}` (e.g. `UsersGet`) | MCP tool name override |
|
|
117
|
+
| `description` | `string` | `@ApiOperation` summary/description, else generated | Tool description |
|
|
118
|
+
| `input` | `Record<string, z.ZodType>` | - | Zod raw-shape override merged over the reflected fields (per-field). The escape hatch for shapes reflection can't express - discriminated unions, custom validators, `@Transform` |
|
|
119
|
+
| `pipes` | `'apply' \| 'skip'` | `'apply'` | Whether to run parameter-bound pipes (`@Param('id', ParseIntPipe)`) when re-binding |
|
|
142
120
|
|
|
143
|
-
|
|
121
|
+
## How reflection works
|
|
144
122
|
|
|
145
|
-
|
|
146
|
-
- `users.ban` (mutation) → `POST /api/users/ban`
|
|
123
|
+
For each `@Mcp` method the adapter builds **one flat Zod input object** by merging, per field, in increasing precedence:
|
|
147
124
|
|
|
148
|
-
|
|
125
|
+
1. TypeScript `design:type` (the parameter/property constructor)
|
|
126
|
+
2. `class-validator` decorators (`@IsString`, `@MinLength`, `@IsOptional`, `@IsEnum`, ...) - **optional** peer
|
|
127
|
+
3. `@nestjs/swagger` decorators - `@ApiParam`/`@ApiQuery` for scalar path/query params, `@ApiProperty` for whole-DTO properties - **optional** peer
|
|
128
|
+
4. An ingested OpenAPI document, matched by HTTP verb + path (`SilkweaveModule.forRoot({ openapi })`)
|
|
129
|
+
5. `@Mcp({ input })` raw-shape override
|
|
149
130
|
|
|
150
|
-
|
|
131
|
+
Field sources are derived from the parameter decorators:
|
|
151
132
|
|
|
152
|
-
|
|
133
|
+
| Controller parameter | Tool input |
|
|
134
|
+
|----------------------|-----------|
|
|
135
|
+
| `@Param('id') id` | scalar field `id` |
|
|
136
|
+
| `@Param() params` | one field per `:param` in the route |
|
|
137
|
+
| `@Query('limit') limit` | scalar field `limit` |
|
|
138
|
+
| `@Query() dto: ListDto` | each property of `ListDto`, flattened to top level |
|
|
139
|
+
| `@Body('x') x` | scalar field `x` |
|
|
140
|
+
| `@Body() dto: CreateDto` | each property of `CreateDto`, flattened to top level |
|
|
141
|
+
| `@Req`/`@Res`/`@Headers`/`@Ip`/`@Session`/files | not exposed; bound at call time (headers/req from the MCP request stand-in, the rest `undefined`) |
|
|
153
142
|
|
|
154
|
-
|
|
155
|
-
|--------|------|---------|-------------|
|
|
156
|
-
| `basePath` | `string` | `'/trpc'` | URL prefix the tRPC handler listens on |
|
|
157
|
-
| `auth` | `AuthConfig` | - | `@silkweave/auth` bearer-token config |
|
|
143
|
+
On a tool call the validated input is split back into the handler's positional arguments per the same parameter map, parameter-bound pipes run (unless `pipes: 'skip'`), and the method is invoked directly.
|
|
158
144
|
|
|
159
|
-
|
|
145
|
+
### Optional OpenAPI ingestion
|
|
160
146
|
|
|
161
|
-
|
|
147
|
+
Pass a pre-built OpenAPI document (e.g. a committed `openapi.json`, or one built in a two-phase bootstrap) to use it as the authoritative schema source. It is matched to each `@Mcp` method by verb + path and overrides decorator reflection for the fields it covers; unmatched operations/fields fall back to reflection.
|
|
162
148
|
|
|
163
149
|
```ts
|
|
164
|
-
|
|
165
|
-
description: '
|
|
166
|
-
|
|
167
|
-
|
|
150
|
+
SilkweaveModule.forRoot({
|
|
151
|
+
silkweave: { name: 'my-app', description: 'My App', version: '1.0.0' },
|
|
152
|
+
adapters: [mcp()],
|
|
153
|
+
openapi: openApiDocument
|
|
168
154
|
})
|
|
169
|
-
async *countdown(input: { from: number }) {
|
|
170
|
-
for (let n = input.from; n >= 0; n -= 1) {
|
|
171
|
-
await new Promise((r) => setTimeout(r, 200))
|
|
172
|
-
yield { n }
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
155
|
```
|
|
176
156
|
|
|
177
|
-
|
|
157
|
+
## Adapter
|
|
178
158
|
|
|
179
159
|
### `mcp(options?)`
|
|
180
160
|
|
|
@@ -190,46 +170,29 @@ Mounts the MCP Streamable HTTP transport directly at `basePath`, with sideload (
|
|
|
190
170
|
|
|
191
171
|
## Guards & DI
|
|
192
172
|
|
|
193
|
-
Native NestJS `@UseGuards()`
|
|
173
|
+
Native NestJS `@UseGuards()` on `@Mcp` methods run before the handler is invoked. Guards receive an `ExecutionContext` with the request in `switchToHttp().getRequest()`, and the `Reflector` is wired up so guards can read custom metadata.
|
|
194
174
|
|
|
195
|
-
**Headers over MCP.**
|
|
175
|
+
**Headers over MCP.** The inbound tool-call request is surfaced as a stand-in `{ headers, url, params, query }` object built from the MCP SDK's `extra.requestInfo`, so a header-based guard - e.g. one reading `getRequest().headers['x-api-key']` - works unchanged. `ExecutionContext.getType()` is `'http'` whenever a request is available (and `'rpc'` for transports with none, e.g. MCP stdio). Caveats:
|
|
196
176
|
|
|
197
|
-
- Only **headers** (and the request `url`) cross the MCP boundary
|
|
177
|
+
- Only **headers** (and the request `url`) cross the MCP boundary - `getRequest().params`/`.query` are empty objects.
|
|
198
178
|
- A guard that denies (returns `false` or throws) produces a clean MCP tool error (`ForbiddenException`), not an HTTP 500.
|
|
199
|
-
- For OAuth 2.1 / bearer-token MCP auth, prefer `mcp({ auth })` and read the resolved identity from the silkweave context (`ctx.get('auth')`)
|
|
179
|
+
- For OAuth 2.1 / bearer-token MCP auth, prefer `mcp({ auth })` and read the resolved identity from the silkweave context (`ctx.get('auth')`); the header stand-in is for custom request-reading guards.
|
|
200
180
|
|
|
201
|
-
|
|
181
|
+
Controllers are normal Nest providers - inject services via the constructor as usual.
|
|
202
182
|
|
|
203
|
-
|
|
204
|
-
@Injectable()
|
|
205
|
-
class MyActions {
|
|
206
|
-
constructor(
|
|
207
|
-
private readonly db: DbService,
|
|
208
|
-
private readonly cache: CacheService
|
|
209
|
-
) {}
|
|
210
|
-
|
|
211
|
-
@UseGuards(AuthGuard, RateLimitGuard)
|
|
212
|
-
@Action({ description: '...', input: z.object({...}) })
|
|
213
|
-
async myAction(input, ctx) {
|
|
214
|
-
return this.db.query(...)
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
```
|
|
183
|
+
## What does *not* run
|
|
218
184
|
|
|
219
|
-
|
|
185
|
+
Because the handler is invoked directly (not through Nest's HTTP request pipeline), the following do **not** apply on a tool call - only `@UseGuards()` and parameter-bound pipes do:
|
|
220
186
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
| `users.list` | `/users/list` | `usersList` | `UsersList` |
|
|
225
|
-
| `posts.get-by-id` | `/posts/get-by-id` | `postsGetById` | `PostsGetById` |
|
|
187
|
+
- Globally-registered `ValidationPipe` / interceptors / exception filters (MCP input is instead validated against the reflected Zod schema).
|
|
188
|
+
- DTO class instantiation - whole-DTO `@Body()`/`@Query()` arguments arrive as plain objects, so `@Transform` and DTO methods do not fire.
|
|
189
|
+
- Discriminated-union / `@ValidateIf` XOR constraints can't be expressed as hard MCP schema constraints - document them in the tool `description` or supply `@Mcp({ input })`.
|
|
226
190
|
|
|
227
191
|
## Notes
|
|
228
192
|
|
|
229
|
-
- **
|
|
230
|
-
- **Express only by default.** `@nestjs/platform-fastify` requires `@fastify/express` registered upfront
|
|
231
|
-
-
|
|
232
|
-
- **`reflect-metadata`** must be imported once at the top of your entry file (typical Nest requirement).
|
|
193
|
+
- **Opt-in.** Only methods carrying `@Mcp()` are exposed - controllers are never auto-published.
|
|
194
|
+
- **Express only by default.** `@nestjs/platform-fastify` requires `@fastify/express` registered upfront.
|
|
195
|
+
- **`reflect-metadata`** must be imported once at the top of your entry file.
|
|
233
196
|
|
|
234
197
|
## License
|
|
235
198
|
|