@mastra/mcp 2.0.0-alpha.5 → 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.
@@ -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 first attempts to use the Streamable HTTP transport and falls back to the legacy SSE transport if the initial connection fails.
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 (Streamable HTTP or SSE): The URL of the server.
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
- **eventSourceInit** (`EventSourceInit`): For SSE fallback: Custom fetch configuration for SSE connections. Required when using custom headers with SSE.
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
- **timeout** (`number`): Server-specific timeout in milliseconds.
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** (`ClientCapabilities`): Server-specific capabilities configuration.
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 enable logging for this server. (Default: `true`)
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` (or a custom `eventSourceInit.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.
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?, version? })`
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
- version?: string;
771
- }): Promise<{ prompt: Prompt; messages: PromptMessage[] }>
644
+ }): Promise<GetPromptResult>
772
645
  ```
773
646
 
774
647
  Example:
775
648
 
776
649
  ```typescript
777
- const { prompt, messages } = await mcpClient.prompts.get({
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
- // Enabled by default; set to false to disable
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 (default), tool calls include a `progressToken` so you can correlate updates to a specific run.
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
- To disable progress tracking for a server:
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
- 1. **Server Request**: An MCP server tool calls `server.elicitation.sendRequest()` with a message and schema
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
- // Set up elicitation handler
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
- // Your logic to collect user input
916
- const userData = await collectUserInput(request.requestedSchema)
767
+ The handler receives:
917
768
 
918
- return {
919
- action: 'accept',
920
- content: userData,
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
- Your elicitation handler must return one of three response types:
777
+ Return an `ElicitResult`:
928
778
 
929
- - **Accept**: User provided data and confirmed submission
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**: User explicitly declined to provide the information
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**: User dismissed or cancelled the request
794
+ - **Cancel**: The user dismissed the request.
945
795
 
946
796
  ```typescript
947
797
  return { action: 'cancel' }
948
798
  ```
949
799
 
950
- ### Schema-Based Input Collection
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
- The `requestedSchema` provides structure for the data the server needs:
802
+ ### Schema-based input collection
803
+
804
+ Walk `requestedSchema` to prompt for each field:
953
805
 
954
806
  ```typescript
955
- await mcpClient.elicitation.onRequest('interactiveServer', async request => {
956
- const { properties, required = [] } = request.requestedSchema
957
- const content: Record<string, any> = {}
807
+ inputRequests: async ({ params }) => {
808
+ if (params.mode === 'url') return { action: 'decline' }
958
809
 
959
- for (const [fieldName, fieldSchema] of Object.entries(properties || {})) {
960
- const field = fieldSchema as any
961
- const isRequired = required.includes(fieldName)
810
+ const { properties, required = [] } = params.requestedSchema
811
+ const content: Record<string, string | number | boolean> = {}
962
812
 
963
- // Collect input based on field type and requirements
813
+ for (const [fieldName, fieldSchema] of Object.entries(properties)) {
964
814
  const value = await promptUser({
965
815
  name: fieldName,
966
- title: field.title,
967
- description: field.description,
968
- type: field.type,
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
- - **Always handle elicitation**: Set up your handler before calling tools that might use elicitation
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
- log: logMessage => {
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/sse'),
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/sse'),
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`, `eventSourceInit`, and `authProvider` become optional, as you can handle these concerns within your custom fetch function.
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. The Streamable HTTP transport in the MCP SDK opens a long-lived `GET /mcp` "standalone listener" stream in the background to receive server-pushed notifications. Errors on that stream are retried with exponential backoff, and a thrown `fetch` or a cleanly-closed stream can produce an indefinite reconnect loop at roughly one attempt per second.
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
- Return `405` for the GET listener only when your server doesn't push notifications back to the client. If your server uses the standalone GET stream, attach the auth token on `GET` requests as well and let the request through.
1251
+ ## Sending request headers
1420
1252
 
1421
- ## Using SSE request headers
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
- // Option 1: Using requestInit and eventSourceInit (required for SSE)
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/sse'),
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).