@mastra/mcp 1.0.0 → 1.0.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/CHANGELOG.md +18 -0
- package/README.md +9 -774
- package/dist/__fixtures__/tools.d.ts +1 -1
- package/dist/__fixtures__/tools.d.ts.map +1 -1
- package/dist/docs/SKILL.md +16 -34
- package/dist/docs/{SOURCE_MAP.json → assets/SOURCE_MAP.json} +1 -1
- package/dist/docs/{mcp/01-overview.md → references/docs-mcp-overview.md} +140 -142
- package/dist/docs/references/docs-mcp-publishing-mcp-server.md +95 -0
- package/dist/docs/references/reference-tools-mcp-client.md +962 -0
- package/dist/docs/{tools/01-reference.md → references/reference-tools-mcp-server.md} +163 -1194
- package/dist/index.cjs +6 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/package.json +8 -9
- package/dist/docs/README.md +0 -33
- package/dist/docs/mcp/02-publishing-mcp-server.md +0 -111
- package/dist/docs/tools-mcp/01-mcp-overview.md +0 -384
package/README.md
CHANGED
|
@@ -1,787 +1,22 @@
|
|
|
1
1
|
# @mastra/mcp
|
|
2
2
|
|
|
3
|
-
Model Context Protocol (MCP)
|
|
3
|
+
Mastra supports the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/docs/getting-started/intro), an open standard for connecting AI agents to external tools and resources. It serves as a universal plugin system, enabling agents to call tools regardless of language or hosting environment.
|
|
4
|
+
|
|
5
|
+
Mastra can also be used to author MCP servers, exposing agents, tools, and other structured resources via the MCP interface. These can then be accessed by any system or agent that supports the protocol.
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
9
|
+
To use MCP, install the required dependency:
|
|
10
|
+
|
|
7
11
|
```bash
|
|
8
12
|
npm install @mastra/mcp@latest
|
|
9
13
|
```
|
|
10
14
|
|
|
11
15
|
## Overview
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
The client automatically detects the transport type based on your server configuration:
|
|
16
|
-
|
|
17
|
-
- If you provide a `command`, it uses the Stdio transport.
|
|
18
|
-
- If you provide a `url`, it first attempts to use the Streamable HTTP transport (protocol version 2025-03-26) and falls back to the legacy SSE transport (protocol version 2024-11-05) if the initial connection fails.
|
|
19
|
-
|
|
20
|
-
## Usage
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
import { MCPClient } from '@mastra/mcp';
|
|
24
|
-
|
|
25
|
-
// Create a client with a Stdio server
|
|
26
|
-
const stdioClient = new MCPClient({
|
|
27
|
-
servers: {
|
|
28
|
-
myStdioClient: {
|
|
29
|
-
command: 'your-mcp-server-command',
|
|
30
|
-
args: ['--your', 'args'],
|
|
31
|
-
env: { API_KEY: 'your-api-key' }, // optional environment variables
|
|
32
|
-
capabilities: {}, // optional ClientCapabilities
|
|
33
|
-
timeout: 60000, // optional timeout for tool calls in milliseconds
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Create a client with an HTTP server (tries Streamable HTTP, falls back to SSE)
|
|
39
|
-
const httpClient = new MCPClient({
|
|
40
|
-
servers: {
|
|
41
|
-
myHttpClient: {
|
|
42
|
-
url: new URL('https://your-mcp-server.com/mcp'), // Use the base URL for Streamable HTTP
|
|
43
|
-
requestInit: {
|
|
44
|
-
// Optional fetch request configuration
|
|
45
|
-
headers: { Authorization: 'Bearer your-token' },
|
|
46
|
-
},
|
|
47
|
-
// eventSourceInit is only needed for custom headers with the legacy SSE fallback
|
|
48
|
-
eventSourceInit: {
|
|
49
|
-
/* ... */
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// Or create a client with SSE server
|
|
56
|
-
const sseClient = new MCPClient({
|
|
57
|
-
servers: {
|
|
58
|
-
mySseClient: {
|
|
59
|
-
url: new URL('https://your-mcp-server.com/sse'),
|
|
60
|
-
requestInit: {
|
|
61
|
-
headers: { Authorization: 'Bearer your-token' },
|
|
62
|
-
},
|
|
63
|
-
eventSourceInit: {
|
|
64
|
-
fetch(input: Request | URL | string, init?: RequestInit) {
|
|
65
|
-
const headers = new Headers(init?.headers || {});
|
|
66
|
-
headers.set('Authorization', 'Bearer your-token');
|
|
67
|
-
return fetch(input, {
|
|
68
|
-
...init,
|
|
69
|
-
headers,
|
|
70
|
-
});
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
timeout: 60000, // optional timeout for tool calls in milliseconds
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// Connect to the MCP server (using one of the clients above)
|
|
79
|
-
await httpClient.connect();
|
|
80
|
-
|
|
81
|
-
// List available resources
|
|
82
|
-
const resources = await httpClient.resources();
|
|
83
|
-
|
|
84
|
-
// Get available tools
|
|
85
|
-
const tools = await httpClient.tools();
|
|
86
|
-
|
|
87
|
-
// Disconnect when done
|
|
88
|
-
await httpClient.disconnect();
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## Managing Multiple MCP Servers
|
|
92
|
-
|
|
93
|
-
For applications that need to interact with multiple MCP servers, the `MCPClient` class provides a convenient way to manage multiple server connections and their tools. It also uses the automatic transport detection based on the `server` configuration:
|
|
94
|
-
|
|
95
|
-
```typescript
|
|
96
|
-
import { MCPClient } from '@mastra/mcp';
|
|
97
|
-
|
|
98
|
-
const mcp = new MCPClient({
|
|
99
|
-
servers: {
|
|
100
|
-
// Stdio-based server
|
|
101
|
-
stockPrice: {
|
|
102
|
-
command: 'npx',
|
|
103
|
-
args: ['tsx', 'stock-price.ts'],
|
|
104
|
-
env: {
|
|
105
|
-
API_KEY: 'your-api-key',
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
// HTTP-based server (tries Streamable HTTP, falls back to SSE)
|
|
109
|
-
weather: {
|
|
110
|
-
url: new URL('http://localhost:8080/mcp'), // Use the base URL for Streamable HTTP
|
|
111
|
-
requestInit: {
|
|
112
|
-
// Optional fetch request configuration
|
|
113
|
-
headers: { 'X-Api-Key': 'weather-key' },
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Get all tools from all configured servers namespaced with the server name
|
|
120
|
-
const tools = await mcp.listTools();
|
|
121
|
-
|
|
122
|
-
// Get tools grouped into a toolset object per-server
|
|
123
|
-
const toolsets = await mcp.listToolsets();
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
## Logging
|
|
127
|
-
|
|
128
|
-
The MCP client provides per-server logging capabilities, allowing you to monitor interactions with each MCP server separately:
|
|
129
|
-
|
|
130
|
-
```typescript
|
|
131
|
-
import { MCPClient, LogMessage, LoggingLevel } from '@mastra/mcp';
|
|
132
|
-
|
|
133
|
-
// Define a custom log handler
|
|
134
|
-
const weatherLogger = (logMessage: LogMessage) => {
|
|
135
|
-
console.log(`[${logMessage.level}] ${logMessage.serverName}: ${logMessage.message}`);
|
|
136
|
-
|
|
137
|
-
// Log data contains valuable information
|
|
138
|
-
console.log('Details:', logMessage.details);
|
|
139
|
-
console.log('Timestamp:', logMessage.timestamp);
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
// Initialize MCP configuration with server-specific loggers
|
|
143
|
-
const mcp = new MCPClient({
|
|
144
|
-
servers: {
|
|
145
|
-
weatherService: {
|
|
146
|
-
command: 'npx',
|
|
147
|
-
args: ['tsx', 'weather-mcp.ts'],
|
|
148
|
-
// Attach the logger to this specific server
|
|
149
|
-
logger: weatherLogger, // Use 'logger' key
|
|
150
|
-
},
|
|
151
|
-
|
|
152
|
-
stockPriceService: {
|
|
153
|
-
command: 'npx',
|
|
154
|
-
args: ['tsx', 'stock-mcp.ts'],
|
|
155
|
-
// Different logger for this service
|
|
156
|
-
logger: logMessage => {
|
|
157
|
-
// Use 'logger' key
|
|
158
|
-
// Just log errors and critical events for this service
|
|
159
|
-
if (['error', 'critical', 'alert', 'emergency'].includes(logMessage.level)) {
|
|
160
|
-
console.error(`Stock service ${logMessage.level}: ${logMessage.message}`);
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Log Message Structure
|
|
169
|
-
|
|
170
|
-
Each log message contains the following information:
|
|
171
|
-
|
|
172
|
-
```typescript
|
|
173
|
-
interface LogMessage {
|
|
174
|
-
level: LoggingLevel; // MCP SDK standard log levels
|
|
175
|
-
message: string;
|
|
176
|
-
timestamp: Date;
|
|
177
|
-
serverName: string;
|
|
178
|
-
details?: Record<string, any>;
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
The `LoggingLevel` type is directly imported from the MCP SDK, ensuring compatibility with all standard MCP log levels: `'debug' | 'info' | 'notice' | 'warning' | 'error' | 'critical' | 'alert' | 'emergency'`.
|
|
183
|
-
|
|
184
|
-
### Creating Reusable Loggers
|
|
185
|
-
|
|
186
|
-
You can create reusable logger factories for common patterns:
|
|
187
|
-
|
|
188
|
-
```typescript
|
|
189
|
-
import fs from 'node:fs';
|
|
190
|
-
|
|
191
|
-
// File logger factory with color coded output for different severity levels
|
|
192
|
-
const createFileLogger = (filePath: string) => {
|
|
193
|
-
return (logMessage: LogMessage) => {
|
|
194
|
-
// Format the message based on level
|
|
195
|
-
const prefix =
|
|
196
|
-
logMessage.level === 'emergency' ? '!!! EMERGENCY !!! ' : logMessage.level === 'alert' ? '! ALERT ! ' : '';
|
|
197
|
-
|
|
198
|
-
// Write to file with timestamp, level, etc.
|
|
199
|
-
fs.appendFileSync(
|
|
200
|
-
filePath,
|
|
201
|
-
`[${logMessage.timestamp.toISOString()}] [${logMessage.level.toUpperCase()}] ${prefix}${logMessage.message}\n`,
|
|
202
|
-
);
|
|
203
|
-
};
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
// Use the factory in configuration
|
|
207
|
-
const mcp = new MCPClient({
|
|
208
|
-
servers: {
|
|
209
|
-
weatherService: {
|
|
210
|
-
command: 'npx',
|
|
211
|
-
args: ['tsx', 'weather-mcp.ts'],
|
|
212
|
-
logger: createFileLogger('./logs/weather.log'), // Use 'logger' key
|
|
213
|
-
},
|
|
214
|
-
},
|
|
215
|
-
});
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
See the `examples/server-logging.ts` file for comprehensive examples of various logging strategies.
|
|
219
|
-
|
|
220
|
-
### Tools vs Toolsets
|
|
221
|
-
|
|
222
|
-
The MCPClient class provides two ways to access MCP tools:
|
|
223
|
-
|
|
224
|
-
#### Tools (`listTools()`)
|
|
225
|
-
|
|
226
|
-
Use this when:
|
|
227
|
-
|
|
228
|
-
- You have a single MCP connection
|
|
229
|
-
- The tools are used by a single user/context (CLI tools, automation scripts, etc)
|
|
230
|
-
- Tool configuration (API keys, credentials) remains constant
|
|
231
|
-
- You want to initialize an Agent with a fixed set of tools
|
|
232
|
-
|
|
233
|
-
```typescript
|
|
234
|
-
import { Agent } from '@mastra/core/agent';
|
|
235
|
-
import { openai } from '@ai-sdk/openai';
|
|
236
|
-
|
|
237
|
-
const agent = new Agent({
|
|
238
|
-
id: 'cli-assistant',
|
|
239
|
-
name: 'CLI Assistant',
|
|
240
|
-
instructions: 'You help users with CLI tasks',
|
|
241
|
-
model: openai('gpt-4'),
|
|
242
|
-
tools: await mcp.listTools(), // Tools are fixed at agent creation
|
|
243
|
-
});
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
#### Toolsets (`listToolsets()`)
|
|
247
|
-
|
|
248
|
-
Use this when:
|
|
249
|
-
|
|
250
|
-
- You need per-request tool configuration
|
|
251
|
-
- Tools need different credentials per user
|
|
252
|
-
- Running in a multi-user environment (web app, API, etc)
|
|
253
|
-
- Tool configuration needs to change dynamically
|
|
254
|
-
|
|
255
|
-
```typescript
|
|
256
|
-
import { MCPClient } from '@mastra/mcp';
|
|
257
|
-
import { Agent } from '@mastra/core/agent';
|
|
258
|
-
import { openai } from '@ai-sdk/openai';
|
|
259
|
-
|
|
260
|
-
// Configure MCP servers with user-specific settings before getting toolsets
|
|
261
|
-
const mcp = new MCPClient({
|
|
262
|
-
servers: {
|
|
263
|
-
stockPrice: {
|
|
264
|
-
command: 'npx',
|
|
265
|
-
args: ['tsx', 'weather-mcp.ts'],
|
|
266
|
-
env: {
|
|
267
|
-
// These would be different per user
|
|
268
|
-
API_KEY: 'user-1-api-key',
|
|
269
|
-
},
|
|
270
|
-
},
|
|
271
|
-
weather: {
|
|
272
|
-
url: new URL('http://localhost:8080/mcp'), // Use the base URL for Streamable HTTP
|
|
273
|
-
requestInit: {
|
|
274
|
-
headers: {
|
|
275
|
-
// These would be different per user
|
|
276
|
-
Authorization: 'Bearer user-1-token',
|
|
277
|
-
},
|
|
278
|
-
},
|
|
279
|
-
// eventSourceInit is only needed for custom headers with the legacy SSE fallback
|
|
280
|
-
eventSourceInit: {
|
|
281
|
-
/* ... */
|
|
282
|
-
},
|
|
283
|
-
},
|
|
284
|
-
},
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// Get the current toolsets configured for this user
|
|
288
|
-
const toolsets = await mcp.listToolsets();
|
|
289
|
-
|
|
290
|
-
// Use the agent with user-specific tool configurations
|
|
291
|
-
const response = await agent.generate('What is the weather in London?', {
|
|
292
|
-
toolsets,
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
console.log(response.text);
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
The `MCPClient` class automatically:
|
|
299
|
-
|
|
300
|
-
- Manages connections to multiple MCP servers
|
|
301
|
-
- Namespaces tools to prevent naming conflicts
|
|
302
|
-
- Handles connection lifecycle and cleanup
|
|
303
|
-
- Provides both flat and grouped access to tools
|
|
304
|
-
|
|
305
|
-
## Accessing MCP Resources
|
|
306
|
-
|
|
307
|
-
MCP servers can expose resources - data or content that can be retrieved and used in your application. The `MCPClient` class provides methods to access these resources across multiple servers:
|
|
308
|
-
|
|
309
|
-
```typescript
|
|
310
|
-
import { MCPClient } from '@mastra/mcp';
|
|
311
|
-
|
|
312
|
-
const mcp = new MCPClient({
|
|
313
|
-
servers: {
|
|
314
|
-
weather: {
|
|
315
|
-
url: new URL('http://localhost:8080/mcp'),
|
|
316
|
-
},
|
|
317
|
-
dataService: {
|
|
318
|
-
command: 'npx',
|
|
319
|
-
args: ['tsx', 'data-service.ts'],
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
// Get resources from all connected MCP servers
|
|
325
|
-
const resources = await mcp.resources.get();
|
|
326
|
-
|
|
327
|
-
// Resources are grouped by server name
|
|
328
|
-
console.log(Object.keys(resources)); // ['weather', 'dataService']
|
|
329
|
-
|
|
330
|
-
// Each server entry contains an array of resources
|
|
331
|
-
if (resources.weather) {
|
|
332
|
-
// Access resources from the weather server
|
|
333
|
-
const weatherResources = resources.weather;
|
|
334
|
-
|
|
335
|
-
// Each resource has uri, name, description, and mimeType
|
|
336
|
-
weatherResources.forEach(resource => {
|
|
337
|
-
console.log(`${resource.uri}: ${resource.name} (${resource.mimeType})`);
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
// Find a specific resource by URI
|
|
341
|
-
const forecast = weatherResources.find(r => r.uri === 'weather://forecast');
|
|
342
|
-
if (forecast) {
|
|
343
|
-
console.log(`Found forecast resource: ${forecast.description}`);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
The `getResources()` method handles errors gracefully - if a server fails or doesn't support resources, it will be omitted from the results without causing the entire operation to fail.
|
|
349
|
-
|
|
350
|
-
## Prompts
|
|
351
|
-
|
|
352
|
-
MCP servers can also expose prompts, which represent structured message templates or conversational context for agents.
|
|
353
|
-
|
|
354
|
-
### Listing Prompts
|
|
355
|
-
|
|
356
|
-
```typescript
|
|
357
|
-
const prompts = await mcp.prompts.list();
|
|
358
|
-
console.log(prompts.weather); // [ { name: 'current', ... }, ... ]
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
### Getting a Prompt and Messages
|
|
362
|
-
|
|
363
|
-
```typescript
|
|
364
|
-
const { prompt, messages } = await mcp.prompts.get({ serverName: 'weather', name: 'current' });
|
|
365
|
-
console.log(prompt); // { name: 'current', version: 'v1', ... }
|
|
366
|
-
console.log(messages); // [ { role: 'assistant', content: { type: 'text', text: '...' } }, ... ]
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
### Handling Prompt List Change Notifications
|
|
370
|
-
|
|
371
|
-
```typescript
|
|
372
|
-
mcp.prompts.onListChanged({
|
|
373
|
-
serverName: 'weather',
|
|
374
|
-
handler: () => {
|
|
375
|
-
// Refresh prompt list or update UI
|
|
376
|
-
},
|
|
377
|
-
});
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
Prompt notifications are delivered via SSE or compatible transports. Register handlers before expecting notifications.
|
|
381
|
-
|
|
382
|
-
## Authentication
|
|
383
|
-
|
|
384
|
-
### OAuth 2.0 Authentication (MCP Auth Spec)
|
|
385
|
-
|
|
386
|
-
Mastra provides full support for the [MCP OAuth specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization), including:
|
|
387
|
-
|
|
388
|
-
- **Server-side**: Protected Resource Metadata (RFC 9728), token validation middleware
|
|
389
|
-
- **Client-side**: OAuth client provider implementation with PKCE, token storage, and automatic refresh
|
|
390
|
-
|
|
391
|
-
#### Client-Side: Connecting to OAuth-Protected MCP Servers
|
|
392
|
-
|
|
393
|
-
Use `MCPOAuthClientProvider` to connect to MCP servers that require OAuth authentication:
|
|
394
|
-
|
|
395
|
-
```typescript
|
|
396
|
-
import { MCPClient, MCPOAuthClientProvider } from '@mastra/mcp';
|
|
397
|
-
|
|
398
|
-
// Create an OAuth provider for your client
|
|
399
|
-
const oauthProvider = new MCPOAuthClientProvider({
|
|
400
|
-
redirectUrl: 'http://localhost:3000/oauth/callback',
|
|
401
|
-
clientMetadata: {
|
|
402
|
-
redirect_uris: ['http://localhost:3000/oauth/callback'],
|
|
403
|
-
client_name: 'My MCP Client',
|
|
404
|
-
grant_types: ['authorization_code', 'refresh_token'],
|
|
405
|
-
response_types: ['code'],
|
|
406
|
-
},
|
|
407
|
-
// Handle authorization redirects (for CLI apps, open browser; for web apps, redirect response)
|
|
408
|
-
onRedirectToAuthorization: url => {
|
|
409
|
-
console.log(`Please visit: ${url}`);
|
|
410
|
-
// Or: window.location.href = url.toString();
|
|
411
|
-
},
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
// Create client with OAuth provider
|
|
415
|
-
const client = new MCPClient({
|
|
416
|
-
servers: {
|
|
417
|
-
protectedServer: {
|
|
418
|
-
url: new URL('https://mcp.example.com/mcp'),
|
|
419
|
-
authProvider: oauthProvider,
|
|
420
|
-
},
|
|
421
|
-
},
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
await client.connect();
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
For testing or when you already have a valid token, use `createSimpleTokenProvider`:
|
|
428
|
-
|
|
429
|
-
```typescript
|
|
430
|
-
import { MCPClient, createSimpleTokenProvider } from '@mastra/mcp';
|
|
431
|
-
|
|
432
|
-
const provider = createSimpleTokenProvider('your-access-token', {
|
|
433
|
-
redirectUrl: 'http://localhost:3000/callback',
|
|
434
|
-
clientMetadata: {
|
|
435
|
-
redirect_uris: ['http://localhost:3000/callback'],
|
|
436
|
-
client_name: 'Test Client',
|
|
437
|
-
},
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
const client = new MCPClient({
|
|
441
|
-
servers: {
|
|
442
|
-
testServer: {
|
|
443
|
-
url: new URL('https://mcp.example.com/mcp'),
|
|
444
|
-
authProvider: provider,
|
|
445
|
-
},
|
|
446
|
-
},
|
|
447
|
-
});
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
#### Server-Side: Protecting Your MCP Server with OAuth
|
|
451
|
-
|
|
452
|
-
Use `createOAuthMiddleware` to protect your MCP server endpoints:
|
|
453
|
-
|
|
454
|
-
```typescript
|
|
455
|
-
import http from 'node:http';
|
|
456
|
-
import { MCPServer, createOAuthMiddleware, createStaticTokenValidator } from '@mastra/mcp';
|
|
457
|
-
|
|
458
|
-
// Create your MCP server
|
|
459
|
-
const mcpServer = new MCPServer({
|
|
460
|
-
id: 'protected-mcp-server',
|
|
461
|
-
name: 'Protected MCP Server',
|
|
462
|
-
version: '1.0.0',
|
|
463
|
-
tools: {
|
|
464
|
-
/* your tools */
|
|
465
|
-
},
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
// Create OAuth middleware
|
|
469
|
-
const oauthMiddleware = createOAuthMiddleware({
|
|
470
|
-
oauth: {
|
|
471
|
-
resource: 'https://mcp.example.com/mcp',
|
|
472
|
-
authorizationServers: ['https://auth.example.com'],
|
|
473
|
-
scopesSupported: ['mcp:read', 'mcp:write'],
|
|
474
|
-
resourceName: 'My Protected MCP Server',
|
|
475
|
-
// For production, use proper token validation (JWT, introspection, etc.)
|
|
476
|
-
validateToken: createStaticTokenValidator(['allowed-token-1', 'allowed-token-2']),
|
|
477
|
-
},
|
|
478
|
-
mcpPath: '/mcp',
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
// Create HTTP server with OAuth protection
|
|
482
|
-
const httpServer = http.createServer(async (req, res) => {
|
|
483
|
-
const url = new URL(req.url || '', 'https://mcp.example.com');
|
|
484
|
-
|
|
485
|
-
// Apply OAuth middleware first
|
|
486
|
-
const result = await oauthMiddleware(req, res, url);
|
|
487
|
-
if (!result.proceed) return; // Middleware handled the response (401, metadata, etc.)
|
|
488
|
-
|
|
489
|
-
// Token is valid, proceed to MCP handler
|
|
490
|
-
await mcpServer.startHTTP({ url, httpPath: '/mcp', req, res });
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
httpServer.listen(3000);
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
The middleware automatically:
|
|
497
|
-
|
|
498
|
-
- Serves Protected Resource Metadata at `/.well-known/oauth-protected-resource`
|
|
499
|
-
- Returns `401 Unauthorized` with proper `WWW-Authenticate` headers when authentication is required
|
|
500
|
-
- Validates bearer tokens using your provided validator
|
|
501
|
-
|
|
502
|
-
#### Token Validation Options
|
|
503
|
-
|
|
504
|
-
For production use, implement proper token validation:
|
|
505
|
-
|
|
506
|
-
```typescript
|
|
507
|
-
import { createOAuthMiddleware, createIntrospectionValidator } from '@mastra/mcp';
|
|
508
|
-
|
|
509
|
-
// Option 1: Token introspection (RFC 7662)
|
|
510
|
-
const middleware = createOAuthMiddleware({
|
|
511
|
-
oauth: {
|
|
512
|
-
resource: 'https://mcp.example.com/mcp',
|
|
513
|
-
authorizationServers: ['https://auth.example.com'],
|
|
514
|
-
validateToken: createIntrospectionValidator('https://auth.example.com/oauth/introspect', {
|
|
515
|
-
clientId: 'mcp-server',
|
|
516
|
-
clientSecret: 'secret',
|
|
517
|
-
}),
|
|
518
|
-
},
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
// Option 2: Custom validation (JWT, database lookup, etc.)
|
|
522
|
-
const middlewareCustom = createOAuthMiddleware({
|
|
523
|
-
oauth: {
|
|
524
|
-
resource: 'https://mcp.example.com/mcp',
|
|
525
|
-
authorizationServers: ['https://auth.example.com'],
|
|
526
|
-
validateToken: async (token, resource) => {
|
|
527
|
-
// Your custom validation logic
|
|
528
|
-
const decoded = await verifyJWT(token);
|
|
529
|
-
if (!decoded) {
|
|
530
|
-
return { valid: false, error: 'invalid_token', errorDescription: 'Token verification failed' };
|
|
531
|
-
}
|
|
532
|
-
return {
|
|
533
|
-
valid: true,
|
|
534
|
-
scopes: decoded.scope?.split(' ') || [],
|
|
535
|
-
subject: decoded.sub,
|
|
536
|
-
expiresAt: decoded.exp,
|
|
537
|
-
};
|
|
538
|
-
},
|
|
539
|
-
},
|
|
540
|
-
});
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
#### Custom OAuth Storage
|
|
544
|
-
|
|
545
|
-
For persistent token storage across sessions, implement the `OAuthStorage` interface:
|
|
546
|
-
|
|
547
|
-
```typescript
|
|
548
|
-
import { MCPOAuthClientProvider, OAuthStorage } from '@mastra/mcp';
|
|
549
|
-
|
|
550
|
-
// Example: Redis-based storage
|
|
551
|
-
class RedisOAuthStorage implements OAuthStorage {
|
|
552
|
-
constructor(
|
|
553
|
-
private redis: RedisClient,
|
|
554
|
-
private prefix: string,
|
|
555
|
-
) {}
|
|
556
|
-
|
|
557
|
-
async set(key: string, value: string): Promise<void> {
|
|
558
|
-
await this.redis.set(`${this.prefix}:${key}`, value);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
async get(key: string): Promise<string | undefined> {
|
|
562
|
-
return (await this.redis.get(`${this.prefix}:${key}`)) ?? undefined;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
async delete(key: string): Promise<void> {
|
|
566
|
-
await this.redis.del(`${this.prefix}:${key}`);
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
const provider = new MCPOAuthClientProvider({
|
|
571
|
-
redirectUrl: 'http://localhost:3000/callback',
|
|
572
|
-
clientMetadata: {
|
|
573
|
-
/* ... */
|
|
574
|
-
},
|
|
575
|
-
storage: new RedisOAuthStorage(redisClient, 'oauth:user123'),
|
|
576
|
-
});
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
### OAuth Token Refresh with AuthProvider
|
|
580
|
-
|
|
581
|
-
For simpler OAuth scenarios where you just need token refresh, you can pass an `authProvider` directly:
|
|
582
|
-
|
|
583
|
-
### Custom Fetch for Dynamic Authentication
|
|
584
|
-
|
|
585
|
-
For HTTP servers, you can provide a custom `fetch` function to handle dynamic authentication, request interception, or other custom behavior. This is particularly useful when you need to refresh tokens on each request or customize request behavior.
|
|
586
|
-
|
|
587
|
-
When `fetch` is provided, `requestInit`, `eventSourceInit`, and `authProvider` become optional, as you can handle these concerns within your custom fetch function.
|
|
588
|
-
|
|
589
|
-
```typescript
|
|
590
|
-
const mcpClient = new MCPClient({
|
|
591
|
-
servers: {
|
|
592
|
-
apiServer: {
|
|
593
|
-
url: new URL('https://api.example.com/mcp'),
|
|
594
|
-
fetch: async (url, init) => {
|
|
595
|
-
// Refresh token on each request
|
|
596
|
-
const token = await getAuthToken(); // Your token refresh logic
|
|
597
|
-
|
|
598
|
-
return fetch(url, {
|
|
599
|
-
...init,
|
|
600
|
-
headers: {
|
|
601
|
-
...init?.headers,
|
|
602
|
-
Authorization: `Bearer ${token}`,
|
|
603
|
-
},
|
|
604
|
-
});
|
|
605
|
-
},
|
|
606
|
-
},
|
|
607
|
-
},
|
|
608
|
-
});
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
The custom `fetch` function is automatically used for both Streamable HTTP and SSE transports, making it a simpler alternative to configuring `requestInit` and `eventSourceInit` separately.
|
|
612
|
-
|
|
613
|
-
### SSE Authentication and Headers (Legacy Fallback)
|
|
614
|
-
|
|
615
|
-
When the client falls back to using the legacy SSE (Server-Sent Events) transport and you need to include authentication or custom headers, you have two options:
|
|
616
|
-
|
|
617
|
-
**Option 1: Using custom `fetch` (Recommended)**
|
|
618
|
-
|
|
619
|
-
The simplest approach is to provide a custom `fetch` function, which will automatically be used for both POST requests and SSE connections:
|
|
620
|
-
|
|
621
|
-
```typescript
|
|
622
|
-
const sseClient = new MCPClient({
|
|
623
|
-
servers: {
|
|
624
|
-
authenticatedSseClient: {
|
|
625
|
-
url: new URL('https://your-mcp-server.com/sse'),
|
|
626
|
-
fetch: async (url, init) => {
|
|
627
|
-
const headers = new Headers(init?.headers || {});
|
|
628
|
-
headers.set('Authorization', 'Bearer your-token');
|
|
629
|
-
return fetch(url, {
|
|
630
|
-
...init,
|
|
631
|
-
headers,
|
|
632
|
-
});
|
|
633
|
-
},
|
|
634
|
-
},
|
|
635
|
-
},
|
|
636
|
-
});
|
|
637
|
-
```
|
|
638
|
-
|
|
639
|
-
**Option 2: Using `requestInit` and `eventSourceInit`**
|
|
640
|
-
|
|
641
|
-
Alternatively, you can use both `requestInit` and `eventSourceInit`. The standard `requestInit` headers won't work alone because SSE connections using the browser's `EventSource` API don't support custom headers directly.
|
|
642
|
-
|
|
643
|
-
The `eventSourceInit` configuration allows you to customize the underlying fetch request used for the SSE connection, ensuring your authentication headers are properly included.
|
|
644
|
-
|
|
645
|
-
To properly include authentication headers or other custom headers in SSE connections when using the legacy fallback, you need to use both `requestInit` and `eventSourceInit`:
|
|
646
|
-
|
|
647
|
-
```typescript
|
|
648
|
-
const sseClient = new MCPClient({
|
|
649
|
-
servers: {
|
|
650
|
-
authenticatedSseClient: {
|
|
651
|
-
url: new URL('https://your-mcp-server.com/sse'), // Note the typical /sse path for legacy servers
|
|
652
|
-
// requestInit alone isn't enough for SSE connections
|
|
653
|
-
requestInit: {
|
|
654
|
-
headers: { Authorization: 'Bearer your-token' },
|
|
655
|
-
},
|
|
656
|
-
// eventSourceInit is required to include headers in the SSE connection
|
|
657
|
-
eventSourceInit: {
|
|
658
|
-
fetch(input: Request | URL | string, init?: RequestInit) {
|
|
659
|
-
const headers = new Headers(init?.headers || {});
|
|
660
|
-
headers.set('Authorization', 'Bearer your-token');
|
|
661
|
-
return fetch(input, {
|
|
662
|
-
...init,
|
|
663
|
-
headers,
|
|
664
|
-
});
|
|
665
|
-
},
|
|
666
|
-
},
|
|
667
|
-
},
|
|
668
|
-
},
|
|
669
|
-
});
|
|
670
|
-
```
|
|
671
|
-
|
|
672
|
-
This configuration ensures that:
|
|
673
|
-
|
|
674
|
-
1. The authentication headers are properly included in the SSE connection request
|
|
675
|
-
2. The connection can be established with the required credentials
|
|
676
|
-
3. Subsequent messages can be received through the authenticated connection
|
|
677
|
-
|
|
678
|
-
```typescript
|
|
679
|
-
const sseClient = new MastraMCPClient({
|
|
680
|
-
name: 'authenticated-sse-client',
|
|
681
|
-
server: {
|
|
682
|
-
url: new URL('https://your-mcp-server.com/sse'), // Note the typical /sse path for legacy servers
|
|
683
|
-
// requestInit alone isn't enough for SSE connections
|
|
684
|
-
requestInit: {
|
|
685
|
-
headers: { Authorization: 'Bearer your-token' },
|
|
686
|
-
},
|
|
687
|
-
// eventSourceInit is required to include headers in the SSE connection
|
|
688
|
-
eventSourceInit: {
|
|
689
|
-
fetch(input: Request | URL | string, init?: RequestInit) {
|
|
690
|
-
const headers = new Headers(init?.headers || {});
|
|
691
|
-
headers.set('Authorization', 'Bearer your-token');
|
|
692
|
-
return fetch(input, {
|
|
693
|
-
...init,
|
|
694
|
-
headers,
|
|
695
|
-
});
|
|
696
|
-
},
|
|
697
|
-
},
|
|
698
|
-
},
|
|
699
|
-
});
|
|
700
|
-
```
|
|
701
|
-
|
|
702
|
-
## Configuration (`MastraMCPServerDefinition`)
|
|
703
|
-
|
|
704
|
-
The `server` parameter for both `MastraMCPClient` and `MCPConfiguration` uses the `MastraMCPServerDefinition` type. The client automatically detects the transport type based on the provided parameters:
|
|
705
|
-
|
|
706
|
-
- If `command` is provided, it uses the Stdio transport.
|
|
707
|
-
- If `url` is provided, it first attempts to use the Streamable HTTP transport and falls back to the legacy SSE transport if the initial connection fails.
|
|
708
|
-
|
|
709
|
-
Here are the available options within `MastraMCPServerDefinition`:
|
|
710
|
-
|
|
711
|
-
- **`command`**: (Optional, string) For Stdio servers: The command to execute.
|
|
712
|
-
- **`args`**: (Optional, string[]) For Stdio servers: Arguments to pass to the command.
|
|
713
|
-
- **`env`**: (Optional, Record<string, string>) For Stdio servers: Environment variables to set for the command.
|
|
714
|
-
- **`url`**: (Optional, URL) For HTTP servers (Streamable HTTP or SSE): The URL of the server.
|
|
715
|
-
- **`fetch`**: (Optional, FetchLike) For HTTP servers: Custom fetch implementation used for all network requests. When provided, this function will be used for all HTTP requests, allowing you to add dynamic authentication headers (e.g., refreshing bearer tokens), customize request behavior per-request, or intercept and modify requests/responses. When `fetch` is provided, `requestInit`, `eventSourceInit`, and `authProvider` become optional, as you can handle these concerns within your custom fetch function.
|
|
716
|
-
- **`requestInit`**: (Optional, RequestInit) For HTTP servers: Request configuration for the fetch API. Used for the initial Streamable HTTP connection attempt and subsequent POST requests. Also used for the initial SSE connection attempt. Optional when `fetch` is provided.
|
|
717
|
-
- **`eventSourceInit`**: (Optional, EventSourceInit) **Only** for the legacy SSE fallback: Custom fetch configuration for SSE connections. Required when using custom headers with SSE. Optional when `fetch` is provided.
|
|
718
|
-
- **`authProvider`**: (Optional, OAuthClientProvider) For HTTP servers: OAuth authentication provider for automatic token refresh. Automatically passed to both Streamable HTTP and SSE transports. Optional when `fetch` is provided.
|
|
719
|
-
- **`logger`**: (Optional, LogHandler) Optional additional handler for logging.
|
|
720
|
-
- **`timeout`**: (Optional, number) Server-specific timeout in milliseconds, overriding the global client/configuration timeout.
|
|
721
|
-
- **`capabilities`**: (Optional, ClientCapabilities) Server-specific capabilities configuration.
|
|
722
|
-
- **`enableServerLogs`**: (Optional, boolean, default: `true`) Whether to enable logging for this server.
|
|
723
|
-
|
|
724
|
-
## Features
|
|
725
|
-
|
|
726
|
-
- Standard MCP client implementation
|
|
727
|
-
- Automatic tool conversion to Mastra format
|
|
728
|
-
- Resource discovery and management
|
|
729
|
-
- Multiple transport layers with automatic detection:
|
|
730
|
-
- Stdio-based for local servers (`command`)
|
|
731
|
-
- HTTP-based for remote servers (`url`): Tries Streamable HTTP first, falls back to legacy SSE.
|
|
732
|
-
- OAuth authentication with automatic token refresh (`authProvider`)
|
|
733
|
-
- Manual authentication headers for static tokens (`requestInit`, `eventSourceInit`)
|
|
734
|
-
- Per-server logging capability using all standard MCP log levels
|
|
735
|
-
- Automatic error handling and logging
|
|
736
|
-
- Tool execution with context
|
|
737
|
-
|
|
738
|
-
## Methods
|
|
739
|
-
|
|
740
|
-
### `connect()`
|
|
741
|
-
|
|
742
|
-
Establishes connection with the MCP server.
|
|
743
|
-
|
|
744
|
-
### `disconnect()`
|
|
745
|
-
|
|
746
|
-
Closes the connection with the MCP server.
|
|
747
|
-
|
|
748
|
-
### `resources()`
|
|
749
|
-
|
|
750
|
-
Lists available resources from the MCP server.
|
|
751
|
-
|
|
752
|
-
### `tools()`
|
|
753
|
-
|
|
754
|
-
Retrieves and converts MCP tools to Mastra-compatible format.
|
|
755
|
-
|
|
756
|
-
## Tool Conversion
|
|
757
|
-
|
|
758
|
-
The package automatically converts MCP tools to Mastra's format:
|
|
759
|
-
|
|
760
|
-
```typescript
|
|
761
|
-
const tools = await client.tools();
|
|
762
|
-
// Returns: { [toolName: string]: MastraTool }
|
|
763
|
-
|
|
764
|
-
// Each tool includes:
|
|
765
|
-
// - Converted JSON schema
|
|
766
|
-
// - Mastra-compatible execution wrapper
|
|
767
|
-
// - Error handling
|
|
768
|
-
// - Automatic context passing
|
|
769
|
-
```
|
|
770
|
-
|
|
771
|
-
## Error Handling
|
|
772
|
-
|
|
773
|
-
The client includes comprehensive error handling:
|
|
774
|
-
|
|
775
|
-
- Connection errors
|
|
776
|
-
- Tool execution errors
|
|
777
|
-
- Resource listing errors
|
|
778
|
-
- Schema conversion errors
|
|
17
|
+
Mastra currently supports two MCP classes:
|
|
779
18
|
|
|
780
|
-
|
|
19
|
+
- `MCPClient`: Connects to one or many MCP servers to access their tools, resources, prompts, and handle elicitation requests.
|
|
20
|
+
- `MCPServer`: Exposes Mastra tools, agents, workflows, prompts, and resources to MCP-compatible clients.
|
|
781
21
|
|
|
782
|
-
|
|
783
|
-
- [MCP Authorization Specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization)
|
|
784
|
-
- [RFC 9728 - OAuth 2.0 Protected Resource Metadata](https://www.rfc-editor.org/rfc/rfc9728.html)
|
|
785
|
-
- [@modelcontextprotocol/sdk Documentation](https://github.com/modelcontextprotocol/typescript-sdk)
|
|
786
|
-
- [Mastra Docs: Using MCP With Mastra](/docs/agents/mcp-guide)
|
|
787
|
-
- [Mastra Docs: MastraMCPClient Reference](/reference/tools/client)
|
|
22
|
+
Read the [official MCP documentation](https://mastra.ai/docs/mcp/overview) to learn more.
|