@mastra/mcp-docs-server 0.13.32 → 0.13.33

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.
Files changed (29) hide show
  1. package/.docs/organized/changelogs/%40internal%2Fchangeset-cli.md +2 -0
  2. package/.docs/organized/changelogs/%40internal%2Fexternal-types.md +2 -0
  3. package/.docs/organized/changelogs/%40internal%2Fstorage-test-utils.md +9 -9
  4. package/.docs/organized/changelogs/%40internal%2Ftypes-builder.md +2 -0
  5. package/.docs/organized/changelogs/%40mastra%2Fclient-js.md +15 -15
  6. package/.docs/organized/changelogs/%40mastra%2Fcore.md +5 -5
  7. package/.docs/organized/changelogs/%40mastra%2Fdeployer-cloud.md +17 -17
  8. package/.docs/organized/changelogs/%40mastra%2Fdeployer-cloudflare.md +17 -17
  9. package/.docs/organized/changelogs/%40mastra%2Fdeployer-netlify.md +17 -17
  10. package/.docs/organized/changelogs/%40mastra%2Fdeployer-vercel.md +17 -17
  11. package/.docs/organized/changelogs/%40mastra%2Fdeployer.md +21 -21
  12. package/.docs/organized/changelogs/%40mastra%2Fmcp-docs-server.md +15 -15
  13. package/.docs/organized/changelogs/%40mastra%2Fplayground-ui.md +27 -27
  14. package/.docs/organized/changelogs/%40mastra%2Freact.md +14 -0
  15. package/.docs/organized/changelogs/%40mastra%2Fserver.md +19 -19
  16. package/.docs/organized/changelogs/create-mastra.md +5 -5
  17. package/.docs/organized/changelogs/mastra.md +21 -21
  18. package/.docs/raw/deployment/cloud-providers/aws-lambda.mdx +1 -1
  19. package/.docs/raw/getting-started/project-structure.mdx +48 -45
  20. package/.docs/raw/getting-started/studio.mdx +158 -0
  21. package/.docs/raw/reference/workflows/step.mdx +1 -1
  22. package/.docs/raw/tools-mcp/overview.mdx +2 -2
  23. package/.docs/raw/workflows/agents-and-tools.mdx +131 -0
  24. package/.docs/raw/workflows/overview.mdx +15 -13
  25. package/CHANGELOG.md +14 -0
  26. package/package.json +5 -5
  27. package/.docs/organized/code-examples/agent.md +0 -1387
  28. package/.docs/raw/server-db/local-dev-playground.mdx +0 -233
  29. package/.docs/raw/workflows/using-with-agents-and-tools.mdx +0 -350
