@mastra/mcp-docs-server 1.2.27 → 1.2.28-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
@@ -209,7 +221,7 @@ const mcp = new MCPClient({
209
221
  Enforcement details:
210
222
 
211
223
  - 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.
224
+ - 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
225
  - 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
226
  - A blocked host fails the connection with a clear error and is never retried by the reconnect logic.
215
227
 
@@ -573,145 +585,6 @@ mcpClient.resources.onListChanged('myWeatherServer', () => {
573
585
  })
574
586
  ```
575
587
 
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
588
  ### `prompts` Property
716
589
 
717
590
  The `MCPClient` instance has a `prompts` property that provides access to prompt-related operations.
@@ -753,7 +626,7 @@ const { prompts, errors, errorDetails } = await mcpClient.prompts.listWithErrors
753
626
  console.log(prompts, errors, errorDetails)
754
627
  ```
755
628
 
756
- #### `prompts.get({ serverName, name, args?, version? })`
629
+ #### `prompts.get({ serverName, name, args? })`
757
630
 
758
631
  Retrieves a specific prompt and its messages from a server.
759
632
 
@@ -762,24 +635,21 @@ async get({
762
635
  serverName,
763
636
  name,
764
637
  args?,
765
- version?,
766
638
  }: {
767
639
  serverName: string;
768
640
  name: string;
769
641
  args?: Record<string, any>;
770
- version?: string;
771
- }): Promise<{ prompt: Prompt; messages: PromptMessage[] }>
642
+ }): Promise<GetPromptResult>
772
643
  ```
773
644
 
774
645
  Example:
775
646
 
776
647
  ```typescript
777
- const { prompt, messages } = await mcpClient.prompts.get({
648
+ const { messages } = await mcpClient.prompts.get({
778
649
  serverName: 'myWeatherServer',
779
650
  name: 'current',
780
651
  args: { location: 'London' },
781
652
  })
782
- console.log(prompt)
783
653
  console.log(messages)
784
654
  ```
785
655
 
@@ -832,7 +702,7 @@ const mcpClient = new MCPClient({
832
702
  servers: {
833
703
  myServer: {
834
704
  url: new URL('http://localhost:4111/api/mcp/myServer/mcp'),
835
- // Enabled by default; set to false to disable
705
+ // Off by default: opt in so tool calls carry a progressToken
836
706
  enableProgressTracking: true,
837
707
  },
838
708
  },
@@ -864,37 +734,12 @@ async onUpdate(
864
734
 
865
735
  Notes:
866
736
 
867
- - When `enableProgressTracking` is true (default), tool calls include a `progressToken` so you can correlate updates to a specific run.
737
+ - 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
738
  - If you pass a `runId` when executing a tool, it will be used as the `progressToken`.
869
739
 
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
740
+ ## Answering input requests
888
741
 
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:
742
+ 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
743
 
899
744
  ```typescript
900
745
  import { MCPClient } from '@mastra/mcp'
@@ -903,30 +748,33 @@ const mcpClient = new MCPClient({
903
748
  servers: {
904
749
  interactiveServer: {
905
750
  url: new URL('http://localhost:3000/mcp'),
751
+ inputRequests: async ({ key, params, signal }) => {
752
+ if (params.mode === 'url') {
753
+ return { action: 'decline' }
754
+ }
755
+ const content = await collectUserInput(params.message, params.requestedSchema, { signal })
756
+ return { action: 'accept', content }
757
+ },
906
758
  },
907
759
  },
908
760
  })
761
+ ```
909
762
 
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}`)
763
+ ### `MCPInputRequest`
914
764
 
915
- // Your logic to collect user input
916
- const userData = await collectUserInput(request.requestedSchema)
765
+ The handler receives:
917
766
 
918
- return {
919
- action: 'accept',
920
- content: userData,
921
- }
922
- })
923
- ```
767
+ - `key`: The server-chosen key the answer is filed under. Pass it through to your UI when several requests arrive in one round.
768
+ - `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`.
769
+ - `signal`: Aborts when the originating call is cancelled or times out.
770
+
771
+ 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
772
 
925
773
  ### Response types
926
774
 
927
- Your elicitation handler must return one of three response types:
775
+ Return an `ElicitResult`:
928
776
 
929
- - **Accept**: User provided data and confirmed submission
777
+ - **Accept**: The user provided data and confirmed submission. `content` must match `requestedSchema`.
930
778
 
931
779
  ```typescript
932
780
  return {
@@ -935,40 +783,37 @@ Your elicitation handler must return one of three response types:
935
783
  }
936
784
  ```
937
785
 
938
- - **Decline**: User explicitly declined to provide the information
786
+ - **Decline**: The user explicitly declined to provide the information.
939
787
 
940
788
  ```typescript
941
789
  return { action: 'decline' }
942
790
  ```
943
791
 
944
- - **Cancel**: User dismissed or cancelled the request
792
+ - **Cancel**: The user dismissed the request.
945
793
 
946
794
  ```typescript
947
795
  return { action: 'cancel' }
948
796
  ```
949
797
 
950
- ### Schema-Based Input Collection
798
+ 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
799
 
952
- The `requestedSchema` provides structure for the data the server needs:
800
+ ### Schema-based input collection
801
+
802
+ Walk `requestedSchema` to prompt for each field:
953
803
 
954
804
  ```typescript
