@rudderjs/mcp 4.0.0 → 5.0.1

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.
@@ -0,0 +1,291 @@
1
+ # @rudderjs/mcp
2
+
3
+ ## Overview
4
+
5
+ MCP (Model Context Protocol) server framework for RudderJS. Lets the app expose **tools**, **resources**, and **prompts** to AI agents (Claude Code, Cursor, etc.) over either HTTP Streamable transport or local stdio. Tools and resources are plain classes with decorator-driven metadata and Zod input schemas. DI resolves constructor dependencies, OAuth 2.1 protects HTTP endpoints via `@rudderjs/passport`, and an observer registry exposes every tool call / resource read / prompt render for Telescope and other collectors.
6
+
7
+ ## Key Patterns
8
+
9
+ ### Server Definition
10
+
11
+ Extend `McpServer` and list the tool/resource/prompt **classes** (not instances). Metadata comes from decorators on the class.
12
+
13
+ ```ts
14
+ import { McpServer, Name, Version, Instructions } from '@rudderjs/mcp'
15
+
16
+ @Name('Weather Server')
17
+ @Version('1.0.0')
18
+ @Instructions('Provide weather information and forecasts.')
19
+ export class WeatherServer extends McpServer {
20
+ protected tools = [CurrentWeatherTool]
21
+ protected resources = [WeatherGuidelinesResource, CityWeatherResource]
22
+ protected prompts = [DescribeWeatherPrompt]
23
+ }
24
+ ```
25
+
26
+ ### Tools
27
+
28
+ Declare input via Zod, return via `McpResponse` helpers. Decorator `@Description` drives what the AI sees.
29
+
30
+ ```ts
31
+ import { McpTool, McpResponse, Description } from '@rudderjs/mcp'
32
+ import { z } from 'zod'
33
+
34
+ @Description('Get current weather for a location.')
35
+ export class CurrentWeatherTool extends McpTool {
36
+ schema() {
37
+ return z.object({
38
+ location: z.string().describe('City name'),
39
+ })
40
+ }
41
+
42
+ async handle(input: Record<string, unknown>) {
43
+ const location = input.location as string
44
+ return McpResponse.text(`Weather in ${location}: sunny, 22°C`)
45
+ }
46
+ }
47
+ ```
48
+
49
+ **Output schemas** — optional, advertises structured response shape to the client:
50
+
51
+ ```ts
52
+ outputSchema() {
53
+ return z.object({ temperature: z.number(), conditions: z.string() })
54
+ }
55
+ // handle() must return JSON matching the schema
56
+ async handle() { return McpResponse.json({ temperature: 22, conditions: 'sunny' }) }
57
+ ```
58
+
59
+ ### Resources
60
+
61
+ Two shapes — **static URIs** and **URI templates**:
62
+
63
+ ```ts
64
+ // Static URI
65
+ @Description('Weather usage guidelines')
66
+ export class WeatherGuidelinesResource extends McpResource {
67
+ uri() { return 'weather://guidelines' }
68
+ async handle() { return 'Always check conditions before...' }
69
+ }
70
+
71
+ // URI template — {param} placeholders
72
+ @Description('Weather for a specific city')
73
+ export class CityWeatherResource extends McpResource {
74
+ uri() { return 'weather://city/{name}' } // template
75
+ async handle(params?: Record<string, string>) {
76
+ return `Weather in ${params?.name}: sunny, 22°C`
77
+ }
78
+ }
79
+ ```
80
+
81
+ Templates are auto-registered via `ListResourceTemplates`. Params are extracted from the URI and passed to `handle()`.
82
+
83
+ ### Prompts
84
+
85
+ Like tools, but return message arrays instead of content:
86
+
87
+ ```ts
88
+ import { McpPrompt, Description } from '@rudderjs/mcp'
89
+
90
+ @Description('Describe weather poetically')
91
+ export class DescribeWeatherPrompt extends McpPrompt {
92
+ arguments() { return z.object({ location: z.string() }) }
93
+
94
+ async handle(args: Record<string, unknown>): Promise<McpPromptMessage[]> {
95
+ return [{ role: 'user', content: `Describe weather in ${args.location} poetically.` }]
96
+ }
97
+ }
98
+ ```
99
+
100
+ ### Response Helpers
101
+
102
+ ```ts
103
+ McpResponse.text('Plain text output')
104
+ McpResponse.json({ key: 'value' })
105
+ McpResponse.error('Something went wrong')
106
+ ```
107
+
108
+ ### Dependency Injection
109
+
110
+ Constructor params are auto-resolved from the DI container when the class is instantiated by the runtime. Falls back to `new T()` if the container isn't available.
111
+
112
+ ```ts
113
+ @Injectable()
114
+ @Description('Query the database')
115
+ export class DbQueryTool extends McpTool {
116
+ constructor(private db: DatabaseService) { super() }
117
+
118
+ schema() { return z.object({ query: z.string() }) }
119
+
120
+ async handle(input: Record<string, unknown>) {
121
+ const result = await this.db.query(input.query as string)
122
+ return McpResponse.json(result)
123
+ }
124
+ }
125
+ ```
126
+
127
+ **Method-level DI** — use `@Handle(TokenA, TokenB, ...)` to request DI-resolved extra parameters beyond the first. This is required under Vite/esbuild because those toolchains drop `design:paramtypes` metadata; explicit tokens are the reliable path.
128
+
129
+ ```ts
130
+ @Handle(GreetingService, Logger)
131
+ async handle(input: Record<string, unknown>, greeter: GreetingService, logger: Logger) {
132
+ logger.info(greeter.say(input.name as string))
133
+ return McpResponse.text('ok')
134
+ }
135
+ ```
136
+
137
+ ### Registering Servers
138
+
139
+ ```ts
140
+ import { Mcp } from '@rudderjs/mcp'
141
+ import { WeatherServer } from '../app/Mcp/Servers/WeatherServer.js'
142
+
143
+ // HTTP (Streamable HTTP transport) — served at the given path
144
+ Mcp.web('/mcp/weather', WeatherServer)
145
+
146
+ // With middleware chain
147
+ Mcp.web('/mcp/weather', WeatherServer).middleware([rateLimitMw, loggingMw])
148
+
149
+ // Protected by OAuth 2.1 Bearer tokens (requires @rudderjs/passport)
150
+ Mcp.web('/mcp/weather', WeatherServer).oauth2({ scopes: ['mcp:read'] })
151
+
152
+ // Local stdio (CLI)
153
+ Mcp.local('weather', WeatherServer)
154
+ ```
155
+
156
+ Registration runs once at boot; it's not per-request. Put these calls in a provider's `boot()` method or a dedicated registration module loaded from there.
157
+
158
+ ### OAuth 2.1 Protection
159
+
160
+ `.oauth2()` on the builder chain does three things: validates the Bearer JWT via `@rudderjs/passport`, enforces required scopes, and registers an RFC 9728 **Protected Resource Metadata** endpoint at `/.well-known/oauth-protected-resource<mcp-path>`. On auth failure, the response carries a `WWW-Authenticate` header pointing the client at that metadata doc — standard MCP client discovery flow.
161
+
162
+ ```ts
163
+ Mcp.web('/mcp/admin', AdminServer).oauth2({
164
+ scopes: ['admin'], // required on the token
165
+ authorizationServers: ['https://auth.example.com'], // defaults to app origin
166
+ scopesSupported: ['admin', 'read', 'write'], // advertised in metadata
167
+ })
168
+ ```
169
+
170
+ Passport must be installed and configured — see `@rudderjs/passport` guidelines. Without it, the OAuth middleware returns `invalid_token` with the metadata URL.
171
+
172
+ ### Observer Registry (Telescope Integration)
173
+
174
+ Every tool call, resource read, and prompt render emits a structured event. Packages like Telescope subscribe to collect telemetry; apps can subscribe too for custom logging.
175
+
176
+ ```ts
177
+ import { mcpObservers } from '@rudderjs/mcp/observers'
178
+
179
+ const unsubscribe = mcpObservers.subscribe((event) => {
180
+ // event.kind: 'tool.called' | 'tool.failed' | 'resource.read'
181
+ // | 'resource.failed' | 'prompt.rendered' | 'prompt.failed'
182
+ console.log(`[${event.kind}] ${event.serverName}/${event.name} (${event.duration}ms)`)
183
+ })
184
+ ```
185
+
186
+ The registry is a singleton stored on `globalThis` so state survives Vite SSR module re-evaluation. Observer errors are swallowed inside `emit()` — a broken subscriber cannot break an MCP server.
187
+
188
+ Don't import `@rudderjs/mcp/observers` in normal app code; it's meant for collector packages.
189
+
190
+ ### Config Shape
191
+
192
+ ```ts
193
+ // config/mcp.ts (optional — no required fields today)
194
+ export default {
195
+ // currently no required configuration; provider reads servers from Mcp.web/Mcp.local
196
+ } satisfies Record<string, unknown>
197
+
198
+ // bootstrap/providers.ts — provider is auto-discovered after `rudder providers:discover`
199
+ import { McpProvider } from '@rudderjs/mcp'
200
+ export default [..., McpProvider]
201
+ ```
202
+
203
+ ### CLI Commands
204
+
205
+ ```bash
206
+ pnpm rudder mcp:start <name> # start a local server via stdio
207
+ pnpm rudder mcp:list # list all registered MCP servers (web + local)
208
+ pnpm rudder mcp:inspector <name> # launch the MCP Inspector UI
209
+ ```
210
+
211
+ ### Scaffolders
212
+
213
+ ```bash
214
+ pnpm rudder make:mcp-server Weather
215
+ pnpm rudder make:mcp-tool CurrentWeather
216
+ pnpm rudder make:mcp-resource WeatherGuidelines
217
+ pnpm rudder make:mcp-prompt DescribeWeather
218
+ ```
219
+
220
+ Scaffolders land in `app/Mcp/{Servers,Tools,Resources,Prompts}/`.
221
+
222
+ ### Testing
223
+
224
+ `McpTestClient` boots an in-process server and exposes a minimal protocol client — no network hop, no stdio spawn.
225
+
226
+ ```ts
227
+ import { McpTestClient } from '@rudderjs/mcp'
228
+ import { WeatherServer } from '../app/Mcp/Servers/WeatherServer.js'
229
+
230
+ const client = new McpTestClient(WeatherServer)
231
+
232
+ const result = await client.callTool('current-weather', { location: 'London' })
233
+
234
+ client.assertToolExists('current-weather')
235
+ client.assertToolCount(1)
236
+ client.assertResourceExists('weather://guidelines')
237
+ client.assertPromptExists('describe-weather')
238
+
239
+ const tools = await client.listTools()
240
+ const resources = await client.listResources()
241
+ const prompts = await client.listPrompts()
242
+ ```
243
+
244
+ ## Common Pitfalls
245
+
246
+ - **Register classes, not instances** — `protected tools = [MyTool]` (class), never `[new MyTool()]`. The runtime instantiates each class via DI when the server boots.
247
+ - **`Mcp.web()` / `Mcp.local()` only run once** — at boot. Put them in a provider's `boot()` or a route-loaded module; never in request handlers.
248
+ - **OAuth 2.1 needs Passport** — `.oauth2()` requires `@rudderjs/passport` installed and configured. Without it, every request fails `invalid_token`.
249
+ - **Constructor DI works, but method DI under Vite needs `@Handle(...)`** — esbuild drops `design:paramtypes` metadata, so `@Handle()` without tokens silently falls back to empty. Always pass explicit tokens.
250
+ - **Zod v4 introspection differences** — the JSON-schema converter in `zod-to-json-schema.ts` handles both v3 and v4 shapes (`.describe()` location, `typeName`→`type`, `array.type`→`element`, `enum.values`→`entries`). When extending the converter, support both.
251
+ - **URI templates: only `{param}` placeholders** — no regex, no optional segments. Extracted params are always `string`. Validate/coerce inside `handle()`.
252
+ - **Output schema must match `handle()` return** — declaring `outputSchema()` but returning an unrelated shape surfaces a validation error to the client. Keep them in sync.
253
+ - **`McpResponse.error()` vs throwing** — `McpResponse.error(msg)` returns an MCP-protocol-shaped error response; throwing inside `handle()` emits a `tool.failed` observer event and surfaces a generic error to the client. Prefer `McpResponse.error()` for expected failures, throw for programmer errors.
254
+ - **Don't import `/observers` in app code** — that subpath is for Telescope-style collectors, not for general subscription. If you need tool-call logging in an app, prefer AI middleware or route-level observability instead.
255
+ - **Scaffolder registers via `registerMakeSpecs`** — `make:*` commands skip `bootApp()` for speed. Don't add boot-dependent logic to scaffolder stubs.
256
+ - **Provider boot order** — `McpProvider` registers web routes with the router, so the router provider must boot first. Auto-discovery handles this; don't add `McpProvider` manually before `@rudderjs/router` in a custom provider list.
257
+
258
+ ## Key Imports
259
+
260
+ ```ts
261
+ // Server + building blocks
262
+ import { McpServer, McpTool, McpResource, McpPrompt } from '@rudderjs/mcp'
263
+
264
+ // Response helpers
265
+ import { McpResponse } from '@rudderjs/mcp'
266
+
267
+ // Decorators
268
+ import { Name, Version, Instructions, Description, Handle } from '@rudderjs/mcp'
269
+
270
+ // Registration facade
271
+ import { Mcp } from '@rudderjs/mcp'
272
+ import type { McpWebEntry, McpWebBuilder } from '@rudderjs/mcp'
273
+
274
+ // OAuth 2.1 protection
275
+ import { oauth2McpMiddleware, registerOAuth2Metadata } from '@rudderjs/mcp'
276
+ import type { OAuth2McpOptions } from '@rudderjs/mcp'
277
+
278
+ // Runtime primitives (rarely needed in app code)
279
+ import { createSdkServer, startStdio, mountHttpTransport } from '@rudderjs/mcp'
280
+ import type { HttpTransportOptions } from '@rudderjs/mcp'
281
+
282
+ // Provider + testing
283
+ import { McpProvider, McpTestClient } from '@rudderjs/mcp'
284
+
285
+ // Observer registry — for collectors only, not app code
286
+ import { mcpObservers } from '@rudderjs/mcp/observers'
287
+ import type { McpObserverEvent, McpObserver, McpObserverRegistry } from '@rudderjs/mcp'
288
+
289
+ // Types
290
+ import type { McpServerMetadata, McpToolResult, McpPromptMessage, InjectToken } from '@rudderjs/mcp'
291
+ ```
@@ -0,0 +1,285 @@
1
+ ---
2
+ name: mcp-servers
3
+ description: Building MCP servers with tools, resources, prompts, decorators, and HTTP/stdio transports in RudderJS
4
+ ---
5
+
6
+ # MCP Servers
7
+
8
+ ## When to use this skill
9
+
10
+ Load this skill when you need to build a Model Context Protocol (MCP) server to expose tools, resources, and prompts to AI coding assistants and other MCP clients.
11
+
12
+ ## Key concepts
13
+
14
+ - **McpServer**: Base class that declares which tools, resources, and prompts to register. Extend it and set `protected tools`, `resources`, and `prompts` arrays.
15
+ - **McpTool**: Base class for tools. Implement `schema()` (Zod) and `handle()`. Name auto-derived from class name (PascalCase -> kebab-case, minus "Tool" suffix).
16
+ - **McpResource**: Base class for resources. Implement `uri()` and `handle()`. Supports URI templates with `{param}` placeholders.
17
+ - **McpPrompt**: Base class for prompts. Implement `handle()` and optionally `arguments()` for a Zod schema.
18
+ - **Decorators**: `@Name`, `@Version`, `@Instructions`, `@Description` set metadata via reflect-metadata.
19
+ - **McpResponse**: Helper for building tool results (`McpResponse.text()`, `.json()`, `.error()`).
20
+ - **Transports**: stdio (local CLI) via `startStdio()`, HTTP/SSE (web) via `mountHttpTransport()`.
21
+ - **DI support**: Tool/resource/prompt classes are resolved via the framework's DI container when available, falling back to plain `new T()`.
22
+ - **McpTestClient**: In-memory test client for unit testing servers without transport overhead.
23
+
24
+ ## Step-by-step
25
+
26
+ ### 1. Create a tool
27
+
28
+ ```ts
29
+ // app/Mcp/Tools/WeatherTool.ts
30
+ import { McpTool, McpResponse, Description } from '@rudderjs/mcp'
31
+ import { z } from 'zod'
32
+
33
+ @Description('Get current weather for a city')
34
+ export class WeatherTool extends McpTool {
35
+ // Name auto-derived: "WeatherTool" -> "weather"
36
+ // Override with: name() { return 'get-weather' }
37
+
38
+ schema() {
39
+ return z.object({
40
+ city: z.string().describe('City name'),
41
+ units: z.enum(['celsius', 'fahrenheit']).default('celsius'),
42
+ })
43
+ }
44
+
45
+ // Optional: advertise the output structure
46
+ outputSchema() {
47
+ return z.object({
48
+ temperature: z.number(),
49
+ conditions: z.string(),
50
+ })
51
+ }
52
+
53
+ async handle(input: Record<string, unknown>) {
54
+ const { city, units } = input as { city: string; units: string }
55
+ const data = await fetchWeather(city, units)
56
+ return McpResponse.json({ temperature: data.temp, conditions: data.conditions })
57
+ }
58
+ }
59
+ ```
60
+
61
+ ### 2. Create a resource
62
+
63
+ ```ts
64
+ // app/Mcp/Resources/SchemaResource.ts
65
+ import { McpResource, Description } from '@rudderjs/mcp'
66
+
67
+ @Description('Returns the database schema')
68
+ export class SchemaResource extends McpResource {
69
+ uri() { return 'db://schema' }
70
+ mimeType() { return 'text/plain' }
71
+
72
+ async handle() {
73
+ const schema = await readFile('prisma/schema.prisma', 'utf-8')
74
+ return schema
75
+ }
76
+ }
77
+ ```
78
+
79
+ ### 3. Create a resource with URI template
80
+
81
+ ```ts
82
+ // app/Mcp/Resources/TableResource.ts
83
+ import { McpResource, Description } from '@rudderjs/mcp'
84
+
85
+ @Description('Read rows from a database table')
86
+ export class TableResource extends McpResource {
87
+ uri() { return 'db://tables/{tableName}' } // {param} makes it a template
88
+ mimeType() { return 'application/json' }
89
+
90
+ async handle(params?: Record<string, string>) {
91
+ const tableName = params?.tableName ?? 'unknown'
92
+ const rows = await db.query(`SELECT * FROM ${tableName} LIMIT 100`)
93
+ return JSON.stringify(rows, null, 2)
94
+ }
95
+ }
96
+ ```
97
+
98
+ ### 4. Create a prompt
99
+
100
+ ```ts
101
+ // app/Mcp/Prompts/ReviewPrompt.ts
102
+ import { McpPrompt, Description } from '@rudderjs/mcp'
103
+ import type { McpPromptMessage } from '@rudderjs/mcp'
104
+ import { z } from 'zod'
105
+
106
+ @Description('Generate a code review prompt for a file')
107
+ export class ReviewPrompt extends McpPrompt {
108
+ // Name auto-derived: "ReviewPrompt" -> "review"
109
+
110
+ arguments() {
111
+ return z.object({
112
+ file: z.string().describe('Path to the file to review'),
113
+ focus: z.string().optional().describe('Area to focus on'),
114
+ })
115
+ }
116
+
117
+ async handle(args: Record<string, unknown>): Promise<McpPromptMessage[]> {
118
+ const { file, focus } = args as { file: string; focus?: string }
119
+ const content = await readFile(file, 'utf-8')
120
+
121
+ return [
122
+ {
123
+ role: 'user',
124
+ content: `Please review this code${focus ? ` with focus on ${focus}` : ''}:\n\n${content}`,
125
+ },
126
+ ]
127
+ }
128
+ }
129
+ ```
130
+
131
+ ### 5. Assemble the server
132
+
133
+ ```ts
134
+ // app/Mcp/AppMcpServer.ts
135
+ import { McpServer, Name, Version, Instructions } from '@rudderjs/mcp'
136
+ import { WeatherTool } from './Tools/WeatherTool.js'
137
+ import { SchemaResource } from './Resources/SchemaResource.js'
138
+ import { TableResource } from './Resources/TableResource.js'
139
+ import { ReviewPrompt } from './Prompts/ReviewPrompt.js'
140
+
141
+ @Name('my-app-mcp')
142
+ @Version('1.0.0')
143
+ @Instructions('An MCP server for my application. Use the weather tool to check conditions.')
144
+ export class AppMcpServer extends McpServer {
145
+ protected tools = [WeatherTool]
146
+ protected resources = [SchemaResource, TableResource]
147
+ protected prompts = [ReviewPrompt]
148
+ }
149
+ ```
150
+
151
+ ### 6. Register for stdio transport (local CLI)
152
+
153
+ ```ts
154
+ // routes/console.ts or bootstrap
155
+ import { Mcp } from '@rudderjs/mcp'
156
+ import { AppMcpServer } from '../app/Mcp/AppMcpServer.js'
157
+
158
+ Mcp.local('app', AppMcpServer)
159
+
160
+ // Run via: pnpm rudder mcp:start app
161
+ ```
162
+
163
+ ### 7. Register for HTTP transport (web endpoint)
164
+
165
+ ```ts
166
+ // routes/console.ts or a provider's boot()
167
+ import { Mcp } from '@rudderjs/mcp'
168
+ import { AppMcpServer } from '../app/Mcp/AppMcpServer.js'
169
+
170
+ Mcp.web('/mcp', AppMcpServer)
171
+
172
+ // Optionally add middleware:
173
+ Mcp.web('/mcp', AppMcpServer).middleware([rateLimitMiddleware])
174
+
175
+ // The endpoint handles:
176
+ // POST /mcp — JSON-RPC messages
177
+ // GET /mcp — SSE stream for server-initiated notifications
178
+ // DELETE /mcp — session termination
179
+ ```
180
+
181
+ ### 8. Register the MCP service provider
182
+
183
+ ```ts
184
+ // bootstrap/providers.ts
185
+ import { mcp } from '@rudderjs/mcp'
186
+
187
+ export default [
188
+ ...(await defaultProviders()),
189
+ mcp(), // registers Mcp facade + boots web/local servers + CLI commands
190
+ ]
191
+ ```
192
+
193
+ ### 9. McpResponse helpers
194
+
195
+ ```ts
196
+ import { McpResponse } from '@rudderjs/mcp'
197
+
198
+ // Text response
199
+ return McpResponse.text('The weather is sunny')
200
+
201
+ // JSON response (pretty-printed)
202
+ return McpResponse.json({ temp: 72, conditions: 'sunny' })
203
+
204
+ // Error response
205
+ return McpResponse.error('City not found')
206
+
207
+ // Raw response (for images etc.)
208
+ return {
209
+ content: [
210
+ { type: 'text', text: 'Here is the chart:' },
211
+ { type: 'image', data: base64Data, mimeType: 'image/png' },
212
+ ],
213
+ }
214
+ ```
215
+
216
+ ### 10. Testing with McpTestClient
217
+
218
+ ```ts
219
+ import { McpTestClient } from '@rudderjs/mcp'
220
+ import { AppMcpServer } from './AppMcpServer.js'
221
+
222
+ const client = new McpTestClient(AppMcpServer)
223
+
224
+ // List tools
225
+ const tools = await client.listTools()
226
+ // [{ name: 'weather', description: 'Get current weather...' }]
227
+
228
+ // Call a tool
229
+ const result = await client.callTool('weather', { city: 'Paris', units: 'celsius' })
230
+
231
+ // Read a resource
232
+ const schema = await client.readResource('db://schema')
233
+
234
+ // Get a prompt
235
+ const messages = await client.getPrompt('review', { file: 'src/index.ts' })
236
+
237
+ // Assertions
238
+ client.assertToolExists('weather')
239
+ client.assertToolCount(1)
240
+ client.assertResourceExists('db://schema')
241
+ client.assertResourceCount(2)
242
+ client.assertPromptExists('review')
243
+ client.assertPromptCount(1)
244
+ ```
245
+
246
+ ### 11. DI-injected tools
247
+
248
+ ```ts
249
+ import { McpTool, McpResponse, Description } from '@rudderjs/mcp'
250
+ import { injectable, inject } from 'tsyringe'
251
+ import { z } from 'zod'
252
+
253
+ @injectable()
254
+ @Description('Search the knowledge base')
255
+ export class SearchTool extends McpTool {
256
+ constructor(@inject('search.service') private search: SearchService) {
257
+ super()
258
+ }
259
+
260
+ schema() {
261
+ return z.object({ query: z.string() })
262
+ }
263
+
264
+ async handle(input: Record<string, unknown>) {
265
+ const results = await this.search.query(input.query as string)
266
+ return McpResponse.json(results)
267
+ }
268
+ }
269
+ // When the RudderJS DI container is available, tools are resolved via
270
+ // container.make(ToolClass), auto-injecting constructor dependencies.
271
+ ```
272
+
273
+ ## Examples
274
+
275
+ See `packages/mcp/src/index.test.ts` for test examples and `packages/mcp/src/runtime.ts` for the full transport implementation.
276
+
277
+ ## Common pitfalls
278
+
279
+ - **Name derivation**: `WeatherTool` -> `weather`, `GetUserInfoTool` -> `get-user-info`. The class name is converted to kebab-case with the `Tool` suffix removed. Override `name()` if the auto-derived name isn't what you want.
280
+ - **Schema must be z.object()**: Both `schema()` and `arguments()` must return `z.object(...)`, not a bare `z.string()` or other type.
281
+ - **@modelcontextprotocol/sdk peer dep**: The MCP SDK (`@modelcontextprotocol/sdk`) is a peer dependency. Install it alongside `@rudderjs/mcp`.
282
+ - **HTTP transport requires @rudderjs/router**: `mountHttpTransport()` dynamically imports `@rudderjs/router` to register the endpoint. Stdio transport has no such requirement.
283
+ - **URI template matching**: Template resources use `{param}` syntax (e.g. `db://tables/{name}`). The extracted params are passed to `handle(params)`.
284
+ - **Error handling**: If `handle()` throws, the runtime catches it and returns `McpResponse.error(err.message)` automatically. You don't need try/catch in every tool.
285
+ - **mcp:list command**: Run `pnpm rudder mcp:list` to see all registered MCP servers (web and local).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rudderjs/mcp",
3
- "version": "4.0.0",
3
+ "version": "5.0.1",
4
4
  "description": "MCP (Model Context Protocol) server framework for RudderJS",
