@mastra/mcp 0.3.10-alpha.0 → 0.3.10-alpha.2

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.
@@ -1,23 +1,23 @@
1
1
 
2
- > @mastra/mcp@0.3.10-alpha.0 build /home/runner/work/mastra/mastra/packages/mcp
2
+ > @mastra/mcp@0.3.10-alpha.2 build /home/runner/work/mastra/mastra/packages/mcp
3
3
  > tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting
4
4
 
5
5
  CLI Building entry: src/index.ts
6
6
  CLI Using tsconfig: tsconfig.json
7
7
  CLI tsup v8.4.0
8
8
  TSC Build start
9
- TSC ⚡️ Build success in 15149ms
9
+ TSC ⚡️ Build success in 13988ms
10
10
  DTS Build start
11
11
  CLI Target: es2022
12
12
  Analysis will use the bundled TypeScript version 5.8.2
13
13
  Writing package typings: /home/runner/work/mastra/mastra/packages/mcp/dist/_tsup-dts-rollup.d.ts
14
14
  Analysis will use the bundled TypeScript version 5.8.2
15
15
  Writing package typings: /home/runner/work/mastra/mastra/packages/mcp/dist/_tsup-dts-rollup.d.cts
16
- DTS ⚡️ Build success in 11903ms
16
+ DTS ⚡️ Build success in 10061ms
17
17
  CLI Cleaning output folder
18
18
  ESM Build start
19
19
  CJS Build start
20
- ESM dist/index.js 7.24 KB
21
- ESM ⚡️ Build success in 895ms
22
- CJS dist/index.cjs 7.27 KB
23
- CJS ⚡️ Build success in 895ms
20
+ CJS dist/index.cjs 7.53 KB
21
+ CJS ⚡️ Build success in 706ms
22
+ ESM dist/index.js 7.48 KB
23
+ ESM ⚡️ Build success in 706ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @mastra/mcp
2
2
 
3
+ ## 0.3.10-alpha.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [0bcc862]
8
+ - @mastra/core@0.8.3-alpha.2
9
+
10
+ ## 0.3.10-alpha.1
11
+
12
+ ### Patch Changes
13
+
14
+ - 37bb612: Add Elastic-2.0 licensing for packages
15
+ - 3e8e000: Added configurable test timeouts to mcp configuration, and updated mcp version
16
+ - 476ce7c: [MASTRA-2997] added documentation on authorization headers in sse connections
17
+ - Updated dependencies [32e7b71]
18
+ - Updated dependencies [37bb612]
19
+ - @mastra/core@0.8.3-alpha.1
20
+
3
21
  ## 0.3.10-alpha.0
4
22
 
5
23
  ### Patch Changes
@@ -1,4 +1,6 @@
1
- Elastic License 2.0 (ELv2)
1
+ # Elastic License 2.0 (ELv2)
2
+
3
+ Copyright (c) 2025 Mastra AI, Inc.
2
4
 
3
5
  **Acceptance**
4
6
  By using the software, you agree to all of the terms and conditions below.
package/README.md CHANGED
@@ -37,6 +37,16 @@ const sseClient = new MastraMCPClient({
37
37
  requestInit: {
38
38
  headers: { Authorization: 'Bearer your-token' },
39
39
  },
40
+ eventSourceInit: {
41
+ fetch(input: Request | URL | string, init?: RequestInit) {
42
+ const headers = new Headers(init?.headers || {});
43
+ headers.set('Authorization', 'Bearer your-token');
44
+ return fetch(input, {
45
+ ...init,
46
+ headers,
47
+ });
48
+ },
49
+ },
40
50
  },
41
51
  timeout: 60000, // optional timeout for tool calls in milliseconds
42
52
  });
@@ -140,6 +150,16 @@ const mcp = new MCPConfiguration({
140
150
  Authorization: 'Bearer user-1-token',
141
151
  },
142
152
  },
153
+ eventSourceInit: {
154
+ fetch(input: Request | URL | string, init?: RequestInit) {
155
+ const headers = new Headers(init?.headers || {});
156
+ headers.set('Authorization', 'Bearer user-1-token');
157
+ return fetch(input, {
158
+ ...init,
159
+ headers,
160
+ });
161
+ },
162
+ },
143
163
  },
