@mastra/mcp 0.4.1-alpha.2 → 0.4.1-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -6,6 +6,7 @@ var utils = require('@mastra/core/utils');
6
6
  var index_js = require('@modelcontextprotocol/sdk/client/index.js');
7
7
  var sse_js = require('@modelcontextprotocol/sdk/client/sse.js');
8
8
  var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
9
+ var streamableHttp_js = require('@modelcontextprotocol/sdk/client/streamableHttp.js');
9
10
  var protocol_js = require('@modelcontextprotocol/sdk/shared/protocol.js');
10
11
  var types_js = require('@modelcontextprotocol/sdk/types.js');
11
12
  var exitHook = require('exit-hook');
@@ -4119,11 +4120,12 @@ function convertLogLevelToLoggerMethod(level) {
4119
4120
  }
4120
4121
  var MastraMCPClient = class extends base.MastraBase {
4121
4122
  name;
4122
- transport;
4123
4123
  client;
4124
4124
  timeout;
4125
4125
  logHandler;
4126
4126
  enableServerLogs;
4127
+ serverConfig;
4128
+ transport;
4127
4129
  constructor({
4128
4130
  name,
4129
4131
  version = "1.0.0",
@@ -4136,19 +4138,7 @@ var MastraMCPClient = class extends base.MastraBase {
4136
4138
  this.timeout = timeout;
4137
4139
  this.logHandler = server.logger;
4138
4140
  this.enableServerLogs = server.enableServerLogs ?? true;
4139
- const { logger: logger3, enableServerLogs, ...serverConfig } = server;
4140
- if (`url` in serverConfig) {
4141
- this.transport = new sse_js.SSEClientTransport(serverConfig.url, {
4142
- requestInit: serverConfig.requestInit,
4143
- eventSourceInit: serverConfig.eventSourceInit
4144
- });
4145
- } else {
4146
- this.transport = new stdio_js.StdioClientTransport({
4147
- ...serverConfig,
4148
- // without ...getDefaultEnvironment() commands like npx will fail because there will be no PATH env var
4149
- env: { ...stdio_js.getDefaultEnvironment(), ...serverConfig.env || {} }
4150
- });
4151
- }
4141
+ this.serverConfig = server;
4152
4142
  this.client = new index_js.Client(
4153
4143
  {
4154
4144
  name,
@@ -4168,11 +4158,12 @@ var MastraMCPClient = class extends base.MastraBase {
4168
4158
  */
4169
4159
  log(level, message, details) {
4170
4160
  const loggerMethod = convertLogLevelToLoggerMethod(level);
4171
- this.logger[loggerMethod](message, details);
4161
+ const msg = `[${this.name}] ${message}`;
4162
+ this.logger[loggerMethod](msg, details);
4172
4163
  if (this.logHandler) {
4173
4164
  this.logHandler({
4174
4165
  level,
4175
- message,
4166
+ message: msg,
4176
4167
  timestamp: /* @__PURE__ */ new Date(),
4177
4168
  serverName: this.name,
4178
4169
  details
@@ -4195,44 +4186,121 @@ var MastraMCPClient = class extends base.MastraBase {
4195
4186
  );
4196
4187
  }
4197
4188
  }
4189
+ async connectStdio(command) {
4190
+ this.log("debug", `Using Stdio transport for command: ${command}`);
4191
+ try {
4192
+ this.transport = new stdio_js.StdioClientTransport({
4193
+ command,
4194
+ args: this.serverConfig.args,
4195
+ env: { ...stdio_js.getDefaultEnvironment(), ...this.serverConfig.env || {} }
4196
+ });
4197
+ await this.client.connect(this.transport, { timeout: this.serverConfig.timeout ?? this.timeout });
4198
+ this.log("debug", `Successfully connected to MCP server via Stdio`);
4199
+ } catch (e) {
4200
+ this.log("error", e instanceof Error ? e.stack || e.message : JSON.stringify(e));
4201
+ throw e;
4202
+ }
4203
+ }
4204
+ async connectHttp(url) {
4205
+ const { requestInit, eventSourceInit } = this.serverConfig;
4206
+ this.log("debug", `Attempting to connect to URL: ${url}`);
4207
+ let shouldTrySSE = url.pathname.endsWith(`/sse`);
4208
+ if (!shouldTrySSE) {
4209
+ try {
4210
+ this.log("debug", "Trying Streamable HTTP transport...");
4211
+ const streamableTransport = new streamableHttp_js.StreamableHTTPClientTransport(url, {
4212
+ requestInit,
4213
+ reconnectionOptions: this.serverConfig.reconnectionOptions,
4214
+ sessionId: this.serverConfig.sessionId
4215
+ });
4216
+ await this.client.connect(streamableTransport, {
4217
+ timeout: (
4218
+ // this is hardcoded to 3s because the long default timeout would be extremely slow for sse backwards compat (60s)
4219
+ 3e3
4220
+ )
4221
+ });
4222
+ this.transport = streamableTransport;
4223
+ this.log("debug", "Successfully connected using Streamable HTTP transport.");
4224
+ } catch (error) {
4225
+ this.log("debug", `Streamable HTTP transport failed: ${error}`);
4226
+ shouldTrySSE = true;
4227
+ }
4228
+ }
4229
+ if (shouldTrySSE) {
4230
+ this.log("debug", "Falling back to deprecated HTTP+SSE transport...");
4231
+ try {
4232
+ const sseTransport = new sse_js.SSEClientTransport(url, { requestInit, eventSourceInit });
4233
+ await this.client.connect(sseTransport, { timeout: this.serverConfig.timeout ?? this.timeout });
4234
+ this.transport = sseTransport;
4235
+ this.log("debug", "Successfully connected using deprecated HTTP+SSE transport.");
4236
+ } catch (sseError) {
4237
+ this.log(
4238
+ "error",
4239
+ `Failed to connect with SSE transport after failing to connect to Streamable HTTP transport first. SSE error: ${sseError}`
4240
+ );
4241
+ throw new Error("Could not connect to server with any available HTTP transport");
4242
+ }
4243
+ }
4244
+ }
4198
4245
  isConnected = false;
4199
4246
  async connect() {
4200
4247
  if (this.isConnected) return;
4248
+ const { command, url } = this.serverConfig;
4249
+ if (command) {
4250
+ await this.connectStdio(command);
4251
+ } else if (url) {
4252
+ await this.connectHttp(url);
4253
+ } else {
4254
+ throw new Error("Server configuration must include either a command or a url.");
4255
+ }
4256
+ this.isConnected = true;
4257
+ const originalOnClose = this.client.onclose;
4258
+ this.client.onclose = () => {
4259
+ this.log("debug", `MCP server connection closed`);
4260
+ this.isConnected = false;
4261
+ if (typeof originalOnClose === `function`) {
4262
+ originalOnClose();
4263
+ }
4264
+ };
4265
+ exitHook.asyncExitHook(
4266
+ async () => {
4267
+ this.log("debug", `Disconnecting MCP server during exit`);
4268
+ await this.disconnect();
4269
+ },
4270
+ { wait: 5e3 }
4271
+ );
4272
+ process.on("SIGTERM", () => exitHook.gracefulExit());
4273
+ this.log("debug", `Successfully connected to MCP server`);
4274
+ }
4275
+ /**
4276
+ * Get the current session ID if using the Streamable HTTP transport.
4277
+ * Returns undefined if not connected or not using Streamable HTTP.
4278
+ */
4279
+ get sessionId() {
4280
+ if (this.transport instanceof streamableHttp_js.StreamableHTTPClientTransport) {
4281
+ return this.transport.sessionId;
4282
+ }
4283
+ return void 0;
4284
+ }
4285
+ async disconnect() {
4286
+ if (!this.transport) {
4287
+ this.log("debug", "Disconnect called but no transport was connected.");
4288
+ return;
4289
+ }
4290
+ this.log("debug", `Disconnecting from MCP server`);
4201
4291
  try {
4202
- this.log("debug", `Connecting to MCP server`);
4203
- await this.client.connect(this.transport, {
4204
- timeout: this.timeout
4205
- });
4206
- this.isConnected = true;
4207
- const originalOnClose = this.client.onclose;
4208
- this.client.onclose = () => {
4209
- this.log("debug", `MCP server connection closed`);
4210
- this.isConnected = false;
4211
- if (typeof originalOnClose === `function`) {
4212
- originalOnClose();
4213
- }
4214
- };
4215
- exitHook.asyncExitHook(
4216
- async () => {
4217
- this.log("debug", `Disconnecting MCP server during exit`);
4218
- await this.disconnect();
4219
- },
4220
- { wait: 5e3 }
4221
- );
4222
- process.on("SIGTERM", () => exitHook.gracefulExit());
4223
- this.log("info", `Successfully connected to MCP server`);
4292
+ await this.transport.close();
4293
+ this.log("debug", "Successfully disconnected from MCP server");
4224
4294
  } catch (e) {
4225
- this.log("error", `Failed connecting to MCP server`, {
4295
+ this.log("error", "Error during MCP server disconnect", {
4226
4296
  error: e instanceof Error ? e.stack : JSON.stringify(e, null, 2)
4227
4297
  });
4228
- this.isConnected = false;
4229
4298
  throw e;
4299
+ } finally {
4300
+ this.transport = void 0;
4301
+ this.isConnected = false;
4230
4302
  }
4231
4303
  }
4232
- async disconnect() {
4233
- this.log("debug", `Disconnecting from MCP server`);
4234
- return await this.client.close();
4235
- }
4236
4304
  // 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"
4237
4305
  async resources() {
4238
4306
  this.log("debug", `Requesting resources from MCP server`);
@@ -4345,6 +4413,19 @@ To fix this you have three different options:
4345
4413
  });
4346
4414
  return connectedToolsets;
4347
4415
  }
4416
+ /**
4417
+ * Get the current session IDs for all connected MCP clients using the Streamable HTTP transport.
4418
+ * Returns an object mapping server names to their session IDs.
4419
+ */
4420
+ get sessionIds() {
4421
+ const sessionIds = {};
4422
+ for (const [serverName, client] of this.mcpClientsById.entries()) {
4423
+ if (client.sessionId) {
4424
+ sessionIds[serverName] = client.sessionId;
4425
+ }
4426
+ }
4427
+ return sessionIds;
4428
+ }
4348
4429
  mcpClientsById = /* @__PURE__ */ new Map();
4349
4430
  async getConnectedClient(name, config) {
4350
4431
  const exists = this.mcpClientsById.has(name);
@@ -4367,7 +4448,9 @@ To fix this you have three different options:
4367
4448
  this.logger.error(`MCPConfiguration errored connecting to MCP server ${name}`, {
4368
4449
  error: e instanceof Error ? e.message : String(e)
4369
4450
  });
4370
- throw new Error(`Failed to connect to MCP server ${name}: ${e instanceof Error ? e.message : String(e)}`);
4451
+ throw new Error(
4452
+ `Failed to connect to MCP server ${name}: ${e instanceof Error ? e.stack || e.message : String(e)}`
4453
+ );
4371
4454
  }
4372
4455
  this.logger.debug(`Connected to ${name} MCP server`);
4373
4456
  return mcpClient;
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ 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
6
  import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/sdk/client/stdio.js';
7
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
7
8
  import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
8
9
  import { ListResourcesResultSchema, CallToolResultSchema, ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
9
10
  import { asyncExitHook, gracefulExit } from 'exit-hook';
@@ -4095,11 +4096,12 @@ function convertLogLevelToLoggerMethod(level) {
4095
4096
  }
4096
4097
  var MastraMCPClient = class extends MastraBase {
4097
4098
  name;
4098
- transport;
4099
4099
  client;
4100
4100
  timeout;
4101
4101
  logHandler;
4102
4102
  enableServerLogs;
4103
+ serverConfig;
4104
+ transport;
4103
4105
  constructor({
4104
4106
  name,
4105
4107
  version = "1.0.0",
@@ -4112,19 +4114,7 @@ var MastraMCPClient = class extends MastraBase {
4112
4114
  this.timeout = timeout;
4113
4115
  this.logHandler = server.logger;
4114
4116
  this.enableServerLogs = server.enableServerLogs ?? true;
4115
- const { logger: logger3, enableServerLogs, ...serverConfig } = server;
4116
- if (`url` in serverConfig) {
4117
- this.transport = new SSEClientTransport(serverConfig.url, {
4118
- requestInit: serverConfig.requestInit,
4119
- eventSourceInit: serverConfig.eventSourceInit
4120
- });
4121
- } else {
4122
- this.transport = new StdioClientTransport({
4123
- ...serverConfig,
4124
- // without ...getDefaultEnvironment() commands like npx will fail because there will be no PATH env var
4125
- env: { ...getDefaultEnvironment(), ...serverConfig.env || {} }
4126
- });
4127
- }
4117
+ this.serverConfig = server;
4128
4118
  this.client = new Client(
4129
4119
  {
4130
4120
  name,
@@ -4144,11 +4134,12 @@ var MastraMCPClient = class extends MastraBase {
4144
4134
  */
4145
4135
  log(level, message, details) {
4146
4136
  const loggerMethod = convertLogLevelToLoggerMethod(level);
4147
- this.logger[loggerMethod](message, details);
4137
+ const msg = `[${this.name}] ${message}`;
4138
+ this.logger[loggerMethod](msg, details);
4148
4139
  if (this.logHandler) {
4149
4140
  this.logHandler({
4150
4141
  level,
4151
- message,
4142
+ message: msg,
4152
4143
  timestamp: /* @__PURE__ */ new Date(),
4153
4144
  serverName: this.name,
4154
4145
  details
@@ -4171,44 +4162,121 @@ var MastraMCPClient = class extends MastraBase {
4171
4162
  );
4172
4163
  }
4173
4164
  }
4165
+ async connectStdio(command) {
4166
+ this.log("debug", `Using Stdio transport for command: ${command}`);
4167
+ try {
4168
+ this.transport = new StdioClientTransport({
4169
+ command,
4170
+ args: this.serverConfig.args,
4171
+ env: { ...getDefaultEnvironment(), ...this.serverConfig.env || {} }
4172
+ });
4173
+ await this.client.connect(this.transport, { timeout: this.serverConfig.timeout ?? this.timeout });
4174
+ this.log("debug", `Successfully connected to MCP server via Stdio`);
4175
+ } catch (e) {
4176
+ this.log("error", e instanceof Error ? e.stack || e.message : JSON.stringify(e));
4177
+ throw e;
4178
+ }
4179
+ }
4180
+ async connectHttp(url) {
4181
+ const { requestInit, eventSourceInit } = this.serverConfig;
4182
+ this.log("debug", `Attempting to connect to URL: ${url}`);
4183
+ let shouldTrySSE = url.pathname.endsWith(`/sse`);
4184
+ if (!shouldTrySSE) {
4185
+ try {
4186
+ this.log("debug", "Trying Streamable HTTP transport...");
4187
+ const streamableTransport = new StreamableHTTPClientTransport(url, {
4188
+ requestInit,
4189
+ reconnectionOptions: this.serverConfig.reconnectionOptions,
4190
+ sessionId: this.serverConfig.sessionId
4191
+ });
4192
+ await this.client.connect(streamableTransport, {
4193
+ timeout: (
4194
+ // this is hardcoded to 3s because the long default timeout would be extremely slow for sse backwards compat (60s)
4195
+ 3e3
4196
+ )
4197
+ });
4198
+ this.transport = streamableTransport;
4199
+ this.log("debug", "Successfully connected using Streamable HTTP transport.");
4200
+ } catch (error) {
4201
+ this.log("debug", `Streamable HTTP transport failed: ${error}`);
4202
+ shouldTrySSE = true;
4203
+ }
4204
+ }
4205
+ if (shouldTrySSE) {
4206
+ this.log("debug", "Falling back to deprecated HTTP+SSE transport...");
4207
+ try {
4208
+ const sseTransport = new SSEClientTransport(url, { requestInit, eventSourceInit });
4209
+ await this.client.connect(sseTransport, { timeout: this.serverConfig.timeout ?? this.timeout });
4210
+ this.transport = sseTransport;
4211
+ this.log("debug", "Successfully connected using deprecated HTTP+SSE transport.");
4212
+ } catch (sseError) {
4213
+ this.log(
4214
+ "error",
4215
+ `Failed to connect with SSE transport after failing to connect to Streamable HTTP transport first. SSE error: ${sseError}`
4216
+ );
4217
+ throw new Error("Could not connect to server with any available HTTP transport");
4218
+ }
4219
+ }
4220
+ }
4174
4221
  isConnected = false;
4175
4222
  async connect() {
4176
4223
  if (this.isConnected) return;
4224
+ const { command, url } = this.serverConfig;
4225
+ if (command) {
4226
+ await this.connectStdio(command);
4227
+ } else if (url) {
4228
+ await this.connectHttp(url);
4229
+ } else {
4230
+ throw new Error("Server configuration must include either a command or a url.");
4231
+ }
4232
+ this.isConnected = true;
4233
+ const originalOnClose = this.client.onclose;
4234
+ this.client.onclose = () => {
4235
+ this.log("debug", `MCP server connection closed`);
4236
+ this.isConnected = false;
4237
+ if (typeof originalOnClose === `function`) {
4238
+ originalOnClose();
4239
+ }
4240
+ };
4241
+ asyncExitHook(
4242
+ async () => {
4243
+ this.log("debug", `Disconnecting MCP server during exit`);
4244
+ await this.disconnect();
4245
+ },
4246
+ { wait: 5e3 }
4247
+ );
4248
+ process.on("SIGTERM", () => gracefulExit());
4249
+ this.log("debug", `Successfully connected to MCP server`);
4250
+ }
4251
+ /**
4252
+ * Get the current session ID if using the Streamable HTTP transport.
4253
+ * Returns undefined if not connected or not using Streamable HTTP.
4254
+ */
4255
+ get sessionId() {
4256
+ if (this.transport instanceof StreamableHTTPClientTransport) {
4257
+ return this.transport.sessionId;
4258
+ }
4259
+ return void 0;
4260
+ }
4261
+ async disconnect() {
4262
+ if (!this.transport) {
4263
+ this.log("debug", "Disconnect called but no transport was connected.");
4264
+ return;
4265
+ }
4266
+ this.log("debug", `Disconnecting from MCP server`);
4177
4267
  try {
4178
- this.log("debug", `Connecting to MCP server`);
4179
- await this.client.connect(this.transport, {
4180
- timeout: this.timeout
4181
- });
4182
- this.isConnected = true;
4183
- const originalOnClose = this.client.onclose;
4184
- this.client.onclose = () => {
4185
- this.log("debug", `MCP server connection closed`);
4186
- this.isConnected = false;
4187
- if (typeof originalOnClose === `function`) {
4188
- originalOnClose();
4189
- }
4190
- };
4191
- asyncExitHook(
4192
- async () => {
4193
- this.log("debug", `Disconnecting MCP server during exit`);
4194
- await this.disconnect();
4195
- },
4196
- { wait: 5e3 }
4197
- );
4198
- process.on("SIGTERM", () => gracefulExit());
4199
- this.log("info", `Successfully connected to MCP server`);
4268
+ await this.transport.close();
4269
+ this.log("debug", "Successfully disconnected from MCP server");
4200
4270
  } catch (e) {
4201
- this.log("error", `Failed connecting to MCP server`, {
4271
+ this.log("error", "Error during MCP server disconnect", {
4202
4272
  error: e instanceof Error ? e.stack : JSON.stringify(e, null, 2)
4203
4273
  });
4204
- this.isConnected = false;
4205
4274
  throw e;
4275
+ } finally {
4276
+ this.transport = void 0;
4277
+ this.isConnected = false;
4206
4278
  }
4207
4279
  }
4208
- async disconnect() {
4209
- this.log("debug", `Disconnecting from MCP server`);
4210
- return await this.client.close();
4211
- }
4212
4280
  // 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"
4213
4281
  async resources() {
4214
4282
  this.log("debug", `Requesting resources from MCP server`);
@@ -4321,6 +4389,19 @@ To fix this you have three different options:
4321
4389
  });
4322
4390
  return connectedToolsets;
4323
4391
  }
4392
+ /**
4393
+ * Get the current session IDs for all connected MCP clients using the Streamable HTTP transport.
4394
+ * Returns an object mapping server names to their session IDs.
4395
+ */
4396
+ get sessionIds() {
4397
+ const sessionIds = {};
4398
+ for (const [serverName, client] of this.mcpClientsById.entries()) {
4399
+ if (client.sessionId) {
4400
+ sessionIds[serverName] = client.sessionId;
4401
+ }
4402
+ }
4403
+ return sessionIds;
4404
+ }
4324
4405
  mcpClientsById = /* @__PURE__ */ new Map();
4325
4406
  async getConnectedClient(name, config) {
4326
4407
  const exists = this.mcpClientsById.has(name);
@@ -4343,7 +4424,9 @@ To fix this you have three different options:
4343
4424
  this.logger.error(`MCPConfiguration errored connecting to MCP server ${name}`, {
4344
4425
  error: e instanceof Error ? e.message : String(e)
4345
4426
  });
4346
- throw new Error(`Failed to connect to MCP server ${name}: ${e instanceof Error ? e.message : String(e)}`);
4427
+ throw new Error(
4428
+ `Failed to connect to MCP server ${name}: ${e instanceof Error ? e.stack || e.message : String(e)}`
4429
+ );
4347
4430
  }
4348
4431
  this.logger.debug(`Connected to ${name} MCP server`);
4349
4432
  return mcpClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/mcp",
3
- "version": "0.4.1-alpha.2",
3
+ "version": "0.4.1-alpha.3",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,11 +22,11 @@
22
22
  "author": "",
23
23
  "license": "Elastic-2.0",
24
24
  "dependencies": {
25
- "@modelcontextprotocol/sdk": "^1.9.0",
25
+ "@modelcontextprotocol/sdk": "^1.10.2",
26
26
  "date-fns": "^4.1.0",
27
27
  "exit-hook": "^4.0.0",
28
28
  "uuid": "^11.1.0",
29
- "@mastra/core": "^0.9.1-alpha.2"
29
+ "@mastra/core": "^0.9.1-alpha.3"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@ai-sdk/anthropic": "^1.1.15",
@@ -1,49 +1,137 @@
1
- import { anthropic } from '@ai-sdk/anthropic';
2
- import { Agent } from '@mastra/core/agent';
3
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
1
+ import { randomUUID } from 'node:crypto';
2
+ import { createServer } from 'node:http';
3
+ import type { Server as HttpServer } from 'node:http';
4
+ import type { AddressInfo } from 'node:net';
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
7
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
8
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
9
+ import { z } from 'zod';
4
10
 
5
11
  import { MastraMCPClient } from './client.js';
6
12
 
7
- const everArtClient = new MastraMCPClient({
8
- name: 'everart',
9
- server: {
10
- command: '/usr/local/bin/docker',
11
- args: ['run', '-i', '--rm', '--network=host', '-e', 'EVERART_API_KEY', 'mcp/everart'],
12
- env: {
13
- EVERART_API_KEY: process.env.EVERART_API_KEY!,
13
+ async function setupTestServer(withSessionManagement: boolean) {
14
+ const httpServer: HttpServer = createServer();
15
+ const mcpServer = new McpServer(
16
+ { name: 'test-http-server', version: '1.0.0' },
17
+ {
18
+ capabilities: {
19
+ logging: {},
20
+ tools: {},
21
+ },
14
22
  },
15
- },
16
- });
23
+ );
17
24
 
18
- const agent = new Agent({
19
- name: 'everart',
20
- instructions: 'You are my artist. Include the url in your response.',
21
- model: anthropic('claude-3-5-sonnet-20241022'),
22
- });
25
+ mcpServer.tool(
26
+ 'greet',
27
+ 'A simple greeting tool',
28
+ {
29
+ name: z.string().describe('Name to greet').default('World'),
30
+ },
31
+ async ({ name }): Promise<CallToolResult> => {
32
+ return {
33
+ content: [{ type: 'text', text: `Hello, ${name}!` }],
34
+ };
35
+ },
36
+ );
23
37
 
24
- describe.skip('MastraMCPClient', () => {
25
- beforeAll(async () => {
26
- await everArtClient.connect();
38
+ const serverTransport = new StreamableHTTPServerTransport({
39
+ sessionIdGenerator: withSessionManagement ? () => randomUUID() : undefined,
27
40
  });
28
41
 
29
- afterAll(async () => {
30
- await everArtClient.disconnect();
42
+ await mcpServer.connect(serverTransport);
43
+
44
+ httpServer.on('request', async (req, res) => {
45
+ await serverTransport.handleRequest(req, res);
31
46
  });
32
47
 
33
- it('Converting tools into Mastra', async () => {
34
- const list = await everArtClient.resources();
48
+ const baseUrl = await new Promise<URL>(resolve => {
49
+ httpServer.listen(0, '127.0.0.1', () => {
50
+ const addr = httpServer.address() as AddressInfo;
51
+ resolve(new URL(`http://127.0.0.1:${addr.port}/mcp`));
52
+ });
53
+ });
35
54
 
36
- expect(list.resources.length).toBeGreaterThan(0);
55
+ return { httpServer, mcpServer, serverTransport, baseUrl };
56
+ }
37
57
 
38
- // The MCP server tools are now available to your Mastra Agents
39
- const tools = await everArtClient.tools();
58
+ describe('MastraMCPClient with Streamable HTTP', () => {
59
+ let testServer: {
60
+ httpServer: HttpServer;
61
+ mcpServer: McpServer;
62
+ serverTransport: StreamableHTTPServerTransport;
63
+ baseUrl: URL;
64
+ };
65
+ let client: MastraMCPClient;
40
66
 
41
- await agent.generate('Can you make me a picture of a dog?', {
42
- toolsets: {
43
- everart: tools,
44
- },
67
+ describe('Stateless Mode', () => {
68
+ beforeEach(async () => {
69
+ testServer = await setupTestServer(false);
70
+ client = new MastraMCPClient({
71
+ name: 'test-stateless-client',
72
+ server: {
73
+ url: testServer.baseUrl,
74
+ },
75
+ });
76
+ await client.connect();
77
+ });
78
+
79
+ afterEach(async () => {
80
+ await client?.disconnect().catch(() => {});
81
+ await testServer?.mcpServer.close().catch(() => {});
82
+ await testServer?.serverTransport.close().catch(() => {});
83
+ testServer?.httpServer.close();
84
+ });
85
+
86
+ it('should connect and list tools', async () => {
87
+ const tools = await client.tools();
88
+ expect(tools).toHaveProperty('greet');
89
+ expect(tools.greet.description).toBe('A simple greeting tool');
45
90
  });
46
91
 
47
- expect(Object.keys(tools).length).toBeGreaterThan(0);
48
- }, 50000);
92
+ it('should call a tool', async () => {
93
+ const tools = await client.tools();
94
+ const result = await tools.greet.execute({ context: { name: 'Stateless' } });
95
+ expect(result).toEqual({ content: [{ type: 'text', text: 'Hello, Stateless!' }] });
96
+ });
97
+ });
98
+
99
+ describe('Stateful Mode', () => {
100
+ beforeEach(async () => {
101
+ testServer = await setupTestServer(true);
102
+ client = new MastraMCPClient({
103
+ name: 'test-stateful-client',
104
+ server: {
105
+ url: testServer.baseUrl,
106
+ },
107
+ });
108
+ await client.connect();
109
+ });
110
+
111
+ afterEach(async () => {
112
+ await client?.disconnect().catch(() => {});
113
+ await testServer?.mcpServer.close().catch(() => {});
114
+ await testServer?.serverTransport.close().catch(() => {});
115
+ testServer?.httpServer.close();
116
+ });
117
+
118
+ it('should connect and list tools', async () => {
119
+ const tools = await client.tools();
120
+ expect(tools).toHaveProperty('greet');
121
+ });
122
+
123
+ it('should capture the session ID after connecting', async () => {
124
+ // The setupTestServer(true) is configured for stateful mode
125
+ // The client should capture the session ID from the server's response
126
+ expect(client.sessionId).toBeDefined();
127
+ expect(typeof client.sessionId).toBe('string');
128
+ expect(client.sessionId?.length).toBeGreaterThan(0);
129
+ });
130
+
131
+ it('should call a tool', async () => {
132
+ const tools = await client.tools();
133
+ const result = await tools.greet.execute({ context: { name: 'Stateful' } });
134
+ expect(result).toEqual({ content: [{ type: 'text', text: 'Hello, Stateful!' }] });
135
+ });
136
+ });
49
137
  });