@mastra/mcp 2.0.0 → 2.1.0-alpha.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/dist/client/client.d.ts.map +1 -1
- package/dist/client/types.d.ts +2 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-connections-mcp.md +15 -9
- package/dist/docs/references/reference-tools-mcp-client.md +80 -277
- package/dist/docs/references/reference-tools-mcp-server.md +205 -537
- package/dist/index.cjs +12 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +12 -8
- package/dist/index.js.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/package.json +8 -8
|
@@ -8,7 +8,9 @@ The `MCPServer` class provides the functionality to expose your existing Mastra
|
|
|
8
8
|
|
|
9
9
|
Note that if you only need to use your tools or agents directly within your Mastra application, you don't necessarily need to create an MCP server. This API is specifically for exposing your Mastra tools and agents to _external_ MCP clients.
|
|
10
10
|
|
|
11
|
-
It supports
|
|
11
|
+
It supports the [stdio (subprocess) and Streamable HTTP MCP transports](https://modelcontextprotocol.io/docs/concepts/transports) of the MCP `2026-07-28` revision.
|
|
12
|
+
|
|
13
|
+
> **Note:** Upgrading from `@mastra/mcp` 1.x? See the [migration guide](https://mastra.ai/reference/migrations/mcp-v2).
|
|
12
14
|
|
|
13
15
|
## Constructor
|
|
14
16
|
|
|
@@ -53,7 +55,7 @@ const server = new MCPServer({
|
|
|
53
55
|
|
|
54
56
|
## Tool schemas and structured results
|
|
55
57
|
|
|
56
|
-
MCP tool input and output schemas are advertised as JSON Schema 2020-12
|
|
58
|
+
MCP tool input and output schemas are advertised as JSON Schema 2020-12 with the dialect declared in `$schema`. Advertised Mastra tool schemas preserve supported references, composition keywords, and tuple (`prefixItems`) shapes.
|
|
57
59
|
|
|
58
60
|
A tool with an `outputSchema` can return any JSON value, including an object, array, string, number, boolean, or `null`. The value is sent as `structuredContent` without wrapping it in an object. The server validates successful structured output against the tool's output schema before returning it.
|
|
59
61
|
|
|
@@ -81,6 +83,12 @@ The constructor accepts an `MCPServerConfig` object with the following propertie
|
|
|
81
83
|
|
|
82
84
|
**fga** (`{ resourceMapping?: Partial<Record<'tool' | 'tools', { fgaResourceType: string; deriveId?: ({ user, resourceId, requestContext }) => string | undefined }>>; permissionMapping?: Record<string, string> }`): Overrides resource and permission mappings for this MCP server's tools/list and tools/call FGA checks. Use this when MCP authorization should be scoped differently from internal agent or workflow tool execution.
|
|
83
85
|
|
|
86
|
+
**requestState** (`{ key?: string | Uint8Array; ttlSeconds?: number }`): Integrity protection for input\_required continuation state. key is an HMAC key of at least 32 bytes that every instance able to answer a continuation must share; ttlSeconds (default 600) is how long a suspended round stays answerable. Without a key the server generates one per process. See the Asking the caller for input section.
|
|
87
|
+
|
|
88
|
+
**cacheHints** (`MCPServerCacheHints`): Cache hints (ttlMs / cacheScope) advertised on cacheable results, keyed by operation: 'tools/list', 'prompts/list', 'resources/list', 'resources/templates/list', 'resources/read' and 'server/discover'.
|
|
89
|
+
|
|
90
|
+
**jsonSchemaValidator** (`jsonSchemaValidator`): Custom JSON Schema validator used for tool input, output and continuation answers, for runtimes where the SDK default is unavailable.
|
|
91
|
+
|
|
84
92
|
**repository** (`Repository`): Optional repository information for the server's source code.
|
|
85
93
|
|
|
86
94
|
**releaseDate** (`string`): Optional release date of this server version (ISO 8601 string). Defaults to the time of instantiation if not provided.
|
|
@@ -99,35 +107,6 @@ The constructor accepts an `MCPServerConfig` object with the following propertie
|
|
|
99
107
|
|
|
100
108
|
**appResources** (`AppResources`): A map of ui:// URIs to app resource configurations. Each entry defines an interactive HTML UI served via the MCP Apps extension (SEP-1865). See the MCP Apps section for details.
|
|
101
109
|
|
|
102
|
-
**protocolVersion** (`'2025-11-25' | '2026-07-28'`): Opt-in MCP protocol revision. Omitted (or '2025-11-25') keeps the legacy behavior exactly. Set to '2026-07-28' to serve the stateless MCP revision. See the Protocol versions section for details.
|
|
103
|
-
|
|
104
|
-
**cacheHints** (`MCPServerCacheHints`): Cache hints (ttlMs / cacheScope) advertised on cacheable results of the '2026-07-28' protocol revision, keyed by operation (e.g. 'tools/list'). Only applied when protocolVersion: '2026-07-28' is set.
|
|
105
|
-
|
|
106
|
-
## Protocol versions
|
|
107
|
-
|
|
108
|
-
By default, `MCPServer` speaks the legacy (2025-era) MCP protocol: sessionful streamable HTTP with an `initialize` handshake. Set `protocolVersion: '2026-07-28'` to serve the stateless MCP revision instead:
|
|
109
|
-
|
|
110
|
-
- HTTP and serverless requests go through a dual-era handler: clients that speak `2026-07-28` are served natively (stateless, per-request envelope), and legacy clients are served through a built-in stateless fallback on the same endpoint.
|
|
111
|
-
- `startStdio()` serves both eras: the opening exchange selects the era for the connection.
|
|
112
|
-
- Tool list, prompt list, resource list, and resource update notifications also reach `2026-07-28` clients through `subscriptions/listen`.
|
|
113
|
-
- Tool log messages honor the caller's per-request `logLevel` opt-in instead of the session-level `logging/setLevel`.
|
|
114
|
-
- Configured `cacheHints` are advertised on cacheable results such as `tools/list`.
|
|
115
|
-
- Tool elicitation (`options.mcp.elicitation.sendRequest()`) works on both eras. On `2026-07-28` requests, it uses the protocol's multi round-trip mechanism. The tool call first returns an `input_required` result. After the client answers, the call retries with the answer attached. The `sendRequest()` promise API is unchanged, but the tool function re-executes from the top on each retry, so keep side effects idempotent (or place them after the last elicitation) and keep the order of `sendRequest()` calls deterministic for an input.
|
|
116
|
-
|
|
117
|
-
```typescript
|
|
118
|
-
const server = new MCPServer({
|
|
119
|
-
name: 'My Server',
|
|
120
|
-
version: '1.0.0',
|
|
121
|
-
tools: { weatherTool },
|
|
122
|
-
protocolVersion: '2026-07-28',
|
|
123
|
-
cacheHints: {
|
|
124
|
-
'tools/list': { ttlMs: 60_000, cacheScope: 'private' },
|
|
125
|
-
},
|
|
126
|
-
})
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
Omitting `protocolVersion` keeps the current behavior unchanged.
|
|
130
|
-
|
|
131
110
|
## Exposing agents as tools
|
|
132
111
|
|
|
133
112
|
A powerful feature of `MCPServer` is its ability to automatically expose your Mastra Agents as callable tools. When you provide agents in the `agents` property of the configuration:
|
|
@@ -155,23 +134,27 @@ For an agent to be converted into a tool, it **must** have a non-empty `descript
|
|
|
155
134
|
|
|
156
135
|
Clients can use MCP to access your agents' generative capabilities and ask them questions directly.
|
|
157
136
|
|
|
158
|
-
### Accessing MCP
|
|
137
|
+
### Accessing MCP context in tools
|
|
159
138
|
|
|
160
|
-
Tools exposed through `MCPServer`
|
|
139
|
+
Tools exposed through `MCPServer` receive the protocol context of the current request as `context.mcp`:
|
|
161
140
|
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
141
|
+
| Member | Description |
|
|
142
|
+
| ----------------------------- | ---------------------------------------------------------------------------- |
|
|
143
|
+
| `context.mcp.extra.signal` | Aborts when the client cancels the request or disconnects |
|
|
144
|
+
| `context.mcp.extra.requestId` | The JSON-RPC id of the request |
|
|
145
|
+
| `context.mcp.extra.authInfo` | Whatever the transport authenticated (see Authentication context) |
|
|
146
|
+
| `context.mcp.extra._meta` | Request metadata: trace headers, the log-level opt-in and the progress token |
|
|
147
|
+
| `context.mcp.log()` | Sends a log message to the calling client (see Logging) |
|
|
148
|
+
| `context.mcp.progress()` | Reports progress to the calling client (see Progress notifications) |
|
|
149
|
+
| `context.mcp.protocolVersion` | `'2026-07-28'` |
|
|
166
150
|
|
|
167
|
-
|
|
151
|
+
The same request also populates `context.requestContext`, the trusted application context, with `authInfo`, the `user` returned by `mapAuthInfoToUser` and the W3C `traceContext` the client sent. Tools invoked by an agent that an MCP client asked (`ask_<agent>`) don't receive `context.mcp`, but the request context is forwarded to the agent, so read auth data from there when a tool can be reached both ways:
|
|
168
152
|
|
|
169
153
|
```typescript
|
|
170
|
-
const
|
|
171
|
-
const authInfo = mcpExtra?.authInfo
|
|
154
|
+
const authInfo = context.mcp?.extra.authInfo ?? context.requestContext?.get('authInfo')
|
|
172
155
|
```
|
|
173
156
|
|
|
174
|
-
#### Example: Tool that
|
|
157
|
+
#### Example: Tool that reads the caller's token
|
|
175
158
|
|
|
176
159
|
```typescript
|
|
177
160
|
import { createTool } from '@mastra/core/tools'
|
|
@@ -184,11 +167,7 @@ const fetchUserData = createTool({
|
|
|
184
167
|
userId: z.string().describe('The ID of the user to fetch'),
|
|
185
168
|
}),
|
|
186
169
|
execute: async (inputData, context) => {
|
|
187
|
-
|
|
188
|
-
// When called directly via MCP: context.mcp.extra
|
|
189
|
-
// When called via agent: context.requestContext.get('mcp.extra')
|
|
190
|
-
const mcpExtra = context?.mcp?.extra || context?.requestContext?.get('mcp.extra')
|
|
191
|
-
const authInfo = mcpExtra?.authInfo
|
|
170
|
+
const authInfo = context.mcp?.extra.authInfo ?? context.requestContext?.get('authInfo')
|
|
192
171
|
|
|
193
172
|
if (!authInfo?.token) {
|
|
194
173
|
throw new Error('Authentication required')
|
|
@@ -198,6 +177,7 @@ const fetchUserData = createTool({
|
|
|
198
177
|
headers: {
|
|
199
178
|
Authorization: `Bearer ${authInfo.token}`,
|
|
200
179
|
},
|
|
180
|
+
signal: context.mcp?.extra.signal,
|
|
201
181
|
})
|
|
202
182
|
|
|
203
183
|
return response.json()
|
|
@@ -227,7 +207,7 @@ The authenticated user is mapped to `authInfo` as:
|
|
|
227
207
|
| `scopes` | `user.scopes`, `user.scope`, or `user.permissions`, normalized to an array |
|
|
228
208
|
| `extra.user` | The full user object returned by the auth provider |
|
|
229
209
|
|
|
230
|
-
If your own middleware performs the verification, set `server.mcpOptions.setRequestAuth` to build `authInfo` yourself. The hook replaces the default mapping
|
|
210
|
+
If your own middleware performs the verification, set `server.mcpOptions.setRequestAuth` to build `authInfo` yourself. The hook replaces the default mapping:
|
|
231
211
|
|
|
232
212
|
```typescript
|
|
233
213
|
export const mastra = new Mastra({
|
|
@@ -250,141 +230,81 @@ export const mastra = new Mastra({
|
|
|
250
230
|
|
|
251
231
|
Leaving `req.auth` unset inside the hook opts the request out of auth info entirely.
|
|
252
232
|
|
|
253
|
-
##
|
|
254
|
-
|
|
255
|
-
These are the functions you can call on an `MCPServer` instance to control its behavior and get information.
|
|
256
|
-
|
|
257
|
-
### `startStdio()`
|
|
233
|
+
## Asking the caller for input
|
|
258
234
|
|
|
259
|
-
|
|
235
|
+
A tool that needs something from the user before it can finish calls `context.suspend(payload)` and returns, exactly as it would inside an agent or a workflow. The server ends the request as an `input_required` result that carries the form described by the tool's `resumeSchema`. When the client answers, the server runs the tool again with the answer in `context.resumeData` and the payload it suspended with in `context.suspendPayload`. Each round is a separate request: the server never replays earlier rounds, so put the state the next round needs in the payload and branch on it.
|
|
260
236
|
|
|
261
237
|
```typescript
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
Here's how you would start the server using stdio:
|
|
238
|
+
import { createTool } from '@mastra/core/tools'
|
|
239
|
+
import { z } from 'zod'
|
|
266
240
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
241
|
+
export const bookDelivery = createTool({
|
|
242
|
+
id: 'bookDelivery',
|
|
243
|
+
description: 'Books a delivery for an order after confirming the address.',
|
|
244
|
+
inputSchema: z.object({ orderId: z.string() }),
|
|
245
|
+
outputSchema: z.object({ confirmed: z.boolean() }),
|
|
246
|
+
suspendSchema: z.object({ phase: z.literal('address'), message: z.string() }),
|
|
247
|
+
resumeSchema: z.object({ address: z.string() }),
|
|
248
|
+
execute: async ({ orderId }, context) => {
|
|
249
|
+
if (!context.resumeData) {
|
|
250
|
+
await context.suspend?.({ phase: 'address', message: 'Delivery address?' })
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
await book(orderId, context.resumeData.address)
|
|
254
|
+
return { confirmed: true }
|
|
255
|
+
},
|
|
273
256
|
})
|
|
274
|
-
await server.startStdio()
|
|
275
257
|
```
|
|
276
258
|
|
|
277
|
-
|
|
259
|
+
`resumeSchema` becomes the form the caller fills in, so it must describe a flat object of primitives (strings, numbers, booleans, enums). A caller that declines or cancels the form ends the call with an error, and the tool doesn't run again. A tool can suspend more than once by changing the phase in its payload.
|
|
278
260
|
|
|
279
|
-
|
|
261
|
+
### Continuation state
|
|
280
262
|
|
|
281
|
-
|
|
263
|
+
The continuation travels as an opaque `requestState` string that the client echoes back with its answer. The server signs it with `requestState.key` and rejects a tampered, expired or foreign state (a different tool, different arguments or a different caller) before your handler runs. The caller is the token subject when the authorization layer provides one, otherwise the `id` of the user `mapAuthInfoToUser` returns, otherwise the bearer token itself, so two users behind the same OAuth client can't resume each other's rounds.
|
|
282
264
|
|
|
283
|
-
|
|
284
|
-
async startSSE({
|
|
285
|
-
url,
|
|
286
|
-
ssePath,
|
|
287
|
-
messagePath,
|
|
288
|
-
req,
|
|
289
|
-
res,
|
|
290
|
-
}: {
|
|
291
|
-
url: URL;
|
|
292
|
-
ssePath: string;
|
|
293
|
-
messagePath: string;
|
|
294
|
-
req: any;
|
|
295
|
-
res: any;
|
|
296
|
-
}): Promise<void>
|
|
297
|
-
```
|
|
265
|
+
On a server without authorization every caller shares one anonymous principal, so a `requestState` behaves like a bearer credential until its `ttlSeconds` expire: anyone who obtains it can answer the round. Put tools whose suspensions carry authority (writes, purchases, account changes) behind authorization.
|
|
298
266
|
|
|
299
|
-
|
|
267
|
+
The payload is signed, not encrypted, so keep it small and non-secret (IDs and phase, not confidential data). Set `requestState.key` from the environment so every instance that may answer a continuation shares the key. Without it the server generates a key per process and continuations only succeed on that process.
|
|
300
268
|
|
|
301
269
|
```typescript
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
messagePath: '/message',
|
|
309
|
-
req,
|
|
310
|
-
res,
|
|
311
|
-
})
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
httpServer.listen(PORT, () => {
|
|
315
|
-
console.log(`HTTP server listening on port ${PORT}`)
|
|
270
|
+
const server = new MCPServer({
|
|
271
|
+
id: 'booking',
|
|
272
|
+
name: 'Booking',
|
|
273
|
+
version: '1.0.0',
|
|
274
|
+
tools: { bookDelivery },
|
|
275
|
+
requestState: { key: process.env.MCP_REQUEST_STATE_KEY!, ttlSeconds: 600 },
|
|
316
276
|
})
|
|
317
277
|
```
|
|
318
278
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
**url** (`URL`): The web address the user is requesting.
|
|
322
|
-
|
|
323
|
-
**ssePath** (`string`): The specific part of the URL where clients will connect for SSE (e.g., '/sse').
|
|
324
|
-
|
|
325
|
-
**messagePath** (`string`): The specific part of the URL where clients will send messages (e.g., '/message').
|
|
326
|
-
|
|
327
|
-
**req** (`any`): The incoming request object from your web server.
|
|
279
|
+
Resource and prompt callbacks can suspend the same way. See [Resource handling](#resource-handling) and [Prompt handling](#prompt-handling).
|
|
328
280
|
|
|
329
|
-
|
|
281
|
+
## Methods
|
|
330
282
|
|
|
331
|
-
|
|
283
|
+
These are the functions you can call on an `MCPServer` instance to control its behavior and get information.
|
|
332
284
|
|
|
333
|
-
|
|
285
|
+
### `startStdio()`
|
|
334
286
|
|
|
335
|
-
|
|
287
|
+
Use this method to start the server so it communicates using standard input and output (stdio). This is typical when running the server as a command-line program.
|
|
336
288
|
|
|
337
289
|
```typescript
|
|
338
|
-
async
|
|
339
|
-
url,
|
|
340
|
-
ssePath,
|
|
341
|
-
messagePath,
|
|
342
|
-
req,
|
|
343
|
-
res,
|
|
344
|
-
}: {
|
|
345
|
-
url: URL;
|
|
346
|
-
ssePath: string;
|
|
347
|
-
messagePath: string;
|
|
348
|
-
req: any;
|
|
349
|
-
res: any;
|
|
350
|
-
}): Promise<void>
|
|
290
|
+
async startStdio(): Promise<void>
|
|
351
291
|
```
|
|
352
292
|
|
|
353
|
-
Here's
|
|
293
|
+
Here's how you would start the server using stdio:
|
|
354
294
|
|
|
355
295
|
```typescript
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
ssePath: '/hono-sse',
|
|
362
|
-
messagePath: '/message',
|
|
363
|
-
req,
|
|
364
|
-
res,
|
|
365
|
-
})
|
|
366
|
-
})
|
|
367
|
-
|
|
368
|
-
httpServer.listen(PORT, () => {
|
|
369
|
-
console.log(`HTTP server listening on port ${PORT}`)
|
|
296
|
+
const server = new MCPServer({
|
|
297
|
+
id: 'my-server',
|
|
298
|
+
name: 'My Server',
|
|
299
|
+
version: '1.0.0',
|
|
300
|
+
tools: {/* ... */},
|
|
370
301
|
})
|
|
302
|
+
await server.startStdio()
|
|
371
303
|
```
|
|
372
304
|
|
|
373
|
-
Here are the details for the values needed by the `startHonoSSE` method:
|
|
374
|
-
|
|
375
|
-
**url** (`URL`): The web address the user is requesting.
|
|
376
|
-
|
|
377
|
-
**ssePath** (`string`): The specific part of the URL where clients will connect for SSE (e.g., '/hono-sse').
|
|
378
|
-
|
|
379
|
-
**messagePath** (`string`): The specific part of the URL where clients will send messages (e.g., '/message').
|
|
380
|
-
|
|
381
|
-
**req** (`any`): The incoming request object from your web server.
|
|
382
|
-
|
|
383
|
-
**res** (`any`): The response object from your web server, used to send data back.
|
|
384
|
-
|
|
385
305
|
### `startHTTP()`
|
|
386
306
|
|
|
387
|
-
This method helps you integrate the MCP server with an existing web server to use
|
|
307
|
+
This method helps you integrate the MCP server with an existing web server to use Streamable HTTP for communication. You'll call this from your web server's code when it receives HTTP requests.
|
|
388
308
|
|
|
389
309
|
```typescript
|
|
390
310
|
async startHTTP({
|
|
@@ -431,7 +351,7 @@ httpServer.listen(PORT, () => {
|
|
|
431
351
|
})
|
|
432
352
|
```
|
|
433
353
|
|
|
434
|
-
Because every request is self-contained, nothing needs to persist between invocations, so `startHTTP` works in serverless environments (Supabase Edge Functions, Cloudflare Workers, Vercel Edge, AWS Lambda, Deno Deploy). The method
|
|
354
|
+
Because every request is self-contained, nothing needs to persist between invocations, so `startHTTP` works in serverless environments (Supabase Edge Functions, Cloudflare Workers, Vercel Edge, AWS Lambda, Deno Deploy). The method takes Node-style `http.IncomingMessage` and `http.ServerResponse` objects, so a Fetch-based runtime has to convert its `Request` with an adapter such as `fetch-to-node` and turn the result back into a `Response`:
|
|
435
355
|
|
|
436
356
|
```typescript
|
|
437
357
|
// Supabase Edge Function example
|
|
@@ -475,15 +395,7 @@ Here are the details for the values needed by the `startHTTP` method:
|
|
|
475
395
|
|
|
476
396
|
**res** (`http.ServerResponse`): The response object from your web server, used to send data back.
|
|
477
397
|
|
|
478
|
-
**options** (`MCPServerHTTPRequestOptions`): Optional request guards. See the options table
|
|
479
|
-
|
|
480
|
-
The `MCPServerHTTPRequestOptions` object carries request guards:
|
|
481
|
-
|
|
482
|
-
**enableDnsRebindingProtection** (`boolean`): If true, the Host and Origin headers are validated against allowedHosts and allowedOrigins before the request is handled. Defaults to false.
|
|
483
|
-
|
|
484
|
-
**allowedHosts** (`string[]`): Hosts (host\[:port]) accepted when DNS rebinding protection is enabled.
|
|
485
|
-
|
|
486
|
-
**allowedOrigins** (`string[]`): Origins accepted when DNS rebinding protection is enabled.
|
|
398
|
+
**options** (`MCPServerHTTPRequestOptions`): Optional request guards. See the options table above for more details.
|
|
487
399
|
|
|
488
400
|
### `close()`
|
|
489
401
|
|
|
@@ -506,7 +418,7 @@ getServerInfo(): ServerInfo
|
|
|
506
418
|
The method returns details about the server's information.
|
|
507
419
|
|
|
508
420
|
```typescript
|
|
509
|
-
getServerDetail():
|
|
421
|
+
getServerDetail(): ServerDetailInfo
|
|
510
422
|
```
|
|
511
423
|
|
|
512
424
|
### `getToolListInfo()`
|
|
@@ -514,7 +426,7 @@ getServerDetail(): ServerDetail
|
|
|
514
426
|
The method returns the tools that were set up when you created the server. It's a read-only list, useful for debugging purposes.
|
|
515
427
|
|
|
516
428
|
```typescript
|
|
517
|
-
getToolListInfo():
|
|
429
|
+
getToolListInfo(): { tools: ToolInfo[] }
|
|
518
430
|
```
|
|
519
431
|
|
|
520
432
|
### `getToolInfo()`
|
|
@@ -522,67 +434,55 @@ getToolListInfo(): ToolListInfo
|
|
|
522
434
|
The method returns details about a specific tool.
|
|
523
435
|
|
|
524
436
|
```typescript
|
|
525
|
-
getToolInfo(
|
|
437
|
+
getToolInfo(toolId: string): ToolInfo | undefined
|
|
526
438
|
```
|
|
527
439
|
|
|
528
440
|
### `executeTool()`
|
|
529
441
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
```typescript
|
|
533
|
-
executeTool(toolName: string, input: any): Promise<any>
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
### `getStdioTransport()`
|
|
537
|
-
|
|
538
|
-
If you started the server with `startStdio()`, you can use this to get the object that manages the stdio communication. This is mostly for checking things internally or for testing.
|
|
539
|
-
|
|
540
|
-
```typescript
|
|
541
|
-
getStdioTransport(): StdioServerTransport | undefined
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
### `getSseTransport()`
|
|
545
|
-
|
|
546
|
-
If you started the server with `startSSE()`, you can use this to get the object that manages the SSE communication. Like `getStdioTransport`, this is mainly for internal checks or testing.
|
|
442
|
+
Runs a tool without a protocol client, which is how the Mastra REST route `POST /api/mcp/:serverId/tools/:toolId/execute` and Studio invoke tools.
|
|
547
443
|
|
|
548
444
|
```typescript
|
|
549
|
-
|
|
445
|
+
async executeTool(
|
|
446
|
+
toolId: string,
|
|
447
|
+
args: unknown,
|
|
448
|
+
executionContext?: {
|
|
449
|
+
messages?: any[];
|
|
450
|
+
toolCallId?: string;
|
|
451
|
+
requestContext?: RequestContext;
|
|
452
|
+
resumeData?: unknown;
|
|
453
|
+
suspendPayload?: unknown;
|
|
454
|
+
},
|
|
455
|
+
): Promise<MCPToolExecutionResultV2>
|
|
550
456
|
```
|
|
551
457
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
If you started the server with `startHonoSSE()`, you can use this to get the object that manages the SSE communication. Like `getSseTransport`, this is mainly for internal checks or testing.
|
|
458
|
+
The result is `{ status: 'completed', output }` for a finished call, or `{ status: 'suspended', suspendPayload, resumeSchema }` when the tool asked for input. Continue by calling again with the same `args` plus `resumeData` and the echoed `suspendPayload`. A tool that throws, or input or resume data that fails its schema, rejects the promise.
|
|
555
459
|
|
|
556
460
|
```typescript
|
|
557
|
-
|
|
461
|
+
const result = await server.executeTool('bookDelivery', { orderId })
|
|
462
|
+
if (result.status === 'suspended') {
|
|
463
|
+
const answer = await askUser(result.suspendPayload, result.resumeSchema)
|
|
464
|
+
await server.executeTool(
|
|
465
|
+
'bookDelivery',
|
|
466
|
+
{ orderId },
|
|
467
|
+
{ resumeData: answer, suspendPayload: result.suspendPayload },
|
|
468
|
+
)
|
|
469
|
+
}
|
|
558
470
|
```
|
|
559
471
|
|
|
560
|
-
|
|
472
|
+
**toolId** (`string`): The ID/name of the tool to execute.
|
|
561
473
|
|
|
562
|
-
|
|
474
|
+
**args** (`unknown`): The arguments to pass to the tool's execute function, validated against its input schema.
|
|
563
475
|
|
|
564
|
-
|
|
565
|
-
getStreamableHTTPTransport(): StreamableHTTPServerTransport | undefined
|
|
566
|
-
```
|
|
476
|
+
**executionContext** (`object`): Optional context for the tool execution. Pass resumeData and suspendPayload to continue a suspended tool.
|
|
567
477
|
|
|
568
478
|
### `tools()`
|
|
569
479
|
|
|
570
|
-
|
|
480
|
+
Returns the registered tool registry, keyed by tool ID.
|
|
571
481
|
|
|
572
482
|
```typescript
|
|
573
|
-
|
|
574
|
-
toolId: string,
|
|
575
|
-
args: any,
|
|
576
|
-
executionContext?: { messages?: any[]; toolCallId?: string },
|
|
577
|
-
): Promise<any>
|
|
483
|
+
tools(): Readonly<Record<string, ConvertedTool>>
|
|
578
484
|
```
|
|
579
485
|
|
|
580
|
-
**toolId** (`string`): The ID/name of the tool to execute.
|
|
581
|
-
|
|
582
|
-
**args** (`any`): The arguments to pass to the tool's execute function.
|
|
583
|
-
|
|
584
|
-
**executionContext** (`object`): Optional context for the tool execution, like messages or a toolCallId.
|
|
585
|
-
|
|
586
486
|
## Resource handling
|
|
587
487
|
|
|
588
488
|
### What are MCP Resources?
|
|
@@ -603,7 +503,7 @@ Clients can discover resources through:
|
|
|
603
503
|
1. **Direct resources**: Servers expose a list of concrete resources via a `resources/list` endpoint.
|
|
604
504
|
2. **Resource templates**: For runtime-defined resources, servers can expose URI templates (RFC 6570) that clients use to construct resource URIs.
|
|
605
505
|
|
|
606
|
-
To read a resource, clients make a `resources/read` request with the URI. Servers can also notify clients about changes to the resource list (`notifications/resources/list_changed`) or updates to specific resource content (`notifications/resources/updated`) if a client
|
|
506
|
+
To read a resource, clients make a `resources/read` request with the URI. Servers can also notify clients about changes to the resource list (`notifications/resources/list_changed`) or updates to specific resource content (`notifications/resources/updated`) if a client is listening for that resource.
|
|
607
507
|
|
|
608
508
|
For more detailed information, refer to the [official MCP documentation on Resources](https://modelcontextprotocol.io/docs/concepts/resources).
|
|
609
509
|
|
|
@@ -614,27 +514,43 @@ The `resources` option takes an object of type `MCPServerResources`. This type d
|
|
|
614
514
|
```typescript
|
|
615
515
|
export type MCPServerResources = {
|
|
616
516
|
// Callback to list available resources
|
|
617
|
-
listResources: (
|
|
517
|
+
listResources: (params: {
|
|
518
|
+
extra: MCPRequestHandlerExtra
|
|
519
|
+
requestContext: RequestContext
|
|
520
|
+
}) => Promise<Resource[]>
|
|
618
521
|
|
|
619
522
|
// Callback to get the content of a specific resource
|
|
620
|
-
getResourceContent: (
|
|
621
|
-
uri,
|
|
622
|
-
|
|
623
|
-
uri: string
|
|
624
|
-
}) => Promise<MCPServerResourceContent | MCPServerResourceContent[]>
|
|
523
|
+
getResourceContent: (
|
|
524
|
+
params: { uri: string } & MCPServerRequest,
|
|
525
|
+
) => Promise<MCPServerResourceContent | MCPServerResourceContent[] | void>
|
|
625
526
|
|
|
626
527
|
// Optional callback to list available resource templates
|
|
627
|
-
resourceTemplates?: (
|
|
528
|
+
resourceTemplates?: (params: {
|
|
529
|
+
extra: MCPRequestHandlerExtra
|
|
530
|
+
requestContext: RequestContext
|
|
531
|
+
}) => Promise<ResourceTemplate[]>
|
|
532
|
+
|
|
533
|
+
// Shape of the answer a suspended getResourceContent expects
|
|
534
|
+
resumeSchema?: StandardSchemaWithJSON
|
|
628
535
|
}
|
|
629
536
|
|
|
630
537
|
export type MCPServerResourceContent = { text?: string } | { blob?: string }
|
|
631
538
|
```
|
|
632
539
|
|
|
540
|
+
Every callback receives `extra`, the same protocol context tools see as `context.mcp.extra` (cancellation `signal`, `requestId`, `authInfo`, `_meta`), and `requestContext`, the trusted application context that carries `authInfo` and the user mapped by `mapAuthInfoToUser`. Use them to scope what a caller can list and read.
|
|
541
|
+
|
|
542
|
+
`getResourceContent` also receives `suspend`, `resumeData` and `suspendPayload` (the `MCPServerRequest` members). Declare `resumeSchema` to make `suspend` usable. It works exactly as it does for tools.
|
|
543
|
+
|
|
633
544
|
Example:
|
|
634
545
|
|
|
635
546
|
```typescript
|
|
636
547
|
import { MCPServer } from '@mastra/mcp'
|
|
637
|
-
import type {
|
|
548
|
+
import type {
|
|
549
|
+
MCPServerResourceContent,
|
|
550
|
+
MCPServerResources,
|
|
551
|
+
Resource,
|
|
552
|
+
ResourceTemplate,
|
|
553
|
+
} from '@mastra/mcp'
|
|
638
554
|
|
|
639
555
|
// Resources/resource templates will generally be dynamically fetched.
|
|
640
556
|
const myResources: Resource[] = [
|
|
@@ -656,7 +572,7 @@ const myResourceTemplates: ResourceTemplate[] = [
|
|
|
656
572
|
|
|
657
573
|
const myResourceHandlers: MCPServerResources = {
|
|
658
574
|
listResources: async () => myResources,
|
|
659
|
-
getResourceContent: async ({ uri }) => {
|
|
575
|
+
getResourceContent: async ({ uri, extra }) => {
|
|
660
576
|
if (myResourceContents[uri]) {
|
|
661
577
|
return myResourceContents[uri]
|
|
662
578
|
}
|
|
@@ -676,11 +592,11 @@ const serverWithResources = new MCPServer({
|
|
|
676
592
|
|
|
677
593
|
### Notifying Clients of Resource Changes
|
|
678
594
|
|
|
679
|
-
If the available resources or their content change, your server can notify
|
|
595
|
+
If the available resources or their content change, your server can notify clients that are listening for the specific resource.
|
|
680
596
|
|
|
681
597
|
#### `server.resources.notifyUpdated({ uri: string })`
|
|
682
598
|
|
|
683
|
-
Call this method when the content of a specific resource (identified by its `uri`) has been updated.
|
|
599
|
+
Call this method when the content of a specific resource (identified by its `uri`) has been updated. Clients subscribed to this URI receive a `notifications/resources/updated` message on their `subscriptions/listen` stream.
|
|
684
600
|
|
|
685
601
|
```typescript
|
|
686
602
|
async server.resources.notifyUpdated({ uri: string }): Promise<void>
|
|
@@ -695,7 +611,7 @@ await serverWithResources.resources.notifyUpdated({ uri: 'file://data.txt' })
|
|
|
695
611
|
|
|
696
612
|
#### `server.resources.notifyListChanged()`
|
|
697
613
|
|
|
698
|
-
Call this method when the list of available resources has changed (e.g., a resource was added or removed). This will send a `notifications/resources/list_changed` message to clients, prompting them to re-fetch the list of resources.
|
|
614
|
+
Call this method when the list of available resources has changed (e.g., a resource was added or removed). This will send a `notifications/resources/list_changed` message to listening clients, prompting them to re-fetch the list of resources.
|
|
699
615
|
|
|
700
616
|
```typescript
|
|
701
617
|
async server.resources.notifyListChanged(): Promise<void>
|
|
@@ -712,9 +628,9 @@ await serverWithResources.resources.notifyListChanged()
|
|
|
712
628
|
|
|
713
629
|
### What are MCP Prompts?
|
|
714
630
|
|
|
715
|
-
Prompts are reusable templates or workflows that MCP servers expose to clients. They can accept arguments and include resource context. They
|
|
631
|
+
Prompts are reusable templates or workflows that MCP servers expose to clients. They can accept arguments and include resource context. They standardize LLM interactions.
|
|
716
632
|
|
|
717
|
-
Prompts are identified by a unique name
|
|
633
|
+
Prompts are identified by a unique name and can be runtime-defined or static.
|
|
718
634
|
|
|
719
635
|
### `MCPServerPrompts` Type
|
|
720
636
|
|
|
@@ -723,21 +639,23 @@ The `prompts` option takes an object of type `MCPServerPrompts`. This type defin
|
|
|
723
639
|
```typescript
|
|
724
640
|
export type MCPServerPrompts = {
|
|
725
641
|
// Callback to list available prompts
|
|
726
|
-
listPrompts: (
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
642
|
+
listPrompts: (params: {
|
|
643
|
+
extra: MCPRequestHandlerExtra
|
|
644
|
+
requestContext: RequestContext
|
|
645
|
+
}) => Promise<Prompt[]>
|
|
646
|
+
|
|
647
|
+
// Callback to get the messages for a specific prompt
|
|
648
|
+
getPromptMessages?: (
|
|
649
|
+
params: { name: string; args?: Record<string, unknown> } & MCPServerRequest,
|
|
650
|
+
) => Promise<PromptMessage[] | void>
|
|
651
|
+
|
|
652
|
+
// Shape of the answer a suspended getPromptMessages expects
|
|
653
|
+
resumeSchema?: StandardSchemaWithJSON
|
|
738
654
|
}
|
|
739
655
|
```
|
|
740
656
|
|
|
657
|
+
The callbacks receive the same `extra` and `requestContext` as resource callbacks, and `getPromptMessages` can `suspend` in the same way when `resumeSchema` is declared. The server validates required prompt arguments before calling `getPromptMessages`.
|
|
658
|
+
|
|
741
659
|
Example:
|
|
742
660
|
|
|
743
661
|
```typescript
|
|
@@ -748,68 +666,44 @@ const prompts: Prompt[] = [
|
|
|
748
666
|
{
|
|
749
667
|
name: 'analyze-code',
|
|
750
668
|
description: 'Analyze code for improvements',
|
|
751
|
-
|
|
752
|
-
},
|
|
753
|
-
{
|
|
754
|
-
name: 'analyze-code',
|
|
755
|
-
description: 'Analyze code for improvements (new logic)',
|
|
756
|
-
version: 'v2',
|
|
669
|
+
arguments: [{ name: 'code', description: 'The code to analyze', required: true }],
|
|
757
670
|
},
|
|
758
671
|
]
|
|
759
672
|
|
|
760
673
|
const myPromptHandlers: MCPServerPrompts = {
|
|
761
674
|
listPrompts: async () => prompts,
|
|
762
|
-
getPromptMessages: async ({ name,
|
|
675
|
+
getPromptMessages: async ({ name, args }) => {
|
|
763
676
|
if (name === 'analyze-code') {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
{
|
|
771
|
-
role: 'user',
|
|
772
|
-
content: {
|
|
773
|
-
type: 'text',
|
|
774
|
-
text: `Analyze this code with the new logic: ${args.code}`,
|
|
775
|
-
},
|
|
776
|
-
},
|
|
777
|
-
],
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
// Default or v1
|
|
781
|
-
const prompt = prompts.find(p => p.name === name && p.version === 'v1')
|
|
782
|
-
if (!prompt) throw new Error('Prompt version not found')
|
|
783
|
-
return {
|
|
784
|
-
prompt,
|
|
785
|
-
messages: [
|
|
786
|
-
{
|
|
787
|
-
role: 'user',
|
|
788
|
-
content: { type: 'text', text: `Analyze this code: ${args.code}` },
|
|
677
|
+
return [
|
|
678
|
+
{
|
|
679
|
+
role: 'user',
|
|
680
|
+
content: {
|
|
681
|
+
type: 'text',
|
|
682
|
+
text: `Analyze this code: ${args?.code}`,
|
|
789
683
|
},
|
|
790
|
-
|
|
791
|
-
|
|
684
|
+
},
|
|
685
|
+
]
|
|
792
686
|
}
|
|
793
687
|
throw new Error('Prompt not found')
|
|
794
688
|
},
|
|
795
689
|
}
|
|
796
690
|
|
|
797
691
|
const serverWithPrompts = new MCPServer({
|
|
798
|
-
id: '
|
|
799
|
-
name: '
|
|
692
|
+
id: 'prompt-server',
|
|
693
|
+
name: 'Prompt Server',
|
|
800
694
|
version: '1.0.0',
|
|
801
|
-
tools: {/* ... */},
|
|
695
|
+
tools: {/* ... your tools ... */},
|
|
802
696
|
prompts: myPromptHandlers,
|
|
803
697
|
})
|
|
804
698
|
```
|
|
805
699
|
|
|
806
700
|
### Notifying Clients of Prompt Changes
|
|
807
701
|
|
|
808
|
-
If the available prompts change, your server can notify
|
|
702
|
+
If the available prompts change, your server can notify listening clients.
|
|
809
703
|
|
|
810
704
|
#### `server.prompts.notifyListChanged()`
|
|
811
705
|
|
|
812
|
-
Call this method when the list of available prompts has changed (e.g., a prompt was added or removed). This will send a `notifications/prompts/list_changed` message to clients, prompting them to re-fetch the list of prompts.
|
|
706
|
+
Call this method when the list of available prompts has changed (e.g., a prompt was added or removed). This will send a `notifications/prompts/list_changed` message to listening clients, prompting them to re-fetch the list of prompts.
|
|
813
707
|
|
|
814
708
|
```typescript
|
|
815
709
|
await serverWithPrompts.prompts.notifyListChanged()
|
|
@@ -819,15 +713,12 @@ await serverWithPrompts.prompts.notifyListChanged()
|
|
|
819
713
|
|
|
820
714
|
- Use clear, descriptive prompt names and descriptions.
|
|
821
715
|
- Validate all required arguments in `getPromptMessages`.
|
|
822
|
-
-
|
|
823
|
-
-
|
|
824
|
-
- Notify clients when prompt lists change.
|
|
825
|
-
- Handle errors with informative messages.
|
|
826
|
-
- Document argument expectations and available versions.
|
|
716
|
+
- Return an error for unknown prompts or missing required arguments.
|
|
717
|
+
- Notify clients whenever prompts change.
|
|
827
718
|
|
|
828
719
|
## Dynamic tool management
|
|
829
720
|
|
|
830
|
-
|
|
721
|
+
Add and remove tools on a running server. Connected clients are notified with `notifications/tools/list_changed`.
|
|
831
722
|
|
|
832
723
|
The property is `toolActions` because `tools()` is the method that returns the registered tool registry.
|
|
833
724
|
|
|
@@ -883,28 +774,9 @@ When the server is registered with a Mastra instance, `toolActions.add()` and `t
|
|
|
883
774
|
|
|
884
775
|
## Logging
|
|
885
776
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
### `sendLoggingMessage()`
|
|
777
|
+
Tools send structured log messages to the calling client with `notifications/message`. Delivery is opted into per request: the client attaches the `io.modelcontextprotocol/logLevel` metadata key to its request, and the server delivers messages at or above that severity (following RFC 5424 ordering) for that request only. Without the opt-in nothing is delivered. A later round of the same tool call is a new request and must opt in again. The Mastra `MCPClient` sends the key on every request when `enableServerLogs` is on.
|
|
889
778
|
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
```typescript
|
|
893
|
-
async server.sendLoggingMessage(params: {
|
|
894
|
-
level: LoggingLevel;
|
|
895
|
-
data: unknown;
|
|
896
|
-
logger?: string;
|
|
897
|
-
}): Promise<void>
|
|
898
|
-
```
|
|
899
|
-
|
|
900
|
-
Example:
|
|
901
|
-
|
|
902
|
-
```typescript
|
|
903
|
-
await server.sendLoggingMessage({
|
|
904
|
-
level: 'info',
|
|
905
|
-
data: { message: 'Sync completed', itemsProcessed: 42 },
|
|
906
|
-
})
|
|
907
|
-
```
|
|
779
|
+
The Mastra logger and observability are unaffected: `context.mcp.log()` only controls what the MCP client receives.
|
|
908
780
|
|
|
909
781
|
### `context.mcp.log()`
|
|
910
782
|
|
|
@@ -922,16 +794,16 @@ Example:
|
|
|
922
794
|
|
|
923
795
|
```typescript
|
|
924
796
|
execute: async ({ location }, context) => {
|
|
925
|
-
await context.mcp
|
|
797
|
+
await context.mcp?.log?.('debug', 'Fetching weather', { location })
|
|
926
798
|
const weather = await fetchWeather(location)
|
|
927
|
-
await context.mcp
|
|
799
|
+
await context.mcp?.log?.('info', 'Weather fetched')
|
|
928
800
|
return weather
|
|
929
801
|
}
|
|
930
802
|
```
|
|
931
803
|
|
|
932
804
|
## Progress notifications
|
|
933
805
|
|
|
934
|
-
Long-running tools can report progress to the calling client with `notifications/progress`. Progress is only sent when the caller requested progress tracking by including a `progressToken` in the request (the Mastra `MCPClient` does this when `enableProgressTracking` is set). When no token was sent, `context.mcp.progress()` is a no-op.
|
|
806
|
+
Long-running tools can report progress to the calling client with `notifications/progress`. Progress is only sent when the caller requested progress tracking by including a `progressToken` in the request's `_meta` (the Mastra `MCPClient` does this when `enableProgressTracking` is set). When no token was sent, `context.mcp.progress()` is a no-op.
|
|
935
807
|
|
|
936
808
|
### `context.mcp.progress()`
|
|
937
809
|
|
|
@@ -949,7 +821,7 @@ Example:
|
|
|
949
821
|
execute: async ({ items }, context) => {
|
|
950
822
|
for (const [index, item] of items.entries()) {
|
|
951
823
|
await processItem(item)
|
|
952
|
-
await context.mcp
|
|
824
|
+
await context.mcp?.progress?.({
|
|
953
825
|
progress: index + 1,
|
|
954
826
|
total: items.length,
|
|
955
827
|
message: `Processed ${item.name}`,
|
|
@@ -969,7 +841,7 @@ A standalone `MCPServer` with no `mastra` instance produces no spans.
|
|
|
969
841
|
|
|
970
842
|
## Notification delivery
|
|
971
843
|
|
|
972
|
-
|
|
844
|
+
Request-scoped notifications (`context.mcp.log()`, `context.mcp.progress()`) stream inside the request that triggered them, so they reach exactly the caller. Notifications that outlive a request (`resources.notifyListChanged()`, `prompts.notifyListChanged()`, `toolActions.notifyListChanged()`, `resources.notifyUpdated()`) are delivered on the `subscriptions/listen` stream each interested client keeps open. `resources.notifyUpdated()` only reaches clients whose stream includes that resource URI. List-changed notifications reach every client listening for that list. Over stdio the connection itself carries the stream.
|
|
973
845
|
|
|
974
846
|
## Examples
|
|
975
847
|
|
|
@@ -977,213 +849,9 @@ For a practical example of packaging a stdio server, see [Publish a stdio server
|
|
|
977
849
|
|
|
978
850
|
The example at the beginning of this page also demonstrates how to instantiate `MCPServer` with both tools and agents.
|
|
979
851
|
|
|
980
|
-
## Elicitation
|
|
981
|
-
|
|
982
|
-
### What's Elicitation?
|
|
983
|
-
|
|
984
|
-
Elicitation is a feature in the Model Context Protocol (MCP) that allows servers to request structured information from users. It supports interactive workflows where servers can collect additional data at runtime.
|
|
985
|
-
|
|
986
|
-
The `MCPServer` class automatically includes elicitation capabilities. Tools receive a `context.mcp` object in their `execute` function that includes an `elicitation.sendRequest()` method for requesting user input.
|
|
987
|
-
|
|
988
|
-
### Tool Execution Signature
|
|
989
|
-
|
|
990
|
-
When tools are executed within an MCP server context, they receive MCP-specific capabilities via the `context.mcp` object:
|
|
991
|
-
|
|
992
|
-
```typescript
|
|
993
|
-
execute: async (inputData, context) => {
|
|
994
|
-
// input contains the tool's inputData parameters
|
|
995
|
-
// context.mcp contains server capabilities like elicitation and authentication info
|
|
996
|
-
|
|
997
|
-
// Access authentication information (when available)
|
|
998
|
-
if (context.mcp?.extra?.authInfo) {
|
|
999
|
-
console.log('Authenticated request from:', context.mcp.extra.authInfo.clientId)
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
// Use elicitation capabilities
|
|
1003
|
-
const result = await context.mcp.elicitation.sendRequest({
|
|
1004
|
-
message: 'Please provide information',
|
|
1005
|
-
requestedSchema: {/* schema */},
|
|
1006
|
-
})
|
|
1007
|
-
|
|
1008
|
-
return result
|
|
1009
|
-
}
|
|
1010
|
-
```
|
|
1011
|
-
|
|
1012
|
-
### How Elicitation Works
|
|
1013
|
-
|
|
1014
|
-
A common use case is during tool execution. When a tool needs user input, it can use the elicitation functionality provided through the context parameter:
|
|
1015
|
-
|
|
1016
|
-
1. The tool calls `context.mcp.elicitation.sendRequest()` with a message and schema
|
|
1017
|
-
2. The request is sent to the connected MCP client
|
|
1018
|
-
3. The client presents the request to the user (via UI, command line, etc.)
|
|
1019
|
-
4. The user provides input, declines, or cancels the request
|
|
1020
|
-
5. The client sends the response back to the server
|
|
1021
|
-
6. The tool receives the response and continues execution
|
|
1022
|
-
|
|
1023
|
-
### Using Elicitation in Tools
|
|
1024
|
-
|
|
1025
|
-
Here's an example of a tool that uses elicitation to collect user contact information:
|
|
1026
|
-
|
|
1027
|
-
```typescript
|
|
1028
|
-
import { MCPServer } from '@mastra/mcp'
|
|
1029
|
-
import { createTool } from '@mastra/core/tools'
|
|
1030
|
-
import { z } from 'zod'
|
|
1031
|
-
|
|
1032
|
-
const server = new MCPServer({
|
|
1033
|
-
id: 'interactive-server',
|
|
1034
|
-
name: 'Interactive Server',
|
|
1035
|
-
version: '1.0.0',
|
|
1036
|
-
tools: {
|
|
1037
|
-
collectContactInfo: createTool({
|
|
1038
|
-
id: 'collectContactInfo',
|
|
1039
|
-
description: 'Collects user contact information through elicitation',
|
|
1040
|
-
inputSchema: z.object({
|
|
1041
|
-
reason: z.string().optional().describe('Reason for collecting contact info'),
|
|
1042
|
-
}),
|
|
1043
|
-
execute: async (inputData, context) => {
|
|
1044
|
-
const { reason } = inputData
|
|
1045
|
-
|
|
1046
|
-
// Log session info if available
|
|
1047
|
-
console.log('Request from session:', context.mcp?.extra?.sessionId)
|
|
1048
|
-
|
|
1049
|
-
try {
|
|
1050
|
-
// Request user input via elicitation
|
|
1051
|
-
const result = await context.mcp.elicitation.sendRequest({
|
|
1052
|
-
message: reason
|
|
1053
|
-
? `Please provide your contact information. ${reason}`
|
|
1054
|
-
: 'Please provide your contact information',
|
|
1055
|
-
requestedSchema: {
|
|
1056
|
-
type: 'object',
|
|
1057
|
-
properties: {
|
|
1058
|
-
name: {
|
|
1059
|
-
type: 'string',
|
|
1060
|
-
title: 'Full Name',
|
|
1061
|
-
description: 'Your full name',
|
|
1062
|
-
},
|
|
1063
|
-
email: {
|
|
1064
|
-
type: 'string',
|
|
1065
|
-
title: 'Email Address',
|
|
1066
|
-
description: 'Your email address',
|
|
1067
|
-
format: 'email',
|
|
1068
|
-
},
|
|
1069
|
-
phone: {
|
|
1070
|
-
type: 'string',
|
|
1071
|
-
title: 'Phone Number',
|
|
1072
|
-
description: 'Your phone number (optional)',
|
|
1073
|
-
},
|
|
1074
|
-
},
|
|
1075
|
-
required: ['name', 'email'],
|
|
1076
|
-
},
|
|
1077
|
-
})
|
|
1078
|
-
|
|
1079
|
-
// Handle the user's response
|
|
1080
|
-
if (result.action === 'accept') {
|
|
1081
|
-
return `Contact information collected: ${JSON.stringify(result.content, null, 2)}`
|
|
1082
|
-
} else if (result.action === 'decline') {
|
|
1083
|
-
return 'Contact information collection was declined by the user.'
|
|
1084
|
-
} else {
|
|
1085
|
-
return 'Contact information collection was cancelled by the user.'
|
|
1086
|
-
}
|
|
1087
|
-
} catch (error) {
|
|
1088
|
-
return `Error collecting contact information: ${error}`
|
|
1089
|
-
}
|
|
1090
|
-
},
|
|
1091
|
-
}),
|
|
1092
|
-
},
|
|
1093
|
-
})
|
|
1094
|
-
```
|
|
1095
|
-
|
|
1096
|
-
### Elicitation Request Schema
|
|
1097
|
-
|
|
1098
|
-
The `requestedSchema` must be a flat object with primitive properties only. Supported types include:
|
|
1099
|
-
|
|
1100
|
-
- **String**: `{ type: 'string', title: 'Display Name', description: 'Help text' }`
|
|
1101
|
-
- **Number**: `{ type: 'number', minimum: 0, maximum: 100 }`
|
|
1102
|
-
- **Boolean**: `{ type: 'boolean', default: false }`
|
|
1103
|
-
- **Enum**: `{ type: 'string', enum: ['option1', 'option2'] }`
|
|
1104
|
-
|
|
1105
|
-
Example schema:
|
|
1106
|
-
|
|
1107
|
-
```typescript
|
|
1108
|
-
{
|
|
1109
|
-
type: 'object',
|
|
1110
|
-
properties: {
|
|
1111
|
-
name: {
|
|
1112
|
-
type: 'string',
|
|
1113
|
-
title: 'Full Name',
|
|
1114
|
-
description: 'Your complete name',
|
|
1115
|
-
},
|
|
1116
|
-
age: {
|
|
1117
|
-
type: 'number',
|
|
1118
|
-
title: 'Age',
|
|
1119
|
-
minimum: 18,
|
|
1120
|
-
maximum: 120,
|
|
1121
|
-
},
|
|
1122
|
-
newsletter: {
|
|
1123
|
-
type: 'boolean',
|
|
1124
|
-
title: 'Subscribe to Newsletter',
|
|
1125
|
-
default: false,
|
|
1126
|
-
},
|
|
1127
|
-
},
|
|
1128
|
-
required: ['name'],
|
|
1129
|
-
}
|
|
1130
|
-
```
|
|
1131
|
-
|
|
1132
|
-
### Response Actions
|
|
1133
|
-
|
|
1134
|
-
Users can respond to elicitation requests in three ways:
|
|
1135
|
-
|
|
1136
|
-
1. **Accept** (`action: 'accept'`): User provided data and confirmed submission
|
|
1137
|
-
- Contains `content` field with the submitted data
|
|
1138
|
-
2. **Decline** (`action: 'decline'`): User explicitly declined to provide information
|
|
1139
|
-
- No content field
|
|
1140
|
-
3. **Cancel** (`action: 'cancel'`): User dismissed the request without deciding
|
|
1141
|
-
- No content field
|
|
1142
|
-
|
|
1143
|
-
Tools should handle all three response types appropriately.
|
|
1144
|
-
|
|
1145
|
-
### Security Considerations
|
|
1146
|
-
|
|
1147
|
-
- **Never request sensitive information** like passwords, SSNs, or credit card numbers
|
|
1148
|
-
- Validate all user input against the provided schema
|
|
1149
|
-
- Handle declining and cancellation gracefully
|
|
1150
|
-
- Provide clear reasons for data collection
|
|
1151
|
-
- Respect user privacy and preferences
|
|
1152
|
-
|
|
1153
|
-
### Tool Execution API
|
|
1154
|
-
|
|
1155
|
-
The elicitation functionality is available through the `options` parameter in tool execution:
|
|
1156
|
-
|
|
1157
|
-
```typescript
|
|
1158
|
-
// Within a tool's execute function
|
|
1159
|
-
execute: async (inputData, context) => {
|
|
1160
|
-
// Use elicitation for user input
|
|
1161
|
-
const result = await context.mcp.elicitation.sendRequest({
|
|
1162
|
-
message: string, // Message to display to user
|
|
1163
|
-
requestedSchema: object // JSON schema defining expected response structure
|
|
1164
|
-
}): Promise<ElicitResult>
|
|
1165
|
-
|
|
1166
|
-
// Access authentication info if needed
|
|
1167
|
-
if (context.mcp?.extra?.authInfo) {
|
|
1168
|
-
// Use context.mcp.extra.authInfo.token, etc.
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
```
|
|
1172
|
-
|
|
1173
|
-
Elicitation is **session-aware** when using HTTP-based transports (SSE or HTTP). When multiple clients are connected to the same server, elicitation requests are routed to the client session that initiated the tool execution.
|
|
1174
|
-
|
|
1175
|
-
The `ElicitResult` type:
|
|
1176
|
-
|
|
1177
|
-
```typescript
|
|
1178
|
-
type ElicitResult = {
|
|
1179
|
-
action: 'accept' | 'decline' | 'cancel'
|
|
1180
|
-
content?: any // Only present when action is 'accept'
|
|
1181
|
-
}
|
|
1182
|
-
```
|
|
1183
|
-
|
|
1184
852
|
## OAuth protection
|
|
1185
853
|
|
|
1186
|
-
To protect your MCP server with OAuth authentication per the [MCP
|
|
854
|
+
To protect your MCP server with OAuth authentication per the [MCP Authorization specification](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization), use the `createOAuthMiddleware` function:
|
|
1187
855
|
|
|
1188
856
|
```typescript
|
|
1189
857
|
import http from 'node:http'
|
|
@@ -1268,6 +936,8 @@ const customMiddleware = createOAuthMiddleware({
|
|
|
1268
936
|
})
|
|
1269
937
|
```
|
|
1270
938
|
|
|
939
|
+
Return `subject` from your validator when you can. The server binds `input_required` continuations to it, so users sharing one OAuth client never share a continuation.
|
|
940
|
+
|
|
1271
941
|
### OAuth Middleware Options
|
|
1272
942
|
|
|
1273
943
|
**oauth.resource** (`string`): The canonical URL of your MCP server. This is returned in Protected Resource Metadata.
|
|
@@ -1284,19 +954,19 @@ const customMiddleware = createOAuthMiddleware({
|
|
|
1284
954
|
|
|
1285
955
|
## Authentication context
|
|
1286
956
|
|
|
1287
|
-
Tools can access request metadata via `context.mcp.extra` when using HTTP
|
|
957
|
+
Tools can access request metadata via `context.mcp.extra` when using the HTTP transport. You can pass authentication info and user context, as well as custom data from your HTTP middleware to your MCP tools.
|
|
1288
958
|
|
|
1289
959
|
### How it works
|
|
1290
960
|
|
|
1291
|
-
Whatever you set on `req.auth` in your HTTP middleware becomes available as `context.mcp.extra.authInfo` in your tools:
|
|
961
|
+
Whatever you set on `req.auth` in your HTTP middleware becomes available as `context.mcp.extra.authInfo` in your tools and as `requestContext.get('authInfo')` in resource and prompt callbacks:
|
|
1292
962
|
|
|
1293
963
|
```text
|
|
1294
|
-
req.auth = { ... } → context
|
|
964
|
+
req.auth = { ... } → context.mcp.extra.authInfo = { ... }
|
|
1295
965
|
```
|
|
1296
966
|
|
|
1297
967
|
### Map auth data for FGA
|
|
1298
968
|
|
|
1299
|
-
When an `MCPServer` is registered on a Mastra instance with a fine-grained authorization (FGA) provider, Mastra checks `requestContext.get('user')` before listing or calling tools. HTTP
|
|
969
|
+
When an `MCPServer` is registered on a Mastra instance with a fine-grained authorization (FGA) provider, Mastra checks `requestContext.get('user')` before listing or calling tools. The HTTP transport passes authenticated data as `extra.authInfo`, so use `mapAuthInfoToUser` to set the user shape expected by your FGA provider.
|
|
1300
970
|
|
|
1301
971
|
```typescript
|
|
1302
972
|
const server = new MCPServer({
|
|
@@ -1408,7 +1078,7 @@ app.use('/mcp', async (req, res, next) => {
|
|
|
1408
1078
|
scopes: user.scopes,
|
|
1409
1079
|
expiresAt: user.expiresAt,
|
|
1410
1080
|
extra: {
|
|
1411
|
-
|
|
1081
|
+
sub: user.userId,
|
|
1412
1082
|
email: user.email,
|
|
1413
1083
|
},
|
|
1414
1084
|
}
|
|
@@ -1424,6 +1094,8 @@ app.all('/mcp', async (req, res) => {
|
|
|
1424
1094
|
})
|
|
1425
1095
|
```
|
|
1426
1096
|
|
|
1097
|
+
Set `extra.sub` (or `extra.subject`) to the user's identifier. The server uses it to bind `input_required` continuations to that user.
|
|
1098
|
+
|
|
1427
1099
|
### Accessing Auth Data in Tools
|
|
1428
1100
|
|
|
1429
1101
|
The `req.auth` object is available as `context.mcp.extra.authInfo` in your tool's execute function:
|
|
@@ -1431,19 +1103,19 @@ The `req.auth` object is available as `context.mcp.extra.authInfo` in your tool'
|
|
|
1431
1103
|
```typescript
|
|
1432
1104
|
execute: async (inputData, context) => {
|
|
1433
1105
|
// Access the auth data you set in middleware
|
|
1434
|
-
const authInfo = context
|
|
1106
|
+
const authInfo = context.mcp?.extra.authInfo
|
|
1435
1107
|
|
|
1436
|
-
if (!authInfo?.extra?.
|
|
1108
|
+
if (!authInfo?.extra?.sub) {
|
|
1437
1109
|
return { error: 'Authentication required' }
|
|
1438
1110
|
}
|
|
1439
1111
|
|
|
1440
1112
|
// Use the auth data
|
|
1441
|
-
console.log('User ID:', authInfo.extra.
|
|
1113
|
+
console.log('User ID:', authInfo.extra.sub)
|
|
1442
1114
|
console.log('Email:', authInfo.extra.email)
|
|
1443
1115
|
|
|
1444
1116
|
const response = await fetch('/api/data', {
|
|
1445
1117
|
headers: { Authorization: `Bearer ${authInfo.token}` },
|
|
1446
|
-
signal: context
|
|
1118
|
+
signal: context.mcp?.extra.signal,
|
|
1447
1119
|
})
|
|
1448
1120
|
|
|
1449
1121
|
return response.json()
|
|
@@ -1452,28 +1124,23 @@ execute: async (inputData, context) => {
|
|
|
1452
1124
|
|
|
1453
1125
|
### Passing `RequestContext` through to agent
|
|
1454
1126
|
|
|
1127
|
+
`context.requestContext` already carries `authInfo` and the mapped `user`, so pass it straight through when a tool calls an agent:
|
|
1128
|
+
|
|
1455
1129
|
```typescript
|
|
1456
1130
|
execute: async (inputData, context) => {
|
|
1457
|
-
|
|
1458
|
-
const authInfo = context?.mcp?.extra?.authInfo
|
|
1459
|
-
|
|
1460
|
-
const requestContext = context.requestContext || new RequestContext().set('someKey', authInfo)
|
|
1131
|
+
const authInfo = context.mcp?.extra.authInfo
|
|
1461
1132
|
|
|
1462
|
-
if (!authInfo?.extra?.
|
|
1133
|
+
if (!authInfo?.extra?.sub) {
|
|
1463
1134
|
return { error: 'Authentication required' }
|
|
1464
1135
|
}
|
|
1465
1136
|
|
|
1466
|
-
|
|
1467
|
-
console.log('User ID:', authInfo.extra.userId)
|
|
1468
|
-
console.log('Email:', authInfo.extra.email)
|
|
1469
|
-
|
|
1470
|
-
const agent = context?.mastra?.getAgentById('some-agent-id')
|
|
1137
|
+
const agent = context.mastra?.getAgentById('some-agent-id')
|
|
1471
1138
|
|
|
1472
1139
|
if (!agent) {
|
|
1473
1140
|
return { error: "Agent 'some-agent-id' not found" }
|
|
1474
1141
|
}
|
|
1475
1142
|
|
|
1476
|
-
const response = await agent.generate(prompt, { requestContext })
|
|
1143
|
+
const response = await agent.generate(prompt, { requestContext: context.requestContext })
|
|
1477
1144
|
|
|
1478
1145
|
return response.text
|
|
1479
1146
|
}
|
|
@@ -1483,13 +1150,13 @@ execute: async (inputData, context) => {
|
|
|
1483
1150
|
|
|
1484
1151
|
The full `context.mcp.extra` object contains:
|
|
1485
1152
|
|
|
1486
|
-
| Property
|
|
1487
|
-
|
|
|
1488
|
-
| `authInfo`
|
|
1489
|
-
| `
|
|
1490
|
-
| `signal`
|
|
1491
|
-
| `
|
|
1492
|
-
| `sendRequest`
|
|
1153
|
+
| Property | Description |
|
|
1154
|
+
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1155
|
+
| `authInfo` | Whatever you set on `req.auth` in your middleware |
|
|
1156
|
+
| `requestId` | The JSON-RPC id of the current request |
|
|
1157
|
+
| `signal` | AbortSignal for request cancellation |
|
|
1158
|
+
| `_meta` | Request metadata sent by the client: W3C trace fields, the `io.modelcontextprotocol/logLevel` log-level opt-in, and `progressToken` |
|
|
1159
|
+
| `sendNotification`, `sendRequest` | Deprecated. The protocol has no server-initiated requests, so both throw with a message naming the replacement: `context.mcp.log()`, `context.mcp.progress()`, or `context.suspend()`. |
|
|
1493
1160
|
|
|
1494
1161
|
### Complete Example
|
|
1495
1162
|
|
|
@@ -1582,15 +1249,15 @@ const getUserData = createTool({
|
|
|
1582
1249
|
description: 'Fetches data for the authenticated user',
|
|
1583
1250
|
inputSchema: z.object({}),
|
|
1584
1251
|
execute: async (inputData, context) => {
|
|
1585
|
-
const authInfo = context
|
|
1252
|
+
const authInfo = context.mcp?.extra.authInfo
|
|
1586
1253
|
|
|
1587
|
-
if (!authInfo?.extra?.
|
|
1254
|
+
if (!authInfo?.extra?.sub) {
|
|
1588
1255
|
return { error: 'Authentication required' }
|
|
1589
1256
|
}
|
|
1590
1257
|
|
|
1591
1258
|
// Access the data you set in middleware
|
|
1592
1259
|
return {
|
|
1593
|
-
userId: authInfo.extra.
|
|
1260
|
+
userId: authInfo.extra.sub,
|
|
1594
1261
|
email: authInfo.extra.email,
|
|
1595
1262
|
}
|
|
1596
1263
|
},
|
|
@@ -1628,7 +1295,7 @@ app.use('/mcp', async (req, res, next) => {
|
|
|
1628
1295
|
scopes: user.scopes,
|
|
1629
1296
|
expiresAt: user.expiresAt,
|
|
1630
1297
|
extra: {
|
|
1631
|
-
|
|
1298
|
+
sub: user.userId,
|
|
1632
1299
|
email: user.email,
|
|
1633
1300
|
},
|
|
1634
1301
|
}
|
|
@@ -1712,4 +1379,5 @@ Link a tool to its app resource by setting `mcp._meta.ui.resourceUri` in `create
|
|
|
1712
1379
|
## Related information
|
|
1713
1380
|
|
|
1714
1381
|
- For connecting to MCP servers in Mastra, see the [MCPClient documentation](https://mastra.ai/reference/tools/mcp-client).
|
|
1382
|
+
- Migrating from `@mastra/mcp` 1.x: [Migrate @mastra/mcp from v1 to v2](https://mastra.ai/reference/migrations/mcp-v2).
|
|
1715
1383
|
- For more about the Model Context Protocol, see the [@modelcontextprotocol/sdk documentation](https://github.com/modelcontextprotocol/typescript-sdk).
|