5
5
  "rudderjs": {
6
6
  "provider": "McpProvider",
@@ -14,7 +14,8 @@
14
14
  },
15
15
  "type": "module",
16
16
  "files": [
17
- "dist"
17
+ "dist",
18
+ "boost"
18
19
  ],
19
20
  "main": "./dist/index.js",
20
21
  "types": "./dist/index.d.ts",
@@ -48,11 +49,11 @@
48
49
  "@modelcontextprotocol/sdk": "^1.13.0",
49
50
  "reflect-metadata": "^0.2.0",
50
51
  "zod": "^3.23.0",
51
- "@rudderjs/core": "^1.0.0"
52
+ "@rudderjs/core": "^1.1.3"
52
53
  },
53
54
  "peerDependencies": {
54
- "@rudderjs/router": "^1.0.0",
55
- "@rudderjs/console": "^0.0.4"
55
+ "@rudderjs/router": "^1.2.0",
56
+ "@rudderjs/console": "^1.0.1"
56
57
  },
57
58
  "peerDependenciesMeta": {
58
59
  "@rudderjs/router": {
@@ -65,8 +66,8 @@
65
66
  "devDependencies": {
66
67
  "@types/node": "^20.0.0",
67
68
  "typescript": "^5.4.0",
68
- "@rudderjs/console": "^0.0.4",
69
- "@rudderjs/router": "^1.0.0"
69
+ "@rudderjs/router": "^1.2.0",
70
+ "@rudderjs/console": "^1.0.1"
70
71
  },
71
72
  "author": "Suleiman Shahbari",
72
73
  "scripts": {