@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
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
The `MCPClient` class provides a way to manage multiple MCP server connections and their tools in a Mastra application. It handles connection lifecycle and tool namespacing while providing access to tools across all configured servers.
|
|
8
8
|
|
|
9
|
+
> **Note:** Upgrading from `@mastra/mcp` 1.x? See the [migration guide](https://mastra.ai/reference/migrations/mcp-v2).
|
|
10
|
+
|
|
9
11
|
## Constructor
|
|
10
12
|
|
|
11
13
|
Creates a new instance of the MCPClient class.
|
|
@@ -31,7 +33,9 @@ constructor({
|
|
|
31
33
|
Each server in the `servers` map is configured using the `MastraMCPServerDefinition` type. The transport type is detected based on the provided parameters:
|
|
32
34
|
|
|
33
35
|
- If `command` is provided, it uses the Stdio transport.
|
|
34
|
-
- If `url` is provided, it
|
|
36
|
+
- If `url` is provided, it uses the Streamable HTTP transport.
|
|
37
|
+
|
|
38
|
+
The client speaks the MCP `2026-07-28` revision. On connect it asks the server which revisions it offers (`server/discover`) and falls back to the pre-2026 `initialize` handshake for servers that haven't upgraded, so tools, resources and prompts keep working against either. Features the older revisions lack (`resources.subscribe()`, `inputRequests`) fail with an error naming the negotiated revision. Set `protocolVersion` to skip the probe.
|
|
35
39
|
|
|
36
40
|
**command** (`string`): For Stdio servers: The command to execute.
|
|
37
41
|
|
|
@@ -41,25 +45,31 @@ Each server in the `servers` map is configured using the `MastraMCPServerDefinit
|
|
|
41
45
|
|
|
42
46
|
**inheritDefaultEnv** (`boolean`): For Stdio servers: Whether the subprocess environment starts from the MCP SDK's default inherited environment. The default is a curated whitelist, not the full process environment: on POSIX it inherits HOME, LOGNAME, PATH, SHELL, TERM, and USER; on Windows it inherits APPDATA, HOMEDRIVE, HOMEPATH, LOCALAPPDATA, PATH, PROCESSOR\_ARCHITECTURE, SYSTEMDRIVE, SYSTEMROOT, TEMP, USERNAME, and USERPROFILE. When set to false, only the variables explicitly listed in env are passed to the subprocess. Note that a subprocess without PATH may fail to spawn commands that are not absolute paths. (Default: `true`)
|
|
43
47
|
|
|
44
|
-
**url** (`URL`): For HTTP servers
|
|
48
|
+
**url** (`URL`): For HTTP servers: The URL of the server.
|
|
45
49
|
|
|
46
50
|
**requestInit** (`RequestInit`): For HTTP servers: Request configuration for the fetch API.
|
|
47
51
|
|
|
48
|
-
**
|
|
49
|
-
|
|
50
|
-
**fetch** (`MastraFetchLike`): For HTTP servers: Custom fetch implementation used for all network requests. Receives an optional third requestContext parameter containing request-scoped data (e.g., authentication cookies, bearer tokens) from the incoming request. When provided, this function will be used for all HTTP requests, allowing you to add dynamic authentication headers, forward request-scoped credentials to the MCP server, customize request behavior per-request, or intercept and modify requests/responses. When fetch is provided, requestInit, eventSourceInit, and authProvider become optional, as you can handle these concerns within your custom fetch function.
|
|
52
|
+
**fetch** (`MastraFetchLike`): For HTTP servers: Custom fetch implementation used for all network requests. Receives an optional third requestContext parameter containing request-scoped data (e.g., authentication cookies, bearer tokens) from the incoming request. When provided, this function will be used for all HTTP requests, allowing you to add dynamic authentication headers, forward request-scoped credentials to the MCP server, customize request behavior per-request, or intercept and modify requests/responses. When fetch is provided, requestInit and authProvider become optional, as you can handle these concerns within your custom fetch function.
|
|
51
53
|
|
|
52
54
|
**allowedHosts** (`string[]`): For HTTP servers: Opt-in allowlist of hosts the client may contact on behalf of this server. Each entry is matched against the URL host (hostname plus port when the URL carries a non-default port), for example "api.example.com" or "localhost:8080". Matching is exact and case-insensitive on the hostname; wildcards are not supported and the URL scheme is not checked. An empty array denies all requests. When unset, no restriction is applied. See the Security section below for enforcement details.
|
|
53
55
|
|
|
54
56
|
**logger** (`LogHandler`): Optional additional handler for logging.
|
|
55
57
|
|
|
56
|
-
**
|
|
58
|
+
**protocolVersion** (`'2026-07-28' | 'legacy'`): Pins the protocol revision instead of probing for it. '2026-07-28' connects only to servers that offer the current revision. 'legacy' uses the pre-2026 initialize handshake without probing, for servers known not to have upgraded. When omitted the client probes with server/discover and speaks whichever revision the server offers.
|
|
59
|
+
|
|
60
|
+
**timeout** (`number`): Server-specific timeout in milliseconds. Falls back to the client-level timeout when omitted.
|
|
57
61
|
|
|
58
|
-
**capabilities** (`
|
|
62
|
+
**capabilities** (`MCPClientCapabilities`): Client capabilities to advertise: elicitation and extensions. elicitation is only accepted together with an inputRequests handler and defaults to form support when a handler is configured. Roots and sampling are not supported.
|
|
63
|
+
|
|
64
|
+
**inputRequests** (`MCPInputRequestHandler`): Answers input requests from this server. When a tool, resource or prompt call ends as input\_required, the handler is called with each embedded request and the client retries the call with the answers. Without a handler an input\_required result surfaces as an error. See Answering input requests below.
|
|
59
65
|
|
|
60
66
|
**authProvider** (`OAuthClientProvider`): For HTTP servers: OAuth authentication provider for automatic token refresh and OAuth flow management. Use MCPOAuthClientProvider for a ready-to-use implementation.
|
|
61
67
|
|
|
62
|
-
**enableServerLogs** (`boolean`): Whether to
|
|
68
|
+
**enableServerLogs** (`boolean`): Whether to request per-request server logs. Attaches the io.modelcontextprotocol/logLevel metadata key to every request so the server delivers notifications/message for it, and forwards delivered messages to logger. (Default: `true`)
|
|
69
|
+
|
|
70
|
+
**serverLogLevel** (`LoggingLevel`): Minimum severity requested from the server for per-request logs. (Default: `'info'`)
|
|
71
|
+
|
|
72
|
+
**enableProgressTracking** (`boolean`): Whether to send a progressToken with tool calls so the server can report progress. See the progress property. (Default: `false`)
|
|
63
73
|
|
|
64
74
|
**traceContext** (`() => MCPTraceContext | undefined`): Returns the W3C traceparent, optional tracestate and baggage to send as request \_meta on every call to this server. Called when each request is sent so it can read a request-local carrier. Explicit \_meta keys on a tool call take precedence. Servers built with MCPServer expose the received values to tools as requestContext.get("traceContext"); they are observability data and are never used for authorization.
|
|
65
75
|
|
|
@@ -69,6 +79,8 @@ Each server in the `servers` map is configured using the `MastraMCPServerDefinit
|
|
|
69
79
|
|
|
70
80
|
**requireToolApproval** (`boolean | (params: RequireToolApprovalContext) => boolean | Promise<boolean>`): Require human approval before executing tools from this server. When set to true, all tools require approval. When set to a function, the function is called with the tool name, arguments, request context, and any tool annotations advertised by the server to dynamically decide whether approval is needed.
|
|
71
81
|
|
|
82
|
+
**onToolError** (`'throw' | 'return'`): How to handle tool execution failures the server reports in-band with isError: true. 'throw' raises a MastraError carrying the server's error text, so the failure reaches tool spans, stream chunks, scorers, and the model. 'return' resolves with the raw result and ignores isError. (Default: `'throw'`)
|
|
83
|
+
|
|
72
84
|
**jsonSchemaValidator** (`JsonSchemaValidator`): Validator used for MCP tool schemas and structured results. The default supports JSON Schema 2020-12. Provide a compatible validator such as CfWorkerJsonSchemaValidator in runtimes that disallow dynamic code generation.
|
|
73
85
|
|
|
74
86
|
## Tool approval
|
|
@@ -139,6 +151,8 @@ Per the MCP specification: **clients MUST consider tool annotations to be untrus
|
|
|
139
151
|
|
|
140
152
|
The same annotations are also exposed on the tools returned by `listTools()` and `listToolsets()` under `tool.mcp.annotations`, so you can inspect them when wiring tools into an agent.
|
|
141
153
|
|
|
154
|
+
Each tool also carries `tool.title`, set from the server's tool `title` and falling back to `annotations.title`. When the server provides neither, `tool.title` is `undefined` and the tool name is the display fallback, matching the MCP display-name precedence.
|
|
155
|
+
|
|
142
156
|
## Server instructions
|
|
143
157
|
|
|
144
158
|
When an MCP server advertises instructions during initialization, `MCPClient` stores them for that server. Forwarding those instructions into an agent's system prompt is **opt-in**: set `forwardInstructions: true` on a server to have agents that use its tools (via `listTools()` or `listToolsets()`) receive its instructions automatically.
|
|
@@ -209,7 +223,7 @@ const mcp = new MCPClient({
|
|
|
209
223
|
Enforcement details:
|
|
210
224
|
|
|
211
225
|
- On the default fetch path, requests to disallowed hosts, including every redirect hop, are blocked **before** they're sent. Redirects are followed manually (up to 5 hops) so each hop is validated, and the `Authorization` header isn't carried across hops to a different origin (any scheme, host, or port change drops it, matching standard fetch behavior).
|
|
212
|
-
- When you supply a custom `fetch
|
|
226
|
+
- When you supply a custom `fetch`, the initial URL is still checked before the request, but redirect hops are validated **after the fact** using `response.url`: the outbound hop may occur, and the response is discarded when its final URL points at a disallowed host. A hand-built `Response` with an empty `response.url` skips this post-hoc check.
|
|
213
227
|
- OAuth requests made through `authProvider` (authorization server metadata discovery, token exchange, refresh) are also validated. If your authorization server runs on a different host than the MCP server, add that host to `allowedHosts` too.
|
|
214
228
|
- A blocked host fails the connection with a clear error and is never retried by the reconnect logic.
|
|
215
229
|
|
|
@@ -277,7 +291,7 @@ When called without options, the method omits only `durations`; `toolsets`, `err
|
|
|
277
291
|
|
|
278
292
|
Returns every server's tools as plain, serializable definitions, grouped by server name and keyed by the server's own tool name (without the `serverName_toolName` namespacing that `listTools()` applies).
|
|
279
293
|
|
|
280
|
-
Unlike `listTools()`, the result contains no functions or references to a live client, so it can be passed through `JSON.stringify` and cached in Redis or a database, as well as a build artifact. Each definition holds the data from the MCP `tools/list` response (name, description, input schema, output schema, annotations, and `_meta`), plus the server name, version, and instructions captured at discovery time.
|
|
294
|
+
Unlike `listTools()`, the result contains no functions or references to a live client, so it can be passed through `JSON.stringify` and cached in Redis or a database, as well as a build artifact. Each definition holds the data from the MCP `tools/list` response (name, title, description, input schema, output schema, annotations, and `_meta`), plus the server name, version, and instructions captured at discovery time.
|
|
281
295
|
|
|
282
296
|
```typescript
|
|
283
297
|
const definitions = await mcp.listToolDefinitions()
|
|
@@ -573,145 +587,6 @@ mcpClient.resources.onListChanged('myWeatherServer', () => {
|
|
|
573
587
|
})
|
|
574
588
|
```
|
|
575
589
|
|
|
576
|
-
### `elicitation` Property
|
|
577
|
-
|
|
578
|
-
The `MCPClient` instance has an `elicitation` property that provides access to elicitation-related operations. Elicitation allows MCP servers to request structured information from users.
|
|
579
|
-
|
|
580
|
-
```typescript
|
|
581
|
-
const mcpClient = new MCPClient({/* ...servers configuration... */})
|
|
582
|
-
|
|
583
|
-
// Set up elicitation handler
|
|
584
|
-
mcpClient.elicitation.onRequest('serverName', async request => {
|
|
585
|
-
// Handle elicitation request from server
|
|
586
|
-
console.log('Server requests:', request.message)
|
|
587
|
-
console.log('Schema:', request.requestedSchema)
|
|
588
|
-
|
|
589
|
-
// Return user response
|
|
590
|
-
return {
|
|
591
|
-
action: 'accept',
|
|
592
|
-
content: { name: 'John Doe', email: 'john@example.com' },
|
|
593
|
-
}
|
|
594
|
-
})
|
|
595
|
-
```
|
|
596
|
-
|
|
597
|
-
#### `elicitation.onRequest(serverName: string, handler: ElicitationHandler)`
|
|
598
|
-
|
|
599
|
-
Sets up a handler function that will be called when any connected MCP server sends an elicitation request. The handler receives the request and must return a response.
|
|
600
|
-
|
|
601
|
-
##### `ElicitationHandler` Function
|
|
602
|
-
|
|
603
|
-
The handler function receives a request object with:
|
|
604
|
-
|
|
605
|
-
- `message`: A human-readable message describing what information is needed
|
|
606
|
-
- `requestedSchema`: A JSON schema defining the structure of the expected response
|
|
607
|
-
|
|
608
|
-
The handler must return an `ElicitResult` with:
|
|
609
|
-
|
|
610
|
-
- `action`: One of `'accept'`, `'decline'`, or `'cancel'`
|
|
611
|
-
- `content`: The user's data (only when action is `'accept'`)
|
|
612
|
-
|
|
613
|
-
**Example:**
|
|
614
|
-
|
|
615
|
-
```typescript
|
|
616
|
-
mcpClient.elicitation.onRequest('serverName', async request => {
|
|
617
|
-
console.log(`Server requests: ${request.message}`)
|
|
618
|
-
|
|
619
|
-
// Example: Simple user input collection
|
|
620
|
-
if (request.requestedSchema.properties.name) {
|
|
621
|
-
// Simulate user accepting and providing data
|
|
622
|
-
return {
|
|
623
|
-
action: 'accept',
|
|
624
|
-
content: {
|
|
625
|
-
name: 'Alice Smith',
|
|
626
|
-
email: 'alice@example.com',
|
|
627
|
-
},
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// Simulate user declining the request
|
|
632
|
-
return { action: 'decline' }
|
|
633
|
-
})
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
**Complete Interactive Example:**
|
|
637
|
-
|
|
638
|
-
```typescript
|
|
639
|
-
import { MCPClient } from '@mastra/mcp'
|
|
640
|
-
import { createInterface } from 'readline'
|
|
641
|
-
|
|
642
|
-
const readline = createInterface({
|
|
643
|
-
input: process.stdin,
|
|
644
|
-
output: process.stdout,
|
|
645
|
-
})
|
|
646
|
-
|
|
647
|
-
function askQuestion(question: string): Promise<string> {
|
|
648
|
-
return new Promise(resolve => {
|
|
649
|
-
readline.question(question, answer => resolve(answer.trim()))
|
|
650
|
-
})
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
const mcpClient = new MCPClient({
|
|
654
|
-
servers: {
|
|
655
|
-
interactiveServer: {
|
|
656
|
-
url: new URL('http://localhost:3000/mcp'),
|
|
657
|
-
},
|
|
658
|
-
},
|
|
659
|
-
})
|
|
660
|
-
|
|
661
|
-
// Set up interactive elicitation handler
|
|
662
|
-
await mcpClient.elicitation.onRequest('interactiveServer', async request => {
|
|
663
|
-
console.log(`\n📋 Server Request: ${request.message}`)
|
|
664
|
-
console.log('Required information:')
|
|
665
|
-
|
|
666
|
-
const schema = request.requestedSchema
|
|
667
|
-
const properties = schema.properties || {}
|
|
668
|
-
const required = schema.required || []
|
|
669
|
-
const content: Record<string, any> = {}
|
|
670
|
-
|
|
671
|
-
// Collect input for each field
|
|
672
|
-
for (const [fieldName, fieldSchema] of Object.entries(properties)) {
|
|
673
|
-
const field = fieldSchema as any
|
|
674
|
-
const isRequired = required.includes(fieldName)
|
|
675
|
-
|
|
676
|
-
let prompt = `${field.title || fieldName}`
|
|
677
|
-
if (field.description) prompt += ` (${field.description})`
|
|
678
|
-
if (isRequired) prompt += ' *required*'
|
|
679
|
-
prompt += ': '
|
|
680
|
-
|
|
681
|
-
const answer = await askQuestion(prompt)
|
|
682
|
-
|
|
683
|
-
// Handle cancellation
|
|
684
|
-
if (answer.toLowerCase() === 'cancel') {
|
|
685
|
-
return { action: 'cancel' }
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
// Validate required fields
|
|
689
|
-
if (answer === '' && isRequired) {
|
|
690
|
-
console.log(`❌ ${fieldName} is required`)
|
|
691
|
-
return { action: 'decline' }
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
if (answer !== '') {
|
|
695
|
-
content[fieldName] = answer
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// Confirm submission
|
|
700
|
-
console.log('\n📝 You provided:')
|
|
701
|
-
console.log(JSON.stringify(content, null, 2))
|
|
702
|
-
|
|
703
|
-
const confirm = await askQuestion('\nSubmit this information? (yes/no/cancel): ')
|
|
704
|
-
|
|
705
|
-
if (confirm.toLowerCase() === 'yes' || confirm.toLowerCase() === 'y') {
|
|
706
|
-
return { action: 'accept', content }
|
|
707
|
-
} else if (confirm.toLowerCase() === 'cancel') {
|
|
708
|
-
return { action: 'cancel' }
|
|
709
|
-
} else {
|
|
710
|
-
return { action: 'decline' }
|
|
711
|
-
}
|
|
712
|
-
})
|
|
713
|
-
```
|
|
714
|
-
|
|
715
590
|
### `prompts` Property
|
|
716
591
|
|
|
717
592
|
The `MCPClient` instance has a `prompts` property that provides access to prompt-related operations.
|
|
@@ -753,7 +628,7 @@ const { prompts, errors, errorDetails } = await mcpClient.prompts.listWithErrors
|
|
|
753
628
|
console.log(prompts, errors, errorDetails)
|
|
754
629
|
```
|
|
755
630
|
|
|
756
|
-
#### `prompts.get({ serverName, name, args
|
|
631
|
+
#### `prompts.get({ serverName, name, args? })`
|
|
757
632
|
|
|
758
633
|
Retrieves a specific prompt and its messages from a server.
|
|
759
634
|
|
|
@@ -762,24 +637,21 @@ async get({
|
|
|
762
637
|
serverName,
|
|
763
638
|
name,
|
|
764
639
|
args?,
|
|
765
|
-
version?,
|
|
766
640
|
}: {
|
|
767
641
|
serverName: string;
|
|
768
642
|
name: string;
|
|
769
643
|
args?: Record<string, any>;
|
|
770
|
-
|
|
771
|
-
}): Promise<{ prompt: Prompt; messages: PromptMessage[] }>
|
|
644
|
+
}): Promise<GetPromptResult>
|
|
772
645
|
```
|
|
773
646
|
|
|
774
647
|
Example:
|
|
775
648
|
|
|
776
649
|
```typescript
|
|
777
|
-
const {
|
|
650
|
+
const { messages } = await mcpClient.prompts.get({
|
|
778
651
|
serverName: 'myWeatherServer',
|
|
779
652
|
name: 'current',
|
|
780
653
|
args: { location: 'London' },
|
|
781
654
|
})
|
|
782
|
-
console.log(prompt)
|
|
783
655
|
console.log(messages)
|
|
784
656
|
```
|
|
785
657
|
|
|
@@ -832,7 +704,7 @@ const mcpClient = new MCPClient({
|
|
|
832
704
|
servers: {
|
|
833
705
|
myServer: {
|
|
834
706
|
url: new URL('http://localhost:4111/api/mcp/myServer/mcp'),
|
|
835
|
-
//
|
|
707
|
+
// Off by default: opt in so tool calls carry a progressToken
|
|
836
708
|
enableProgressTracking: true,
|
|
837
709
|
},
|
|
838
710
|
},
|
|
@@ -864,37 +736,12 @@ async onUpdate(
|
|
|
864
736
|
|
|
865
737
|
Notes:
|
|
866
738
|
|
|
867
|
-
- When `enableProgressTracking` is true
|
|
739
|
+
- When `enableProgressTracking` is true, tool calls include a `progressToken` so you can correlate updates to a specific run. Servers only send progress for requests that carry a token.
|
|
868
740
|
- If you pass a `runId` when executing a tool, it will be used as the `progressToken`.
|
|
869
741
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
```typescript
|
|
873
|
-
const mcpClient = new MCPClient({
|
|
874
|
-
servers: {
|
|
875
|
-
myServer: {
|
|
876
|
-
url: new URL('http://localhost:4111/api/mcp/myServer/mcp'),
|
|
877
|
-
enableProgressTracking: false,
|
|
878
|
-
},
|
|
879
|
-
},
|
|
880
|
-
})
|
|
881
|
-
```
|
|
882
|
-
|
|
883
|
-
## Elicitation
|
|
884
|
-
|
|
885
|
-
Elicitation is a feature that allows MCP servers to request structured information from users. When a server needs additional data, it can send an elicitation request that the client handles by prompting the user. A common example is during a tool call.
|
|
886
|
-
|
|
887
|
-
### How Elicitation Works
|
|
742
|
+
## Answering input requests
|
|
888
743
|
|
|
889
|
-
|
|
890
|
-
2. **Client Handler**: Your elicitation handler function is called with the request
|
|
891
|
-
3. **User Interaction**: Your handler collects user input (via UI, CLI, etc.)
|
|
892
|
-
4. **Response**: Your handler returns the user's response (accept/decline/cancel)
|
|
893
|
-
5. **Tool Continuation**: The server tool receives the response and continues execution
|
|
894
|
-
|
|
895
|
-
### Setting Up Elicitation
|
|
896
|
-
|
|
897
|
-
You must set up an elicitation handler before tools that use elicitation are called:
|
|
744
|
+
A server tool, resource or prompt that needs something from the user ends its call with an `input_required` result instead of a value. The result carries one or more embedded requests, each with a server-chosen `key` and either a form (`requestedSchema`) or a URL the user must visit. Configure `inputRequests` on the server definition to answer them: the client calls the handler once per embedded request, retries the original call with the answers, and resolves the tool call with the final result. Without a handler an `input_required` result surfaces as an error.
|
|
898
745
|
|
|
899
746
|
```typescript
|
|
900
747
|
import { MCPClient } from '@mastra/mcp'
|
|
@@ -903,30 +750,33 @@ const mcpClient = new MCPClient({
|
|
|
903
750
|
servers: {
|
|
904
751
|
interactiveServer: {
|
|
905
752
|
url: new URL('http://localhost:3000/mcp'),
|
|
753
|
+
inputRequests: async ({ key, params, signal }) => {
|
|
754
|
+
if (params.mode === 'url') {
|
|
755
|
+
return { action: 'decline' }
|
|
756
|
+
}
|
|
757
|
+
const content = await collectUserInput(params.message, params.requestedSchema, { signal })
|
|
758
|
+
return { action: 'accept', content }
|
|
759
|
+
},
|
|
906
760
|
},
|
|
907
761
|
},
|
|
908
762
|
})
|
|
763
|
+
```
|
|
909
764
|
|
|
910
|
-
|
|
911
|
-
mcpClient.elicitation.onRequest('interactiveServer', async request => {
|
|
912
|
-
// Handle the server's request for user input
|
|
913
|
-
console.log(`Server needs: ${request.message}`)
|
|
765
|
+
### `MCPInputRequest`
|
|
914
766
|
|
|
915
|
-
|
|
916
|
-
const userData = await collectUserInput(request.requestedSchema)
|
|
767
|
+
The handler receives:
|
|
917
768
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
```
|
|
769
|
+
- `key`: The server-chosen key the answer is filed under. Pass it through to your UI when several requests arrive in one round.
|
|
770
|
+
- `params`: The request. In form mode it has `message` and `requestedSchema`, a flat JSON Schema object of primitive fields. In url mode it has `message` and `url`.
|
|
771
|
+
- `signal`: Aborts when the originating call is cancelled or times out.
|
|
772
|
+
|
|
773
|
+
Configuring a handler advertises form support to the server. To accept url-mode requests as well, widen `capabilities.elicitation` on the same server definition.
|
|
924
774
|
|
|
925
775
|
### Response types
|
|
926
776
|
|
|
927
|
-
|
|
777
|
+
Return an `ElicitResult`:
|
|
928
778
|
|
|
929
|
-
- **Accept**:
|
|
779
|
+
- **Accept**: The user provided data and confirmed submission. `content` must match `requestedSchema`.
|
|
930
780
|
|
|
931
781
|
```typescript
|
|
932
782
|
return {
|
|
@@ -935,40 +785,37 @@ Your elicitation handler must return one of three response types:
|
|
|
935
785
|
}
|
|
936
786
|
```
|
|
937
787
|
|
|
938
|
-
- **Decline**:
|
|
788
|
+
- **Decline**: The user explicitly declined to provide the information.
|
|
939
789
|
|
|
940
790
|
```typescript
|
|
941
791
|
return { action: 'decline' }
|
|
942
792
|
```
|
|
943
793
|
|
|
944
|
-
- **Cancel**:
|
|
794
|
+
- **Cancel**: The user dismissed the request.
|
|
945
795
|
|
|
946
796
|
```typescript
|
|
947
797
|
return { action: 'cancel' }
|
|
948
798
|
```
|
|
949
799
|
|
|
950
|
-
|
|
800
|
+
A declined or cancelled request ends the tool call with an error. A server can ask again after an accepted answer, so the handler may run several times for one tool call.
|
|
951
801
|
|
|
952
|
-
|
|
802
|
+
### Schema-based input collection
|
|
803
|
+
|
|
804
|
+
Walk `requestedSchema` to prompt for each field:
|
|
953
805
|
|
|
954
806
|
```typescript
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
const content: Record<string, any> = {}
|
|
807
|
+
inputRequests: async ({ params }) => {
|
|
808
|
+
if (params.mode === 'url') return { action: 'decline' }
|
|
958
809
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
const isRequired = required.includes(fieldName)
|
|
810
|
+
const { properties, required = [] } = params.requestedSchema
|
|
811
|
+
const content: Record<string, string | number | boolean> = {}
|
|
962
812
|
|
|
963
|
-
|
|
813
|
+
for (const [fieldName, fieldSchema] of Object.entries(properties)) {
|
|
964
814
|
const value = await promptUser({
|
|
965
815
|
name: fieldName,
|
|
966
|
-
title:
|
|
967
|
-
description:
|
|
968
|
-
|
|
969
|
-
required: isRequired,
|
|
970
|
-
format: field.format,
|
|
971
|
-
enum: field.enum,
|
|
816
|
+
title: fieldSchema.title,
|
|
817
|
+
description: fieldSchema.description,
|
|
818
|
+
required: required.includes(fieldName),
|
|
972
819
|
})
|
|
973
820
|
|
|
974
821
|
if (value !== null) {
|
|
@@ -977,16 +824,16 @@ await mcpClient.elicitation.onRequest('interactiveServer', async request => {
|
|
|
977
824
|
}
|
|
978
825
|
|
|
979
826
|
return { action: 'accept', content }
|
|
980
|
-
}
|
|
827
|
+
}
|
|
981
828
|
```
|
|
982
829
|
|
|
983
830
|
### Best practices
|
|
984
831
|
|
|
985
|
-
- **
|
|
986
|
-
- **Validate input**: Check that required fields are provided
|
|
987
|
-
- **Respect user choice**: Handle decline and cancel responses gracefully
|
|
988
|
-
- **Clear UI**: Make it obvious what information is being requested and why
|
|
989
|
-
- **Security**: Never auto-accept requests for sensitive information
|
|
832
|
+
- **Configure the handler up front**: it's part of the server definition, so a tool that asks for input never runs without one.
|
|
833
|
+
- **Validate input**: Check that required fields are provided.
|
|
834
|
+
- **Respect user choice**: Handle decline and cancel responses gracefully.
|
|
835
|
+
- **Clear UI**: Make it obvious what information is being requested and why.
|
|
836
|
+
- **Security**: Never auto-accept requests for sensitive information, and treat url-mode requests as links to untrusted sites.
|
|
990
837
|
|
|
991
838
|
## OAuth authentication
|
|
992
839
|
|
|
@@ -1189,12 +1036,12 @@ const mcp = new MCPClient({
|
|
|
1189
1036
|
env: {
|
|
1190
1037
|
API_KEY: 'your-api-key',
|
|
1191
1038
|
},
|
|
1192
|
-
|
|
1039
|
+
logger: logMessage => {
|
|
1193
1040
|
console.log(`[${logMessage.level}] ${logMessage.message}`)
|
|
1194
1041
|
},
|
|
1195
1042
|
},
|
|
1196
1043
|
weather: {
|
|
1197
|
-
url: new URL('http://localhost:8080/
|
|
1044
|
+
url: new URL('http://localhost:8080/mcp'),
|
|
1198
1045
|
},
|
|
1199
1046
|
},
|
|
1200
1047
|
timeout: 30000, // Global 30s timeout
|
|
@@ -1271,7 +1118,7 @@ const mcp = new MCPClient({
|
|
|
1271
1118
|
timeout: 20000, // Server-specific timeout
|
|
1272
1119
|
},
|
|
1273
1120
|
weather: {
|
|
1274
|
-
url: new URL('http://localhost:8080/
|
|
1121
|
+
url: new URL('http://localhost:8080/mcp'),
|
|
1275
1122
|
requestInit: {
|
|
1276
1123
|
headers: {
|
|
1277
1124
|
Authorization: `Bearer user-123-token`,
|
|
@@ -1337,7 +1184,7 @@ For HTTP servers, you can provide a custom `fetch` function to handle runtime-de
|
|
|
1337
1184
|
|
|
1338
1185
|
The custom `fetch` function receives an optional third `requestContext` parameter, which provides access to request-scoped data (e.g., authentication cookies, bearer tokens) set by middleware or passed during agent/tool execution. The `requestContext` is `null` during the initial connection handshake.
|
|
1339
1186
|
|
|
1340
|
-
When `fetch` is provided, `requestInit
|
|
1187
|
+
When `fetch` is provided, `requestInit` and `authProvider` become optional, as you can handle these concerns within your custom fetch function.
|
|
1341
1188
|
|
|
1342
1189
|
```typescript
|
|
1343
1190
|
const mcpClient = new MCPClient({
|
|
@@ -1373,11 +1220,7 @@ await agent.generate('Hello!', {
|
|
|
1373
1220
|
|
|
1374
1221
|
## Handling auth failures inside custom fetch
|
|
1375
1222
|
|
|
1376
|
-
A custom `fetch` shouldn't `throw` when authentication is unavailable.
|
|
1377
|
-
|
|
1378
|
-
Return a synthetic `Response` instead. The [MCP Streamable HTTP specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports) defines `405 Method Not Allowed` as the signal a server returns when it doesn't offer the GET SSE stream, and the SDK honors it as a terminal status that stops the listener cleanly. Use this to disable the listener when your server doesn't push notifications.
|
|
1379
|
-
|
|
1380
|
-
The following pattern waits for an auth token on POST requests, attaches it to outgoing headers, and short-circuits the GET listener with a synthetic 405:
|
|
1223
|
+
A custom `fetch` shouldn't `throw` when authentication is unavailable. Every MCP request is a `POST`, and the SDK surfaces a non-2xx response as an error to the caller of `listTools()`, `tools/call` and so on, which is the behavior you want. A thrown `fetch` is reported the same way but loses the server's status and body. The only long-lived request is the `subscriptions/listen` `POST` the client opens for `resources.subscribe()`, and it's retried after a failure, so an unauthenticated stream can loop. Wait for the token, then forward the request:
|
|
1381
1224
|
|
|
1382
1225
|
```typescript
|
|
1383
1226
|
async function waitForToken(timeoutMs = 5000): Promise<string | null> {
|
|
@@ -1390,20 +1233,9 @@ const mcpClient = new MCPClient({
|
|
|
1390
1233
|
apiServer: {
|
|
1391
1234
|
url: new URL('https://api.example.com/mcp'),
|
|
1392
1235
|
fetch: async (url, init) => {
|
|
1393
|
-
const method = (init?.method || 'GET').toUpperCase()
|
|
1394
|
-
|
|
1395
|
-
// The SDK opens a background GET stream for server-pushed notifications.
|
|
1396
|
-
// If your server does not use it, short-circuit with 405 to stop reconnect attempts.
|
|
1397
|
-
if (method === 'GET') {
|
|
1398
|
-
return new Response(null, { status: 405, statusText: 'Method Not Allowed' })
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
// POST: wait for the token, then forward the request with an Authorization header.
|
|
1402
1236
|
const token = await waitForToken()
|
|
1403
1237
|
if (!token) {
|
|
1404
1238
|
// Forward the request without a token and let the server reject it.
|
|
1405
|
-
// The SDK surfaces non-2xx POST responses as errors to the caller of
|
|
1406
|
-
// tools/list, tools/call, etc., which is the desired behavior here.
|
|
1407
1239
|
return fetch(url, init)
|
|
1408
1240
|
}
|
|
1409
1241
|
|
|
@@ -1416,58 +1248,29 @@ const mcpClient = new MCPClient({
|
|
|
1416
1248
|
})
|
|
1417
1249
|
```
|
|
1418
1250
|
|
|
1419
|
-
|
|
1251
|
+
## Sending request headers
|
|
1420
1252
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
When using the legacy SSE MCP transport, you must configure both `requestInit` and `eventSourceInit` due to a bug in the MCP SDK. Alternatively, you can use a custom `fetch` function which will be automatically used for both POST requests and SSE connections:
|
|
1253
|
+
Static headers go in `requestInit`. They're sent on every request, including the `subscriptions/listen` stream:
|
|
1424
1254
|
|
|
1425
1255
|
```ts
|
|
1426
|
-
|
|
1427
|
-
const sseClient = new MCPClient({
|
|
1256
|
+
const client = new MCPClient({
|
|
1428
1257
|
servers: {
|
|
1429
1258
|
exampleServer: {
|
|
1430
|
-
url: new URL('https://your-mcp-server.com/
|
|
1431
|
-
// Note: requestInit alone isn't enough for SSE
|
|
1259
|
+
url: new URL('https://your-mcp-server.com/mcp'),
|
|
1432
1260
|
requestInit: {
|
|
1433
1261
|
headers: {
|
|
1434
1262
|
Authorization: 'Bearer your-token',
|
|
1435
1263
|
},
|
|
1436
1264
|
},
|
|
1437
|
-
// This is also required for SSE connections with custom headers
|
|
1438
|
-
eventSourceInit: {
|
|
1439
|
-
fetch(input: Request | URL | string, init?: RequestInit) {
|
|
1440
|
-
const headers = new Headers(init?.headers || {})
|
|
1441
|
-
headers.set('Authorization', 'Bearer your-token')
|
|
1442
|
-
return fetch(input, {
|
|
1443
|
-
...init,
|
|
1444
|
-
headers,
|
|
1445
|
-
})
|
|
1446
|
-
},
|
|
1447
|
-
},
|
|
1448
|
-
},
|
|
1449
|
-
},
|
|
1450
|
-
})
|
|
1451
|
-
|
|
1452
|
-
// Option 2: Using custom fetch (simpler, works for both Streamable HTTP and SSE)
|
|
1453
|
-
const sseClientWithFetch = new MCPClient({
|
|
1454
|
-
servers: {
|
|
1455
|
-
exampleServer: {
|
|
1456
|
-
url: new URL('https://your-mcp-server.com/sse'),
|
|
1457
|
-
fetch: async (url, init) => {
|
|
1458
|
-
const headers = new Headers(init?.headers || {})
|
|
1459
|
-
headers.set('Authorization', 'Bearer your-token')
|
|
1460
|
-
return fetch(url, {
|
|
1461
|
-
...init,
|
|
1462
|
-
headers,
|
|
1463
|
-
})
|
|
1464
|
-
},
|
|
1465
1265
|
},
|
|
1466
1266
|
},
|
|
1467
1267
|
})
|
|
1468
1268
|
```
|
|
1469
1269
|
|
|
1270
|
+
Use a custom `fetch` when the value changes per request.
|
|
1271
|
+
|
|
1470
1272
|
## Related information
|
|
1471
1273
|
|
|
1472
1274
|
- For creating MCP servers, see the [MCPServer documentation](https://mastra.ai/reference/tools/mcp-server).
|
|
1275
|
+
- Migrating from `@mastra/mcp` 1.x: [Migrate @mastra/mcp from v1 to v2](https://mastra.ai/reference/migrations/mcp-v2).
|
|
1473
1276
|
- For more about the Model Context Protocol, see the [@modelcontextprotocol/sdk documentation](https://github.com/modelcontextprotocol/typescript-sdk).
|