@mastra/mcp 0.10.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +34 -0
- package/dist/_tsup-dts-rollup.d.cts +332 -40
- package/dist/_tsup-dts-rollup.d.ts +332 -40
- package/dist/index.cjs +599 -45
- package/dist/index.d.cts +14 -9
- package/dist/index.d.ts +14 -9
- package/dist/index.js +604 -50
- package/package.json +4 -3
- package/src/__fixtures__/fire-crawl-complex-schema.ts +1 -1
- package/src/__fixtures__/server-weather.ts +1 -1
- package/src/__fixtures__/weather.ts +122 -190
- package/src/{client.test.ts → client/client.test.ts} +27 -0
- package/src/{client.ts → client/client.ts} +58 -5
- package/src/{configuration.test.ts → client/configuration.test.ts} +133 -21
- package/src/{configuration.ts → client/configuration.ts} +64 -48
- package/src/client/resourceActions.ts +121 -0
- package/src/index.ts +4 -4
- package/src/server/resourceActions.ts +62 -0
- package/src/{server-logging.test.ts → server/server-logging.test.ts} +9 -7
- package/src/server/server.test.ts +1126 -0
- package/src/{server.ts → server/server.ts} +444 -16
- package/src/server.test.ts +0 -467
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/mcp",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@ai-sdk/anthropic": "^1.1.15",
|
|
39
39
|
"@ai-sdk/openai": "^1.3.22",
|
|
40
|
+
"ai": "4.3.16",
|
|
40
41
|
"@hono/node-server": "^1.13.8",
|
|
41
42
|
"@mendable/firecrawl-js": "^1.24.0",
|
|
42
43
|
"@microsoft/api-extractor": "^7.52.5",
|
|
@@ -49,8 +50,8 @@
|
|
|
49
50
|
"vitest": "^3.1.2",
|
|
50
51
|
"zod": "^3.24.3",
|
|
51
52
|
"zod-to-json-schema": "^3.24.5",
|
|
52
|
-
"@internal/lint": "0.0.
|
|
53
|
-
"@mastra/core": "0.10.
|
|
53
|
+
"@internal/lint": "0.0.7",
|
|
54
|
+
"@mastra/core": "0.10.1"
|
|
54
55
|
},
|
|
55
56
|
"scripts": {
|
|
56
57
|
"build": "tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting",
|
|
@@ -4,7 +4,7 @@ import type { ToolsInput } from '@mastra/core/agent';
|
|
|
4
4
|
import FirecrawlApp from '@mendable/firecrawl-js';
|
|
5
5
|
import type { ScrapeParams, MapParams, CrawlParams, FirecrawlDocument } from '@mendable/firecrawl-js';
|
|
6
6
|
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
-
import { MCPServer } from '../server';
|
|
7
|
+
import { MCPServer } from '../server/server';
|
|
8
8
|
|
|
9
9
|
// Tool definitions
|
|
10
10
|
const SCRAPE_TOOL: Tool = {
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'http';
|
|
2
2
|
import { createServer } from 'http';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
CallToolRequestSchema,
|
|
7
|
-
ListToolsRequestSchema,
|
|
8
|
-
ListResourcesRequestSchema,
|
|
9
|
-
ReadResourceRequestSchema,
|
|
10
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { createTool } from '@mastra/core';
|
|
4
|
+
import type { Resource, ResourceTemplate } from '@modelcontextprotocol/sdk/types.js';
|
|
11
5
|
import { z } from 'zod';
|
|
12
|
-
import {
|
|
6
|
+
import { MCPServer } from '../server/server';
|
|
7
|
+
import type { MCPServerResources, MCPServerResourceContent } from '../server/server';
|
|
13
8
|
|
|
14
9
|
const getWeather = async (location: string) => {
|
|
15
10
|
// Return mock data for testing
|
|
@@ -24,29 +19,20 @@ const getWeather = async (location: string) => {
|
|
|
24
19
|
};
|
|
25
20
|
};
|
|
26
21
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
name: 'Weather Server',
|
|
30
|
-
version: '1.0.0',
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
capabilities: {
|
|
34
|
-
tools: {},
|
|
35
|
-
resources: {},
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
);
|
|
22
|
+
const serverId = 'weather-server-fixture';
|
|
23
|
+
console.log(`[${serverId}] Initializing`);
|
|
39
24
|
|
|
40
25
|
const weatherInputSchema = z.object({
|
|
41
26
|
location: z.string().describe('City name'),
|
|
42
27
|
});
|
|
43
28
|
|
|
44
|
-
const
|
|
45
|
-
|
|
29
|
+
const weatherToolDefinition = createTool({
|
|
30
|
+
id: 'getWeather',
|
|
46
31
|
description: 'Get current weather for a location',
|
|
47
|
-
|
|
32
|
+
inputSchema: weatherInputSchema,
|
|
33
|
+
execute: async ({ context }) => {
|
|
48
34
|
try {
|
|
49
|
-
const weatherData = await getWeather(
|
|
35
|
+
const weatherData = await getWeather(context.location);
|
|
50
36
|
return {
|
|
51
37
|
content: [
|
|
52
38
|
{
|
|
@@ -57,43 +43,21 @@ const weatherTool = {
|
|
|
57
43
|
isError: false,
|
|
58
44
|
};
|
|
59
45
|
} catch (error) {
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
content: [
|
|
63
|
-
{
|
|
64
|
-
type: 'text',
|
|
65
|
-
text: `Weather fetch failed: ${error.message}`,
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
isError: true,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
46
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
71
47
|
return {
|
|
72
48
|
content: [
|
|
73
49
|
{
|
|
74
50
|
type: 'text',
|
|
75
|
-
text:
|
|
51
|
+
text: `Weather fetch failed: ${message}`,
|
|
76
52
|
},
|
|
77
53
|
],
|
|
78
54
|
isError: true,
|
|
79
55
|
};
|
|
80
56
|
}
|
|
81
57
|
},
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
// Set up request handlers
|
|
85
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
86
|
-
tools: [
|
|
87
|
-
{
|
|
88
|
-
name: weatherTool.name,
|
|
89
|
-
description: weatherTool.description,
|
|
90
|
-
inputSchema: zodToJsonSchema(weatherInputSchema),
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
}));
|
|
58
|
+
});
|
|
94
59
|
|
|
95
|
-
|
|
96
|
-
const weatherResources = [
|
|
60
|
+
const weatherResourceDefinitions: Resource[] = [
|
|
97
61
|
{
|
|
98
62
|
uri: 'weather://current',
|
|
99
63
|
name: 'Current Weather Data',
|
|
@@ -114,164 +78,132 @@ const weatherResources = [
|
|
|
114
78
|
},
|
|
115
79
|
];
|
|
116
80
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (uri === 'weather://current') {
|
|
127
|
-
return {
|
|
128
|
-
contents: [
|
|
129
|
-
{
|
|
130
|
-
uri,
|
|
131
|
-
mimeType: 'application/json',
|
|
132
|
-
text: JSON.stringify({
|
|
133
|
-
location: 'San Francisco',
|
|
134
|
-
temperature: 18,
|
|
135
|
-
conditions: 'Partly Cloudy',
|
|
136
|
-
humidity: 65,
|
|
137
|
-
windSpeed: 12,
|
|
138
|
-
updated: new Date().toISOString(),
|
|
139
|
-
}),
|
|
140
|
-
},
|
|
141
|
-
],
|
|
142
|
-
};
|
|
143
|
-
} else if (uri === 'weather://forecast') {
|
|
144
|
-
return {
|
|
145
|
-
contents: [
|
|
146
|
-
{
|
|
147
|
-
uri,
|
|
148
|
-
mimeType: 'application/json',
|
|
149
|
-
text: JSON.stringify([
|
|
150
|
-
{ day: 1, high: 19, low: 12, conditions: 'Sunny' },
|
|
151
|
-
{ day: 2, high: 22, low: 14, conditions: 'Clear' },
|
|
152
|
-
{ day: 3, high: 20, low: 13, conditions: 'Partly Cloudy' },
|
|
153
|
-
{ day: 4, high: 18, low: 11, conditions: 'Rain' },
|
|
154
|
-
{ day: 5, high: 17, low: 10, conditions: 'Showers' },
|
|
155
|
-
]),
|
|
156
|
-
},
|
|
157
|
-
],
|
|
158
|
-
};
|
|
159
|
-
} else if (uri === 'weather://historical') {
|
|
160
|
-
return {
|
|
161
|
-
contents: [
|
|
162
|
-
{
|
|
163
|
-
uri,
|
|
164
|
-
mimeType: 'application/json',
|
|
165
|
-
text: JSON.stringify({
|
|
166
|
-
averageHigh: 20,
|
|
167
|
-
averageLow: 12,
|
|
168
|
-
rainDays: 8,
|
|
169
|
-
sunnyDays: 18,
|
|
170
|
-
recordHigh: 28,
|
|
171
|
-
recordLow: 7,
|
|
172
|
-
}),
|
|
173
|
-
},
|
|
174
|
-
],
|
|
175
|
-
};
|
|
176
|
-
}
|
|
81
|
+
const weatherResourceTemplatesDefinitions: ResourceTemplate[] = [
|
|
82
|
+
{
|
|
83
|
+
uriTemplate: 'weather://custom/{city}/{days}',
|
|
84
|
+
name: 'Custom Weather Forecast',
|
|
85
|
+
description: 'Generates a custom weather forecast for a city and number of days.',
|
|
86
|
+
mimeType: 'application/json',
|
|
87
|
+
},
|
|
88
|
+
];
|
|
177
89
|
|
|
178
|
-
|
|
179
|
-
|
|
90
|
+
const weatherResourceContents: Record<string, MCPServerResourceContent> = {
|
|
91
|
+
'weather://current': {
|
|
92
|
+
text: JSON.stringify({
|
|
93
|
+
location: 'San Francisco',
|
|
94
|
+
temperature: 18,
|
|
95
|
+
conditions: 'Partly Cloudy',
|
|
96
|
+
humidity: 65,
|
|
97
|
+
windSpeed: 12,
|
|
98
|
+
updated: new Date().toISOString(),
|
|
99
|
+
}),
|
|
100
|
+
},
|
|
101
|
+
'weather://forecast': {
|
|
102
|
+
text: JSON.stringify([
|
|
103
|
+
{ day: 1, high: 19, low: 12, conditions: 'Sunny' },
|
|
104
|
+
{ day: 2, high: 22, low: 14, conditions: 'Clear' },
|
|
105
|
+
{ day: 3, high: 20, low: 13, conditions: 'Partly Cloudy' },
|
|
106
|
+
{ day: 4, high: 18, low: 11, conditions: 'Rain' },
|
|
107
|
+
{ day: 5, high: 17, low: 10, conditions: 'Showers' },
|
|
108
|
+
]),
|
|
109
|
+
},
|
|
110
|
+
'weather://historical': {
|
|
111
|
+
text: JSON.stringify({
|
|
112
|
+
averageHigh: 20,
|
|
113
|
+
averageLow: 12,
|
|
114
|
+
rainDays: 8,
|
|
115
|
+
sunnyDays: 18,
|
|
116
|
+
recordHigh: 28,
|
|
117
|
+
recordLow: 7,
|
|
118
|
+
}),
|
|
119
|
+
},
|
|
120
|
+
};
|
|
180
121
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return await weatherTool.execute(args);
|
|
187
|
-
}
|
|
188
|
-
default:
|
|
189
|
-
return {
|
|
190
|
-
content: [
|
|
191
|
-
{
|
|
192
|
-
type: 'text',
|
|
193
|
-
text: `Unknown tool: ${request.params.name}`,
|
|
194
|
-
},
|
|
195
|
-
],
|
|
196
|
-
isError: true,
|
|
197
|
-
};
|
|
122
|
+
const mcpServerResources: MCPServerResources = {
|
|
123
|
+
listResources: async () => weatherResourceDefinitions,
|
|
124
|
+
getResourceContent: async ({ uri }: { uri: string }) => {
|
|
125
|
+
if (weatherResourceContents[uri]) {
|
|
126
|
+
return weatherResourceContents[uri];
|
|
198
127
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
{
|
|
204
|
-
type: 'text',
|
|
205
|
-
text: `Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
|
|
206
|
-
},
|
|
207
|
-
],
|
|
208
|
-
isError: true,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
return {
|
|
212
|
-
content: [
|
|
213
|
-
{
|
|
214
|
-
type: 'text',
|
|
215
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
216
|
-
},
|
|
217
|
-
],
|
|
218
|
-
isError: true,
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
});
|
|
128
|
+
throw new Error(`Mock resource content not found for ${uri}`);
|
|
129
|
+
},
|
|
130
|
+
resourceTemplates: async () => weatherResourceTemplatesDefinitions,
|
|
131
|
+
};
|
|
222
132
|
|
|
223
|
-
|
|
224
|
-
|
|
133
|
+
const mcpServer = new MCPServer({
|
|
134
|
+
name: serverId,
|
|
135
|
+
version: '1.0.0',
|
|
136
|
+
tools: {
|
|
137
|
+
getWeather: weatherToolDefinition,
|
|
138
|
+
},
|
|
139
|
+
resources: mcpServerResources,
|
|
140
|
+
});
|
|
225
141
|
|
|
226
142
|
const httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
227
143
|
const url = new URL(req.url || '', `http://${req.headers.host}`);
|
|
144
|
+
const connectionLogPrefix = `[${serverId}] REQ: ${req.method} ${url.pathname}`;
|
|
145
|
+
console.log(connectionLogPrefix);
|
|
146
|
+
|
|
147
|
+
await mcpServer.startSSE({
|
|
148
|
+
url,
|
|
149
|
+
ssePath: '/sse',
|
|
150
|
+
messagePath: '/message',
|
|
151
|
+
req,
|
|
152
|
+
res,
|
|
153
|
+
});
|
|
154
|
+
});
|
|
228
155
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
156
|
+
const PORT = process.env.WEATHER_SERVER_PORT || 60808;
|
|
157
|
+
console.log(`[${serverId}] Starting HTTP server on port ${PORT}`);
|
|
158
|
+
httpServer.listen(PORT, () => {
|
|
159
|
+
console.log(`[${serverId}] Weather server is running on SSE at http://localhost:${PORT}`);
|
|
160
|
+
});
|
|
233
161
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
162
|
+
// --- Interval-based Notifications ---
|
|
163
|
+
const NOTIFICATION_INTERVAL_MS = 1500;
|
|
164
|
+
let resourceUpdateCounter = 0;
|
|
165
|
+
|
|
166
|
+
const notificationInterval = setInterval(async () => {
|
|
167
|
+
// Simulate resource update for weather://current
|
|
168
|
+
resourceUpdateCounter++;
|
|
169
|
+
const newCurrentWeatherText = JSON.stringify({
|
|
170
|
+
location: 'San Francisco',
|
|
171
|
+
temperature: 18 + (resourceUpdateCounter % 5), // Vary temperature slightly
|
|
172
|
+
conditions: resourceUpdateCounter % 2 === 0 ? 'Sunny' : 'Partly Cloudy',
|
|
173
|
+
humidity: 65 + (resourceUpdateCounter % 3),
|
|
174
|
+
windSpeed: 12 + (resourceUpdateCounter % 4),
|
|
175
|
+
updated: new Date().toISOString(),
|
|
176
|
+
});
|
|
177
|
+
weatherResourceContents['weather://current'] = { text: newCurrentWeatherText };
|
|
238
178
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
console.log('Received message');
|
|
245
|
-
if (!transport) {
|
|
246
|
-
res.writeHead(503);
|
|
247
|
-
res.end('SSE connection not established');
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
await transport.handlePostMessage(req, res);
|
|
251
|
-
} else {
|
|
252
|
-
console.log('Unknown path:', url.pathname);
|
|
253
|
-
res.writeHead(404);
|
|
254
|
-
res.end();
|
|
179
|
+
const updatePrefix = `[${serverId}] IntervalUpdate`;
|
|
180
|
+
try {
|
|
181
|
+
await mcpServer.resources.notifyUpdated({ uri: 'weather://current' });
|
|
182
|
+
} catch (e: any) {
|
|
183
|
+
console.error(`${updatePrefix} - Error sending resourceUpdated for weather://current via MCPServer: ${e.message}`);
|
|
255
184
|
}
|
|
256
|
-
});
|
|
257
185
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
186
|
+
// Simulate resource list changed (less frequently, e.g., every 3rd interval)
|
|
187
|
+
if (resourceUpdateCounter % 3 === 0) {
|
|
188
|
+
const listChangePrefix = `[${serverId}] IntervalListChange`;
|
|
189
|
+
try {
|
|
190
|
+
await mcpServer.resources.notifyListChanged();
|
|
191
|
+
} catch (e: any) {
|
|
192
|
+
console.error(`${listChangePrefix} - Error sending resourceListChanged via MCPServer: ${e.message}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}, NOTIFICATION_INTERVAL_MS);
|
|
196
|
+
// --- End Interval-based Notifications ---
|
|
262
197
|
|
|
263
198
|
// Handle graceful shutdown
|
|
264
199
|
process.on('SIGINT', async () => {
|
|
265
200
|
console.log('Shutting down weather server...');
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
transport = undefined;
|
|
269
|
-
}
|
|
270
|
-
// Close the HTTP server
|
|
201
|
+
clearInterval(notificationInterval); // Clear the interval
|
|
202
|
+
await mcpServer.close();
|
|
271
203
|
httpServer.close(() => {
|
|
272
204
|
console.log('Weather server shut down complete');
|
|
273
205
|
process.exit(0);
|
|
274
206
|
});
|
|
275
207
|
});
|
|
276
208
|
|
|
277
|
-
export { server };
|
|
209
|
+
export { mcpServer as server };
|
|
@@ -18,6 +18,7 @@ async function setupTestServer(withSessionManagement: boolean) {
|
|
|
18
18
|
capabilities: {
|
|
19
19
|
logging: {},
|
|
20
20
|
tools: {},
|
|
21
|
+
resources: {},
|
|
21
22
|
},
|
|
22
23
|
},
|
|
23
24
|
);
|
|
@@ -35,6 +36,17 @@ async function setupTestServer(withSessionManagement: boolean) {
|
|
|
35
36
|
},
|
|
36
37
|
);
|
|
37
38
|
|
|
39
|
+
mcpServer.resource('test-resource', 'resource://test', () => {
|
|
40
|
+
return {
|
|
41
|
+
contents: [
|
|
42
|
+
{
|
|
43
|
+
uri: 'resource://test',
|
|
44
|
+
text: 'Hello, world!',
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
|
|
38
50
|
const serverTransport = new StreamableHTTPServerTransport({
|
|
39
51
|
sessionIdGenerator: withSessionManagement ? () => randomUUID() : undefined,
|
|
40
52
|
});
|
|
@@ -94,6 +106,21 @@ describe('MastraMCPClient with Streamable HTTP', () => {
|
|
|
94
106
|
const result = await tools.greet.execute({ context: { name: 'Stateless' } });
|
|
95
107
|
expect(result).toEqual({ content: [{ type: 'text', text: 'Hello, Stateless!' }] });
|
|
96
108
|
});
|
|
109
|
+
|
|
110
|
+
it('should list resources', async () => {
|
|
111
|
+
const resourcesResult = await client.listResources();
|
|
112
|
+
const resources = resourcesResult.resources;
|
|
113
|
+
expect(resources).toBeInstanceOf(Array);
|
|
114
|
+
const testResource = resources.find((r) => r.uri === 'resource://test');
|
|
115
|
+
expect(testResource).toBeDefined();
|
|
116
|
+
expect(testResource!.name).toBe('test-resource');
|
|
117
|
+
expect(testResource!.uri).toBe('resource://test');
|
|
118
|
+
|
|
119
|
+
const readResult = await client.readResource('resource://test');
|
|
120
|
+
expect(readResult.contents).toBeInstanceOf(Array);
|
|
121
|
+
expect(readResult.contents.length).toBe(1);
|
|
122
|
+
expect(readResult.contents[0].text).toBe('Hello, world!');
|
|
123
|
+
});
|
|
97
124
|
});
|
|
98
125
|
|
|
99
126
|
describe('Stateful Mode', () => {
|
|
@@ -10,14 +10,22 @@ import { getDefaultEnvironment, StdioClientTransport } from '@modelcontextprotoc
|
|
|
10
10
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
11
11
|
import type { StreamableHTTPClientTransportOptions } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
12
12
|
import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
13
|
-
import type { Protocol } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
14
13
|
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
15
14
|
import type { ClientCapabilities, LoggingLevel } from '@modelcontextprotocol/sdk/types.js';
|
|
16
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
CallToolResultSchema,
|
|
17
|
+
ListResourcesResultSchema,
|
|
18
|
+
ReadResourceResultSchema,
|
|
19
|
+
ResourceListChangedNotificationSchema,
|
|
20
|
+
ResourceUpdatedNotificationSchema,
|
|
21
|
+
ListResourceTemplatesResultSchema,
|
|
22
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
23
|
+
|
|
17
24
|
import { asyncExitHook, gracefulExit } from 'exit-hook';
|
|
18
25
|
import { z } from 'zod';
|
|
19
26
|
import { convertJsonSchemaToZod } from 'zod-from-json-schema';
|
|
20
27
|
import type { JSONSchema } from 'zod-from-json-schema';
|
|
28
|
+
import { ResourceClientActions } from './resourceActions';
|
|
21
29
|
|
|
22
30
|
// Re-export MCP SDK LoggingLevel for convenience
|
|
23
31
|
export type { LoggingLevel } from '@modelcontextprotocol/sdk/types.js';
|
|
@@ -110,6 +118,7 @@ export class InternalMastraMCPClient extends MastraBase {
|
|
|
110
118
|
private serverConfig: MastraMCPServerDefinition;
|
|
111
119
|
private transport?: Transport;
|
|
112
120
|
private currentOperationContext: RuntimeContext | null = null;
|
|
121
|
+
public readonly resources: ResourceClientActions;
|
|
113
122
|
|
|
114
123
|
constructor({
|
|
115
124
|
name,
|
|
@@ -137,6 +146,8 @@ export class InternalMastraMCPClient extends MastraBase {
|
|
|
137
146
|
|
|
138
147
|
// Set up log message capturing
|
|
139
148
|
this.setupLogging();
|
|
149
|
+
|
|
150
|
+
this.resources = new ResourceClientActions({ client: this, logger: this.logger });
|
|
140
151
|
}
|
|
141
152
|
|
|
142
153
|
/**
|
|
@@ -317,15 +328,57 @@ export class InternalMastraMCPClient extends MastraBase {
|
|
|
317
328
|
}
|
|
318
329
|
}
|
|
319
330
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
async resources(): Promise<ReturnType<Protocol<any, any, any>['request']>> {
|
|
331
|
+
async listResources() {
|
|
323
332
|
this.log('debug', `Requesting resources from MCP server`);
|
|
324
333
|
return await this.client.request({ method: 'resources/list' }, ListResourcesResultSchema, {
|
|
325
334
|
timeout: this.timeout,
|
|
326
335
|
});
|
|
327
336
|
}
|
|
328
337
|
|
|
338
|
+
async readResource(uri: string) {
|
|
339
|
+
this.log('debug', `Reading resource from MCP server: ${uri}`);
|
|
340
|
+
return await this.client.request({ method: 'resources/read', params: { uri } }, ReadResourceResultSchema, {
|
|
341
|
+
timeout: this.timeout,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async subscribeResource(uri: string) {
|
|
346
|
+
this.log('debug', `Subscribing to resource on MCP server: ${uri}`);
|
|
347
|
+
return await this.client.request({ method: 'resources/subscribe', params: { uri } }, z.object({}), {
|
|
348
|
+
timeout: this.timeout,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async unsubscribeResource(uri: string) {
|
|
353
|
+
this.log('debug', `Unsubscribing from resource on MCP server: ${uri}`);
|
|
354
|
+
return await this.client.request({ method: 'resources/unsubscribe', params: { uri } }, z.object({}), {
|
|
355
|
+
timeout: this.timeout,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async listResourceTemplates() {
|
|
360
|
+
this.log('debug', `Requesting resource templates from MCP server`);
|
|
361
|
+
return await this.client.request({ method: 'resources/templates/list' }, ListResourceTemplatesResultSchema, {
|
|
362
|
+
timeout: this.timeout,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
setResourceUpdatedNotificationHandler(
|
|
367
|
+
handler: (params: z.infer<typeof ResourceUpdatedNotificationSchema>['params']) => void,
|
|
368
|
+
): void {
|
|
369
|
+
this.log('debug', 'Setting resource updated notification handler');
|
|
370
|
+
this.client.setNotificationHandler(ResourceUpdatedNotificationSchema, notification => {
|
|
371
|
+
handler(notification.params);
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
setResourceListChangedNotificationHandler(handler: () => void): void {
|
|
376
|
+
this.log('debug', 'Setting resource list changed notification handler');
|
|
377
|
+
this.client.setNotificationHandler(ResourceListChangedNotificationSchema, () => {
|
|
378
|
+
handler();
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
329
382
|
private convertInputSchema(
|
|
330
383
|
inputSchema: Awaited<ReturnType<Client['listTools']>>['tools'][0]['inputSchema'] | JSONSchema,
|
|
331
384
|
): z.ZodType {
|