144
164
  },
145
165
  });
@@ -162,6 +182,44 @@ The `MCPConfiguration` class automatically:
162
182
  - Handles connection lifecycle and cleanup
163
183
  - Provides both flat and grouped access to tools
164
184
 
185
+ ## SSE Authentication and Headers
186
+
187
+ When using SSE (Server-Sent Events) connections with authentication or custom headers, you need to configure headers in a specific way. The standard `requestInit` headers won't work alone because SSE connections use the browser's `EventSource` API, which doesn't support custom headers directly.
188
+
189
+ The `eventSourceInit` configuration allows you to customize the underlying fetch request used for the SSE connection, ensuring your authentication headers are properly included.
190
+
191
+ To properly include authentication headers or other custom headers in SSE connections, you need to use both `requestInit` and `eventSourceInit`:
192
+
193
+ ```typescript
194
+ const sseClient = new MastraMCPClient({
195
+ name: 'authenticated-sse-client',
196
+ server: {
197
+ url: new URL('https://your-mcp-server.com/sse'),
198
+ // requestInit alone isn't enough for SSE connections
199
+ requestInit: {
200
+ headers: { Authorization: 'Bearer your-token' },
201
+ },
202
+ // eventSourceInit is required to include headers in the SSE connection
203
+ eventSourceInit: {
204
+ fetch(input: Request | URL | string, init?: RequestInit) {
205
+ const headers = new Headers(init?.headers || {});
206
+ headers.set('Authorization', 'Bearer your-token');
207
+ return fetch(input, {
208
+ ...init,
209
+ headers,
210
+ });
211
+ },
212
+ },
213
+ },
214
+ });
215
+ ```
216
+
217
+ This configuration ensures that:
218
+
219
+ 1. The authentication headers are properly included in the SSE connection request
220
+ 2. The connection can be established with the required credentials
221
+ 3. Subsequent messages can be received through the authenticated connection
222
+
165
223
  ## Configuration
166
224
 
167
225
  ### Required Parameters
@@ -2,7 +2,7 @@ import type { ClientCapabilities } from '@modelcontextprotocol/sdk/types.js';
2
2
  import { MastraBase } from '@mastra/core/base';
3
3
  import type { Protocol } from '@modelcontextprotocol/sdk/shared/protocol.js';
4
4
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
5
- import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
5
+ import type { SSEClientTransportOptions } from '@modelcontextprotocol/sdk/client/sse.js';
6
6
  import type { StdioServerParameters } from '@modelcontextprotocol/sdk/client/stdio.js';
7
7
 
