@mastra/mcp 0.5.0-alpha.6 → 0.5.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +54 -0
- package/README.md +2 -2
- package/dist/_tsup-dts-rollup.d.cts +49 -8
- package/dist/_tsup-dts-rollup.d.ts +49 -8
- package/dist/index.cjs +119 -7
- package/dist/index.js +119 -7
- package/integration-tests/src/server.test.ts +97 -2
- package/package.json +3 -3
- package/src/__fixtures__/weather.ts +25 -25
- package/src/server.test.ts +194 -1
- package/src/server.ts +143 -9
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
> @mastra/mcp@0.5.
|
|
2
|
+
> @mastra/mcp@0.5.1-alpha.0 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
|
[34mCLI[39m Building entry: src/index.ts
|
|
6
6
|
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
7
|
[34mCLI[39m tsup v8.4.0
|
|
8
8
|
[34mTSC[39m Build start
|
|
9
|
-
[32mTSC[39m ⚡️ Build success in
|
|
9
|
+
[32mTSC[39m ⚡️ Build success in 19696ms
|
|
10
10
|
[34mDTS[39m Build start
|
|
11
11
|
[34mCLI[39m Target: es2022
|
|
12
12
|
Analysis will use the bundled TypeScript version 5.8.3
|
|
13
13
|
[36mWriting package typings: /home/runner/work/mastra/mastra/packages/mcp/dist/_tsup-dts-rollup.d.ts[39m
|
|
14
14
|
Analysis will use the bundled TypeScript version 5.8.3
|
|
15
15
|
[36mWriting package typings: /home/runner/work/mastra/mastra/packages/mcp/dist/_tsup-dts-rollup.d.cts[39m
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 14884ms
|
|
17
17
|
[34mCLI[39m Cleaning output folder
|
|
18
18
|
[34mESM[39m Build start
|
|
19
19
|
[34mCJS[39m Build start
|
|
20
|
-
[
|
|
21
|
-
[
|
|
22
|
-
[
|
|
23
|
-
[
|
|
20
|
+
[32mESM[39m [1mdist/index.js [22m[32m36.70 KB[39m
|
|
21
|
+
[32mESM[39m ⚡️ Build success in 1126ms
|
|
22
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m37.02 KB[39m
|
|
23
|
+
[32mCJS[39m ⚡️ Build success in 1130ms
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
# @mastra/mcp
|
|
2
2
|
|
|
3
|
+
## 0.5.1-alpha.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 23f258c: Add new list and get routes for mcp servers. Changed route make-up for more consistency with existing API routes. Lastly, added in a lot of extra detail that can be optionally passed to the mcp server per the mcp spec.
|
|
8
|
+
- 2672a05: Add MCP servers and tool call execution to playground
|
|
9
|
+
- Updated dependencies [f53a6ac]
|
|
10
|
+
- Updated dependencies [eabdcd9]
|
|
11
|
+
- Updated dependencies [90be034]
|
|
12
|
+
- Updated dependencies [99f050a]
|
|
13
|
+
- Updated dependencies [d0ee3c6]
|
|
14
|
+
- Updated dependencies [23f258c]
|
|
15
|
+
- Updated dependencies [2672a05]
|
|
16
|
+
- @mastra/core@0.9.5-alpha.0
|
|
17
|
+
|
|
18
|
+
## 0.5.0
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- e229660: MCPClient: expose connected client resources.
|
|
23
|
+
|
|
24
|
+
Added a new `getResources()` method to the MCPClient class that allows clients to retrieve resources from connected MCP servers. Resources are data or content exposed by MCP servers that can be accessed by clients.
|
|
25
|
+
|
|
26
|
+
The implementation includes:
|
|
27
|
+
|
|
28
|
+
- Direct access to resources from all connected MCP servers, grouped by server name
|
|
29
|
+
- Robust error handling that allows partial results when some servers fail
|
|
30
|
+
- Comprehensive test coverage with real server implementation
|
|
31
|
+
|
|
32
|
+
This feature enables applications to access data and content exposed by MCP servers through the resources capability, such as files, databases, or other content sources.
|
|
33
|
+
|
|
34
|
+
### Patch Changes
|
|
35
|
+
|
|
36
|
+
- 8baa6c8: passes runtimeContext to the logger function inside MCPClient tool calls
|
|
37
|
+
- 396be50: updated mcp server routes for MCP SSE for use with hono server
|
|
38
|
+
- da082f8: Switch from serializing json schema string as a function to a library that creates a zod object in memory from the json schema. This reduces the errors we were seeing from zod schema code that could not be serialized.
|
|
39
|
+
- bb1e2c8: Make sure mcp handlers can only be registered once
|
|
40
|
+
- edf1e88: allows ability to pass McpServer into the mastra class and creates an endpoint /api/servers/:serverId/mcp to POST messages to an MCP server
|
|
41
|
+
- Updated dependencies [396be50]
|
|
42
|
+
- Updated dependencies [ab80e7e]
|
|
43
|
+
- Updated dependencies [c3bd795]
|
|
44
|
+
- Updated dependencies [da082f8]
|
|
45
|
+
- Updated dependencies [a5810ce]
|
|
46
|
+
- Updated dependencies [3e9c131]
|
|
47
|
+
- Updated dependencies [3171b5b]
|
|
48
|
+
- Updated dependencies [973e5ac]
|
|
49
|
+
- Updated dependencies [daf942f]
|
|
50
|
+
- Updated dependencies [0b8b868]
|
|
51
|
+
- Updated dependencies [9e1eff5]
|
|
52
|
+
- Updated dependencies [6fa1ad1]
|
|
53
|
+
- Updated dependencies [c28d7a0]
|
|
54
|
+
- Updated dependencies [edf1e88]
|
|
55
|
+
- @mastra/core@0.9.4
|
|
56
|
+
|
|
3
57
|
## 0.5.0-alpha.6
|
|
4
58
|
|
|
5
59
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -330,12 +330,12 @@ console.log(Object.keys(resources)); // ['weather', 'dataService']
|
|
|
330
330
|
if (resources.weather) {
|
|
331
331
|
// Access resources from the weather server
|
|
332
332
|
const weatherResources = resources.weather;
|
|
333
|
-
|
|
333
|
+
|
|
334
334
|
// Each resource has uri, name, description, and mimeType
|
|
335
335
|
weatherResources.forEach(resource => {
|
|
336
336
|
console.log(`${resource.uri}: ${resource.name} (${resource.mimeType})`);
|
|
337
337
|
});
|
|
338
|
-
|
|
338
|
+
|
|
339
339
|
// Find a specific resource by URI
|
|
340
340
|
const forecast = weatherResources.find(r => r.uri === 'weather://forecast');
|
|
341
341
|
if (forecast) {
|
|
@@ -4,11 +4,14 @@ import type * as http from 'node:http';
|
|
|
4
4
|
import { LoggingLevel } from '@modelcontextprotocol/sdk/types.js';
|
|
5
5
|
import { MastraBase } from '@mastra/core/base';
|
|
6
6
|
import { MCPServerBase } from '@mastra/core/mcp';
|
|
7
|
+
import type { MCPServerConfig } from '@mastra/core/mcp';
|
|
7
8
|
import type { MCPServerHonoSSEOptions } from '@mastra/core/mcp';
|
|
8
9
|
import type { MCPServerSSEOptions } from '@mastra/core/mcp';
|
|
9
10
|
import type { Protocol } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
10
11
|
import type { RuntimeContext } from '@mastra/core/di';
|
|
11
12
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
13
|
+
import type { ServerDetailInfo } from '@mastra/core/mcp';
|
|
14
|
+
import type { ServerInfo } from '@mastra/core/mcp';
|
|
12
15
|
import type { SSEClientTransportOptions } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
13
16
|
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
14
17
|
import type { SSEStreamingApi } from 'hono/streaming';
|
|
@@ -218,15 +221,9 @@ declare class MCPServer extends MCPServerBase {
|
|
|
218
221
|
getStreamableHTTPTransport(): StreamableHTTPServerTransport | undefined;
|
|
219
222
|
/**
|
|
220
223
|
* Construct a new MCPServer instance.
|
|
221
|
-
* @param opts
|
|
222
|
-
* @param opts.version - Server version
|
|
223
|
-
* @param opts.tools - Tool definitions to register
|
|
224
|
+
* @param opts - Configuration options for the server, including registry metadata.
|
|
224
225
|
*/
|
|
225
|
-
constructor(
|
|
226
|
-
name: string;
|
|
227
|
-
version: string;
|
|
228
|
-
tools: ToolsInput;
|
|
229
|
-
});
|
|
226
|
+
constructor(opts: MCPServerConfig);
|
|
230
227
|
/**
|
|
231
228
|
* Convert and validate all provided tools, logging registration status.
|
|
232
229
|
* @param tools Tool definitions
|
|
@@ -295,6 +292,50 @@ declare class MCPServer extends MCPServerBase {
|
|
|
295
292
|
* Close the MCP server and all its connections
|
|
296
293
|
*/
|
|
297
294
|
close(): Promise<void>;
|
|
295
|
+
/**
|
|
296
|
+
* Gets the basic information about the server, conforming to the Server schema.
|
|
297
|
+
* @returns ServerInfo object.
|
|
298
|
+
*/
|
|
299
|
+
getServerInfo(): ServerInfo;
|
|
300
|
+
/**
|
|
301
|
+
* Gets detailed information about the server, conforming to the ServerDetail schema.
|
|
302
|
+
* @returns ServerDetailInfo object.
|
|
303
|
+
*/
|
|
304
|
+
getServerDetail(): ServerDetailInfo;
|
|
305
|
+
/**
|
|
306
|
+
* Gets a list of tools provided by this MCP server, including their schemas.
|
|
307
|
+
* This leverages the same tool information used by the internal ListTools MCP request.
|
|
308
|
+
* @returns An object containing an array of tool information.
|
|
309
|
+
*/
|
|
310
|
+
getToolListInfo(): {
|
|
311
|
+
tools: Array<{
|
|
312
|
+
name: string;
|
|
313
|
+
description?: string;
|
|
314
|
+
inputSchema: any;
|
|
315
|
+
}>;
|
|
316
|
+
};
|
|
317
|
+
/**
|
|
318
|
+
* Gets information for a specific tool provided by this MCP server.
|
|
319
|
+
* @param toolId The ID/name of the tool to retrieve.
|
|
320
|
+
* @returns Tool information (name, description, inputSchema) or undefined if not found.
|
|
321
|
+
*/
|
|
322
|
+
getToolInfo(toolId: string): {
|
|
323
|
+
name: string;
|
|
324
|
+
description?: string;
|
|
325
|
+
inputSchema: any;
|
|
326
|
+
} | undefined;
|
|
327
|
+
/**
|
|
328
|
+
* Executes a specific tool provided by this MCP server.
|
|
329
|
+
* @param toolId The ID/name of the tool to execute.
|
|
330
|
+
* @param args The arguments to pass to the tool's execute function.
|
|
331
|
+
* @param executionContext Optional context for the tool execution.
|
|
332
|
+
* @returns A promise that resolves to the result of the tool execution.
|
|
333
|
+
* @throws Error if the tool is not found, validation fails, or execution fails.
|
|
334
|
+
*/
|
|
335
|
+
executeTool(toolId: string, args: any, executionContext?: {
|
|
336
|
+
messages?: any[];
|
|
337
|
+
toolCallId?: string;
|
|
338
|
+
}): Promise<any>;
|
|
298
339
|
}
|
|
299
340
|
export { MCPServer }
|
|
300
341
|
export { MCPServer as MCPServer_alias_1 }
|
|
@@ -4,11 +4,14 @@ import type * as http from 'node:http';
|
|
|
4
4
|
import { LoggingLevel } from '@modelcontextprotocol/sdk/types.js';
|
|
5
5
|
import { MastraBase } from '@mastra/core/base';
|
|
6
6
|
import { MCPServerBase } from '@mastra/core/mcp';
|
|
7
|
+
import type { MCPServerConfig } from '@mastra/core/mcp';
|
|
7
8
|
import type { MCPServerHonoSSEOptions } from '@mastra/core/mcp';
|
|
8
9
|
import type { MCPServerSSEOptions } from '@mastra/core/mcp';
|
|
9
10
|
import type { Protocol } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
10
11
|
import type { RuntimeContext } from '@mastra/core/di';
|
|
11
12
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
13
|
+
import type { ServerDetailInfo } from '@mastra/core/mcp';
|
|
14
|
+
import type { ServerInfo } from '@mastra/core/mcp';
|
|
12
15
|
import type { SSEClientTransportOptions } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
13
16
|
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
14
17
|
import type { SSEStreamingApi } from 'hono/streaming';
|
|
@@ -218,15 +221,9 @@ declare class MCPServer extends MCPServerBase {
|
|
|
218
221
|
getStreamableHTTPTransport(): StreamableHTTPServerTransport | undefined;
|
|
219
222
|
/**
|
|
220
223
|
* Construct a new MCPServer instance.
|
|
221
|
-
* @param opts
|
|
222
|
-
* @param opts.version - Server version
|
|
223
|
-
* @param opts.tools - Tool definitions to register
|
|
224
|
+
* @param opts - Configuration options for the server, including registry metadata.
|
|
224
225
|
*/
|
|
225
|
-
constructor(
|
|
226
|
-
name: string;
|
|
227
|
-
version: string;
|
|
228
|
-
tools: ToolsInput;
|
|
229
|
-
});
|
|
226
|
+
constructor(opts: MCPServerConfig);
|
|
230
227
|
/**
|
|
231
228
|
* Convert and validate all provided tools, logging registration status.
|
|
232
229
|
* @param tools Tool definitions
|
|
@@ -295,6 +292,50 @@ declare class MCPServer extends MCPServerBase {
|
|
|
295
292
|
* Close the MCP server and all its connections
|
|
296
293
|
*/
|
|
297
294
|
close(): Promise<void>;
|
|
295
|
+
/**
|
|
296
|
+
* Gets the basic information about the server, conforming to the Server schema.
|
|
297
|
+
* @returns ServerInfo object.
|
|
298
|
+
*/
|
|
299
|
+
getServerInfo(): ServerInfo;
|
|
300
|
+
/**
|
|
301
|
+
* Gets detailed information about the server, conforming to the ServerDetail schema.
|
|
302
|
+
* @returns ServerDetailInfo object.
|
|
303
|
+
*/
|
|
304
|
+
getServerDetail(): ServerDetailInfo;
|
|
305
|
+
/**
|
|
306
|
+
* Gets a list of tools provided by this MCP server, including their schemas.
|
|
307
|
+
* This leverages the same tool information used by the internal ListTools MCP request.
|
|
308
|
+
* @returns An object containing an array of tool information.
|
|
309
|
+
*/
|
|
310
|
+
getToolListInfo(): {
|
|
311
|
+
tools: Array<{
|
|
312
|
+
name: string;
|
|
313
|
+
description?: string;
|
|
314
|
+
inputSchema: any;
|
|
315
|
+
}>;
|
|
316
|
+
};
|
|
317
|
+
/**
|
|
318
|
+
* Gets information for a specific tool provided by this MCP server.
|
|
319
|
+
* @param toolId The ID/name of the tool to retrieve.
|
|
320
|
+
* @returns Tool information (name, description, inputSchema) or undefined if not found.
|
|
321
|
+
*/
|
|
322
|
+
getToolInfo(toolId: string): {
|
|
323
|
+
name: string;
|
|
324
|
+
description?: string;
|
|
325
|
+
inputSchema: any;
|
|
326
|
+
} | undefined;
|
|
327
|
+
/**
|
|
328
|
+
* Executes a specific tool provided by this MCP server.
|
|
329
|
+
* @param toolId The ID/name of the tool to execute.
|
|
330
|
+
* @param args The arguments to pass to the tool's execute function.
|
|
331
|
+
* @param executionContext Optional context for the tool execution.
|
|
332
|
+
* @returns A promise that resolves to the result of the tool execution.
|
|
333
|
+
* @throws Error if the tool is not found, validation fails, or execution fails.
|
|
334
|
+
*/
|
|
335
|
+
executeTool(toolId: string, args: any, executionContext?: {
|
|
336
|
+
messages?: any[];
|
|
337
|
+
toolCallId?: string;
|
|
338
|
+
}): Promise<any>;
|
|
298
339
|
}
|
|
299
340
|
export { MCPServer }
|
|
300
341
|
export { MCPServer as MCPServer_alias_1 }
|
package/dist/index.cjs
CHANGED
|
@@ -630,15 +630,16 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
630
630
|
}
|
|
631
631
|
/**
|
|
632
632
|
* Construct a new MCPServer instance.
|
|
633
|
-
* @param opts
|
|
634
|
-
* @param opts.version - Server version
|
|
635
|
-
* @param opts.tools - Tool definitions to register
|
|
633
|
+
* @param opts - Configuration options for the server, including registry metadata.
|
|
636
634
|
*/
|
|
637
|
-
constructor(
|
|
638
|
-
super(
|
|
639
|
-
this.server = new index_js.Server(
|
|
635
|
+
constructor(opts) {
|
|
636
|
+
super(opts);
|
|
637
|
+
this.server = new index_js.Server(
|
|
638
|
+
{ name: this.name, version: this.version },
|
|
639
|
+
{ capabilities: { tools: {}, logging: { enabled: true } } }
|
|
640
|
+
);
|
|
640
641
|
this.logger.info(
|
|
641
|
-
`Initialized MCPServer '${name}' v${version} with tools: ${Object.keys(this.convertedTools).join(", ")}`
|
|
642
|
+
`Initialized MCPServer '${this.name}' v${this.version} (ID: ${this.id}) with tools: ${Object.keys(this.convertedTools).join(", ")}`
|
|
642
643
|
);
|
|
643
644
|
this.sseHonoTransports = /* @__PURE__ */ new Map();
|
|
644
645
|
this.registerListToolsHandler();
|
|
@@ -965,6 +966,117 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
965
966
|
this.logger.error("Error closing MCP server:", { error });
|
|
966
967
|
}
|
|
967
968
|
}
|
|
969
|
+
/**
|
|
970
|
+
* Gets the basic information about the server, conforming to the Server schema.
|
|
971
|
+
* @returns ServerInfo object.
|
|
972
|
+
*/
|
|
973
|
+
getServerInfo() {
|
|
974
|
+
return {
|
|
975
|
+
id: this.id,
|
|
976
|
+
name: this.name,
|
|
977
|
+
description: this.description,
|
|
978
|
+
repository: this.repository,
|
|
979
|
+
version_detail: {
|
|
980
|
+
version: this.version,
|
|
981
|
+
release_date: this.releaseDate,
|
|
982
|
+
is_latest: this.isLatest
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Gets detailed information about the server, conforming to the ServerDetail schema.
|
|
988
|
+
* @returns ServerDetailInfo object.
|
|
989
|
+
*/
|
|
990
|
+
getServerDetail() {
|
|
991
|
+
return {
|
|
992
|
+
...this.getServerInfo(),
|
|
993
|
+
package_canonical: this.packageCanonical,
|
|
994
|
+
packages: this.packages,
|
|
995
|
+
remotes: this.remotes
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Gets a list of tools provided by this MCP server, including their schemas.
|
|
1000
|
+
* This leverages the same tool information used by the internal ListTools MCP request.
|
|
1001
|
+
* @returns An object containing an array of tool information.
|
|
1002
|
+
*/
|
|
1003
|
+
getToolListInfo() {
|
|
1004
|
+
this.logger.debug(`Getting tool list information for MCPServer '${this.name}'`);
|
|
1005
|
+
return {
|
|
1006
|
+
tools: Object.entries(this.convertedTools).map(([toolId, tool]) => ({
|
|
1007
|
+
id: toolId,
|
|
1008
|
+
name: tool.name,
|
|
1009
|
+
description: tool.description,
|
|
1010
|
+
inputSchema: tool.parameters?.jsonSchema || tool.parameters
|
|
1011
|
+
}))
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Gets information for a specific tool provided by this MCP server.
|
|
1016
|
+
* @param toolId The ID/name of the tool to retrieve.
|
|
1017
|
+
* @returns Tool information (name, description, inputSchema) or undefined if not found.
|
|
1018
|
+
*/
|
|
1019
|
+
getToolInfo(toolId) {
|
|
1020
|
+
const tool = this.convertedTools[toolId];
|
|
1021
|
+
if (!tool) {
|
|
1022
|
+
this.logger.debug(`Tool '${toolId}' not found on MCPServer '${this.name}'`);
|
|
1023
|
+
return void 0;
|
|
1024
|
+
}
|
|
1025
|
+
this.logger.debug(`Getting info for tool '${toolId}' on MCPServer '${this.name}'`);
|
|
1026
|
+
return {
|
|
1027
|
+
name: tool.name,
|
|
1028
|
+
description: tool.description,
|
|
1029
|
+
inputSchema: tool.parameters?.jsonSchema || tool.parameters
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Executes a specific tool provided by this MCP server.
|
|
1034
|
+
* @param toolId The ID/name of the tool to execute.
|
|
1035
|
+
* @param args The arguments to pass to the tool's execute function.
|
|
1036
|
+
* @param executionContext Optional context for the tool execution.
|
|
1037
|
+
* @returns A promise that resolves to the result of the tool execution.
|
|
1038
|
+
* @throws Error if the tool is not found, validation fails, or execution fails.
|
|
1039
|
+
*/
|
|
1040
|
+
async executeTool(toolId, args, executionContext) {
|
|
1041
|
+
const tool = this.convertedTools[toolId];
|
|
1042
|
+
if (!tool) {
|
|
1043
|
+
this.logger.warn(`ExecuteTool: Unknown tool '${toolId}' requested on MCPServer '${this.name}'.`);
|
|
1044
|
+
throw new Error(`Unknown tool: ${toolId}`);
|
|
1045
|
+
}
|
|
1046
|
+
this.logger.debug(`ExecuteTool: Invoking '${toolId}' with arguments:`, args);
|
|
1047
|
+
let validatedArgs = args;
|
|
1048
|
+
if (tool.parameters instanceof zod.z.ZodType && typeof tool.parameters.safeParse === "function") {
|
|
1049
|
+
const validation = tool.parameters.safeParse(args ?? {});
|
|
1050
|
+
if (!validation.success) {
|
|
1051
|
+
const errorMessages = validation.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
|
|
1052
|
+
this.logger.warn(`ExecuteTool: Invalid tool arguments for '${toolId}': ${errorMessages}`, {
|
|
1053
|
+
errors: validation.error.format()
|
|
1054
|
+
});
|
|
1055
|
+
throw new zod.z.ZodError(validation.error.issues);
|
|
1056
|
+
}
|
|
1057
|
+
validatedArgs = validation.data;
|
|
1058
|
+
} else {
|
|
1059
|
+
this.logger.debug(
|
|
1060
|
+
`ExecuteTool: Tool '${toolId}' parameters is not a Zod schema with safeParse or is undefined. Skipping validation.`
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
if (!tool.execute) {
|
|
1064
|
+
this.logger.error(`ExecuteTool: Tool '${toolId}' does not have an execute function.`);
|
|
1065
|
+
throw new Error(`Tool '${toolId}' cannot be executed.`);
|
|
1066
|
+
}
|
|
1067
|
+
try {
|
|
1068
|
+
const finalExecutionContext = {
|
|
1069
|
+
messages: executionContext?.messages || [],
|
|
1070
|
+
toolCallId: executionContext?.toolCallId || crypto$1.randomUUID()
|
|
1071
|
+
};
|
|
1072
|
+
const result = await tool.execute(validatedArgs, finalExecutionContext);
|
|
1073
|
+
this.logger.info(`ExecuteTool: Tool '${toolId}' executed successfully.`);
|
|
1074
|
+
return result;
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
this.logger.error(`ExecuteTool: Tool execution failed for '${toolId}':`, { error });
|
|
1077
|
+
throw error instanceof Error ? error : new Error(`Execution of tool '${toolId}' failed: ${String(error)}`);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
968
1080
|
};
|
|
969
1081
|
|
|
970
1082
|
exports.MCPClient = MCPClient;
|
package/dist/index.js
CHANGED
|
@@ -624,15 +624,16 @@ var MCPServer = class extends MCPServerBase {
|
|
|
624
624
|
}
|
|
625
625
|
/**
|
|
626
626
|
* Construct a new MCPServer instance.
|
|
627
|
-
* @param opts
|
|
628
|
-
* @param opts.version - Server version
|
|
629
|
-
* @param opts.tools - Tool definitions to register
|
|
627
|
+
* @param opts - Configuration options for the server, including registry metadata.
|
|
630
628
|
*/
|
|
631
|
-
constructor(
|
|
632
|
-
super(
|
|
633
|
-
this.server = new Server(
|
|
629
|
+
constructor(opts) {
|
|
630
|
+
super(opts);
|
|
631
|
+
this.server = new Server(
|
|
632
|
+
{ name: this.name, version: this.version },
|
|
633
|
+
{ capabilities: { tools: {}, logging: { enabled: true } } }
|
|
634
|
+
);
|
|
634
635
|
this.logger.info(
|
|
635
|
-
`Initialized MCPServer '${name}' v${version} with tools: ${Object.keys(this.convertedTools).join(", ")}`
|
|
636
|
+
`Initialized MCPServer '${this.name}' v${this.version} (ID: ${this.id}) with tools: ${Object.keys(this.convertedTools).join(", ")}`
|
|
636
637
|
);
|
|
637
638
|
this.sseHonoTransports = /* @__PURE__ */ new Map();
|
|
638
639
|
this.registerListToolsHandler();
|
|
@@ -959,6 +960,117 @@ var MCPServer = class extends MCPServerBase {
|
|
|
959
960
|
this.logger.error("Error closing MCP server:", { error });
|
|
960
961
|
}
|
|
961
962
|
}
|
|
963
|
+
/**
|
|
964
|
+
* Gets the basic information about the server, conforming to the Server schema.
|
|
965
|
+
* @returns ServerInfo object.
|
|
966
|
+
*/
|
|
967
|
+
getServerInfo() {
|
|
968
|
+
return {
|
|
969
|
+
id: this.id,
|
|
970
|
+
name: this.name,
|
|
971
|
+
description: this.description,
|
|
972
|
+
repository: this.repository,
|
|
973
|
+
version_detail: {
|
|
974
|
+
version: this.version,
|
|
975
|
+
release_date: this.releaseDate,
|
|
976
|
+
is_latest: this.isLatest
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Gets detailed information about the server, conforming to the ServerDetail schema.
|
|
982
|
+
* @returns ServerDetailInfo object.
|
|
983
|
+
*/
|
|
984
|
+
getServerDetail() {
|
|
985
|
+
return {
|
|
986
|
+
...this.getServerInfo(),
|
|
987
|
+
package_canonical: this.packageCanonical,
|
|
988
|
+
packages: this.packages,
|
|
989
|
+
remotes: this.remotes
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Gets a list of tools provided by this MCP server, including their schemas.
|
|
994
|
+
* This leverages the same tool information used by the internal ListTools MCP request.
|
|
995
|
+
* @returns An object containing an array of tool information.
|
|
996
|
+
*/
|
|
997
|
+
getToolListInfo() {
|
|
998
|
+
this.logger.debug(`Getting tool list information for MCPServer '${this.name}'`);
|
|
999
|
+
return {
|
|
1000
|
+
tools: Object.entries(this.convertedTools).map(([toolId, tool]) => ({
|
|
1001
|
+
id: toolId,
|
|
1002
|
+
name: tool.name,
|
|
1003
|
+
description: tool.description,
|
|
1004
|
+
inputSchema: tool.parameters?.jsonSchema || tool.parameters
|
|
1005
|
+
}))
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Gets information for a specific tool provided by this MCP server.
|
|
1010
|
+
* @param toolId The ID/name of the tool to retrieve.
|
|
1011
|
+
* @returns Tool information (name, description, inputSchema) or undefined if not found.
|
|
1012
|
+
*/
|
|
1013
|
+
getToolInfo(toolId) {
|
|
1014
|
+
const tool = this.convertedTools[toolId];
|
|
1015
|
+
if (!tool) {
|
|
1016
|
+
this.logger.debug(`Tool '${toolId}' not found on MCPServer '${this.name}'`);
|
|
1017
|
+
return void 0;
|
|
1018
|
+
}
|
|
1019
|
+
this.logger.debug(`Getting info for tool '${toolId}' on MCPServer '${this.name}'`);
|
|
1020
|
+
return {
|
|
1021
|
+
name: tool.name,
|
|
1022
|
+
description: tool.description,
|
|
1023
|
+
inputSchema: tool.parameters?.jsonSchema || tool.parameters
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Executes a specific tool provided by this MCP server.
|
|
1028
|
+
* @param toolId The ID/name of the tool to execute.
|
|
1029
|
+
* @param args The arguments to pass to the tool's execute function.
|
|
1030
|
+
* @param executionContext Optional context for the tool execution.
|
|
1031
|
+
* @returns A promise that resolves to the result of the tool execution.
|
|
1032
|
+
* @throws Error if the tool is not found, validation fails, or execution fails.
|
|
1033
|
+
*/
|
|
1034
|
+
async executeTool(toolId, args, executionContext) {
|
|
1035
|
+
const tool = this.convertedTools[toolId];
|
|
1036
|
+
if (!tool) {
|
|
1037
|
+
this.logger.warn(`ExecuteTool: Unknown tool '${toolId}' requested on MCPServer '${this.name}'.`);
|
|
1038
|
+
throw new Error(`Unknown tool: ${toolId}`);
|
|
1039
|
+
}
|
|
1040
|
+
this.logger.debug(`ExecuteTool: Invoking '${toolId}' with arguments:`, args);
|
|
1041
|
+
let validatedArgs = args;
|
|
1042
|
+
if (tool.parameters instanceof z.ZodType && typeof tool.parameters.safeParse === "function") {
|
|
1043
|
+
const validation = tool.parameters.safeParse(args ?? {});
|
|
1044
|
+
if (!validation.success) {
|
|
1045
|
+
const errorMessages = validation.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
|
|
1046
|
+
this.logger.warn(`ExecuteTool: Invalid tool arguments for '${toolId}': ${errorMessages}`, {
|
|
1047
|
+
errors: validation.error.format()
|
|
1048
|
+
});
|
|
1049
|
+
throw new z.ZodError(validation.error.issues);
|
|
1050
|
+
}
|
|
1051
|
+
validatedArgs = validation.data;
|
|
1052
|
+
} else {
|
|
1053
|
+
this.logger.debug(
|
|
1054
|
+
`ExecuteTool: Tool '${toolId}' parameters is not a Zod schema with safeParse or is undefined. Skipping validation.`
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
if (!tool.execute) {
|
|
1058
|
+
this.logger.error(`ExecuteTool: Tool '${toolId}' does not have an execute function.`);
|
|
1059
|
+
throw new Error(`Tool '${toolId}' cannot be executed.`);
|
|
1060
|
+
}
|
|
1061
|
+
try {
|
|
1062
|
+
const finalExecutionContext = {
|
|
1063
|
+
messages: executionContext?.messages || [],
|
|
1064
|
+
toolCallId: executionContext?.toolCallId || randomUUID()
|
|
1065
|
+
};
|
|
1066
|
+
const result = await tool.execute(validatedArgs, finalExecutionContext);
|
|
1067
|
+
this.logger.info(`ExecuteTool: Tool '${toolId}' executed successfully.`);
|
|
1068
|
+
return result;
|
|
1069
|
+
} catch (error) {
|
|
1070
|
+
this.logger.error(`ExecuteTool: Tool execution failed for '${toolId}':`, { error });
|
|
1071
|
+
throw error instanceof Error ? error : new Error(`Execution of tool '${toolId}' failed: ${String(error)}`);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
962
1074
|
};
|
|
963
1075
|
|
|
964
1076
|
export { MCPClient, MCPConfiguration, MCPServer, MastraMCPClient };
|
|
@@ -3,6 +3,7 @@ import { createServer } from 'node:http';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { MCPClient } from '@mastra/mcp';
|
|
5
5
|
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
|
|
6
|
+
import { ServerInfo } from '@mastra/core/mcp';
|
|
6
7
|
|
|
7
8
|
vi.setConfig({ testTimeout: 20000, hookTimeout: 20000 });
|
|
8
9
|
|
|
@@ -62,7 +63,7 @@ describe('MCPServer through Mastra HTTP Integration (Subprocess)', () => {
|
|
|
62
63
|
client = new MCPClient({
|
|
63
64
|
servers: {
|
|
64
65
|
[mcpServerId]: {
|
|
65
|
-
url: new URL(`http://localhost:${port}/api/
|
|
66
|
+
url: new URL(`http://localhost:${port}/api/mcp/${mcpServerId}/mcp`),
|
|
66
67
|
},
|
|
67
68
|
},
|
|
68
69
|
});
|
|
@@ -112,7 +113,7 @@ describe('MCPServer through Mastra HTTP Integration (Subprocess)', () => {
|
|
|
112
113
|
}, 25000);
|
|
113
114
|
|
|
114
115
|
it('should allow a client to call a tool via Mastra MCP SSE endpoints (Subprocess)', async () => {
|
|
115
|
-
const sseUrl = new URL(`http://localhost:${port}/api/
|
|
116
|
+
const sseUrl = new URL(`http://localhost:${port}/api/mcp/${mcpServerId}/sse`);
|
|
116
117
|
|
|
117
118
|
// Configure MCPClient for SSE transport
|
|
118
119
|
const sseClient = new MCPClient({
|
|
@@ -146,4 +147,98 @@ describe('MCPServer through Mastra HTTP Integration (Subprocess)', () => {
|
|
|
146
147
|
const expectedToolResult = 15; // 10 + 5
|
|
147
148
|
expect(JSON.parse(toolOutput.text)).toEqual(expectedToolResult);
|
|
148
149
|
}, 25000);
|
|
150
|
+
|
|
151
|
+
// --- New tests for MCP Registry API Style Routes ---
|
|
152
|
+
describe('MCP Registry API Style Endpoints', () => {
|
|
153
|
+
const defaultMcpServerLogicalId = 'myMcpServer'; // Assuming this is the ID of the default server
|
|
154
|
+
|
|
155
|
+
it('GET /api/mcp/v0/servers - should list available MCP servers', async () => {
|
|
156
|
+
const response = await fetch(`http://localhost:${port}/api/mcp/v0/servers`);
|
|
157
|
+
expect(response.status).toBe(200);
|
|
158
|
+
expect(response.headers.get('content-type')).toContain('application/json');
|
|
159
|
+
const body = await response.json();
|
|
160
|
+
|
|
161
|
+
expect(body).toHaveProperty('servers');
|
|
162
|
+
expect(body).toHaveProperty('total_count');
|
|
163
|
+
expect(Array.isArray(body.servers)).toBe(true);
|
|
164
|
+
expect(body.total_count).toBeGreaterThanOrEqual(1); // Expect at least the default server
|
|
165
|
+
|
|
166
|
+
const defaultServerInfo: ServerInfo = body.servers.find((s: ServerInfo) => s.id === defaultMcpServerLogicalId);
|
|
167
|
+
expect(defaultServerInfo).toBeDefined();
|
|
168
|
+
expect(defaultServerInfo).toHaveProperty('name');
|
|
169
|
+
expect(defaultServerInfo).toHaveProperty('version_detail');
|
|
170
|
+
// Based on default mastra dev setup, if myMcpServer is the key, its id becomes 'myMcpServer'
|
|
171
|
+
// And its name might be something like 'my-mcp-server' if not explicitly set in MCPServerConfig for it.
|
|
172
|
+
// For this test, we assume the `id` is the key used in Mastra config.
|
|
173
|
+
expect(defaultServerInfo.id).toBe(defaultMcpServerLogicalId);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('GET /api/mcp/v0/servers/:id - should get specific server details', async () => {
|
|
177
|
+
// First, get all servers to find the actual version of the default server
|
|
178
|
+
const listResponse = await fetch(`http://localhost:${port}/api/mcp/v0/servers`);
|
|
179
|
+
const listBody = await listResponse.json();
|
|
180
|
+
const defaultServer = listBody.servers.find((s: any) => s.id === defaultMcpServerLogicalId);
|
|
181
|
+
expect(defaultServer).toBeDefined();
|
|
182
|
+
const actualVersion = defaultServer.version_detail.version;
|
|
183
|
+
|
|
184
|
+
const response = await fetch(`http://localhost:${port}/api/mcp/v0/servers/${defaultMcpServerLogicalId}`);
|
|
185
|
+
expect(response.status).toBe(200);
|
|
186
|
+
const body = await response.json();
|
|
187
|
+
|
|
188
|
+
expect(body.id).toBe(defaultMcpServerLogicalId);
|
|
189
|
+
expect(body).toHaveProperty('name');
|
|
190
|
+
expect(body).toHaveProperty('version_detail');
|
|
191
|
+
expect(body.version_detail.version).toBe(actualVersion);
|
|
192
|
+
// Add more assertions for package_canonical, packages, remotes if they are expected for the default server
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('GET /api/mcp/v0/servers/:id - should get specific server version if it matches', async () => {
|
|
196
|
+
const listResponse = await fetch(`http://localhost:${port}/api/mcp/v0/servers`);
|
|
197
|
+
const listBody = await listResponse.json();
|
|
198
|
+
const defaultServer = listBody.servers.find((s: any) => s.id === defaultMcpServerLogicalId);
|
|
199
|
+
expect(defaultServer).toBeDefined();
|
|
200
|
+
const actualVersion = defaultServer.version_detail.version;
|
|
201
|
+
|
|
202
|
+
const response = await fetch(
|
|
203
|
+
`http://localhost:${port}/api/mcp/v0/servers/${defaultMcpServerLogicalId}?version=${actualVersion}`,
|
|
204
|
+
);
|
|
205
|
+
expect(response.status).toBe(200);
|
|
206
|
+
const body = await response.json();
|
|
207
|
+
expect(body.id).toBe(defaultMcpServerLogicalId);
|
|
208
|
+
expect(body.version_detail.version).toBe(actualVersion);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('GET /api/mcp/v0/servers/:id - should return 404 if specific server version does not match', async () => {
|
|
212
|
+
const nonExistentVersion = '0.0.0-nonexistent';
|
|
213
|
+
const response = await fetch(
|
|
214
|
+
`http://localhost:${port}/api/mcp/v0/servers/${defaultMcpServerLogicalId}?version=${nonExistentVersion}`,
|
|
215
|
+
);
|
|
216
|
+
expect(response.status).toBe(404);
|
|
217
|
+
const body = await response.json();
|
|
218
|
+
expect(body).toHaveProperty('error');
|
|
219
|
+
expect(body.error).toContain(`but not version '${nonExistentVersion}'`);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('GET /api/mcp/v0/servers/:id - should return 404 for a non-existent server ID', async () => {
|
|
223
|
+
const nonExistentId = 'non-existent-server-id-12345';
|
|
224
|
+
const response = await fetch(`http://localhost:${port}/api/mcp/v0/servers/${nonExistentId}`);
|
|
225
|
+
expect(response.status).toBe(404);
|
|
226
|
+
const body = await response.json();
|
|
227
|
+
expect(body).toHaveProperty('error');
|
|
228
|
+
expect(body.error).toContain(`MCP server with ID '${nonExistentId}' not found`);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('GET /api/mcp/v0/servers - should handle pagination (limit=1, offset=0)', async () => {
|
|
232
|
+
const response = await fetch(`http://localhost:${port}/api/mcp/v0/servers?limit=1&offset=0`);
|
|
233
|
+
expect(response.status).toBe(200);
|
|
234
|
+
const body = await response.json();
|
|
235
|
+
expect(body.servers.length).toBe(1);
|
|
236
|
+
expect(body.total_count).toBeGreaterThanOrEqual(1);
|
|
237
|
+
if (body.total_count > 1) {
|
|
238
|
+
expect(body.next).not.toBeNull();
|
|
239
|
+
} else {
|
|
240
|
+
expect(body.next).toBeNull();
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
149
244
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1-alpha.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"hono": "^4.7.4",
|
|
30
30
|
"uuid": "^11.1.0",
|
|
31
31
|
"zod-from-json-schema": "^0.0.5",
|
|
32
|
-
"@mastra/core": "^0.9.
|
|
32
|
+
"@mastra/core": "^0.9.5-alpha.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"zod": "^3.0.0"
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"vitest": "^3.1.2",
|
|
50
50
|
"zod": "^3.24.3",
|
|
51
51
|
"zod-to-json-schema": "^3.24.5",
|
|
52
|
-
"@internal/lint": "0.0.
|
|
52
|
+
"@internal/lint": "0.0.5"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting",
|
|
@@ -2,11 +2,11 @@ import type { IncomingMessage, ServerResponse } from 'http';
|
|
|
2
2
|
import { createServer } from 'http';
|
|
3
3
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
4
|
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
5
|
-
import {
|
|
6
|
-
CallToolRequestSchema,
|
|
7
|
-
ListToolsRequestSchema,
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
8
|
ListResourcesRequestSchema,
|
|
9
|
-
ReadResourceRequestSchema
|
|
9
|
+
ReadResourceRequestSchema,
|
|
10
10
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
11
11
|
import { z } from 'zod';
|
|
12
12
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
@@ -32,7 +32,7 @@ const server = new Server(
|
|
|
32
32
|
{
|
|
33
33
|
capabilities: {
|
|
34
34
|
tools: {},
|
|
35
|
-
resources: {}
|
|
35
|
+
resources: {},
|
|
36
36
|
},
|
|
37
37
|
},
|
|
38
38
|
);
|
|
@@ -98,31 +98,31 @@ const weatherResources = [
|
|
|
98
98
|
uri: 'weather://current',
|
|
99
99
|
name: 'Current Weather Data',
|
|
100
100
|
description: 'Real-time weather data for the current location',
|
|
101
|
-
mimeType: 'application/json'
|
|
101
|
+
mimeType: 'application/json',
|
|
102
102
|
},
|
|
103
103
|
{
|
|
104
104
|
uri: 'weather://forecast',
|
|
105
105
|
name: 'Weather Forecast',
|
|
106
106
|
description: '5-day weather forecast',
|
|
107
|
-
mimeType: 'application/json'
|
|
107
|
+
mimeType: 'application/json',
|
|
108
108
|
},
|
|
109
109
|
{
|
|
110
110
|
uri: 'weather://historical',
|
|
111
111
|
name: 'Historical Weather Data',
|
|
112
112
|
description: 'Weather data from the past 30 days',
|
|
113
|
-
mimeType: 'application/json'
|
|
114
|
-
}
|
|
113
|
+
mimeType: 'application/json',
|
|
114
|
+
},
|
|
115
115
|
];
|
|
116
116
|
|
|
117
117
|
// List available resources
|
|
118
118
|
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
119
|
-
resources: weatherResources
|
|
119
|
+
resources: weatherResources,
|
|
120
120
|
}));
|
|
121
121
|
|
|
122
122
|
// Read resource contents
|
|
123
|
-
server.setRequestHandler(ReadResourceRequestSchema, async
|
|
123
|
+
server.setRequestHandler(ReadResourceRequestSchema, async request => {
|
|
124
124
|
const uri = request.params.uri;
|
|
125
|
-
|
|
125
|
+
|
|
126
126
|
if (uri === 'weather://current') {
|
|
127
127
|
return {
|
|
128
128
|
contents: [
|
|
@@ -135,10 +135,10 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
135
135
|
conditions: 'Partly Cloudy',
|
|
136
136
|
humidity: 65,
|
|
137
137
|
windSpeed: 12,
|
|
138
|
-
updated: new Date().toISOString()
|
|
139
|
-
})
|
|
140
|
-
}
|
|
141
|
-
]
|
|
138
|
+
updated: new Date().toISOString(),
|
|
139
|
+
}),
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
142
|
};
|
|
143
143
|
} else if (uri === 'weather://forecast') {
|
|
144
144
|
return {
|
|
@@ -151,10 +151,10 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
151
151
|
{ day: 2, high: 22, low: 14, conditions: 'Clear' },
|
|
152
152
|
{ day: 3, high: 20, low: 13, conditions: 'Partly Cloudy' },
|
|
153
153
|
{ day: 4, high: 18, low: 11, conditions: 'Rain' },
|
|
154
|
-
{ day: 5, high: 17, low: 10, conditions: 'Showers' }
|
|
155
|
-
])
|
|
156
|
-
}
|
|
157
|
-
]
|
|
154
|
+
{ day: 5, high: 17, low: 10, conditions: 'Showers' },
|
|
155
|
+
]),
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
158
|
};
|
|
159
159
|
} else if (uri === 'weather://historical') {
|
|
160
160
|
return {
|
|
@@ -168,13 +168,13 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
168
168
|
rainDays: 8,
|
|
169
169
|
sunnyDays: 18,
|
|
170
170
|
recordHigh: 28,
|
|
171
|
-
recordLow: 7
|
|
172
|
-
})
|
|
173
|
-
}
|
|
174
|
-
]
|
|
171
|
+
recordLow: 7,
|
|
172
|
+
}),
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
175
|
};
|
|
176
176
|
}
|
|
177
|
-
|
|
177
|
+
|
|
178
178
|
throw new Error(`Resource not found: ${uri}`);
|
|
179
179
|
});
|
|
180
180
|
|
package/src/server.test.ts
CHANGED
|
@@ -2,9 +2,12 @@ import http from 'node:http';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import type { ServerType } from '@hono/node-server';
|
|
4
4
|
import { serve } from '@hono/node-server';
|
|
5
|
+
import type { ToolsInput } from '@mastra/core/agent';
|
|
6
|
+
import type { MCPServerConfig, Repository, PackageInfo, RemoteInfo } from '@mastra/core/mcp';
|
|
5
7
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
8
|
import { Hono } from 'hono';
|
|
7
|
-
import { describe, it, expect, beforeAll, afterAll, afterEach, vi } from 'vitest';
|
|
9
|
+
import { describe, it, expect, beforeAll, afterAll, afterEach, vi, beforeEach } from 'vitest';
|
|
10
|
+
import { z } from 'zod';
|
|
8
11
|
import { weatherTool } from './__fixtures__/tools';
|
|
9
12
|
import { MCPClient } from './configuration';
|
|
10
13
|
import { MCPServer } from './server';
|
|
@@ -15,7 +18,197 @@ let httpServer: http.Server;
|
|
|
15
18
|
|
|
16
19
|
vi.setConfig({ testTimeout: 20000, hookTimeout: 20000 });
|
|
17
20
|
|
|
21
|
+
// Mock Date constructor for predictable release dates
|
|
22
|
+
const mockDateISO = '2024-01-01T00:00:00.000Z';
|
|
23
|
+
const mockDate = new Date(mockDateISO);
|
|
24
|
+
const OriginalDate = global.Date; // Store original Date
|
|
25
|
+
|
|
26
|
+
// Mock a simple tool
|
|
27
|
+
const mockToolExecute = vi.fn(async (args: any) => ({ result: 'tool executed', args }));
|
|
28
|
+
const mockTools: ToolsInput = {
|
|
29
|
+
testTool: {
|
|
30
|
+
description: 'A test tool',
|
|
31
|
+
parameters: z.object({ input: z.string().optional() }),
|
|
32
|
+
execute: mockToolExecute,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const minimalConfig: MCPServerConfig = {
|
|
37
|
+
name: 'TestServer',
|
|
38
|
+
version: '1.0.0',
|
|
39
|
+
tools: mockTools,
|
|
40
|
+
};
|
|
41
|
+
|
|
18
42
|
describe('MCPServer', () => {
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
vi.clearAllMocks();
|
|
45
|
+
|
|
46
|
+
// @ts-ignore - Mocking Date completely
|
|
47
|
+
global.Date = vi.fn((...args: any[]) => {
|
|
48
|
+
if (args.length === 0) {
|
|
49
|
+
// new Date()
|
|
50
|
+
return mockDate;
|
|
51
|
+
}
|
|
52
|
+
// @ts-ignore
|
|
53
|
+
return new OriginalDate(...args); // new Date('some-string') or new Date(timestamp)
|
|
54
|
+
}) as any;
|
|
55
|
+
|
|
56
|
+
// @ts-ignore
|
|
57
|
+
global.Date.now = vi.fn(() => mockDate.getTime());
|
|
58
|
+
// @ts-ignore
|
|
59
|
+
global.Date.prototype.toISOString = vi.fn(() => mockDateISO);
|
|
60
|
+
// @ts-ignore // Static Date.toISOString() might be used by some libraries
|
|
61
|
+
global.Date.toISOString = vi.fn(() => mockDateISO);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('Constructor and Metadata Initialization', () => {
|
|
65
|
+
it('should initialize with default metadata if not provided', () => {
|
|
66
|
+
const server = new MCPServer(minimalConfig);
|
|
67
|
+
expect(server.id).toBeDefined();
|
|
68
|
+
expect(server.name).toBe('TestServer');
|
|
69
|
+
expect(server.version).toBe('1.0.0');
|
|
70
|
+
expect(server.description).toBeUndefined();
|
|
71
|
+
expect(server.repository).toBeUndefined();
|
|
72
|
+
// MCPServerBase stores releaseDate as string, compare directly or re-parse
|
|
73
|
+
expect(server.releaseDate).toBe(mockDateISO);
|
|
74
|
+
expect(server.isLatest).toBe(true);
|
|
75
|
+
expect(server.packageCanonical).toBeUndefined();
|
|
76
|
+
expect(server.packages).toBeUndefined();
|
|
77
|
+
expect(server.remotes).toBeUndefined();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should initialize with custom metadata when provided', () => {
|
|
81
|
+
const repository: Repository = { url: 'https://github.com/test/repo', source: 'github', id: 'repo-id' };
|
|
82
|
+
const packages: PackageInfo[] = [{ registry_name: 'npm', name: 'test-package', version: '1.0.0' }];
|
|
83
|
+
const remotes: RemoteInfo[] = [{ transport_type: 'sse', url: 'https://test.com/sse' }];
|
|
84
|
+
const customReleaseDate = '2023-12-31T00:00:00.000Z';
|
|
85
|
+
const customConfig: MCPServerConfig = {
|
|
86
|
+
...minimalConfig,
|
|
87
|
+
id: 'custom-id-doesnt-need-uuid-format-if-set-explicitly',
|
|
88
|
+
description: 'A custom server description',
|
|
89
|
+
repository,
|
|
90
|
+
releaseDate: customReleaseDate,
|
|
91
|
+
isLatest: false,
|
|
92
|
+
packageCanonical: 'npm',
|
|
93
|
+
packages,
|
|
94
|
+
remotes,
|
|
95
|
+
};
|
|
96
|
+
const server = new MCPServer(customConfig);
|
|
97
|
+
|
|
98
|
+
expect(server.id).toBe('custom-id-doesnt-need-uuid-format-if-set-explicitly');
|
|
99
|
+
expect(server.description).toBe('A custom server description');
|
|
100
|
+
expect(server.repository).toEqual(repository);
|
|
101
|
+
expect(server.releaseDate).toBe(customReleaseDate);
|
|
102
|
+
expect(server.isLatest).toBe(false);
|
|
103
|
+
expect(server.packageCanonical).toBe('npm');
|
|
104
|
+
expect(server.packages).toEqual(packages);
|
|
105
|
+
expect(server.remotes).toEqual(remotes);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('getServerInfo()', () => {
|
|
110
|
+
it('should return correct ServerInfo with default metadata', () => {
|
|
111
|
+
const server = new MCPServer(minimalConfig);
|
|
112
|
+
const serverInfo = server.getServerInfo();
|
|
113
|
+
|
|
114
|
+
expect(serverInfo).toEqual({
|
|
115
|
+
id: expect.any(String),
|
|
116
|
+
name: 'TestServer',
|
|
117
|
+
description: undefined,
|
|
118
|
+
repository: undefined,
|
|
119
|
+
version_detail: {
|
|
120
|
+
version: '1.0.0',
|
|
121
|
+
release_date: mockDateISO,
|
|
122
|
+
is_latest: true,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should return correct ServerInfo with custom metadata', () => {
|
|
128
|
+
const repository: Repository = { url: 'https://github.com/test/repo', source: 'github', id: 'repo-id' };
|
|
129
|
+
const customReleaseDate = '2023-11-01T00:00:00.000Z';
|
|
130
|
+
const customConfig: MCPServerConfig = {
|
|
131
|
+
...minimalConfig,
|
|
132
|
+
id: 'custom-id-for-info',
|
|
133
|
+
description: 'Custom description',
|
|
134
|
+
repository,
|
|
135
|
+
releaseDate: customReleaseDate,
|
|
136
|
+
isLatest: false,
|
|
137
|
+
};
|
|
138
|
+
const server = new MCPServer(customConfig);
|
|
139
|
+
const serverInfo = server.getServerInfo();
|
|
140
|
+
|
|
141
|
+
expect(serverInfo).toEqual({
|
|
142
|
+
id: 'custom-id-for-info',
|
|
143
|
+
name: 'TestServer',
|
|
144
|
+
description: 'Custom description',
|
|
145
|
+
repository,
|
|
146
|
+
version_detail: {
|
|
147
|
+
version: '1.0.0',
|
|
148
|
+
release_date: customReleaseDate,
|
|
149
|
+
is_latest: false,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('getServerDetail()', () => {
|
|
156
|
+
it('should return correct ServerDetailInfo with default metadata', () => {
|
|
157
|
+
const server = new MCPServer(minimalConfig);
|
|
158
|
+
const serverDetail = server.getServerDetail();
|
|
159
|
+
|
|
160
|
+
expect(serverDetail).toEqual({
|
|
161
|
+
id: expect.any(String),
|
|
162
|
+
name: 'TestServer',
|
|
163
|
+
description: undefined,
|
|
164
|
+
repository: undefined,
|
|
165
|
+
version_detail: {
|
|
166
|
+
version: '1.0.0',
|
|
167
|
+
release_date: mockDateISO,
|
|
168
|
+
is_latest: true,
|
|
169
|
+
},
|
|
170
|
+
package_canonical: undefined,
|
|
171
|
+
packages: undefined,
|
|
172
|
+
remotes: undefined,
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should return correct ServerDetailInfo with custom metadata', () => {
|
|
177
|
+
const repository: Repository = { url: 'https://github.com/test/repo', source: 'github', id: 'repo-id' };
|
|
178
|
+
const packages: PackageInfo[] = [{ registry_name: 'npm', name: 'test-package', version: '1.0.0' }];
|
|
179
|
+
const remotes: RemoteInfo[] = [{ transport_type: 'sse', url: 'https://test.com/sse' }];
|
|
180
|
+
const customReleaseDate = '2023-10-01T00:00:00.000Z';
|
|
181
|
+
const customConfig: MCPServerConfig = {
|
|
182
|
+
...minimalConfig,
|
|
183
|
+
id: 'custom-id-for-detail',
|
|
184
|
+
description: 'Custom detail description',
|
|
185
|
+
repository,
|
|
186
|
+
releaseDate: customReleaseDate,
|
|
187
|
+
isLatest: true,
|
|
188
|
+
packageCanonical: 'docker',
|
|
189
|
+
packages,
|
|
190
|
+
remotes,
|
|
191
|
+
};
|
|
192
|
+
const server = new MCPServer(customConfig);
|
|
193
|
+
const serverDetail = server.getServerDetail();
|
|
194
|
+
|
|
195
|
+
expect(serverDetail).toEqual({
|
|
196
|
+
id: 'custom-id-for-detail',
|
|
197
|
+
name: 'TestServer',
|
|
198
|
+
description: 'Custom detail description',
|
|
199
|
+
repository,
|
|
200
|
+
version_detail: {
|
|
201
|
+
version: '1.0.0',
|
|
202
|
+
release_date: customReleaseDate,
|
|
203
|
+
is_latest: true,
|
|
204
|
+
},
|
|
205
|
+
package_canonical: 'docker',
|
|
206
|
+
packages,
|
|
207
|
+
remotes,
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
19
212
|
describe('MCPServer SSE transport', () => {
|
|
20
213
|
let sseRes: Response | undefined;
|
|
21
214
|
let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;
|
package/src/server.ts
CHANGED
|
@@ -4,7 +4,14 @@ import type { InternalCoreTool } from '@mastra/core';
|
|
|
4
4
|
import { makeCoreTool } from '@mastra/core';
|
|
5
5
|
import type { ToolsInput } from '@mastra/core/agent';
|
|
6
6
|
import { MCPServerBase } from '@mastra/core/mcp';
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
MCPServerConfig,
|
|
9
|
+
ServerInfo,
|
|
10
|
+
ServerDetailInfo,
|
|
11
|
+
ConvertedTool,
|
|
12
|
+
MCPServerHonoSSEOptions,
|
|
13
|
+
MCPServerSSEOptions,
|
|
14
|
+
} from '@mastra/core/mcp';
|
|
8
15
|
import { RuntimeContext } from '@mastra/core/runtime-context';
|
|
9
16
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
10
17
|
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
@@ -56,17 +63,18 @@ export class MCPServer extends MCPServerBase {
|
|
|
56
63
|
|
|
57
64
|
/**
|
|
58
65
|
* Construct a new MCPServer instance.
|
|
59
|
-
* @param opts
|
|
60
|
-
* @param opts.version - Server version
|
|
61
|
-
* @param opts.tools - Tool definitions to register
|
|
66
|
+
* @param opts - Configuration options for the server, including registry metadata.
|
|
62
67
|
*/
|
|
63
|
-
constructor(
|
|
64
|
-
super(
|
|
68
|
+
constructor(opts: MCPServerConfig) {
|
|
69
|
+
super(opts);
|
|
65
70
|
|
|
66
|
-
this.server = new Server(
|
|
71
|
+
this.server = new Server(
|
|
72
|
+
{ name: this.name, version: this.version },
|
|
73
|
+
{ capabilities: { tools: {}, logging: { enabled: true } } },
|
|
74
|
+
);
|
|
67
75
|
|
|
68
76
|
this.logger.info(
|
|
69
|
-
`Initialized MCPServer '${name}' v${version} with tools: ${Object.keys(this.convertedTools).join(', ')}`,
|
|
77
|
+
`Initialized MCPServer '${this.name}' v${this.version} (ID: ${this.id}) with tools: ${Object.keys(this.convertedTools).join(', ')}`,
|
|
70
78
|
);
|
|
71
79
|
|
|
72
80
|
this.sseHonoTransports = new Map();
|
|
@@ -107,7 +115,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
107
115
|
name: toolName,
|
|
108
116
|
description: coreTool.description,
|
|
109
117
|
parameters: coreTool.parameters,
|
|
110
|
-
execute: coreTool.execute
|
|
118
|
+
execute: coreTool.execute!,
|
|
111
119
|
};
|
|
112
120
|
this.logger.info(`Registered tool: '${toolName}' [${toolInstance?.description || 'No description'}]`);
|
|
113
121
|
}
|
|
@@ -430,4 +438,130 @@ export class MCPServer extends MCPServerBase {
|
|
|
430
438
|
this.logger.error('Error closing MCP server:', { error });
|
|
431
439
|
}
|
|
432
440
|
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Gets the basic information about the server, conforming to the Server schema.
|
|
444
|
+
* @returns ServerInfo object.
|
|
445
|
+
*/
|
|
446
|
+
public getServerInfo(): ServerInfo {
|
|
447
|
+
return {
|
|
448
|
+
id: this.id,
|
|
449
|
+
name: this.name,
|
|
450
|
+
description: this.description,
|
|
451
|
+
repository: this.repository,
|
|
452
|
+
version_detail: {
|
|
453
|
+
version: this.version,
|
|
454
|
+
release_date: this.releaseDate,
|
|
455
|
+
is_latest: this.isLatest,
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Gets detailed information about the server, conforming to the ServerDetail schema.
|
|
462
|
+
* @returns ServerDetailInfo object.
|
|
463
|
+
*/
|
|
464
|
+
public getServerDetail(): ServerDetailInfo {
|
|
465
|
+
return {
|
|
466
|
+
...this.getServerInfo(),
|
|
467
|
+
package_canonical: this.packageCanonical,
|
|
468
|
+
packages: this.packages,
|
|
469
|
+
remotes: this.remotes,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Gets a list of tools provided by this MCP server, including their schemas.
|
|
475
|
+
* This leverages the same tool information used by the internal ListTools MCP request.
|
|
476
|
+
* @returns An object containing an array of tool information.
|
|
477
|
+
*/
|
|
478
|
+
public getToolListInfo(): { tools: Array<{ name: string; description?: string; inputSchema: any }> } {
|
|
479
|
+
this.logger.debug(`Getting tool list information for MCPServer '${this.name}'`);
|
|
480
|
+
return {
|
|
481
|
+
tools: Object.entries(this.convertedTools).map(([toolId, tool]) => ({
|
|
482
|
+
id: toolId,
|
|
483
|
+
name: tool.name,
|
|
484
|
+
description: tool.description,
|
|
485
|
+
inputSchema: tool.parameters?.jsonSchema || tool.parameters,
|
|
486
|
+
})),
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Gets information for a specific tool provided by this MCP server.
|
|
492
|
+
* @param toolId The ID/name of the tool to retrieve.
|
|
493
|
+
* @returns Tool information (name, description, inputSchema) or undefined if not found.
|
|
494
|
+
*/
|
|
495
|
+
public getToolInfo(toolId: string): { name: string; description?: string; inputSchema: any } | undefined {
|
|
496
|
+
const tool = this.convertedTools[toolId];
|
|
497
|
+
if (!tool) {
|
|
498
|
+
this.logger.debug(`Tool '${toolId}' not found on MCPServer '${this.name}'`);
|
|
499
|
+
return undefined;
|
|
500
|
+
}
|
|
501
|
+
this.logger.debug(`Getting info for tool '${toolId}' on MCPServer '${this.name}'`);
|
|
502
|
+
return {
|
|
503
|
+
name: tool.name,
|
|
504
|
+
description: tool.description,
|
|
505
|
+
inputSchema: tool.parameters?.jsonSchema || tool.parameters,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Executes a specific tool provided by this MCP server.
|
|
511
|
+
* @param toolId The ID/name of the tool to execute.
|
|
512
|
+
* @param args The arguments to pass to the tool's execute function.
|
|
513
|
+
* @param executionContext Optional context for the tool execution.
|
|
514
|
+
* @returns A promise that resolves to the result of the tool execution.
|
|
515
|
+
* @throws Error if the tool is not found, validation fails, or execution fails.
|
|
516
|
+
*/
|
|
517
|
+
public async executeTool(
|
|
518
|
+
toolId: string,
|
|
519
|
+
args: any,
|
|
520
|
+
executionContext?: { messages?: any[]; toolCallId?: string },
|
|
521
|
+
): Promise<any> {
|
|
522
|
+
const tool = this.convertedTools[toolId];
|
|
523
|
+
if (!tool) {
|
|
524
|
+
this.logger.warn(`ExecuteTool: Unknown tool '${toolId}' requested on MCPServer '${this.name}'.`);
|
|
525
|
+
throw new Error(`Unknown tool: ${toolId}`);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
this.logger.debug(`ExecuteTool: Invoking '${toolId}' with arguments:`, args);
|
|
529
|
+
|
|
530
|
+
let validatedArgs = args;
|
|
531
|
+
if (tool.parameters instanceof z.ZodType && typeof tool.parameters.safeParse === 'function') {
|
|
532
|
+
const validation = tool.parameters.safeParse(args ?? {});
|
|
533
|
+
if (!validation.success) {
|
|
534
|
+
const errorMessages = validation.error.errors
|
|
535
|
+
.map((e: z.ZodIssue) => `${e.path.join('.')}: ${e.message}`)
|
|
536
|
+
.join(', ');
|
|
537
|
+
this.logger.warn(`ExecuteTool: Invalid tool arguments for '${toolId}': ${errorMessages}`, {
|
|
538
|
+
errors: validation.error.format(),
|
|
539
|
+
});
|
|
540
|
+
throw new z.ZodError(validation.error.issues);
|
|
541
|
+
}
|
|
542
|
+
validatedArgs = validation.data;
|
|
543
|
+
} else {
|
|
544
|
+
this.logger.debug(
|
|
545
|
+
`ExecuteTool: Tool '${toolId}' parameters is not a Zod schema with safeParse or is undefined. Skipping validation.`,
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (!tool.execute) {
|
|
550
|
+
this.logger.error(`ExecuteTool: Tool '${toolId}' does not have an execute function.`);
|
|
551
|
+
throw new Error(`Tool '${toolId}' cannot be executed.`);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
try {
|
|
555
|
+
const finalExecutionContext = {
|
|
556
|
+
messages: executionContext?.messages || [],
|
|
557
|
+
toolCallId: executionContext?.toolCallId || randomUUID(),
|
|
558
|
+
};
|
|
559
|
+
const result = await tool.execute(validatedArgs, finalExecutionContext);
|
|
560
|
+
this.logger.info(`ExecuteTool: Tool '${toolId}' executed successfully.`);
|
|
561
|
+
return result;
|
|
562
|
+
} catch (error) {
|
|
563
|
+
this.logger.error(`ExecuteTool: Tool execution failed for '${toolId}':`, { error });
|
|
564
|
+
throw error instanceof Error ? error : new Error(`Execution of tool '${toolId}' failed: ${String(error)}`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
433
567
|
}
|