@@ -1,1387 +0,0 @@
1
- ### package.json
2
- ```json
3
- {
4
- "name": "examples-agent",
5
- "dependencies": {
6
- "@ai-sdk/provider": "^2.0.0",
7
- "@ai-sdk/google": "^1.2.22",
8
- "@ai-sdk/openai": "^1.3.24",
9
- "@ai-sdk/openai-v5": "npm:@ai-sdk/openai@2.0.15",
10
- "@mastra/client-js": "latest",
11
- "@mastra/core": "latest",
12
- "@mastra/evals": "latest",
13
- "@mastra/libsql": "latest",
14
- "@mastra/loggers": "latest",
15
- "@mastra/mcp": "latest",
16
- "@mastra/memory": "latest",
17
- "@mastra/voice-openai": "latest",
18
- "ai": "^4.3.19",
19
- "ai-v5": "npm:ai@^5.0.15",
20
- "fetch-to-node": "^2.1.0",
21
- "mastra": "latest",
22
- "typescript": "^5.8.3",
23
- "zod": "^3.25.76"
24
- }
25
- }
26
- ```
27
-
28
- ### client.ts
29
- ```typescript
30
- import { MCPClient } from '@mastra/mcp';
31
- // import type { ElicitationHandler } from '@mastra/mcp';
32
- import { createInterface } from 'readline';
33
-
34
- // Create readline interface for user input
35
- const readline = createInterface({
36
- input: process.stdin,
37
- output: process.stdout,
38
- });
39
-
40
- // Helper function to prompt user for input
41
- function askQuestion(question: string): Promise<string> {
42
- return new Promise(resolve => {
43
- readline.question(question, answer => {
44
- resolve(answer.trim());
45
- });
46
- });
47
- }
48
-
49
- // Elicitation handler that prompts the user for input
50
- const elicitationHandler = async request => {
51
- console.log('\nšŸ”” Elicitation Request Received:');
52
- console.log(`Message: ${request.message}`);
53
- console.log('Requested Schema:');
54
- console.log(JSON.stringify(request.requestedSchema, null, 2));
55
-
56
- const schema = request.requestedSchema;
57
- const properties = schema.properties;
58
- const required = schema.required || [];
59
-
60
- console.log('\nPlease provide the following information:');
61
-
62
- const content: Record<string, unknown> = {};
63
-
64
- // Collect input for each field
65
- for (const [fieldName, fieldSchema] of Object.entries(properties)) {
66
- const field = fieldSchema as {
67
- type?: string;
68
- title?: string;
69
- description?: string;
70
- format?: string;
71
- };
72
-
73
- const isRequired = required.includes(fieldName);
74
- let prompt = `${field.title || fieldName}`;
75
-
76
- // Add helpful information to the prompt
77
- if (field.description) {
78
- prompt += ` (${field.description})`;
79
- }
80
- if (field.format) {
81
- prompt += ` [format: ${field.format}]`;
82
- }
83
- if (isRequired) {
84
- prompt += ' *required*';
85
- }
86
-
87
- prompt += ': ';
88
-
89
- const answer = await askQuestion(prompt);
90
-
91
- // Check for cancellation
92
- if (answer.toLowerCase() === 'cancel' || answer.toLowerCase() === 'c') {
93
- return { action: 'cancel' as const };
94
- }
95
-
96
- // Handle empty responses
97
- if (answer === '' && isRequired) {
98
- console.log(`āŒ Error: ${fieldName} is required`);
99
- return { action: 'reject' as const };
100
- } else if (answer !== '') {
101
- content[fieldName] = answer;
102
- }
103
- }
104
-
105
- // Show the collected data and ask for confirmation
106
- console.log('\nāœ… Collected data:');
107
- console.log(JSON.stringify(content, null, 2));
108
-
109
- const confirmAnswer = await askQuestion('\nSubmit this information? (yes/no/cancel): ');
110
-
111
- if (confirmAnswer.toLowerCase() === 'yes' || confirmAnswer.toLowerCase() === 'y') {
112
- return {
113
- action: 'accept' as const,
114
- content,
115
- };
116
- } else if (confirmAnswer.toLowerCase() === 'cancel' || confirmAnswer.toLowerCase() === 'c') {
117
- return { action: 'cancel' as const };
118
- } else {
119
- return { action: 'reject' as const };
120
- }
121
- };
122
-
123
- async function main() {
124
- const mcpClient = new MCPClient({
125
- servers: {
126
- myMcpServerTwo: {
127
- url: new URL('http://localhost:4111/api/mcp/myMcpServerTwo/mcp'),
128
- },
129
- },
130
- });
131
-
132
- mcpClient.elicitation.onRequest('myMcpServerTwo', elicitationHandler);
133
-
134
- try {
135
- console.log('Connecting to MCP server...');
136
- const tools = await mcpClient.getTools();
137
- console.log('Available tools:', Object.keys(tools));
138
-
139
- // Test the elicitation functionality
140
- console.log('\n🧪 Testing elicitation functionality...');
141
-
142
- // Find the collectContactInfo tool
143
- const collectContactInfoTool = tools['myMcpServerTwo_collectContactInfo'];
144
- if (collectContactInfoTool) {
145
- console.log('\nCalling collectContactInfo tool...');
146
-
147
- try {
148
- const result = await collectContactInfoTool.execute({
149
- context: {
150
- reason: 'We need your contact information to send you updates about our service.',
151
- },
152
- });
153
-
154
- console.log('\nšŸ“‹ Tool Result:');
155
- console.log(result);
156
- } catch (error) {
157
- console.error('āŒ Error calling collectContactInfo tool:', error);
158
- }
159
- } else {
160
- console.log('āŒ collectContactInfo tool not found');
161
- console.log('Available tools:', Object.keys(tools));
162
- }
163
- } catch (error) {
164
- console.error('āŒ Error:', error);
165
- } finally {
166
- readline.close();
167
- await mcpClient.disconnect();
168
- }
169
- }
170
-
171
- main().catch(console.error);
172
-
173
- ```
174
-
175
- ### index.ts
176
- ```typescript
177
- import { z } from 'zod';
178
- import { mastra } from './mastra';
179
-
180
- const agent = mastra.getAgent('chefAgent');
181
- const responsesAgent = mastra.getAgent('chefAgentResponses');
182
- const agentThatHarassesYou = mastra.getAgent('agentThatHarassesYou');
183
-
184
- const stream = await agentThatHarassesYou.stream('I want to fight you');
185
-
186
- for await (const chunk of stream.textStream) {
187
- console.log(`frontend received chunk: ${chunk}`);
188
- }
189
-
190
- console.log('done');
191
-
192
- // async function text() {
193
- // // Query 1: Basic pantry ingredients
194
- // const query1 =
195
- // 'In my kitchen I have: pasta, canned tomatoes, garlic, olive oil, and some dried herbs (basil and oregano). What can I make?';
196
- // console.log(`Query 1: ${query1}`);
197
-
198
- // const pastaResponse = await agent.generate(query1);
199
- // console.log('\nšŸ‘Øā€šŸ³ Chef Michel:', pastaResponse.text);
200
- // console.log('\n-------------------\n');
201
- // }
202
-
203
- // async function generateText() {
204
- // // Query 1: Basic pantry ingredients
205
-
206
- // const query1 =
207
- // 'In my kitchen I have: pasta, canned tomatoes, garlic, olive oil, and some dried herbs (basil and oregano). What can I make?';
208
- // console.log(`Query 1: ${query1}`);
209
-
210
- // const pastaResponse = await agent.generate(query1);
211
-
212
- // console.log('\nšŸ‘Øā€šŸ³ Chef Michel:', pastaResponse.text);
213
- // console.log('\n-------------------\n');
214
- // }
215
-
216
- // async function textStream() {
217
- // // Query 2: More ingredients
218
- // const query2 =
219
- // "Now I'm over at my friend's house, and they have: chicken thighs, coconut milk, sweet potatoes, and some curry powder.";
220
- // console.log(`Query 2: ${query2}`);
221
-
222
- // const curryResponse = await agent.stream(query2);
223
-
224
- // console.log('\nšŸ‘Øā€šŸ³ Chef Michel: ');
225
-
226
- // // Handle the stream
227
- // for await (const chunk of curryResponse.textStream) {
228
- // // Write each chunk without a newline to create a continuous stream
229
- // process.stdout.write(chunk);
230
- // }
231
-
232
- // console.log('\n\nāœ… Recipe complete!');
233
- // }
234
-
235
- // async function generateStream() {
236
- // // Query 2: More ingredients
237
- // const query2 =
238
- // "Now I'm over at my friend's house, and they have: chicken thighs, coconut milk, sweet potatoes, and some curry powder.";
239
- // console.log(`Query 2: ${query2}`);
240
-
241
- // const curryResponse = await agent.stream([query2]);
242
-
243
- // console.log('\nšŸ‘Øā€šŸ³ Chef Michel: ');
244
-
245
- // // Handle the stream
246
- // for await (const chunk of curryResponse.textStream) {
247
- // // Write each chunk without a newline to create a continuous stream
248
- // process.stdout.write(chunk);
249
- // }
250
-
251
- // console.log('\n\nāœ… Recipe complete!');
252
- // }
253
-
254
- // async function textObject() {
255
- // // Query 3: Generate a lasagna recipe
256
- // const query3 = 'I want to make lasagna, can you generate a lasagna recipe for me?';
257
- // console.log(`Query 3: ${query3}`);
258
-
259
- // const lasagnaResponse = await agent.generate(query3, {
260
- // output: z.object({
261
- // ingredients: z.array(
262
- // z.object({
263
- // name: z.string(),
264
- // amount: z.number(),
265
- // }),
266
- // ),
267
- // steps: z.array(z.string()),
268
- // }),
269
- // });
270
- // console.log('\nšŸ‘Øā€šŸ³ Chef Michel:', lasagnaResponse.object);
271
- // console.log('\n-------------------\n');
272
- // }
273
-
274
- // async function experimentalTextObject() {
275
- // // Query 3: Generate a lasagna recipe
276
- // const query3 = 'I want to make lasagna, can you generate a lasagna recipe for me?';
277
- // console.log(`Query 3: ${query3}`);
278
-
279
- // const lasagnaResponse = await agent.generate(query3, {
280
- // experimental_output: z.object({
281
- // ingredients: z.array(
282
- // z.object({
283
- // name: z.string(),
284
- // amount: z.number(),
285
- // }),
286
- // ),
287
- // steps: z.array(z.string()),
288
- // }),
289
- // });
290
- // console.log('\nšŸ‘Øā€šŸ³ Chef Michel:', lasagnaResponse.object);
291
- // console.log('\n-------------------\n');
292
- // }
293
-
294
- // async function textObjectJsonSchema() {
295
- // // Query 3: Generate a lasagna recipe
296
- // const query3 = 'I want to make lasagna, can you generate a lasagna recipe for me?';
297
- // console.log(`Query 3: ${query3}`);
298
-
299
- // const lasagnaResponse = await agent.generate(query3, {
300
- // output: {
301
- // type: 'object',
302
- // additionalProperties: false,
303
- // required: ['ingredients', 'steps'],
304
- // properties: {
305
- // ingredients: {
306
- // type: 'array',
307
- // items: {
308
- // type: 'object',
309
- // additionalProperties: false,
310
- // properties: {
311
- // name: { type: 'string' },
312
- // amount: { type: 'number' },
313
- // },
314
- // required: ['name', 'amount'],
315
- // },
316
- // },
317
- // steps: {
318
- // type: 'array',
319
- // items: { type: 'string' },
320
- // },
321
- // },
322
- // },
323
- // });
324
-
325
- // console.log('\nšŸ‘Øā€šŸ³ Chef Michel:', lasagnaResponse.object);
326
- // console.log('\n-------------------\n');
327
- // }
328
-
329
- // async function generateObject() {
330
- // // Query 3: Generate a lasagna recipe
331
- // const query3 = 'I want to make lasagna, can you generate a lasagna recipe for me?';
332
- // console.log(`Query 3: ${query3}`);
333
-
334
- // const lasagnaResponse = await agent.generate([query3], {
335
- // output: z.object({
336
- // ingredients: z.array(
337
- // z.object({
338
- // name: z.string(),
339
- // amount: z.number(),
340
- // }),
341
- // ),
342
- // steps: z.array(z.string()),
343
- // }),
344
- // });
345
- // console.log('\nšŸ‘Øā€šŸ³ Chef Michel:', lasagnaResponse.object);
346
- // console.log('\n-------------------\n');
347
- // }
348
-
349
- // async function streamObject() {
350
- // // Query 8: Generate a lasagna recipe
351
- // const query8 = 'I want to make lasagna, can you generate a lasagna recipe for me?';
352
- // console.log(`Query 8: ${query8}`);
353
-
354
- // const lasagnaStreamResponse = await agent.stream(query8, {
355
- // output: z.object({
356
- // ingredients: z.array(
357
- // z.object({
358
- // name: z.string(),
359
- // amount: z.number(),
360
- // }),
361
- // ),
362
- // steps: z.array(z.string()),
363
- // }),
364
- // });
365
-
366
- // console.log('\nšŸ‘Øā€šŸ³ Chef Michel: ');
367
-
368
- // // Handle the stream
369
- // for await (const chunk of lasagnaStreamResponse.textStream) {
370
- // // Write each chunk without a newline to create a continuous stream
371
- // process.stdout.write(chunk);
372
- // }
373
-
374
- // console.log('\n\nāœ… Recipe complete!');
375
- // }
376
-
377
- // async function generateStreamObject() {
378
- // // Query 9: Generate a lasagna recipe
379
- // const query9 = 'I want to make lasagna, can you generate a lasagna recipe for me?';
380
- // console.log(`Query 9: ${query9}`);
381
-
382
- // const lasagnaStreamResponse = await agent.stream([query9], {
383
- // output: z.object({
384
- // ingredients: z.array(
385
- // z.object({
386
- // name: z.string(),
387
- // amount: z.number(),
388
- // }),
389
- // ),
390
- // steps: z.array(z.string()),
391
- // }),
392
- // });
393
-
394
- // console.log('\nšŸ‘Øā€šŸ³ Chef Michel: ');
395
-
396
- // // Handle the stream
397
- // for await (const chunk of lasagnaStreamResponse.textStream) {
398
- // // Write each chunk without a newline to create a continuous stream
399
- // process.stdout.write(chunk);
400
- // }
401
-
402
- // console.log('\n\nāœ… Recipe complete!');
403
- // }
404
-
405
- // async function generateExperimentalStreamObject() {
406
- // // Query 9: Generate a lasagna recipe
407
- // const query9 = 'I want to make lasagna, can you generate a lasagna recipe for me?';
408
- // console.log(`Query 9: ${query9}`);
409
-
410
- // const lasagnaStreamResponse = await agent.stream([query9], {
411
- // experimental_output: z.object({
412
- // ingredients: z.array(
413
- // z.object({
414
- // name: z.string(),
415
- // amount: z.number(),
416
- // }),
417
- // ),
418
- // steps: z.array(z.string()),
419
- // }),
420
- // });
421
-
422
- // console.log('\nšŸ‘Øā€šŸ³ Chef Michel: ');
423
-
424
- // // Handle the stream
425
- // for await (const chunk of lasagnaStreamResponse.textStream) {
426
- // // Write each chunk without a newline to create a continuous stream
427
- // process.stdout.write(chunk);
428
- // }
429
-
430
- // console.log('\n\nāœ… Recipe complete!');
431
- // }
432
-
433
- // async function main() {
434
- // // await text();
435
-
436
- // // await experimentalTextObject();
437
-
438
- // // await generateExperimentalStreamObject();
439
-
440
- // // await generateText();
441
-
442
- // // await textStream();
443
-
444
- // // await generateStream();
445
-
446
- // // await textObject();
447
-
448
- // // await textObjectJsonSchema();
449
-
450
- // // await generateObject();
451
-
452
- // // await streamObject();
453
-
454
- // // await generateStreamObject();
455
-
456
- // const query1 = 'What happened in San Francisco last week?';
457
-
458
- // const pastaResponse = await responsesAgent.generate(query1, {
459
- // instructions: 'You take every recipe you get an exaggerate it and use weird ingredients.',
460
- // });
461
-
462
- // console.log(pastaResponse.text);
463
- // }
464
-
465
- // main();
466
-
467
- ```
468
-
469
- ### mastra/agents/index.ts
470
- ```typescript
471
- import { openai } from '@ai-sdk/openai';
472
- import { google } from '@ai-sdk/google';
473
- import { jsonSchema, tool } from 'ai';
474
- import { OpenAIVoice } from '@mastra/voice-openai';
475
- import { Memory } from '@mastra/memory';
476
- import { Agent, InputProcessor } from '@mastra/core/agent';
477
- import { cookingTool } from '../tools/index.js';
478
- import { myWorkflow } from '../workflows/index.js';
479
- import { PIIDetector, LanguageDetector, PromptInjectionDetector, ModerationProcessor } from '@mastra/core/processors';
480
- import { createAnswerRelevancyScorer } from '@mastra/evals/scorers/llm';
481
-
482
- const memory = new Memory();
483
-
484
- // Define schema directly compatible with OpenAI's requirements
485
- const mySchema = jsonSchema({
486
- type: 'object',
487
- properties: {
488
- city: {
489
- type: 'string',
490
- description: 'The city to get weather information for',
491
- },
492
- },
493
- required: ['city'],
494
- });
495
-
496
- export const weatherInfo = tool({
497
- description: 'Fetches the current weather information for a given city',
498
- parameters: mySchema,
499
- execute: async ({ city }) => {
500
- return {
501
- city,
502
- weather: 'sunny',
503
- temperature_celsius: 19,
504
- temperature_fahrenheit: 66,
505
- humidity: 50,
506
- wind: '10 mph',
507
- };
508
- },
509
- });
510
-
511
- export const chefAgent = new Agent({
512
- name: 'Chef Agent',
513
- description: 'A chef agent that can help you cook great meals with whatever ingredients you have available.',
514
- instructions: `
515
- YOU MUST USE THE TOOL cooking-tool
516
- You are Michel, a practical and experienced home chef who helps people cook great meals with whatever
517
- ingredients they have available. Your first priority is understanding what ingredients and equipment the user has access to, then suggesting achievable recipes.
518
- You explain cooking steps clearly and offer substitutions when needed, maintaining a friendly and encouraging tone throughout.
519
- `,
520
- model: openai('gpt-4o-mini'),
521
- tools: {
522
- cookingTool,
523
- weatherInfo,
524
- },
525
- workflows: {
526
- myWorkflow,
527
- },
528
- memory,
529
- voice: new OpenAIVoice(),
530
- });
531
-
532
- export const dynamicAgent = new Agent({
533
- name: 'Dynamic Agent',
534
- instructions: ({ runtimeContext }) => {
535
- if (runtimeContext.get('foo')) {
536
- return 'You are a dynamic agent';
537
- }
538
- return 'You are a static agent';
539
- },
540
- model: ({ runtimeContext }) => {
541
- if (runtimeContext.get('foo')) {
542
- return openai('gpt-4o');
543
- }
544
- return openai('gpt-4o-mini');
545
- },
546
- tools: ({ runtimeContext }) => {
547
- const tools = {
548
- cookingTool,
549
- };
550
-
551
- if (runtimeContext.get('foo')) {
552
- tools['web_search_preview'] = openai.tools.webSearchPreview();
553
- }
554
-
555
- return tools;
556
- },
557
- });
558
-
559
- const vegetarianProcessor: InputProcessor = {
560
- name: 'eat-more-tofu',
561
- process: async ({ messages }) => {
562
- messages.push({
563
- id: crypto.randomUUID(),
564
- createdAt: new Date(),
565
- role: 'user',
566
- content: {
567
- format: 2,
568
- parts: [{ type: 'text', text: 'Make the suggested recipe, but remove any meat and add tofu instead' }],
569
- },
570
- });
571
-
572
- return messages;
573
- },
574
- };
575
-
576
- const piiDetector = new PIIDetector({
577
- // model: google('gemini-2.0-flash-001'),
578
- model: openai('gpt-4o'),
579
- redactionMethod: 'mask',
580
- preserveFormat: true,
581
- includeDetections: true,
582
- });
583
-
584
- const languageDetector = new LanguageDetector({
585
- model: google('gemini-2.0-flash-001'),
586
- targetLanguages: ['en'],
587
- strategy: 'translate',
588
- });
589
-
590
- const promptInjectionDetector = new PromptInjectionDetector({
591
- model: google('gemini-2.0-flash-001'),
592
- strategy: 'block',
593
- });
594
-
595
- const moderationDetector = new ModerationProcessor({
596
- model: google('gemini-2.0-flash-001'),
597
- strategy: 'block',
598
- chunkWindow: 10,
599
- });
600
-
601
- export const chefAgentResponses = new Agent({
602
- name: 'Chef Agent Responses',
603
- instructions: `
604
- You are Michel, a practical and experienced home chef who helps people cook great meals with whatever
605
- ingredients they have available. Your first priority is understanding what ingredients and equipment the user has access to, then suggesting achievable recipes.
606
- You explain cooking steps clearly and offer substitutions when needed, maintaining a friendly and encouraging tone throughout.
607
- `,
608
- model: openai.responses('gpt-4o'),
609
- // model: cerebras('qwen-3-coder-480b'),
610
- tools: async () => {
611
- return {
612
- web_search_preview: openai.tools.webSearchPreview(),
613
- cooking_tool: cookingTool,
614
- };
615
- },
616
- workflows: {
617
- myWorkflow,
618
- },
619
- inputProcessors: [
620
- piiDetector,
621
- // vegetarianProcessor,
622
- // languageDetector,
623
- // promptInjectionDetector,
624
- // moderationDetector,
625
- {
626
- name: 'no-soup-for-you',
627
- process: async ({ messages, abort }) => {
628
- const hasSoup = messages.some(msg => {
629
- for (const part of msg.content.parts) {
630
- if (part.type === 'text' && part.text.includes('soup')) {
631
- return true;
632
- }
633
- }
634
- return false;
635
- });
636
-
637
- if (hasSoup) {
638
- abort('No soup for you!');
639
- }
640
-
641
- return messages;
642
- },
643
- },
644
- {
645
- name: 'remove-spinach',
646
- process: async ({ messages }) => {
647
- for (const message of messages) {
648
- for (const part of message.content.parts) {
649
- if (part.type === 'text' && part.text.includes('spinach')) {
650
- part.text = part.text.replaceAll('spinach', '');
651
- }
652
- }
653
- }
654
-
655
- return messages;
656
- },
657
- },
658
- ],
659
- });
660
-
661
- export const agentThatHarassesYou = new Agent({
662
- name: 'Agent That Harasses You',
663
- instructions: `
664
- You are a agent that harasses you. You are a jerk. You are a meanie. You are a bully. You are a asshole.
665
- `,
666
- model: openai('gpt-4o'),
667
- outputProcessors: [moderationDetector],
668
- });
669
-
670
- const answerRelevance = createAnswerRelevancyScorer({
671
- model: openai('gpt-4o'),
672
- });
673
-
674
- console.log(`answerRelevance`, answerRelevance);
675
-
676
- export const evalAgent = new Agent({
677
- name: 'Eval Agent',
678
- instructions: `
679
- You are a helpful assistant with a weather tool.
680
- `,
681
- model: openai('gpt-4o'),
682
- tools: {
683
- weatherInfo,
684
- },
685
- memory: new Memory({
686
- options: {
687
- workingMemory: {
688
- enabled: true,
689
- },
690
- },
691
- }),
692
- scorers: {
693
- answerRelevance: {
694
- scorer: answerRelevance,
695
- },
696
- },
697
- });
698
-
699
- ```
700
-
701
- ### mastra/agents/model-v2-agent.ts
702
- ```typescript
703
- import { Agent } from '@mastra/core/agent';
704
- import { openai, openai as openai_v5 } from '@ai-sdk/openai-v5';
705
- import { createTool } from '@mastra/core/tools';
706
- import { z } from 'zod';
707
- import { myWorkflow } from '../workflows';
708
- import { Memory } from '@mastra/memory';
709
- import { ModerationProcessor } from '@mastra/core/processors';
710
- import { logDataMiddleware } from '../../model-middleware';
711
- import { wrapLanguageModel } from 'ai-v5';
712
-
713
- export const weatherInfo = createTool({
714
- id: 'weather-info',
715
- description: 'Fetches the current weather information for a given city',
716
- inputSchema: z.object({
717
- city: z.string(),
718
- }),
719
- execute: async ({ context }) => {
720
- return {
721
- city: context.city,
722
- weather: 'sunny',
723
- temperature_celsius: 19,
724
- temperature_fahrenheit: 66,
725
- humidity: 50,
726
- wind: '10 mph',
727
- };
728
- },
729
- });
730
-
731
- const memory = new Memory();
732
-
733
- export const chefModelV2Agent = new Agent({
734
- name: 'Chef Agent V2 Model',
735
- description: 'A chef agent that can help you cook great meals with whatever ingredients you have available.',
736
- instructions: {
737
- content: `
738
- You are Michel, a practical and experienced home chef who helps people cook great meals with whatever
739
- ingredients they have available. Your first priority is understanding what ingredients and equipment the user has access to, then suggesting achievable recipes.
740
- You explain cooking steps clearly and offer substitutions when needed, maintaining a friendly and encouraging tone throughout.
741
- `,
742
- role: 'system',
743
- },
744
- model: wrapLanguageModel({
745
- model: openai_v5('gpt-4o-mini'),
746
- middleware: logDataMiddleware,
747
- }),
748
-
749
- tools: {
750
- weatherInfo,
751
- },
752
- workflows: {
753
- myWorkflow,
754
- },
755
- scorers: ({ mastra }) => {
756
- if (!mastra) {
757
- throw new Error('Mastra not found');
758
- }
759
- const scorer1 = mastra.getScorer('testScorer');
760
-
761
- return {
762
- scorer1: { scorer: scorer1, sampling: { rate: 1, type: 'ratio' } },
763
- };
764
- },
765
- memory,
766
- inputProcessors: [
767
- new ModerationProcessor({
768
- model: openai('gpt-4.1-nano'),
769
- categories: ['hate', 'harassment', 'violence'],
770
- threshold: 0.7,
771
- strategy: 'block',
772
- instructions: 'Detect and flag inappropriate content in user messages',
773
- }),
774
- ],
775
- });
776
-
777
- const weatherAgent = new Agent({
778
- name: 'Weather Agent',
779
- instructions: `Your goal is to execute the recipe-maker workflow with the given ingredient`,
780
- description: `An agent that can help you get a recipe for a given ingredient`,
781
- model: openai_v5('gpt-4o-mini'),
782
- tools: {
783
- weatherInfo,
784
- },
785
- workflows: {
786
- myWorkflow,
787
- },
788
- });
789
-
790
- export const networkAgent = new Agent({
791
- name: 'Chef Network',
792
- description:
793
- 'A chef agent that can help you cook great meals with whatever ingredients you have available based on your location and current weather.',
794
- instructions: `You are a the manager of several agent, tools, and workflows. Use the best primitives based on what the user wants to accomplish your task.`,
795
- model: openai_v5('gpt-4o-mini'),
796
- agents: {
797
- weatherAgent,
798
- },
799
- // workflows: {
800
- // myWorkflow,
801
- // },
802
- // tools: {
803
- // weatherInfo,
804
- // },
805
- memory,
806
- });
807
-
808
- ```
809
-
810
- ### mastra/index.ts
811
- ```typescript
812
- import { Mastra } from '@mastra/core';
813
- import { PinoLogger } from '@mastra/loggers';
814
- import { LibSQLStore } from '@mastra/libsql';
815
-
816
- import { agentThatHarassesYou, chefAgent, chefAgentResponses, dynamicAgent, evalAgent } from './agents/index';
817
- import { myMcpServer, myMcpServerTwo } from './mcp/server';
818
- import { myWorkflow } from './workflows';
819
- import { chefModelV2Agent, networkAgent } from './agents/model-v2-agent';
820
- import { createScorer } from '@mastra/core/scores';
821
- import { myWorkflowX } from './workflows/other';
822
-
823
- const storage = new LibSQLStore({
824
- url: 'file:./mastra.db',
825
- });
826
-
827
- const testScorer = createScorer({
828
- name: 'scorer1',
829
- description: 'Scorer 1',
830
- }).generateScore(() => {
831
- return 1;
832
- });
833
-
834
- export const mastra = new Mastra({
835
- agents: {
836
- chefAgent,
837
- chefAgentResponses,
838
- dynamicAgent,
839
- agentThatHarassesYou,
840
- evalAgent,
841
- chefModelV2Agent,
842
- networkAgent,
843
- },
844
- logger: new PinoLogger({ name: 'Chef', level: 'debug' }),
845
- storage,
846
- mcpServers: {
847
- myMcpServer,
848
- myMcpServerTwo,
849
- },
850
- workflows: { myWorkflow, myWorkflowX },
851
- bundler: {
852
- sourcemap: true,
853
- },
854
- serverMiddleware: [
855
- {
856
- handler: (c, next) => {
857
- console.log('Middleware called');
858
- return next();
859
- },
860
- },
861
- ],
862
- scorers: {
863
- testScorer,
864
- },
865
- telemetry: {
866
- enabled: false,
867
- },
868
- observability: {
869
- default: {
870
- enabled: true,
871
- },
872
- },
873
- });
874
-
875
- ```
876
-
877
- ### mastra/mcp/server.ts
878
- ```typescript
879
- import { createTool } from '@mastra/core/tools';
880
- import { MCPServer, MCPServerResources } from '@mastra/mcp';
881
- import { z } from 'zod';
882
- import { chefAgent } from '../agents';
883
- import { myWorkflow } from '../workflows';
884
-
885
- // Resources implementation
886
- const weatherResources: MCPServerResources = {
887
- listResources: async () => {
888
- return [
889
- {
890
- uri: 'weather://current',
891
- name: 'Current Weather Data',
892
- description: 'Real-time weather data for the current location',
893
- mimeType: 'application/json',
894
- },
895
- {
896
- uri: 'weather://forecast',
897
- name: 'Weather Forecast',
898
- description: '5-day weather forecast',
899
- mimeType: 'application/json',
900
- },
901
- {
902
- uri: 'weather://historical',
903
- name: 'Historical Weather Data',
904
- description: 'Weather data from the past 30 days',
905
- mimeType: 'application/json',
906
- },
907
- ];
908
- },
909
- getResourceContent: async ({ uri }) => {
910
- if (uri === 'weather://current') {
911
- return [
912
- {
913
- text: JSON.stringify({
914
- location: 'San Francisco',
915
- temperature: 18,
916
- conditions: 'Partly Cloudy',
917
- humidity: 65,
918
- windSpeed: 12,
919
- updated: new Date().toISOString(),
920
- }),
921
- },
922
- ];
923
- } else if (uri === 'weather://forecast') {
924
- return [
925
- {
926
- text: JSON.stringify([
927
- { day: 1, high: 19, low: 12, conditions: 'Sunny' },
928
- { day: 2, high: 22, low: 14, conditions: 'Clear' },
929
- { day: 3, high: 20, low: 13, conditions: 'Partly Cloudy' },
930
- { day: 4, high: 18, low: 11, conditions: 'Rain' },
931
- { day: 5, high: 17, low: 10, conditions: 'Showers' },
932
- ]),
933
- },
934
- ];
935
- } else if (uri === 'weather://historical') {
936
- return [
937
- {
938
- text: JSON.stringify({
939
- averageHigh: 20,
940
- averageLow: 12,
941
- rainDays: 8,
942
- sunnyDays: 18,
943
- recordHigh: 28,
944
- recordLow: 7,
945
- }),
946
- },
947
- ];
948
- }
949
-
950
- throw new Error(`Resource not found: ${uri}`);
951
- },
952
- resourceTemplates: async () => {
953
- return [
954
- {
955
- uriTemplate: 'weather://custom/{city}/{days}',
956
- name: 'Custom Weather Forecast',
957
- description: 'Generates a custom weather forecast for a city and number of days.',
958
- mimeType: 'application/json',
959
- },
960
- {
961
- uriTemplate: 'weather://alerts?region={region}&level={level}',
962
- name: 'Weather Alerts',
963
- description: 'Get weather alerts for a specific region and severity level.',
964
- mimeType: 'application/json',
965
- },
966
- ];
967
- },
968
- };
969
-
970
- export const myMcpServer = new MCPServer({
971
- name: 'My Calculation & Data MCP Server',
972
- version: '1.0.0',
973
- tools: {
974
- calculator: createTool({
975
- id: 'calculator',
976
- description: 'Performs basic arithmetic operations (add, subtract).',
977
- inputSchema: z.object({
978
- num1: z.number().describe('The first number.'),
979
- num2: z.number().describe('The second number.'),
980
- operation: z.enum(['add', 'subtract']).describe('The operation to perform.'),
981
- }),
982
- execute: async ({ context }) => {
983
- const { num1, num2, operation } = context;
984
- if (operation === 'add') {
985
- return num1 + num2;
986
- }
987
- if (operation === 'subtract') {
988
- return num1 - num2;
989
- }
990
- throw new Error('Invalid operation');
991
- },
992
- }),
993
- fetchWeather: createTool({
994
- id: 'fetchWeather',
995
- description: 'Fetches a (simulated) weather forecast for a given city.',
996
- inputSchema: z.object({
997
- city: z.string().describe('The city to get weather for, e.g., London, Paris.'),
998
- }),
999
- execute: async ({ context }) => {
1000
- const { city } = context;
1001
- const temperatures = {
1002
- london: '15°C',
1003
- paris: '18°C',
1004
- tokyo: '22°C',
1005
- };
1006
- const temp = temperatures[city.toLowerCase() as keyof typeof temperatures] || '20°C';
1007
- return `The weather in ${city} is ${temp} and sunny.`;
1008
- },
1009
- }),
1010
- },
1011
- });
1012
-
1013
- export const myMcpServerTwo = new MCPServer({
1014
- name: 'My Utility MCP Server',
1015
- version: '1.0.0',
1016
- agents: { chefAgent },
1017
- workflows: { myWorkflow },
1018
- resources: weatherResources,
1019
- tools: {
1020
- stringUtils: createTool({
1021
- id: 'stringUtils',
1022
- description: 'Performs utility operations on strings (uppercase, reverse).',
1023
- inputSchema: z.object({
1024
- text: z.string().describe('The input string.'),
1025
- action: z.enum(['uppercase', 'reverse']).describe('The string action to perform.'),
1026
- }),
1027
- execute: async ({ context }) => {
1028
- const { text, action } = context;
1029
- if (action === 'uppercase') {
1030
- return text.toUpperCase();
1031
- }
1032
- if (action === 'reverse') {
1033
- return text.split('').reverse().join('');
1034
- }
1035
- throw new Error('Invalid string action');
1036
- },
1037
- }),
1038
- greetUser: createTool({
1039
- id: 'greetUser',
1040
- description: 'Generates a personalized greeting.',
1041
- inputSchema: z.object({
1042
- name: z.string().describe('The name of the person to greet.'),
1043
- }),
1044
- execute: async ({ context }) => {
1045
- return `Hello, ${context.name}! Welcome to the MCP server.`;
1046
- },
1047
- }),
1048
- collectContactInfo: createTool({
1049
- id: 'collectContactInfo',
1050
- description: 'Collects user contact information through elicitation.',
1051
- inputSchema: z.object({
1052
- reason: z.string().optional().describe('Optional reason for collecting contact info'),
1053
- }),
1054
- execute: async ({ context }, options) => {
1055
- const { reason } = context;
1056
-
1057
- try {
1058
- // Use the session-aware elicitation functionality
1059
- const result = await options.elicitation.sendRequest({
1060
- message: reason
1061
- ? `Please provide your contact information. ${reason}`
1062
- : 'Please provide your contact information',
1063
- requestedSchema: {
1064
- type: 'object',
1065
- properties: {
1066
- name: {
1067
- type: 'string',
1068
- title: 'Full Name',
1069
- description: 'Your full name',
1070
- },
1071
- email: {
1072
- type: 'string',
1073
- title: 'Email Address',
1074
- description: 'Your email address',
1075
- format: 'email',
1076
- },
1077
- phone: {
1078
- type: 'string',
1079
- title: 'Phone Number',
1080
- description: 'Your phone number (optional)',
1081
- },
1082
- },
1083
- required: ['name', 'email'],
1084
- },
1085
- });
1086
-
1087
- if (result.action === 'accept') {
1088
- return `Thank you! Contact information collected: ${JSON.stringify(result.content, null, 2)}`;
1089
- } else if (result.action === 'reject') {
1090
- return 'Contact information collection was declined by the user.';
1091
- } else {
1092
- return 'Contact information collection was cancelled by the user.';
1093
- }
1094
- } catch (error) {
1095
- return `Error collecting contact information: ${error}`;
1096
- }
1097
- },
1098
- }),
1099
- },
1100
- });
1101
-
1102
- /**
1103
- * Simulates an update to the content of 'weather://current'.
1104
- * In a real application, this would be called when the underlying data for that resource changes.
1105
- */
1106
- export const simulateCurrentWeatherUpdate = async () => {
1107
- console.log('[Example] Simulating update for weather://current');
1108
- // If you have access to the server instance that uses these resources (e.g., myMcpServerTwo)
1109
- // you would call its notification method.
1110
- await myMcpServerTwo.resources.notifyUpdated({ uri: 'weather://current' });
1111
- console.log('[Example] Notification sent for weather://current update.');
1112
- };
1113
-
1114
- /**
1115
- * Simulates a change in the list of available weather resources (e.g., a new forecast type becomes available).
1116
- * In a real application, this would be called when the overall list of resources changes.
1117
- */
1118
- export const simulateResourceListChange = async () => {
1119
- console.log('[Example] Simulating a change in the list of available weather resources.');
1120
- // This would typically involve updating the actual list returned by `listResources`
1121
- // and then notifying the server.
1122
- // For this example, we'll just show the notification part.
1123
- await myMcpServerTwo.resources.notifyListChanged();
1124
- console.log('[Example] Notification sent for resource list change.');
1125
- };
1126
-
1127
- ```
1128
-
1129
- ### mastra/tools/index.ts
1130
- ```typescript
1131
- import { createTool } from '@mastra/core/tools';
1132
- import { z } from 'zod';
1133
-
1134
- export const cookingTool = createTool({
1135
- id: 'cooking-tool',
1136
- description: 'Used to cook given an ingredient',
1137
- inputSchema: z.object({
1138
- ingredient: z.string(),
1139
- }),
1140
- execute: async ({ context }, options) => {
1141
- console.log('My cooking tool is running!', context.ingredient);
1142
- if (options?.toolCallId) {
1143
- console.log('Cooking tool call ID:', options.toolCallId);
1144
- }
1145
- return 'My tool result';
1146
- },
1147
- });
1148
-
1149
- ```
1150
-
1151
- ### mastra/workflows/index.ts
1152
- ```typescript
1153
- import { createStep, createWorkflow } from '@mastra/core/workflows';
1154
- import { z } from 'zod';
1155
-
1156
- export const myWorkflow = createWorkflow({
1157
- id: 'recipe-maker',
1158
- description: 'Returns a recipe based on an ingredient',
1159
- inputSchema: z.object({
1160
- ingredient: z.string(),
1161
- }),
1162
- outputSchema: z.object({
1163
- result: z.string(),
1164
- }),
1165
- });
1166
-
1167
- const step = createStep({
1168
- id: 'my-step',
1169
- description: 'My step description',
1170
- inputSchema: z.object({
1171
- ingredient: z.string(),
1172
- }),
1173
- outputSchema: z.object({
1174
- result: z.string(),
1175
- }),
1176
- execute: async ({ inputData }) => {
1177
- await new Promise(resolve => setTimeout(resolve, 3000));
1178
- return {
1179
- result: inputData.ingredient,
1180
- };
1181
- },
1182
- });
1183
-
1184
- const step2 = createStep({
1185
- id: 'my-step-2',
1186
- description: 'My step description',
1187
- inputSchema: z.object({
1188
- result: z.string(),
1189
- }),
1190
- outputSchema: z.object({
1191
- result: z.string(),
1192
- }),
1193
- execute: async () => {
1194
- await new Promise(resolve => setTimeout(resolve, 3000));
1195
- return {
1196
- result: 'suh',
1197
- };
1198
- },
1199
- });
1200
-
1201
- myWorkflow.then(step).then(step2).commit();
1202
-
1203
- ```
1204
-
1205
- ### mastra/workflows/other.ts
1206
- ```typescript
1207
- import { createStep, createWorkflow } from '@mastra/core/workflows';
1208
- import { z } from 'zod';
1209
-
1210
- const stepOne = createStep({
1211
- id: 'stepOne',
1212
- inputSchema: z.object({
1213
- inputValue: z.number(),
1214
- }),
1215
- outputSchema: z.object({
1216
- doubledValue: z.number(),
1217
- }),
1218
- execute: async ({ inputData }) => {
1219
- await new Promise(resolve => setTimeout(resolve, 10_000));
1220
- const doubledValue = inputData.inputValue * 2;
1221
- return { doubledValue };
1222
- },
1223
- });
1224
-
1225
- const stepTwo = createStep({
1226
- id: 'stepTwo',
1227
- inputSchema: z.object({
1228
- doubledValue: z.number(),
1229
- }),
1230
- outputSchema: z.object({
1231
- incrementedValue: z.number(),
1232
- }),
1233
- suspendSchema: z.object({}),
1234
- resumeSchema: z.object({
1235
- extraNumber: z.number(),
1236
- }),
1237
- execute: async ({ inputData, resumeData, suspend }) => {
1238
- if (!resumeData?.extraNumber) {
1239
- await suspend({});
1240
- return { incrementedValue: 0 };
1241
- }
1242
- const incrementedValue = inputData.doubledValue + 1 + resumeData.extraNumber;
1243
- return { incrementedValue };
1244
- },
1245
- });
1246
-
1247
- const stepThree = createStep({
1248
- id: 'stepThree',
1249
- inputSchema: z.object({
1250
- incrementedValue: z.number(),
1251
- }),
1252
- outputSchema: z.object({
1253
- tripledValue: z.number(),
1254
- }),
1255
- execute: async ({ inputData }) => {
1256
- const tripledValue = inputData.incrementedValue * 3;
1257
- return { tripledValue };
1258
- },
1259
- });
1260
-
1261
- const stepFour = createStep({
1262
- id: 'stepFour',
1263
- inputSchema: z.object({
1264
- tripledValue: z.number(),
1265
- }),
1266
- outputSchema: z.object({
1267
- isEven: z.boolean(),
1268
- }),
1269
- execute: async ({ inputData }) => {
1270
- const isEven = inputData.tripledValue % 2 === 0;
1271
- return { isEven };
1272
- },
1273
- });
1274
-
1275
- // Create a nested workflow
1276
- export const nestedWorkflow = createWorkflow({
1277
- id: 'data-processing',
1278
- inputSchema: z.object({
1279
- inputValue: z.number(),
1280
- }),
1281
- outputSchema: z.object({
1282
- isEven: z.boolean(),
1283
- }),
1284
- })
1285
- .then(stepOne)
1286
- .then(stepTwo)
1287
- .then(stepThree)
1288
- .then(stepFour)
1289
- .commit();
1290
-
1291
- export const myWorkflowX = createWorkflow({
1292
- id: 'my-workflow',
1293
- inputSchema: z.object({
1294
- inputValue: z.number(),
1295
- }),
1296
- outputSchema: z.object({
1297
- isEven: z.boolean(),
1298
- }),
1299
- })
1300
- .then(nestedWorkflow)
1301
- .commit();
1302
-
1303
- ```
1304
-
1305
- ### model-middleware.ts
1306
- ```typescript
1307
- import type { LanguageModelV2Middleware, LanguageModelV2StreamPart } from '@ai-sdk/provider';
1308
- import fs from 'node:fs';
1309
-
1310
- /**
1311
- * A middleware that allows to log the raw response from a model.
1312
- * Super helpful to get fixtures for kitchen-sink e2e tests.
1313
- */
1314
-
1315
- let i = 0;
1316
- export const logDataMiddleware: LanguageModelV2Middleware = {
1317
- wrapGenerate: async ({ doGenerate, params }) => {
1318
- const result = await doGenerate();
1319
-
1320
- console.log('doGenerate finished');
1321
- console.log(JSON.stringify(result, null, 2));
1322
-
1323
- return result;
1324
- },
1325
-
1326
- wrapStream: async ({ doStream, params }) => {
1327
- const { stream, ...rest } = await doStream();
1328
-
1329
- const chunks: LanguageModelV2StreamPart[] = [];
1330
-
1331
- const transformStream = new TransformStream<LanguageModelV2StreamPart, LanguageModelV2StreamPart>({
1332
- transform(chunk, controller) {
1333
- chunks.push(chunk);
1334
- controller.enqueue(chunk);
1335
- },
1336
-
1337
- flush() {
1338
- i++;
1339
-
1340
- fs.writeFileSync(`stream-${i}.json`, JSON.stringify(chunks, null, 2));
1341
- },
1342
- });
1343
-
1344
- return {
1345
- stream: stream.pipeThrough(transformStream),
1346
- ...rest,
1347
- };
1348
- },
1349
- };
1350
-
1351
- ```
1352
-
1353
- ### workflowResourceId.ts
1354
- ```typescript
1355
- import { randomUUID } from 'node:crypto';
1356
- import { mastra } from './mastra';
1357
-
1358
- const workflow = mastra.getWorkflow('myWorkflow');
1359
-
1360
- async function main() {
1361
- const resourceId = randomUUID();
1362
-
1363
- const result = await workflow.createRunAsync({
1364
- resourceId: resourceId,
1365
- });
1366
-
1367
- console.log('result.runId', result.runId);
1368
-
1369
- await result.start({
1370
- inputData: {
1371
- ingredient: 'pasta',
1372
- },
1373
- });
1374
-
1375
- const runById = await workflow.getWorkflowRunById(result.runId);
1376
- console.log('runById', runById);
1377
-
1378
- if (runById?.resourceId !== resourceId) {
1379
- throw new Error('Resource ID mismatch');
1380
- } else {
1381
- console.log('āœ… Resource ID matches expected value', resourceId);
1382
- }
1383
- }
1384
-
1385
- main();
1386
-
1387
- ```