8
8
  declare class MastraMCPClient extends MastraBase {
@@ -26,16 +26,18 @@ declare class MastraMCPClient extends MastraBase {
26
26
  export { MastraMCPClient }
27
27
  export { MastraMCPClient as MastraMCPClient_alias_1 }
28
28
 
29
- declare type MastraMCPServerDefinition = StdioServerParameters | SSEClientParameters;
29
+ declare type MastraMCPServerDefinition = StdioServerParametersWithTimeout | SSEClientParameters;
30
30
  export { MastraMCPServerDefinition }
31
31
  export { MastraMCPServerDefinition as MastraMCPServerDefinition_alias_1 }
32
32
 
33
33
  declare class MCPConfiguration extends MastraBase {
34
34
  private serverConfigs;
35
35
  private id;
36
+ private defaultTimeout;
36
37
  constructor(args: {
37
38
  id?: string;
38
39
  servers: Record<string, MastraMCPServerDefinition>;
40
+ timeout?: number;
39
41
  });
40
42
  private addToInstanceCache;
41
43
  private makeId;
@@ -99,6 +101,11 @@ export declare const server_alias_1: Server<{
99
101
 
100
102
  declare type SSEClientParameters = {
101
103
  url: URL;
102
- } & ConstructorParameters<typeof SSEClientTransport>[1];
104
+ timeout?: number;
105
+ } & SSEClientTransportOptions;
106
+
107
+ declare type StdioServerParametersWithTimeout = StdioServerParameters & {
108
+ timeout?: number;
109
+ };
103
110
 
104
111
  export { }
@@ -2,7 +2,7 @@ import type { ClientCapabilities } from '@modelcontextprotocol/sdk/types.js';
2
2
  import { MastraBase } from '@mastra/core/base';
3
3
  import type { Protocol } from '@modelcontextprotocol/sdk/shared/protocol.js';
4
4
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
5
- import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
5
+ import type { SSEClientTransportOptions } from '@modelcontextprotocol/sdk/client/sse.js';
6
6
  import type { StdioServerParameters } from '@modelcontextprotocol/sdk/client/stdio.js';
7
7
 
8
8
  declare class MastraMCPClient extends MastraBase {
@@ -26,16 +26,18 @@ declare class MastraMCPClient extends MastraBase {
26
26
  export { MastraMCPClient }
27
27
  export { MastraMCPClient as MastraMCPClient_alias_1 }
28
28
 
29
- declare type MastraMCPServerDefinition = StdioServerParameters | SSEClientParameters;
29
+ declare type MastraMCPServerDefinition = StdioServerParametersWithTimeout | SSEClientParameters;
30
30
  export { MastraMCPServerDefinition }
31
31
  export { MastraMCPServerDefinition as MastraMCPServerDefinition_alias_1 }
32
32
 
33
33
  declare class MCPConfiguration extends MastraBase {
34
34
  private serverConfigs;
35
35
  private id;
36
+ private defaultTimeout;
36
37
  constructor(args: {
37
38
  id?: string;
38
39
  servers: Record<string, MastraMCPServerDefinition>;
40
+ timeout?: number;
39
41
  });
40
42
  private addToInstanceCache;
41
43
  private makeId;
@@ -99,6 +101,11 @@ export declare const server_alias_1: Server<{
99
101
 
100
102
  declare type SSEClientParameters = {
101
103
  url: URL;
102
- } & ConstructorParameters<typeof SSEClientTransport>[1];
104
+ timeout?: number;
105
+ } & SSEClientTransportOptions;
106
+
107
+ declare type StdioServerParametersWithTimeout = StdioServerParameters & {
108
+ timeout?: number;
109
+ };
103
110
 
104
111
  export { }
package/dist/index.cjs CHANGED
@@ -53,7 +53,9 @@ var MastraMCPClient = class extends base.MastraBase {
53
53
  async connect() {
54
54
  if (this.isConnected) return;
55
55
  try {
56
- await this.client.connect(this.transport);
56
+ await this.client.connect(this.transport, {
57
+ timeout: this.timeout
58
+ });
57
59
  this.isConnected = true;
58
60
  const originalOnClose = this.client.onclose;
59
61
  this.client.onclose = () => {
@@ -84,10 +86,12 @@ ${e instanceof Error ? e.stack : JSON.stringify(e, null, 2)}`
84
86
  }
85
87
  // TODO: do the type magic to return the right method type. Right now we get infinitely deep infered type errors from Zod without using "any"
86
88
  async resources() {
87
- return await this.client.request({ method: "resources/list" }, types_js.ListResourcesResultSchema);
89
+ return await this.client.request({ method: "resources/list" }, types_js.ListResourcesResultSchema, {
90
+ timeout: this.timeout
91
+ });
88
92
  }
89
93
  async tools() {
90
- const { tools: tools$1 } = await this.client.listTools();
94
+ const { tools: tools$1 } = await this.client.listTools({ timeout: this.timeout });
91
95
  const toolsRes = {};
92
96
  tools$1.forEach((tool) => {
93
97
  const s = utils.jsonSchemaToModel(tool.inputSchema);
@@ -126,8 +130,10 @@ var mastraMCPConfigurationInstances = /* @__PURE__ */ new Map();
126
130
  var MCPConfiguration = class extends base.MastraBase {
127
131
  serverConfigs = {};
128
132
  id;
133
+ defaultTimeout;
129
134
  constructor(args) {
130
135
  super({ name: "MCPConfiguration" });
136
+ this.defaultTimeout = args.timeout ?? protocol_js.DEFAULT_REQUEST_TIMEOUT_MSEC;
131
137
  this.serverConfigs = args.servers;
132
138
  this.id = args.id ?? this.makeId();
133
139
  const existingInstance = mastraMCPConfigurationInstances.get(this.id);
@@ -198,7 +204,8 @@ To fix this you have three different options:
198
204
  this.logger.debug(`Connecting to ${name} MCP server`);
199
205
  const mcpClient = new MastraMCPClient({
200
206
  name,
201
- server: config
207
+ server: config,
208
+ timeout: config.timeout ?? this.defaultTimeout
202
209
  });
203
210
  this.mcpClientsById.set(name, mcpClient);
204
211
  try {
package/dist/index.js CHANGED
@@ -51,7 +51,9 @@ var MastraMCPClient = class extends MastraBase {
51
51
  async connect() {
52
52
  if (this.isConnected) return;
53
53
  try {
54
- await this.client.connect(this.transport);
54
+ await this.client.connect(this.transport, {
55
+ timeout: this.timeout
56
+ });
55
57
  this.isConnected = true;
56
58
  const originalOnClose = this.client.onclose;
57
59
  this.client.onclose = () => {
@@ -82,10 +84,12 @@ ${e instanceof Error ? e.stack : JSON.stringify(e, null, 2)}`
82
84
  }
83
85
  // TODO: do the type magic to return the right method type. Right now we get infinitely deep infered type errors from Zod without using "any"
84
86
  async resources() {
85
- return await this.client.request({ method: "resources/list" }, ListResourcesResultSchema);
87
+ return await this.client.request({ method: "resources/list" }, ListResourcesResultSchema, {
88
+ timeout: this.timeout
89
+ });
86
90
  }
87
91
  async tools() {
88
- const { tools } = await this.client.listTools();
92
+ const { tools } = await this.client.listTools({ timeout: this.timeout });
89
93
  const toolsRes = {};
90
94
  tools.forEach((tool) => {
91
95
  const s = jsonSchemaToModel(tool.inputSchema);
@@ -124,8 +128,10 @@ var mastraMCPConfigurationInstances = /* @__PURE__ */ new Map();
124
128
  var MCPConfiguration = class extends MastraBase {
125
129
  serverConfigs = {};
126
130
  id;
131
+ defaultTimeout;
127
132
  constructor(args) {
128
133
  super({ name: "MCPConfiguration" });
134
+ this.defaultTimeout = args.timeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC;
129
135
  this.serverConfigs = args.servers;
130
136
  this.id = args.id ?? this.makeId();
131
137
  const existingInstance = mastraMCPConfigurationInstances.get(this.id);
@@ -196,7 +202,8 @@ To fix this you have three different options:
196
202
  this.logger.debug(`Connecting to ${name} MCP server`);
197
203
  const mcpClient = new MastraMCPClient({
198
204
  name,
199
- server: config
205
+ server: config,
206
+ timeout: config.timeout ?? this.defaultTimeout
200
207
  });
201
208
  this.mcpClientsById.set(name, mcpClient);
202
209
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/mcp",
3
- "version": "0.3.10-alpha.0",
3
+ "version": "0.3.10-alpha.2",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -20,13 +20,13 @@
20
20
  },
21
21
  "keywords": [],
22
22
  "author": "",
23
- "license": "ISC",
23
+ "license": "Elastic-2.0",
24
24
  "dependencies": {
25
- "@modelcontextprotocol/sdk": "^1.7.0",
25
+ "@modelcontextprotocol/sdk": "^1.9.0",
26
26
  "date-fns": "^4.1.0",
27
27
  "exit-hook": "^4.0.0",
28
28
  "uuid": "^11.1.0",
29
- "@mastra/core": "^0.8.3-alpha.0"
29
+ "@mastra/core": "^0.8.3-alpha.2"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@ai-sdk/anthropic": "^1.1.15",
@@ -38,7 +38,7 @@
38
38
  "vitest": "^3.0.9",
39
39
  "zod": "^3.24.2",
40
40
  "zod-to-json-schema": "^3.22.4",
41
- "@internal/lint": "0.0.1"
41
+ "@internal/lint": "0.0.2-alpha.0"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting",
@@ -1,5 +1,7 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http';
2
+ import { createServer } from 'http';
1
3
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
3
5
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
6
  import { z } from 'zod';
5
7
  import { zodToJsonSchema } from 'zod-to-json-schema';
@@ -127,7 +129,57 @@ server.setRequestHandler(CallToolRequestSchema, async request => {
127
129
  });
128
130
 
129
131
  // Start the server
130
- const transport = new StdioServerTransport();
131
- await server.connect(transport);
132
+ let transport: SSEServerTransport | undefined;
133
+
134
+ const httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {
135
+ const url = new URL(req.url || '', `http://${req.headers.host}`);
136
+
137
+ if (url.pathname === '/sse') {
138
+ console.log('Received SSE connection');
139
+ transport = new SSEServerTransport('/message', res);
140
+ await server.connect(transport);
141
+
142
+ server.onclose = async () => {
143
+ await server.close();
144
+ transport = undefined;
145
+ };
146
+
147
+ // Handle client disconnection
148
+ res.on('close', () => {
149
+ transport = undefined;
150
+ });
151
+ } else if (url.pathname === '/message') {
152
+ console.log('Received message');
153
+ if (!transport) {
154
+ res.writeHead(503);
155
+ res.end('SSE connection not established');
156
+ return;
157
+ }
158
+ await transport.handlePostMessage(req, res);
159
+ } else {
160
+ console.log('Unknown path:', url.pathname);
161
+ res.writeHead(404);
162
+ res.end();
163
+ }
164
+ });
165
+
166
+ const PORT = process.env.PORT || 60808;
167
+ httpServer.listen(PORT, () => {
168
+ console.log(`Weather server is running on SSE at http://localhost:${PORT}`);
169
+ });
170
+
171
+ // Handle graceful shutdown
172
+ process.on('SIGINT', async () => {
173
+ console.log('Shutting down weather server...');
174
+ if (transport) {
175
+ await server.close();
176
+ transport = undefined;
177
+ }
178
+ // Close the HTTP server
179
+ httpServer.close(() => {
180
+ console.log('Weather server shut down complete');
181
+ process.exit(0);
182
+ });
183
+ });
132
184
 
133
185
  export { server };
package/src/client.ts CHANGED
@@ -3,6 +3,7 @@ import { createTool } from '@mastra/core/tools';
3
3
  import { jsonSchemaToModel } from '@mastra/core/utils';
4
4
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
5
5
  import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
6
+ import type { SSEClientTransportOptions } from '@modelcontextprotocol/sdk/client/sse.js';
6
7
  import { getDefaultEnvironment, StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
7
8
  import type { StdioServerParameters } from '@modelcontextprotocol/sdk/client/stdio.js';
8
9
  import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
@@ -13,11 +14,17 @@ import { CallToolResultSchema, ListResourcesResultSchema } from '@modelcontextpr
13
14
 
14
15
  import { asyncExitHook, gracefulExit } from 'exit-hook';
15
16
 
17
+ // Omit the fields we want to control from the SDK options
16
18
  type SSEClientParameters = {
17
19
  url: URL;
18
- } & ConstructorParameters<typeof SSEClientTransport>[1];
20
+ timeout?: number;
21
+ } & SSEClientTransportOptions;
19
22
 
20
- export type MastraMCPServerDefinition = StdioServerParameters | SSEClientParameters;
23
+ type StdioServerParametersWithTimeout = StdioServerParameters & {
24
+ timeout?: number;
25
+ };
26
+
27
+ export type MastraMCPServerDefinition = StdioServerParametersWithTimeout | SSEClientParameters;
21
28
 
22
29
  export class MastraMCPClient extends MastraBase {
23
30
  name: string;
@@ -70,7 +77,9 @@ export class MastraMCPClient extends MastraBase {
70
77
  async connect() {
71
78
  if (this.isConnected) return;
72
79
  try {
73
- await this.client.connect(this.transport);
80
+ await this.client.connect(this.transport, {
81
+ timeout: this.timeout,
82
+ });
74
83
  this.isConnected = true;
75
84
  const originalOnClose = this.client.onclose;
76
85
  this.client.onclose = () => {
@@ -104,11 +113,13 @@ export class MastraMCPClient extends MastraBase {
104
113
  // TODO: do the type magic to return the right method type. Right now we get infinitely deep infered type errors from Zod without using "any"
105
114
 
106
115
  async resources(): Promise<ReturnType<Protocol<any, any, any>['request']>> {
107
- return await this.client.request({ method: 'resources/list' }, ListResourcesResultSchema);
116
+ return await this.client.request({ method: 'resources/list' }, ListResourcesResultSchema, {
117
+ timeout: this.timeout,
118
+ });
108
119
  }
109
120
 
110
121
  async tools() {
111
- const { tools } = await this.client.listTools();
122
+ const { tools } = await this.client.listTools({ timeout: this.timeout });
112
123
  const toolsRes: Record<string, any> = {};
113
124
  tools.forEach(tool => {
114
125
  const s = jsonSchemaToModel(tool.inputSchema);
@@ -1,8 +1,10 @@
1
1
  import { spawn } from 'child_process';
2
2
  import path from 'path';
3
- import { describe, it, expect, beforeEach, afterEach, afterAll, beforeAll } from 'vitest';
3
+ import { describe, it, expect, beforeEach, afterEach, afterAll, beforeAll, vi } from 'vitest';
4
4
  import { MCPConfiguration } from './configuration';
5
5
 
6
+ vi.setConfig({ testTimeout: 80000, hookTimeout: 80000 });
7
+
6
8
  describe('MCPConfiguration', () => {
7
9
  let mcp: MCPConfiguration;
8
10
  let weatherProcess: ReturnType<typeof spawn>;
@@ -178,4 +180,132 @@ describe('MCPConfiguration', () => {
178
180
  await existingConfig.disconnect();
179
181
  });
180
182
  });
183
+ describe('MCPConfiguration Operation Timeouts', () => {
184
+ it('should respect custom timeout in configuration', async () => {
185
+ const config = new MCPConfiguration({
186
+ id: 'test-timeout-config',
187
+ timeout: 3000, // 3 second timeout
188
+ servers: {
189
+ test: {
190
+ command: 'node',
191
+ args: [
192
+ '-e',
193
+ `
194
+ const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
195
+ const server = new Server({ name: 'test', version: '1.0.0' });
196
+ setTimeout(() => process.exit(0), 2000); // 2 second delay
197
+ `,
198
+ ],
199
+ },
200
+ },
201
+ });
202
+
203
+ const error = await config.getTools().catch(e => e);
204
+ expect(error).toBeDefined(); // Will throw since server exits before responding
205
+ expect(error.message).not.toMatch(/Request timed out/);
206
+
207
+ await config.disconnect();
208
+ });
209
+
210
+ it('should respect per-server timeout override', async () => {
211
+ const config = new MCPConfiguration({
212
+ id: 'test-server-timeout-config',
213
+ timeout: 500, // Global timeout of 500ms
214
+ servers: {
215
+ test: {
216
+ command: 'node',
217
+ args: [
218
+ '-e',
219
+ `
220
+ const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
221
+ const server = new Server({ name: 'test', version: '1.0.0' });
222
+ setTimeout(() => process.exit(0), 2000); // 2 second delay
223
+ `,
224
+ ],
225
+ timeout: 3000, // Server-specific timeout of 3s
226
+ },
227
+ },
228
+ });
229
+
230
+ // This should succeed since server timeout (3s) is longer than delay (2s)
231
+ const error = await config.getTools().catch(e => e);
232
+ expect(error).toBeDefined(); // Will throw since server exits before responding
233
+ expect(error.message).not.toMatch(/Request timed out/);
234
+
235
+ await config.disconnect();
236
+ });
237
+ });
238
+
239
+ describe('MCPConfiguration Connection Timeout', () => {
240
+ it('should throw timeout error for slow starting server', async () => {
241
+ const slowConfig = new MCPConfiguration({
242
+ id: 'test-slow-server',
243
+ servers: {
244
+ slowServer: {
245
+ command: 'node',
246
+ args: ['-e', 'setTimeout(() => process.exit(0), 65000)'], // Simulate a server that takes 65 seconds to start
247
+ },
248
+ },
249
+ });
250
+
251
+ await expect(slowConfig.getTools()).rejects.toThrow(/Request timed out/);
252
+ await slowConfig.disconnect();
253
+ });
254
+
255
+ it('timeout should be longer than default timeout', async () => {
256
+ const slowConfig = new MCPConfiguration({
257
+ id: 'test-slow-server',
258
+ timeout: 70000,
259
+ servers: {
260
+ slowServer: {
261
+ command: 'node',
262
+ args: ['-e', 'setTimeout(() => process.exit(0), 65000)'], // Simulate a server that takes 65 seconds to start
263
+ },
264
+ },
265
+ });
266
+
267
+ const error = await slowConfig.getTools().catch(e => e);
268
+ expect(error).toBeDefined();
269
+ expect(error.message).not.toMatch(/Request timed out/);
270
+ await slowConfig.disconnect();
271
+ });
272
+
273
+ it('should respect custom timeout configuration', async () => {
274
+ const quickConfig = new MCPConfiguration({
275
+ id: 'test-quick-timeout',
276
+ timeout: 1000, // Very short global timeout
277
+ servers: {
278
+ slowServer: {
279
+ command: 'node',
280
+ args: ['-e', 'setTimeout(() => process.exit(0), 30000)'], // Takes 30 seconds to exit
281
+ },
282
+ },
283
+ });
284
+
285
+ await expect(quickConfig.getTools()).rejects.toThrow(/Request timed out/);
286
+ await quickConfig.disconnect();
287
+ });
288
+
289
+ it('should respect per-server timeout configuration', async () => {
290
+ const mixedConfig = new MCPConfiguration({
291
+ id: 'test-mixed-timeout',
292
+ timeout: 1000, // Short global timeout
293
+ servers: {
294
+ quickServer: {
295
+ command: 'node',
296
+ args: ['-e', 'setTimeout(() => process.exit(0), 2000)'], // Takes 2 seconds to exit
297
+ },
298
+ slowServer: {
299
+ command: 'node',
300
+ args: ['-e', 'setTimeout(() => process.exit(0), 2000)'], // Takes 2 seconds to exit
301
+ timeout: 3000, // But has a longer timeout
302
+ },
303
+ },
304
+ });
305
+
306
+ // Quick server should timeout
307
+ await expect(mixedConfig.getTools()).rejects.toThrow(/Request timed out/);
308
+ await mixedConfig.disconnect();
309
+ });
310
+ });
181
311
  });
@@ -1,4 +1,5 @@
1
1
  import { MastraBase } from '@mastra/core/base';
2
+ import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
2
3
  import { v5 as uuidv5 } from 'uuid';
3
4
  import { MastraMCPClient } from './client';
4
5
  import type { MastraMCPServerDefinition } from './client';
@@ -8,9 +9,15 @@ const mastraMCPConfigurationInstances = new Map<string, InstanceType<typeof MCPC
8
9
  export class MCPConfiguration extends MastraBase {
9
10
  private serverConfigs: Record<string, MastraMCPServerDefinition> = {};
10
11
  private id: string;
12
+ private defaultTimeout: number;
11
13
 
12
- constructor(args: { id?: string; servers: Record<string, MastraMCPServerDefinition> }) {
14
+ constructor(args: {
15
+ id?: string;
16
+ servers: Record<string, MastraMCPServerDefinition>;
17
+ timeout?: number; // Optional global timeout
18
+ }) {
13
19
  super({ name: 'MCPConfiguration' });
20
+ this.defaultTimeout = args.timeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC;
14
21
  this.serverConfigs = args.servers;
15
22
  this.id = args.id ?? this.makeId();
16
23
 
@@ -100,6 +107,7 @@ To fix this you have three different options:
100
107
  const mcpClient = new MastraMCPClient({
101
108
  name,
102
109
  server: config,
110
+ timeout: config.timeout ?? this.defaultTimeout,
103
111
  });
104
112
 
105
113
  this.mcpClientsById.set(name, mcpClient);