955
- await mcpClient.elicitation.onRequest('interactiveServer', async request => {
956
- const { properties, required = [] } = request.requestedSchema
957
- const content: Record<string, any> = {}
805
+ inputRequests: async ({ params }) => {
806
+ if (params.mode === 'url') return { action: 'decline' }
958
807
 
959
- for (const [fieldName, fieldSchema] of Object.entries(properties || {})) {
960
- const field = fieldSchema as any
961
- const isRequired = required.includes(fieldName)
808
+ const { properties, required = [] } = params.requestedSchema
809
+ const content: Record<string, string | number | boolean> = {}
962
810
 
963
- // Collect input based on field type and requirements
811
+ for (const [fieldName, fieldSchema] of Object.entries(properties)) {
964
812
  const value = await promptUser({
965
813
  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,
814
+ title: fieldSchema.title,
815
+ description: fieldSchema.description,
816
+ required: required.includes(fieldName),
972
817
  })
973
818
 
974
819
  if (value !== null) {
@@ -977,16 +822,16 @@ await mcpClient.elicitation.onRequest('interactiveServer', async request => {
977
822
  }
978
823
 
979
824
  return { action: 'accept', content }
980
- })
825
+ }
981
826
  ```
982
827
 
983
828
  ### Best practices
984
829
 
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
830
+ - **Configure the handler up front**: it's part of the server definition, so a tool that asks for input never runs without one.
831
+ - **Validate input**: Check that required fields are provided.
832
+ - **Respect user choice**: Handle decline and cancel responses gracefully.
833
+ - **Clear UI**: Make it obvious what information is being requested and why.
834
+ - **Security**: Never auto-accept requests for sensitive information, and treat url-mode requests as links to untrusted sites.
990
835
 
991
836
  ## OAuth authentication
992
837
 
@@ -1189,12 +1034,12 @@ const mcp = new MCPClient({
1189
1034
  env: {
1190
1035
  API_KEY: 'your-api-key',
1191
1036
  },
1192
- log: logMessage => {
1037
+ logger: logMessage => {
1193
1038
  console.log(`[${logMessage.level}] ${logMessage.message}`)
1194
1039
  },
1195
1040
  },
1196
1041
  weather: {
1197
- url: new URL('http://localhost:8080/sse'),
1042
+ url: new URL('http://localhost:8080/mcp'),
1198
1043
  },
1199
1044
  },
1200
1045
  timeout: 30000, // Global 30s timeout
@@ -1271,7 +1116,7 @@ const mcp = new MCPClient({
1271
1116
  timeout: 20000, // Server-specific timeout
1272
1117
  },
1273
1118
  weather: {
1274
- url: new URL('http://localhost:8080/sse'),
1119
+ url: new URL('http://localhost:8080/mcp'),
1275
1120
  requestInit: {
1276
1121
  headers: {
1277
1122
  Authorization: `Bearer user-123-token`,
@@ -1337,7 +1182,7 @@ For HTTP servers, you can provide a custom `fetch` function to handle runtime-de
1337
1182
 
1338
1183
  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
1184
 
1340
- When `fetch` is provided, `requestInit`, `eventSourceInit`, and `authProvider` become optional, as you can handle these concerns within your custom fetch function.
1185
+ When `fetch` is provided, `requestInit` and `authProvider` become optional, as you can handle these concerns within your custom fetch function.
1341
1186
 
1342
1187
  ```typescript
1343
1188
  const mcpClient = new MCPClient({
@@ -1373,11 +1218,7 @@ await agent.generate('Hello!', {
1373
1218
 
1374
1219
  ## Handling auth failures inside custom fetch
1375
1220
 
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:
1221
+ 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
1222
 
1382
1223
  ```typescript
1383
1224
  async function waitForToken(timeoutMs = 5000): Promise<string | null> {
@@ -1390,20 +1231,9 @@ const mcpClient = new MCPClient({
1390
1231
  apiServer: {
1391
1232
  url: new URL('https://api.example.com/mcp'),
1392
1233
  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
1234
  const token = await waitForToken()
1403
1235
  if (!token) {
1404
1236
  // 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
1237
  return fetch(url, init)
1408
1238
  }
1409
1239
 
@@ -1416,58 +1246,29 @@ const mcpClient = new MCPClient({
1416
1246
  })
1417
1247
  ```
1418
1248
 
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.
1249
+ ## Sending request headers
1420
1250
 
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:
1251
+ Static headers go in `requestInit`. They're sent on every request, including the `subscriptions/listen` stream:
1424
1252
 
1425
1253
  ```ts
1426
- // Option 1: Using requestInit and eventSourceInit (required for SSE)
1427
- const sseClient = new MCPClient({
1254
+ const client = new MCPClient({
1428
1255
  servers: {
1429
1256
  exampleServer: {
1430
- url: new URL('https://your-mcp-server.com/sse'),
1431
- // Note: requestInit alone isn't enough for SSE
1257
+ url: new URL('https://your-mcp-server.com/mcp'),
1432
1258
  requestInit: {
1433
1259
  headers: {
1434
1260
  Authorization: 'Bearer your-token',
1435
1261
  },
1436
1262
  },
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
1263
  },
1466
1264
  },
1467
1265
  })
1468
1266
  ```
1469
1267
 
1268
+ Use a custom `fetch` when the value changes per request.
1269
+
1470
1270
  ## Related information
1471
1271
 
1472
1272
  - For creating MCP servers, see the [MCPServer documentation](https://mastra.ai/reference/tools/mcp-server).
1273
+ - Migrating from `@mastra/mcp` 1.x: [Migrate @mastra/mcp from v1 to v2](https://mastra.ai/reference/migrations/mcp-v2).
1473
1274
  - For more about the Model Context Protocol, see the [@modelcontextprotocol/sdk documentation](https://github.com/modelcontextprotocol/typescript-sdk).