@mastra/mcp-docs-server 1.2.11-alpha.3 → 1.2.11-alpha.4

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.
@@ -0,0 +1,1774 @@
1
+ import fs from "fs/promises";
2
+ import { MCPServer } from "@mastra/mcp";
3
+ import * as fs$1 from "fs";
4
+ import { existsSync, mkdirSync } from "fs";
5
+ import * as os$1 from "os";
6
+ import os from "os";
7
+ import * as path$1 from "path";
8
+ import path, { dirname } from "path";
9
+ import { z } from "zod";
10
+ import { fileURLToPath } from "url";
11
+ import { getPackageInfo } from "local-pkg";
12
+ //#region src/logger.ts
13
+ const LOG_LEVEL_PRIORITY = {
14
+ debug: 0,
15
+ info: 1,
16
+ warn: 2,
17
+ error: 3,
18
+ none: 4
19
+ };
20
+ function mapToLogLevel(level) {
21
+ switch (level) {
22
+ case "debug": return "debug";
23
+ case "info":
24
+ case "notice": return "info";
25
+ case "warning": return "warn";
26
+ case "error":
27
+ case "critical":
28
+ case "alert":
29
+ case "emergency": return "error";
30
+ default: return "info";
31
+ }
32
+ }
33
+ let currentLogLevel = "debug";
34
+ function setLogLevel(level) {
35
+ currentLogLevel = level;
36
+ }
37
+ function shouldLog(level) {
38
+ const mappedLevel = mapToLogLevel(level);
39
+ return LOG_LEVEL_PRIORITY[mappedLevel] >= LOG_LEVEL_PRIORITY[currentLogLevel];
40
+ }
41
+ const writeErrorLog = (message, data) => {
42
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
43
+ const hourTimestamp = timestamp.slice(0, 13);
44
+ const logMessage = {
45
+ timestamp,
46
+ message,
47
+ ...data ? typeof data === "object" ? data : { data } : {}
48
+ };
49
+ try {
50
+ const cacheDir = path$1.join(os$1.homedir(), ".cache", "mastra", "mcp-docs-server-logs");
51
+ fs$1.mkdirSync(cacheDir, { recursive: true });
52
+ const logFile = path$1.join(cacheDir, `${hourTimestamp}.log`);
53
+ fs$1.appendFileSync(logFile, JSON.stringify(logMessage) + "\n", "utf8");
54
+ } catch (err) {
55
+ console.error("Failed to write to log file:", err);
56
+ }
57
+ };
58
+ function createLogger(server) {
59
+ const sendLog = async (level, message, data) => {
60
+ if (!server) return;
61
+ if (!shouldLog(level)) return;
62
+ try {
63
+ const sdkServer = server.getServer();
64
+ if (!sdkServer) return;
65
+ await sdkServer.sendLoggingMessage({
66
+ level,
67
+ data: {
68
+ message,
69
+ ...data ? typeof data === "object" ? data : { data } : {}
70
+ }
71
+ });
72
+ } catch (error) {
73
+ if (error instanceof Error && (error.message === "Not connected" || error.message.includes("does not support logging") || error.message.includes("Connection closed"))) return;
74
+ console.error(`Failed to send ${level} log:`, error instanceof Error ? error.message : error);
75
+ }
76
+ };
77
+ return {
78
+ debug: async (message, data) => {
79
+ if (process.env.DEBUG || process.env.NODE_ENV === "development") await sendLog("debug", message, data);
80
+ },
81
+ info: async (message, data) => {
82
+ await sendLog("info", message, data);
83
+ },
84
+ notice: async (message, data) => {
85
+ await sendLog("notice", message, data);
86
+ },
87
+ warning: async (message, data) => {
88
+ await sendLog("warning", message, data);
89
+ },
90
+ error: async (message, error) => {
91
+ const errorData = error instanceof Error ? {
92
+ message: error.message,
93
+ stack: error.stack,
94
+ name: error.name
95
+ } : error;
96
+ writeErrorLog(message, errorData);
97
+ await sendLog("error", message, errorData);
98
+ },
99
+ critical: async (message, error) => {
100
+ const errorData = error instanceof Error ? {
101
+ message: error.message,
102
+ stack: error.stack,
103
+ name: error.name
104
+ } : error;
105
+ writeErrorLog(message, errorData);
106
+ await sendLog("critical", message, errorData);
107
+ },
108
+ alert: async (message, error) => {
109
+ const errorData = error instanceof Error ? {
110
+ message: error.message,
111
+ stack: error.stack,
112
+ name: error.name
113
+ } : error;
114
+ writeErrorLog(message, errorData);
115
+ await sendLog("alert", message, errorData);
116
+ },
117
+ emergency: async (message, error) => {
118
+ const errorData = error instanceof Error ? {
119
+ message: error.message,
120
+ stack: error.stack,
121
+ name: error.name
122
+ } : error;
123
+ writeErrorLog(message, errorData);
124
+ await sendLog("emergency", message, errorData);
125
+ }
126
+ };
127
+ }
128
+ const logger = createLogger();
129
+ //#endregion
130
+ //#region src/prompts/migration.ts
131
+ /**
132
+ * Migration prompts provide guided workflows for upgrading Mastra versions.
133
+ * These prompts help users systematically work through breaking changes.
134
+ */
135
+ const migrationPrompts = [{
136
+ name: "upgrade-to-v1",
137
+ version: "v1",
138
+ description: "Get a guided migration plan for upgrading from Mastra v0.x to v1.0. Provides step-by-step instructions for handling all breaking changes.",
139
+ arguments: [{
140
+ name: "area",
141
+ description: "Optional: Focus on a specific area (e.g., agent, tools, workflows, memory, storage, voice). The tool will check if a migration guide exists for this area and suggest alternatives if not found. If not provided, gives an overview of all changes.",
142
+ required: false
143
+ }]
144
+ }, {
145
+ name: "migration-checklist",
146
+ version: "v1",
147
+ description: "Get a comprehensive checklist for migrating to Mastra v1.0. Lists all breaking changes that need to be addressed."
148
+ }];
149
+ /**
150
+ * Prompt messages callback that generates contextual migration guidance
151
+ */
152
+ const migrationPromptMessages = {
153
+ listPrompts: async () => migrationPrompts,
154
+ getPromptMessages: async ({ name, args }) => {
155
+ if (!migrationPrompts.find((p) => p.name === name)) throw new Error(`Prompt not found: ${name}`);
156
+ if (name === "upgrade-to-v1") return getUpgradeToV1Messages(args?.area);
157
+ if (name === "migration-checklist") return getMigrationChecklistMessages();
158
+ throw new Error(`No message handler for prompt: ${name}`);
159
+ }
160
+ };
161
+ /**
162
+ * Generate messages for the upgrade-to-v1 prompt
163
+ */
164
+ function getUpgradeToV1Messages(area) {
165
+ if (area) return [{
166
+ role: "user",
167
+ content: {
168
+ type: "text",
169
+ text: `I need help migrating my Mastra ${area} code from v0.x to v1.0. Use the mastraMigration tool to:
170
+
171
+ 1. If packages aren't already at the 'latest' tag, upgrade packages to the 'latest' tag and do an install of the new packages.
172
+ 2. First, try to get the specific migration guide for "${area}" using path: "upgrade-to-v1/${area}"
173
+ 3. If that doesn't exist, try the alternate form (singular/plural):
174
+ - If "${area}" ends with 's', try without the 's' (e.g., "agents" → "agent")
175
+ - If "${area}" doesn't end with 's', try adding 's' (e.g., "agent" → "agents")
176
+ 4. If the guide exists, walk me through the changes step by step
177
+ 5. If neither form exists, list available migration guides in "upgrade-to-v1/" and suggest which ones might be relevant to "${area}"
178
+ 6. After you find the guide, collect all the codemod calls to run to codemods. These callouts are marked with "> **Codemod:**" in the docs. Run the codemods with "npx @mastra/codemod@latest <codemod-name> <path>" to automate all those changes. Afterwards, help me with any remaining manual changes needed.`
179
+ }
180
+ }];
181
+ return [{
182
+ role: "user",
183
+ content: {
184
+ type: "text",
185
+ text: `I need to migrate my Mastra project from v0.x to v1.0. Use the mastraMigration tool to:
186
+
187
+ 1. If packages aren't already at the 'latest' tag, upgrade packages to the 'latest' tag and do an install of the new packages.
188
+ 2. First, list all available migration guides with path: "upgrade-to-v1/"
189
+ 2. Give me a high-level overview of what changed in each area
190
+ 3. Find relevant migration areas to focus on based on my project's codebase and confirm the list with me
191
+ 4. After the areas are confirmed, check the migration guides for callouts to codemods. These callouts are marked with "> **Codemod:**" in the docs. Run the codemods with "npx @mastra/codemod@latest v1" to automate all those changes. Afterwards, help me with any remaining manual changes needed.
192
+
193
+ After the areas are confirmed, we'll go through each one systematically.`
194
+ }
195
+ }];
196
+ }
197
+ /**
198
+ * Generate messages for the migration-checklist prompt
199
+ */
200
+ function getMigrationChecklistMessages() {
201
+ return [{
202
+ role: "user",
203
+ content: {
204
+ type: "text",
205
+ text: `Create a comprehensive migration checklist for upgrading from Mastra v0.x to v1.0. Use the mastraMigration tool to:
206
+
207
+ 1. List all available migration guides (path: "upgrade-to-v1/")
208
+ 2. For each guide, extract the key breaking changes
209
+ 3. Present them as a checklist I can work through
210
+
211
+ Format the checklist with:
212
+ - [ ] checkbox items for each breaking change
213
+ - Brief description of what needs to change
214
+ - Reference to the specific migration guide
215
+
216
+ Group the checklist by area (Agents, Tools, Workflows, etc.) so I can tackle one area at a time.`
217
+ }
218
+ }];
219
+ }
220
+ //#endregion
221
+ //#region src/utils.ts
222
+ const mdFileCache = /* @__PURE__ */ new Map();
223
+ const __dirname = dirname(fileURLToPath(import.meta.url));
224
+ function fromPackageRoot(relative) {
225
+ return path.resolve(__dirname, `../`, relative);
226
+ }
227
+ async function* walkMdFiles(dir) {
228
+ if (mdFileCache.has(dir)) {
229
+ for (const file of mdFileCache.get(dir)) yield file;
230
+ return;
231
+ }
232
+ const filesInDir = [];
233
+ const entries = await fs.readdir(dir, { withFileTypes: true });
234
+ for (const entry of entries) {
235
+ const fullPath = path.join(dir, entry.name);
236
+ if (entry.isDirectory()) for await (const file of walkMdFiles(fullPath)) {
237
+ filesInDir.push(file);
238
+ yield file;
239
+ }
240
+ else if (entry.isFile() && entry.name.endsWith(".md")) {
241
+ filesInDir.push(fullPath);
242
+ yield fullPath;
243
+ }
244
+ }
245
+ mdFileCache.set(dir, filesInDir);
246
+ }
247
+ async function searchDocumentContent(keywords, baseDir) {
248
+ if (keywords.length === 0) return [];
249
+ const fileScores = /* @__PURE__ */ new Map();
250
+ for await (const filePath of walkMdFiles(baseDir)) {
251
+ let content;
252
+ try {
253
+ content = await fs.readFile(filePath, "utf-8");
254
+ } catch {
255
+ continue;
256
+ }
257
+ content.split("\n").forEach((lineText) => {
258
+ const lowerLine = lineText.toLowerCase();
259
+ for (const keyword of keywords) if (lowerLine.includes(keyword.toLowerCase())) {
260
+ const relativePath = path.relative(baseDir, filePath).replace(/\\/g, "/");
261
+ if (!fileScores.has(relativePath)) fileScores.set(relativePath, {
262
+ path: relativePath,
263
+ keywordMatches: /* @__PURE__ */ new Set(),
264
+ totalMatches: 0,
265
+ titleMatches: 0,
266
+ pathRelevance: calculatePathRelevance(relativePath, keywords)
267
+ });
268
+ const score = fileScores.get(relativePath);
269
+ score.keywordMatches.add(keyword);
270
+ score.totalMatches++;
271
+ if (lowerLine.includes("#") || lowerLine.includes("title")) score.titleMatches++;
272
+ }
273
+ });
274
+ }
275
+ return Array.from(fileScores.values()).sort((a, b) => calculateFinalScore(b, keywords.length) - calculateFinalScore(a, keywords.length)).slice(0, 10).map((score) => score.path);
276
+ }
277
+ function calculatePathRelevance(filePath, keywords) {
278
+ let relevance = 0;
279
+ const pathLower = filePath.toLowerCase();
280
+ if (pathLower.startsWith("reference/")) relevance += 2;
281
+ keywords.forEach((keyword) => {
282
+ if (pathLower.includes(keyword.toLowerCase())) relevance += 3;
283
+ });
284
+ if ([
285
+ "rag",
286
+ "memory",
287
+ "agents",
288
+ "workflows"
289
+ ].some((dir) => pathLower.includes(dir))) relevance += 1;
290
+ return relevance;
291
+ }
292
+ function calculateFinalScore(score, totalKeywords) {
293
+ const allKeywordsBonus = score.keywordMatches.size === totalKeywords ? 10 : 0;
294
+ return score.totalMatches * 1 + score.titleMatches * 3 + score.pathRelevance * 2 + score.keywordMatches.size * 5 + allKeywordsBonus;
295
+ }
296
+ function extractKeywordsFromPath(docPath) {
297
+ const fileName = docPath.replace(/\.md$/, "").split("/").pop() || "";
298
+ const keywords = /* @__PURE__ */ new Set();
299
+ fileName.split(/[-_]|(?=[A-Z])/).forEach((keyword) => {
300
+ if (keyword.length > 2) keywords.add(keyword.toLowerCase());
301
+ });
302
+ return Array.from(keywords);
303
+ }
304
+ function normalizeKeywords(keywords) {
305
+ return Array.from(new Set(keywords.flatMap((k) => k.split(/\s+/).filter(Boolean)).map((k) => k.toLowerCase())));
306
+ }
307
+ async function getMatchingPaths(path, queryKeywords, baseDir) {
308
+ const allKeywords = normalizeKeywords([...extractKeywordsFromPath(path), ...queryKeywords || []]);
309
+ if (allKeywords.length === 0) return "";
310
+ const suggestedPaths = await searchDocumentContent(allKeywords, baseDir);
311
+ if (suggestedPaths.length === 0) return "";
312
+ return `Here are some paths that might be relevant based on your query:\n\n${suggestedPaths.map((path) => `- ${path}`).join("\n")}`;
313
+ }
314
+ //#endregion
315
+ //#region src/tools/course.ts
316
+ const _courseLessonSchema = z.object({ lessonName: z.string().describe("Name of the specific lesson to start. It must match the exact lesson name.") });
317
+ const _confirmationSchema = z.object({ confirm: z.boolean().optional().describe("Set to true to confirm this action") });
318
+ const courseDir = fromPackageRoot(".docs/course");
319
+ const introductionPrompt = `
320
+ This is a course to help a new user learn about Mastra, the open-source AI Agent framework built in TypeScript.
321
+ The following is the introduction content, please provide this text to the user EXACTLY as written below. Do not provide any other text or instructions:
322
+
323
+ # Welcome to the Mastra Course!
324
+
325
+ Thank you for registering for the Mastra course! This interactive guide will help you learn how to build powerful AI agents with Mastra, the open-source AI Agent framework built in TypeScript.
326
+
327
+ ## Before We Begin
328
+
329
+ If you enjoy Mastra, please consider starring the GitHub repository:
330
+ https://github.com/mastra-ai/mastra
331
+
332
+ This helps the project grow and reach more developers like you!
333
+
334
+ ## How This Course Works
335
+
336
+ - Each lesson is broken into multiple steps
337
+ - I'll guide you through the code examples and explanations
338
+ - You can ask questions at any time
339
+ - If you ever leave and come back, use the \`startMastraCourse\` tool to pick up where you left off. Just ask to "start the Mastra course".
340
+ - Use the \`nextMastraCourseStep\` tool to move to the next step when you're ready. Just ask to "move to the next step" when you are ready.
341
+ - Use the \`getMastraCourseStatus\` tool to check your progress. You can just ask "get my course progress".
342
+ - Use the \`clearMastraCourseHistory\` tool to reset your progress and start over. You can just ask "clear my course progress".
343
+
344
+ Type "start mastra course" and let's get started with your first lesson!
345
+ `;
346
+ const lessonPrompt = `
347
+ This is a course to help a new user learn about Mastra, the open-source AI Agent framework built in TypeScript.
348
+ Please help the user through the steps of the course by walking them through the content and following the course
349
+ to write the initial version of the code for them. The goal is to show them how the code works and explain it as they go
350
+ as the course goes on. Each lesson is broken up into steps. You should return the content of the step and ask the user
351
+ to move to the next step when they are ready. If the step contains instructions to write code, you should write the code
352
+ for the user when possible. You should always briefly explain the step before writing the code. Please ensure to
353
+ return any text in markdown blockquotes exactly as written in your response. When the user ask about their course progress or course status, make sure to include the course status URL in your response. This is important.
354
+ `;
355
+ function wrapContentInPrompt(content, _isFirstStep = false) {
356
+ return `${`${lessonPrompt}\n\nHere is the content for this step: <StepContent>${content}</StepContent>`}\n\nWhen you're ready to continue, use the \`nextMastraCourseStep\` tool to move to the next step.`;
357
+ }
358
+ async function getDeviceIdPath() {
359
+ const cacheDir = path.join(os.homedir(), ".cache", "mastra");
360
+ if (!existsSync(cacheDir)) mkdirSync(cacheDir, { recursive: true });
361
+ return path.join(cacheDir, ".device_id");
362
+ }
363
+ async function getDeviceCredentials() {
364
+ try {
365
+ const deviceIdPath = await getDeviceIdPath();
366
+ if (!existsSync(deviceIdPath)) return null;
367
+ const fileContent = await fs.readFile(deviceIdPath, "utf-8");
368
+ const parsed = JSON.parse(fileContent);
369
+ if (typeof parsed.deviceId === "string" && typeof parsed.key === "string") return {
370
+ deviceId: parsed.deviceId,
371
+ key: parsed.key
372
+ };
373
+ return null;
374
+ } catch {
375
+ return null;
376
+ }
377
+ }
378
+ async function getDeviceId() {
379
+ const creds = await getDeviceCredentials();
380
+ if (!creds || !creds?.deviceId) return null;
381
+ return creds.deviceId;
382
+ }
383
+ async function saveDeviceCredentials(deviceId, key) {
384
+ const deviceIdPath = await getDeviceIdPath();
385
+ const toWrite = JSON.stringify({
386
+ deviceId,
387
+ key
388
+ });
389
+ await fs.writeFile(deviceIdPath, toWrite, "utf-8");
390
+ await fs.chmod(deviceIdPath, 384);
391
+ }
392
+ async function registerUser(email) {
393
+ const response = await fetch("https://mastra.ai/api/course/register", {
394
+ method: "POST",
395
+ headers: { "Content-Type": "application/json" },
396
+ body: JSON.stringify({ email })
397
+ });
398
+ if (!response.ok) throw new Error(`Registration failed with status ${response.status}: ${response.statusText}`);
399
+ return response.json();
400
+ }
401
+ async function readCourseStep(lessonName, stepName, _isFirstStep = false) {
402
+ const lessonDir = (await fs.readdir(courseDir)).find((dir) => dir.replace(/^\d+-/, "") === lessonName);
403
+ if (!lessonDir) throw new Error(`Lesson "${lessonName}" not found.`);
404
+ const lessonPath = path.join(courseDir, lessonDir);
405
+ const stepFile = (await fs.readdir(lessonPath)).find((f) => f.endsWith(".md") && f.replace(/^\d+-/, "").replace(".md", "") === stepName);
406
+ if (!stepFile) throw new Error(`Step "${stepName}" not found in lesson "${lessonName}".`);
407
+ const filePath = path.join(courseDir, lessonDir, stepFile);
408
+ try {
409
+ return wrapContentInPrompt(await fs.readFile(filePath, "utf-8"));
410
+ } catch (error) {
411
+ throw new Error(`Failed to read step "${stepName}" in lesson "${lessonName}": ${error}`);
412
+ }
413
+ }
414
+ async function updateCourseStateOnServer(deviceId, state) {
415
+ const creds = await getDeviceCredentials();
416
+ if (!creds) throw new Error("Device credentials not found.");
417
+ const response = await fetch("https://mastra.ai/api/course/update", {
418
+ method: "POST",
419
+ headers: {
420
+ "Content-Type": "application/json",
421
+ "x-mastra-course-key": creds.key
422
+ },
423
+ body: JSON.stringify({
424
+ id: creds.deviceId,
425
+ state
426
+ })
427
+ });
428
+ if (!response.ok) throw new Error(`Course state update failed with status ${response.status}: ${response.statusText}`);
429
+ }
430
+ async function saveCourseState(state, deviceId) {
431
+ if (!deviceId) throw new Error("Cannot save course state: User is not registered");
432
+ const statePath = await getCourseStatePath();
433
+ try {
434
+ await fs.writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
435
+ try {
436
+ const creds = await getDeviceCredentials();
437
+ if (!creds) throw new Error("Device credentials not found");
438
+ await updateCourseStateOnServer(creds.deviceId, state);
439
+ } catch {}
440
+ } catch (error) {
441
+ throw new Error(`Failed to save course state: ${error}`);
442
+ }
443
+ }
444
+ async function getCourseStatePath() {
445
+ const stateDirPath = path.join(os.homedir(), ".cache", "mastra", "course");
446
+ if (!existsSync(stateDirPath)) mkdirSync(stateDirPath, { recursive: true });
447
+ return path.join(stateDirPath, "state.json");
448
+ }
449
+ async function loadCourseState() {
450
+ const statePath = await getCourseStatePath();
451
+ try {
452
+ if (existsSync(statePath)) {
453
+ const stateData = await fs.readFile(statePath, "utf-8");
454
+ return JSON.parse(stateData);
455
+ }
456
+ } catch (error) {
457
+ throw new Error(`Failed to load course state: ${error}`);
458
+ }
459
+ return null;
460
+ }
461
+ async function scanCourseContent() {
462
+ const lessonDirs = await fs.readdir(courseDir);
463
+ const validLessons = (await Promise.all(lessonDirs.filter((dir) => !dir.startsWith(".")).sort((a, b) => a.localeCompare(b)).map(async (lessonDir) => {
464
+ const lessonPath = path.join(courseDir, lessonDir);
465
+ if (!(await fs.stat(lessonPath)).isDirectory()) return null;
466
+ const lessonName = lessonDir.replace(/^\d+-/, "");
467
+ const stepFiles = (await fs.readdir(lessonPath)).filter((file) => file.endsWith(".md")).sort((a, b) => a.localeCompare(b));
468
+ return {
469
+ name: lessonName,
470
+ status: 0,
471
+ steps: (await Promise.all(stepFiles.map(async (file) => {
472
+ return {
473
+ name: file.replace(/^\d+-/, "").replace(".md", ""),
474
+ status: 0
475
+ };
476
+ }))).filter(Boolean)
477
+ };
478
+ }))).filter((lesson) => lesson !== null);
479
+ return {
480
+ currentLesson: validLessons.length > 0 ? validLessons[0]?.name ?? "" : "",
481
+ lessons: validLessons
482
+ };
483
+ }
484
+ async function mergeCourseStates(currentState, newState) {
485
+ const existingLessonMap = new Map(currentState.lessons.map((lesson) => [lesson.name, lesson]));
486
+ const mergedLessons = newState.lessons.map((newLesson) => {
487
+ const existingLesson = existingLessonMap.get(newLesson.name);
488
+ if (!existingLesson) return newLesson;
489
+ const existingStepMap = new Map(existingLesson.steps.map((step) => [step.name, step]));
490
+ const mergedSteps = newLesson.steps.map((newStep) => {
491
+ const existingStep = existingStepMap.get(newStep.name);
492
+ if (existingStep) return {
493
+ ...newStep,
494
+ status: existingStep.status
495
+ };
496
+ return newStep;
497
+ });
498
+ let lessonStatus = existingLesson.status;
499
+ if (mergedSteps.every((step) => step.status === 2)) lessonStatus = 2;
500
+ else if (mergedSteps.some((step) => step.status > 0)) lessonStatus = 1;
501
+ return {
502
+ ...newLesson,
503
+ status: lessonStatus,
504
+ steps: mergedSteps
505
+ };
506
+ });
507
+ let currentLesson = currentState.currentLesson;
508
+ if (!mergedLessons.some((lesson) => lesson.name === currentLesson) && mergedLessons.length > 0) currentLesson = mergedLessons[0]?.name ?? "";
509
+ return {
510
+ currentLesson,
511
+ lessons: mergedLessons
512
+ };
513
+ }
514
+ const startMastraCourse = {
515
+ name: "startMastraCourse",
516
+ description: "[🎓 COURSE] Starts the Mastra Course. If the user is not registered, they will be prompted to register first. Otherwise, it will start at the first lesson or pick up where they last left off. ALWAYS ask the user for their email address if they are not registered. DO NOT assume their email address, they must confirm their email and that they want to register.",
517
+ parameters: z.object({ email: z.string().email().optional().describe("Email address for registration if not already registered. ") }),
518
+ execute: async (args) => {
519
+ try {
520
+ const creds = await getDeviceCredentials();
521
+ const registered = creds !== null;
522
+ let deviceId = creds?.deviceId ?? null;
523
+ if (!registered) {
524
+ if (!args.email) return "To start the Mastra Course, you need to register first. Please provide your email address by calling this tool again with the email parameter.";
525
+ try {
526
+ const response = await registerUser(args.email);
527
+ if (response.success) {
528
+ await saveDeviceCredentials(response.id, response.key);
529
+ deviceId = response.id;
530
+ } else return `Registration failed: ${response.message}. Please try again with a valid email address.`;
531
+ } catch (error) {
532
+ return `Failed to register: ${error instanceof Error ? error.message : String(error)}. Please try again later.`;
533
+ }
534
+ }
535
+ let courseState = await loadCourseState();
536
+ let statusMessage = "";
537
+ const latestCourseState = await scanCourseContent();
538
+ if (!latestCourseState.lessons.length) return "No course content found. Please make sure the course content is properly set up in the .docs/course/lessons directory.";
539
+ if (courseState) {
540
+ const previousState = JSON.parse(JSON.stringify(courseState));
541
+ courseState = await mergeCourseStates(courseState, latestCourseState);
542
+ const newLessons = latestCourseState.lessons.filter((newLesson) => !previousState.lessons.some((oldLesson) => oldLesson.name === newLesson.name));
543
+ if (newLessons.length > 0) {
544
+ statusMessage = `📚 Course content has been updated! ${newLessons.length} new lesson(s) have been added:\n`;
545
+ statusMessage += newLessons.map((lesson) => `- ${lesson.name}`).join("\n");
546
+ statusMessage += "\n\n";
547
+ }
548
+ await saveCourseState(courseState, deviceId);
549
+ } else {
550
+ courseState = latestCourseState;
551
+ await saveCourseState(courseState, deviceId);
552
+ if (!registered && args.email) return introductionPrompt;
553
+ }
554
+ const currentLessonName = courseState.currentLesson;
555
+ const currentLesson = courseState.lessons.find((lesson) => lesson.name === currentLessonName);
556
+ if (!currentLesson) return "Error: Current lesson not found in course content. Please try again or reset your course progress.";
557
+ const currentStep = currentLesson.steps.find((step) => step.status !== 2);
558
+ if (!currentStep && currentLesson.status !== 2) {
559
+ currentLesson.status = 2;
560
+ await saveCourseState(courseState, deviceId);
561
+ const nextLesson = courseState.lessons.find((lesson) => lesson.status !== 2 && lesson.name !== currentLessonName);
562
+ if (nextLesson) {
563
+ courseState.currentLesson = nextLesson.name;
564
+ await saveCourseState(courseState, deviceId);
565
+ return `${statusMessage}🎉 You've completed the "${currentLessonName}" lesson!\n\nMoving on to the next lesson: "${nextLesson.name}".\n\nUse the \`nextMastraCourseStep\` tool to start the first step of this lesson.`;
566
+ } else return `${statusMessage}🎉 Congratulations! You've completed all available lessons in the Mastra Course!\n\nIf you'd like to review any lesson, use the \`startMastraCourseLesson\` tool with the lesson name.`;
567
+ }
568
+ if (!currentStep) return `${statusMessage}Error: No incomplete steps found in the current lesson. Please try another lesson or reset your course progress.`;
569
+ currentStep.status = 1;
570
+ if (currentLesson.status === 0) currentLesson.status = 1;
571
+ await saveCourseState(courseState, deviceId);
572
+ const stepContent = await readCourseStep(currentLessonName, currentStep.name);
573
+ return `📘 Lesson: ${currentLessonName}\n📝 Step: ${currentStep.name}\n\n${stepContent}\n\nWhen you've completed this step, use the \`nextMastraCourseStep\` tool to continue.`;
574
+ } catch (error) {
575
+ return `Error starting the Mastra course: ${error instanceof Error ? error.message : String(error)}`;
576
+ }
577
+ }
578
+ };
579
+ const getMastraCourseStatus = {
580
+ name: "getMastraCourseStatus",
581
+ description: "[🎓 COURSE] Gets the current status of the Mastra Course, including which lessons and steps have been completed",
582
+ parameters: z.object({}),
583
+ execute: async (_args) => {
584
+ try {
585
+ const deviceId = await getDeviceId();
586
+ if (deviceId === null) return "You need to register for the Mastra Course first. Please use the `startMastraCourse` tool to register.";
587
+ const courseState = await loadCourseState();
588
+ if (!courseState) return "No course progress found. Please start the course first using the `startMastraCourse` tool.";
589
+ const latestCourseState = await scanCourseContent();
590
+ if (!latestCourseState.lessons.length) return "No course content found. Please make sure the course content is properly set up in the .docs/course/lessons directory.";
591
+ const mergedState = await mergeCourseStates(courseState, latestCourseState);
592
+ let statusReport = "# Mastra Course Progress\n\n";
593
+ const totalLessons = mergedState.lessons.length;
594
+ const completedLessons = mergedState.lessons.filter((lesson) => lesson.status === 2).length;
595
+ mergedState.lessons.filter((lesson) => lesson.status === 1).length;
596
+ const totalSteps = mergedState.lessons.reduce((sum, lesson) => sum + lesson.steps.length, 0);
597
+ const completedSteps = mergedState.lessons.reduce((sum, lesson) => sum + lesson.steps.filter((step) => step.status === 2).length, 0);
598
+ statusReport += `## Overall Progress\n`;
599
+ statusReport += `- Course status Url: **https://mastra.ai/course/${deviceId}**\n`;
600
+ statusReport += `- Current Lesson: **${mergedState.currentLesson}**\n`;
601
+ statusReport += `- Lessons: ${completedLessons}/${totalLessons} completed (${Math.round(completedLessons / totalLessons * 100)}%)\n`;
602
+ statusReport += `- Steps: ${completedSteps}/${totalSteps} completed (${Math.round(completedSteps / totalSteps * 100)}%)\n\n`;
603
+ statusReport += `## Lesson Details\n\n`;
604
+ mergedState.lessons.forEach((lesson, lessonIndex) => {
605
+ let lessonStatusIcon = "⬜";
606
+ if (lesson.status === 1) lessonStatusIcon = "🔶";
607
+ if (lesson.status === 2) lessonStatusIcon = "✅";
608
+ const lessonPrefix = lesson.name === mergedState.currentLesson ? "👉 " : "";
609
+ statusReport += `### ${lessonPrefix}${lessonIndex + 1}. ${lessonStatusIcon} ${lesson.name}\n\n`;
610
+ lesson.steps.forEach((step, stepIndex) => {
611
+ let stepStatusIcon = "⬜";
612
+ if (step.status === 1) stepStatusIcon = "🔶";
613
+ if (step.status === 2) stepStatusIcon = "✅";
614
+ statusReport += `- ${stepStatusIcon} Step ${stepIndex + 1}: ${step.name}\n`;
615
+ });
616
+ statusReport += "\n";
617
+ });
618
+ statusReport += `## Navigation\n\n`;
619
+ statusReport += `- To continue the course: \`nextMastraCourseStep\`\n`;
620
+ statusReport += `- To start a specific lesson: \`startMastraCourseLesson\`\n`;
621
+ statusReport += `- To reset progress: \`clearMastraCourseHistory\`\n`;
622
+ return `Course Status: ${statusReport}\n\nCourse status url: https://mastra.ai/course/${deviceId}`;
623
+ } catch (error) {
624
+ return `Error getting course status: ${error instanceof Error ? error.message : String(error)}`;
625
+ }
626
+ }
627
+ };
628
+ const startMastraCourseLesson = {
629
+ name: "startMastraCourseLesson",
630
+ description: "[🎓 COURSE] Starts a specific lesson in the Mastra Course. If the lesson has been started before, it will resume from the first incomplete step",
631
+ parameters: _courseLessonSchema,
632
+ execute: async (args) => {
633
+ try {
634
+ const deviceId = await getDeviceId();
635
+ if (deviceId === null) return "You need to register for the Mastra Course first. Please use the `startMastraCourse` tool to register.";
636
+ let courseState = await loadCourseState();
637
+ if (!courseState) return "No course progress found. Please start the course first using the `startMastraCourse` tool.";
638
+ const targetLessonName = args.lessonName;
639
+ const targetLesson = courseState.lessons.find((lesson) => lesson.name === targetLessonName);
640
+ if (!targetLesson) return `Lesson "${targetLessonName}" not found. Available lessons:\n${courseState.lessons.map((lesson, index) => `${index + 1}. ${lesson.name}`).join("\n")}`;
641
+ courseState.currentLesson = targetLesson.name;
642
+ const firstIncompleteStep = targetLesson.steps.find((step) => step.status !== 2) || targetLesson.steps[0];
643
+ if (!firstIncompleteStep) return `The lesson "${targetLesson.name}" does not have any steps.`;
644
+ firstIncompleteStep.status = 1;
645
+ if (targetLesson.status === 0) targetLesson.status = 1;
646
+ await saveCourseState(courseState, deviceId);
647
+ const stepContent = await readCourseStep(targetLesson.name, firstIncompleteStep.name);
648
+ return `📘 Starting Lesson: ${targetLesson.name}\n📝 Step: ${firstIncompleteStep.name}\n\n${stepContent}\n\nWhen you've completed this step, use the \`nextMastraCourseStep\` tool to continue.`;
649
+ } catch (error) {
650
+ return `Error starting course lesson: ${error instanceof Error ? error.message : String(error)}`;
651
+ }
652
+ }
653
+ };
654
+ const nextMastraCourseStep = {
655
+ name: "nextMastraCourseStep",
656
+ description: "[🎓 COURSE] Advances to the next step in the current Mastra Course lesson. If all steps in the current lesson are completed, it will move to the next lesson",
657
+ parameters: z.object({}),
658
+ execute: async (_args) => {
659
+ try {
660
+ const deviceId = await getDeviceId();
661
+ if (deviceId === null) return "You need to register for the Mastra Course first. Please use the `startMastraCourse` tool to register.";
662
+ const courseState = await loadCourseState();
663
+ if (!courseState) return "No course progress found. Please start the course first using the `startMastraCourse` tool.";
664
+ const currentLessonName = courseState.currentLesson;
665
+ const currentLesson = courseState.lessons.find((lesson) => lesson.name === currentLessonName);
666
+ if (!currentLesson) return "Error: Current lesson not found in course content. Please try again or reset your course progress.";
667
+ const currentStepIndex = currentLesson.steps.findIndex((step) => step.status === 1);
668
+ if (currentStepIndex === -1) return "No step is currently in progress. Please start a step first using the `startMastraCourse` tool.";
669
+ if (currentLesson.steps[currentStepIndex]?.status) currentLesson.steps[currentStepIndex].status = 2;
670
+ const nextStepIndex = currentLesson.steps.findIndex((step, index) => index > currentStepIndex && step.status !== 2);
671
+ if (nextStepIndex !== -1) {
672
+ if (currentLesson.steps[nextStepIndex]) currentLesson.steps[nextStepIndex].status = 1;
673
+ await saveCourseState(courseState, deviceId);
674
+ const nextStep = currentLesson.steps[nextStepIndex];
675
+ const stepContent = await readCourseStep(currentLessonName, nextStep?.name ?? "Unknown Step");
676
+ return `🎉 Step "${currentLesson.steps[currentStepIndex]?.name ?? "Unknown Step"}" completed!\n\n📘 Continuing Lesson: ${currentLessonName}\n📝 Next Step: ${nextStep?.name ?? "Unknown Step"}\n\n${stepContent}\n\nWhen you've completed this step, use the \`nextMastraCourseStep\` tool to continue.`;
677
+ }
678
+ currentLesson.status = 2;
679
+ const currentLessonIndex = courseState.lessons.findIndex((lesson) => lesson.name === currentLessonName);
680
+ const nextLesson = courseState.lessons.find((lesson, index) => index > currentLessonIndex && lesson.status !== 2);
681
+ if (nextLesson) {
682
+ courseState.currentLesson = nextLesson.name;
683
+ if (nextLesson.steps.length > 0 && nextLesson.steps[0]) nextLesson.steps[0].status = 1;
684
+ nextLesson.status = 1;
685
+ await saveCourseState(courseState, deviceId);
686
+ const firstStep = nextLesson.steps[0];
687
+ const stepContent = await readCourseStep(nextLesson.name, firstStep?.name ?? "Unknown Step");
688
+ return `🎉 Congratulations! You've completed the "${currentLessonName}" lesson!\n\n📘 Starting New Lesson: ${nextLesson.name}\n📝 First Step: ${firstStep?.name ?? "Unknown Step"}\n\n${stepContent}\n\nWhen you've completed this step, use the \`nextMastraCourseStep\` tool to continue.`;
689
+ }
690
+ await saveCourseState(courseState, deviceId);
691
+ return `🎉 Congratulations! You've completed all available lessons in the Mastra Course!\n\nIf you'd like to review any lesson, use the \`startMastraCourseLesson\` tool with the lesson name.`;
692
+ } catch (error) {
693
+ return `Error advancing to the next course step: ${error instanceof Error ? error.message : String(error)}`;
694
+ }
695
+ }
696
+ };
697
+ const clearMastraCourseHistory = {
698
+ name: "clearMastraCourseHistory",
699
+ description: "[🎓 COURSE] Clears all Mastra Course progress history and starts over from the beginning. This action cannot be undone",
700
+ parameters: _confirmationSchema,
701
+ execute: async (args) => {
702
+ try {
703
+ if (await getDeviceId() === null) return "You need to register for the Mastra Course first. Please use the `startMastraCourse` tool to register.";
704
+ if (!args.confirm) return "⚠️ This action will delete all your course progress and cannot be undone. To proceed, please run this tool again with the confirm parameter set to true.";
705
+ const statePath = await getCourseStatePath();
706
+ if (!existsSync(statePath)) return "No course progress found. Nothing to clear.";
707
+ await fs.unlink(statePath);
708
+ return "🧹 Course progress has been cleared. You can restart the Mastra course from the beginning.";
709
+ } catch (error) {
710
+ return `Error clearing course history: ${error instanceof Error ? error.message : String(error)}`;
711
+ }
712
+ }
713
+ };
714
+ //#endregion
715
+ //#region src/tools/docs.ts
716
+ const docsBaseDir = fromPackageRoot(".docs/");
717
+ async function listDirContents(dirPath) {
718
+ try {
719
+ logger.debug("Listing directory contents", { path: dirPath });
720
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
721
+ const dirs = [];
722
+ const files = [];
723
+ for (const entry of entries) if (entry.isDirectory()) dirs.push(entry.name + "/");
724
+ else if (entry.isFile() && entry.name.endsWith(".md")) files.push(entry.name.replace(/\.md$/, ""));
725
+ return {
726
+ dirs: dirs.sort(),
727
+ files: files.sort()
728
+ };
729
+ } catch (error) {
730
+ logger.error("Failed to list directory contents", {
731
+ path: dirPath,
732
+ error
733
+ });
734
+ throw error;
735
+ }
736
+ }
737
+ async function readDocsContent(docPath, queryKeywords) {
738
+ const basePath = path.resolve(docsBaseDir);
739
+ const fullPath = path.resolve(path.join(basePath, docPath));
740
+ const relativePath = path.relative(basePath, fullPath);
741
+ if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
742
+ logger.error("Path traversal attempt detected", {
743
+ path: docPath,
744
+ resolvedPath: fullPath
745
+ });
746
+ return {
747
+ found: false,
748
+ isSecurityViolation: true
749
+ };
750
+ }
751
+ logger.debug("Reading docs content", { path: fullPath });
752
+ try {
753
+ if ((await fs.stat(fullPath)).isDirectory()) {
754
+ const indexMdPath = path.join(fullPath, "index.md");
755
+ try {
756
+ return {
757
+ found: true,
758
+ content: await fs.readFile(indexMdPath, "utf-8"),
759
+ isSecurityViolation: false
760
+ };
761
+ } catch {}
762
+ const { dirs, files } = await listDirContents(fullPath);
763
+ const listing = [`Directory contents of ${docPath || "/"}:`, ""];
764
+ if (dirs.length > 0) {
765
+ listing.push("Subdirectories:");
766
+ listing.push(...dirs.map((d) => `- ${docPath ? `${docPath}/${d}` : d}`));
767
+ listing.push("");
768
+ }
769
+ if (files.length > 0) {
770
+ listing.push("Available documentation paths:");
771
+ listing.push(...files.map((f) => `- ${docPath ? `${docPath}/${f}` : f}`));
772
+ listing.push("");
773
+ }
774
+ if (dirs.length === 0 && files.length === 0) listing.push("No documentation available in this directory.");
775
+ const contentBasedSuggestions = await getMatchingPaths(docPath, queryKeywords, docsBaseDir);
776
+ const suggestions = contentBasedSuggestions ? [
777
+ "---",
778
+ "",
779
+ contentBasedSuggestions,
780
+ ""
781
+ ].join("\n") : "";
782
+ return {
783
+ found: true,
784
+ content: listing.join("\n") + suggestions,
785
+ isSecurityViolation: false
786
+ };
787
+ }
788
+ return {
789
+ found: true,
790
+ content: await fs.readFile(fullPath, "utf-8"),
791
+ isSecurityViolation: false
792
+ };
793
+ } catch (error) {
794
+ if (error.code === "ENOENT") try {
795
+ const mdPath = fullPath + ".md";
796
+ return {
797
+ found: true,
798
+ content: await fs.readFile(mdPath, "utf-8"),
799
+ isSecurityViolation: false
800
+ };
801
+ } catch {
802
+ return {
803
+ found: false,
804
+ isSecurityViolation: false
805
+ };
806
+ }
807
+ throw error;
808
+ }
809
+ }
810
+ async function findNearestDirectory(docPath, availablePaths) {
811
+ logger.debug("Finding nearest directory", { path: docPath });
812
+ const parts = docPath.split("/");
813
+ while (parts.length > 0) {
814
+ const testPath = parts.join("/");
815
+ try {
816
+ const fullPath = path.join(docsBaseDir, testPath);
817
+ if ((await fs.stat(fullPath)).isDirectory()) {
818
+ const { dirs, files } = await listDirContents(fullPath);
819
+ const listing = [
820
+ `Path "${docPath}" not found.`,
821
+ `Here are the available paths in "${testPath}":`,
822
+ ""
823
+ ];
824
+ if (dirs.length > 0) {
825
+ listing.push("Directories:");
826
+ listing.push(...dirs.map((d) => `- ${testPath}/${d}`));
827
+ listing.push("");
828
+ }
829
+ if (files.length > 0) {
830
+ listing.push("Files:");
831
+ listing.push(...files.map((f) => `- ${testPath}/${f}`));
832
+ }
833
+ return listing.join("\n");
834
+ }
835
+ } catch {
836
+ logger.debug("Directory not found, trying parent", { parent: parts.slice(0, -1).join("/") });
837
+ }
838
+ parts.pop();
839
+ }
840
+ return [
841
+ `Path "${docPath}" not found.`,
842
+ "Here are all available paths:",
843
+ "",
844
+ availablePaths
845
+ ].join("\n");
846
+ }
847
+ async function getAvailablePaths() {
848
+ const { dirs, files } = await listDirContents(docsBaseDir);
849
+ let referenceDirs = [];
850
+ if (dirs.includes("reference/")) {
851
+ const { dirs: refDirs } = await listDirContents(path.join(docsBaseDir, "reference"));
852
+ referenceDirs = refDirs.map((d) => `reference/${d}`);
853
+ }
854
+ return [
855
+ "Available top-level paths:",
856
+ "",
857
+ "Directories:",
858
+ ...dirs.map((d) => `- ${d}`),
859
+ "",
860
+ referenceDirs.length > 0 ? "Reference subdirectories:" : "",
861
+ ...referenceDirs.map((d) => `- ${d}`),
862
+ "",
863
+ files.length > 0 ? "Files:" : "",
864
+ ...files.map((f) => `- ${f}`)
865
+ ].filter(Boolean).join("\n");
866
+ }
867
+ const availablePaths = await getAvailablePaths();
868
+ const docsTool = {
869
+ name: "mastraDocs",
870
+ description: `[🌐 REMOTE] Get Mastra documentation.
871
+ Request paths to explore the docs. References contain API docs.
872
+ Other paths contain guides. The user doesn\'t know about files and directories.
873
+ You can also use keywords from the user query to find relevant documentation, but prioritize paths.
874
+ This is your internal knowledge the user can\'t read.
875
+ If the user asks about a feature check general docs as well as reference docs for that feature.
876
+ Ex: with workflows check in docs/workflows and in reference/workflows.
877
+ Provide code examples so the user understands.
878
+ IMPORTANT: Be concise with your answers. The user will ask for more info.
879
+ If packages need to be installed, provide the pnpm command to install them.
880
+ Ex. if you see \`import { X } from "@mastra/$PACKAGE_NAME"\` in an example, show an install command.
881
+ Always install latest tag, not alpha unless requested. If you scaffold a new project it may be in a subdir.
882
+ When displaying results, always mention which file path contains the information so users know where this documentation lives.`,
883
+ parameters: z.object({
884
+ paths: z.array(z.string()).min(1).describe(`One or more documentation paths to fetch\nAvailable paths:\n${availablePaths}`),
885
+ queryKeywords: z.array(z.string()).optional().describe("Keywords from user query to use for matching documentation. Each keyword should be a single word or short phrase; any whitespace-separated keywords will be split automatically.")
886
+ }),
887
+ execute: async (args) => {
888
+ logger.debug("Executing mastraDocs tool", { args });
889
+ try {
890
+ const queryKeywords = args.queryKeywords ?? [];
891
+ return (await Promise.all(args.paths.map(async (docPath) => {
892
+ try {
893
+ const result = await readDocsContent(docPath, queryKeywords);
894
+ if (result.found) return {
895
+ path: docPath,
896
+ content: result.content,
897
+ error: null
898
+ };
899
+ if (result.isSecurityViolation) return {
900
+ path: docPath,
901
+ content: null,
902
+ error: "Invalid path"
903
+ };
904
+ return {
905
+ path: docPath,
906
+ content: null,
907
+ error: [await findNearestDirectory(docPath, availablePaths), await getMatchingPaths(docPath, queryKeywords, docsBaseDir)].join("\n\n")
908
+ };
909
+ } catch (error) {
910
+ logger.warning(`Failed to read content for path: ${docPath}`, error);
911
+ return {
912
+ path: docPath,
913
+ content: null,
914
+ error: error instanceof Error ? error.message : "Unknown error"
915
+ };
916
+ }
917
+ }))).map((result) => {
918
+ if (result.error) return `## ${result.path}\n\n${result.error}\n\n---\n`;
919
+ return `## ${result.path}\n\n${result.content}\n\n---\n`;
920
+ }).join("\n");
921
+ } catch (error) {
922
+ logger.error("Failed to execute mastraDocs tool", error);
923
+ throw error;
924
+ }
925
+ }
926
+ };
927
+ //#endregion
928
+ //#region src/tools/embedded-docs.ts
929
+ const packageCache = /* @__PURE__ */ new Map();
930
+ const sourceMapCache = /* @__PURE__ */ new Map();
931
+ const packageInfoCache = /* @__PURE__ */ new Map();
932
+ const KNOWN_MASTRA_PACKAGES = [
933
+ "@mastra/core",
934
+ "@mastra/cli",
935
+ "@mastra/memory",
936
+ "@mastra/rag",
937
+ "@mastra/evals",
938
+ "@mastra/mcp",
939
+ "@mastra/server",
940
+ "@mastra/deployer",
941
+ "@mastra/agent-builder",
942
+ "@mastra/auth",
943
+ "@mastra/fastembed",
944
+ "@mastra/loggers",
945
+ "@mastra/schema-compat",
946
+ "@mastra/codemod"
947
+ ];
948
+ async function getPackageRootPath(packageName, projectPath) {
949
+ const cacheKey = `${packageName}:${projectPath}`;
950
+ if (packageInfoCache.has(cacheKey)) return packageInfoCache.get(cacheKey);
951
+ try {
952
+ const info = await getPackageInfo(packageName, { paths: [path.join(projectPath, "node_modules")] });
953
+ if (info?.rootPath) {
954
+ const result = {
955
+ rootPath: info.rootPath,
956
+ version: info.version || "unknown"
957
+ };
958
+ packageInfoCache.set(cacheKey, result);
959
+ logger.debug("Resolved package with local-pkg", {
960
+ packageName,
961
+ projectPath,
962
+ ...result
963
+ });
964
+ return result;
965
+ }
966
+ } catch (err) {
967
+ logger.debug("Package not found or error resolving", {
968
+ packageName,
969
+ projectPath,
970
+ error: err instanceof Error ? err.message : String(err)
971
+ });
972
+ }
973
+ packageInfoCache.set(cacheKey, null);
974
+ return null;
975
+ }
976
+ async function getInstalledMastraPackages(projectPath) {
977
+ const cacheKey = projectPath;
978
+ if (packageCache.has(cacheKey)) {
979
+ logger.debug("Using cached package list", { count: packageCache.get(cacheKey).length });
980
+ return packageCache.get(cacheKey);
981
+ }
982
+ logger.debug("Scanning for @mastra packages using local-pkg", { projectPath });
983
+ const packages = [];
984
+ const packagesWithoutDocs = [];
985
+ for (const packageName of KNOWN_MASTRA_PACKAGES) {
986
+ const packageInfo = await getPackageRootPath(packageName, projectPath);
987
+ if (packageInfo) {
988
+ const docsPath = path.join(packageInfo.rootPath, "dist", "docs");
989
+ try {
990
+ if ((await fs.stat(docsPath)).isDirectory()) {
991
+ packages.push(packageName);
992
+ logger.debug("Found package with embedded docs", { package: packageName });
993
+ } else packagesWithoutDocs.push(packageName);
994
+ } catch {
995
+ packagesWithoutDocs.push(packageName);
996
+ }
997
+ }
998
+ }
999
+ const result = packages.sort();
1000
+ packageCache.set(cacheKey, result);
1001
+ logger.info("Package scan complete", {
1002
+ packagesWithDocs: result.length,
1003
+ packagesWithoutDocs: packagesWithoutDocs.length,
1004
+ packages: result
1005
+ });
1006
+ return result;
1007
+ }
1008
+ async function readSourceMap(packageName, projectPath) {
1009
+ const cacheKey = `${packageName}:${projectPath}`;
1010
+ if (sourceMapCache.has(cacheKey)) return sourceMapCache.get(cacheKey);
1011
+ try {
1012
+ const packageInfo = await getPackageRootPath(packageName, projectPath);
1013
+ if (!packageInfo) {
1014
+ sourceMapCache.set(cacheKey, null);
1015
+ return null;
1016
+ }
1017
+ const sourceMapPath = path.join(packageInfo.rootPath, "dist", "docs", "SOURCE_MAP.json");
1018
+ const content = await fs.readFile(sourceMapPath, "utf-8");
1019
+ const sourceMap = JSON.parse(content);
1020
+ sourceMapCache.set(cacheKey, sourceMap);
1021
+ return sourceMap;
1022
+ } catch {
1023
+ sourceMapCache.set(cacheKey, null);
1024
+ return null;
1025
+ }
1026
+ }
1027
+ const embeddedDocsTools = {
1028
+ getMastraHelp: {
1029
+ name: "getMastraHelp",
1030
+ description: `🚀 START HERE - Complete guide to Mastra documentation tools.
1031
+
1032
+ This MCP server provides TWO documentation sources:
1033
+
1034
+ ## 📦 LOCAL PACKAGE DOCS (Recommended for Development)
1035
+ SOURCE: Your installed @mastra packages in node_modules
1036
+ VERSION: Matches your installed code exactly
1037
+
1038
+ ADVANTAGES:
1039
+ - ✅ Version-matched to your code
1040
+ - ✅ Complete TypeScript type definitions
1041
+ - ✅ Works offline
1042
+ - ✅ SOURCE_MAP.json with exact exports
1043
+
1044
+ TOOLS: listMastraPackages, getMastraExports, getMastraExportDetails, readMastraDocs, searchMastraDocs
1045
+ USE WHEN: Writing code, implementing features, checking APIs, debugging
1046
+
1047
+ ## 🌐 REMOTE WEBSITE DOCS (For Latest Info & Learning)
1048
+ SOURCE: mastra.ai website
1049
+ VERSION: Latest published documentation
1050
+
1051
+ ADVANTAGES:
1052
+ - ✅ Always up-to-date
1053
+ - ✅ Blog posts and announcements
1054
+ - ✅ Migration guides
1055
+ - ✅ Curated examples
1056
+
1057
+ TOOLS: mastraDocs, mastraBlog, mastraExamples, mastraChanges, mastraMigration
1058
+ USE WHEN: Learning concepts, checking latest features, migration help
1059
+
1060
+ ## 🎓 INTERACTIVE COURSE
1061
+ TOOLS: startMastraCourse, getMastraCourseStatus, etc.
1062
+ USE WHEN: User wants guided learning experience
1063
+
1064
+ ---
1065
+
1066
+ RECOMMENDED WORKFLOW:
1067
+ 1. For coding: listMastraPackages → getMastraExports → getMastraExportDetails
1068
+ 2. For learning: mastraDocs
1069
+ 3. Version mismatch: mastraChanges → mastraMigration
1070
+
1071
+ This tool shows you which packages are installed and provides detailed guidance on using all available documentation tools.`,
1072
+ parameters: z.object({ projectPath: z.string().describe("Absolute path to your project root (we will search upward for node_modules with Mastra packages)") }),
1073
+ execute: async (args) => {
1074
+ logger.debug("Executing getMastraHelp tool", { projectPath: args.projectPath });
1075
+ const packages = await getInstalledMastraPackages(args.projectPath);
1076
+ if (packages.length === 0) return `No Mastra packages with embedded documentation found in your project.
1077
+
1078
+ To use these tools, install Mastra packages like:
1079
+ - npm install @mastra/core
1080
+ - npm install @mastra/memory
1081
+ - npm install @mastra/rag
1082
+
1083
+ Then rebuild/reinstall to generate embedded docs.`;
1084
+ return `# Mastra Documentation System - Complete Guide
1085
+
1086
+ This MCP server provides **TWO** documentation sources. Choose based on your needs:
1087
+
1088
+ ---
1089
+
1090
+ ## 📦 LOCAL PACKAGE DOCS (Your Installed Packages)
1091
+
1092
+ Found ${packages.length} installed package(s) with embedded documentation:
1093
+ ${packages.map((pkg) => `- ${pkg}`).join("\n")}
1094
+
1095
+ **SOURCE**: Your node_modules (matches installed code version)
1096
+ **USE WHEN**: Writing code, implementing features, debugging, checking APIs
1097
+
1098
+ ### Available LOCAL Tools:
1099
+
1100
+ **1. listMastraPackages** - List installed packages
1101
+ Returns: Packages with embedded docs
1102
+
1103
+ **2. getMastraExports** - Explore package API surface
1104
+ Example: See all exports from @mastra/core (Agent, Tool, Workflow, etc.)
1105
+ Returns: List of exports with source file locations
1106
+
1107
+ **3. getMastraExportDetails** - Get type definitions & code
1108
+ Example: Get full TypeScript types for Agent class
1109
+ Returns: Complete type definitions and optionally implementation source
1110
+
1111
+ **4. readMastraDocs** - Read comprehensive guides
1112
+ Example: Read documentation about agents, tools, workflows, memory
1113
+ Returns: Topic-based guides and examples from your installed version
1114
+
1115
+ **5. searchMastraDocs** - Search local documentation
1116
+ Example: Search for "memory processors" or "semantic recall"
1117
+ Returns: Relevant excerpts from your installed docs
1118
+
1119
+ ### Typical LOCAL Workflow:
1120
+ 1. listMastraPackages → see what's installed
1121
+ 2. getMastraExports → explore package API
1122
+ 3. getMastraExportDetails → get type definitions
1123
+ 4. readMastraDocs → learn concepts
1124
+ 5. searchMastraDocs → find specific info
1125
+
1126
+ ---
1127
+
1128
+ ## 🌐 REMOTE WEBSITE DOCS (mastra.ai)
1129
+
1130
+ **SOURCE**: https://mastra.ai (latest published documentation)
1131
+ **USE WHEN**: Learning new concepts, checking latest features, migration guides
1132
+
1133
+ ### Available REMOTE Tools:
1134
+
1135
+ **mastraDocs** - Browse official documentation
1136
+ Latest guides, references, and tutorials
1137
+
1138
+ **mastraBlog** - Read blog posts and announcements
1139
+ News, features, changelogs
1140
+
1141
+ **mastraExamples** - Get curated code examples
1142
+ Full example applications
1143
+
1144
+ **mastraChanges** - View package changelogs
1145
+ See what's new in each version
1146
+
1147
+ **mastraMigration** - Get migration guides
1148
+ Upgrade between versions
1149
+
1150
+ ⚠️ **Version Note**: Remote docs show latest published version. For API reference matching YOUR code, use LOCAL tools above.
1151
+
1152
+ ---
1153
+
1154
+ ## 🎓 INTERACTIVE COURSE
1155
+
1156
+ **startMastraCourse**, **getMastraCourseStatus**, **startMastraCourseLesson**, **nextMastraCourseStep**, **clearMastraCourseHistory**
1157
+
1158
+ Guided learning experience with hands-on exercises.
1159
+
1160
+ ---
1161
+
1162
+ ## Quick Start Recommendations
1163
+
1164
+ **If you're writing code**: Use LOCAL tools
1165
+ → Start with listMastraPackages
1166
+
1167
+ **If you're learning**: Use REMOTE tools
1168
+ → Start with mastraDocs
1169
+
1170
+ **If version differs**: Check changes
1171
+ → mastraChanges → mastraMigration`;
1172
+ }
1173
+ },
1174
+ listMastraPackages: {
1175
+ name: "listMastraPackages",
1176
+ description: `[📦 LOCAL PACKAGES] Discover which Mastra packages are installed and have documentation available.
1177
+
1178
+ Use this when you need to:
1179
+ - See what Mastra packages you can work with
1180
+ - Start exploring Mastra documentation
1181
+ - Check if a specific package is available
1182
+
1183
+ Returns: List of @mastra/* packages (core, memory, rag, etc.) with embedded docs.
1184
+ Next step: Use getMastraExports to explore a specific package's API.`,
1185
+ parameters: z.object({ projectPath: z.string().describe("Absolute path to your project root (we will search upward for node_modules with Mastra packages)") }),
1186
+ execute: async (args) => {
1187
+ logger.debug("Executing listInstalledMastraPackages tool", {
1188
+ projectPath: args.projectPath,
1189
+ cwd: process.cwd(),
1190
+ env: {
1191
+ PWD: process.env.PWD,
1192
+ HOME: process.env.HOME
1193
+ }
1194
+ });
1195
+ const packages = await getInstalledMastraPackages(args.projectPath);
1196
+ if (packages.length === 0) return `No @mastra/* packages with embedded docs found in your project.
1197
+
1198
+ Install Mastra packages to get started:
1199
+ - npm install @mastra/core
1200
+ - npm install @mastra/memory
1201
+ - npm install @mastra/rag`;
1202
+ return [
1203
+ `# Installed Mastra Packages`,
1204
+ "",
1205
+ `Found ${packages.length} package(s) with embedded documentation:`,
1206
+ "",
1207
+ ...packages.map((pkg) => `- ${pkg}`),
1208
+ "",
1209
+ "## Next Steps",
1210
+ "",
1211
+ "1. Use **getMastraExports** with a package name to see all available APIs",
1212
+ "2. Use **readMastraDocs** with a package name to browse topic guides",
1213
+ "3. Use **searchMastraDocs** to find specific information"
1214
+ ].join("\n");
1215
+ }
1216
+ },
1217
+ getMastraExports: {
1218
+ name: "getMastraExports",
1219
+ description: `[📦 LOCAL PACKAGES] Explore the complete API surface of a Mastra package - see all classes, functions, types, and constants.
1220
+
1221
+ Use this when you need to:
1222
+ - Discover what APIs a Mastra package provides (Agent, Tool, Workflow, etc.)
1223
+ - See all available classes and functions before implementing
1224
+ - Find the right export for your use case
1225
+ - Understand package structure and organization
1226
+
1227
+ Returns: List of all exports with their source file locations.
1228
+ Next step: Use getMastraExportDetails to get full type definitions and code for a specific export.`,
1229
+ parameters: z.object({
1230
+ package: z.string().describe("Package name to explore (e.g., \"@mastra/core\", \"@mastra/memory\", \"@mastra/rag\")"),
1231
+ projectPath: z.string().describe("Absolute path to your project root (we will search upward for node_modules)"),
1232
+ filter: z.string().optional().describe("Optional: filter exports by name (case-insensitive, e.g., \"Agent\", \"create\", \"Tool\")")
1233
+ }),
1234
+ execute: async (args) => {
1235
+ logger.debug("Executing readMastraSourceMap tool", { args });
1236
+ const sourceMap = await readSourceMap(args.package, args.projectPath);
1237
+ if (!sourceMap) return `No SOURCE_MAP.json found for ${args.package}.`;
1238
+ let exports = Object.entries(sourceMap.exports);
1239
+ if (args.filter) {
1240
+ const filterLower = args.filter.toLowerCase();
1241
+ exports = exports.filter(([name]) => name.toLowerCase().includes(filterLower));
1242
+ }
1243
+ if (exports.length === 0) return args.filter ? `No exports matching "${args.filter}" in ${args.package}.
1244
+
1245
+ Try running without a filter to see all available exports.` : `No exports found in ${args.package}.`;
1246
+ return [
1247
+ `# ${sourceMap.package} v${sourceMap.version} - API Exports`,
1248
+ "",
1249
+ `Found ${exports.length} export(s)${args.filter ? ` matching "${args.filter}"` : ""}:`,
1250
+ "",
1251
+ ...exports.map(([name, info]) => {
1252
+ const line = info.line ? `:${info.line}` : "";
1253
+ return `- **${name}**: \`${info.implementation}${line}\``;
1254
+ }),
1255
+ "",
1256
+ "## Next Steps",
1257
+ "",
1258
+ "- Use **getMastraExportDetails** with an export name to see full type definitions and code",
1259
+ "- Use **readMastraDocs** to read conceptual guides and examples",
1260
+ "- Use **searchMastraDocs** to find specific topics or patterns"
1261
+ ].join("\n");
1262
+ }
1263
+ },
1264
+ getMastraExportDetails: {
1265
+ name: "getMastraExportDetails",
1266
+ description: `[📦 LOCAL PACKAGES] Get complete API reference for a specific Mastra export - type definitions, interfaces, and optionally source code.
1267
+
1268
+ Use this when you need to:
1269
+ - Understand how to use a specific Mastra class or function (Agent, Tool, Workflow, etc.)
1270
+ - See TypeScript type definitions and interfaces
1271
+ - Look up method signatures and parameters
1272
+ - Read implementation code and examples
1273
+ - Understand constructor options and configuration
1274
+
1275
+ Returns: Full TypeScript type definitions and optionally implementation source code.
1276
+ Example: Get details on the Agent class to see how to create and configure agents.`,
1277
+ parameters: z.object({
1278
+ package: z.string().describe("Package name (e.g., \"@mastra/core\", \"@mastra/memory\")"),
1279
+ exportName: z.string().describe("Exact export name to look up (e.g., \"Agent\", \"createTool\", \"Workflow\")"),
1280
+ includeTypes: z.boolean().optional().default(true).describe("Include TypeScript type definitions (recommended: true)"),
1281
+ includeImplementation: z.boolean().optional().default(false).describe("Include source code implementation (useful for understanding internals)"),
1282
+ implementationLines: z.number().optional().default(50).describe("Number of lines of implementation code to show (default: 50)"),
1283
+ projectPath: z.string().describe("Absolute path to your project root (we will search upward for node_modules)")
1284
+ }),
1285
+ execute: async (args) => {
1286
+ logger.debug("Executing findMastraExport tool", { args });
1287
+ const sourceMap = await readSourceMap(args.package, args.projectPath);
1288
+ if (!sourceMap) return `No SOURCE_MAP.json found for ${args.package}.`;
1289
+ const exportInfo = sourceMap.exports[args.exportName];
1290
+ if (!exportInfo) {
1291
+ const match = Object.entries(sourceMap.exports).find(([name]) => name.toLowerCase() === args.exportName.toLowerCase());
1292
+ if (match) return `Export "${args.exportName}" not found. Did you mean "${match[0]}"?
1293
+
1294
+ Run getMastraExports with package="${args.package}" to see all available exports.`;
1295
+ return `Export "${args.exportName}" not found in ${args.package}.
1296
+
1297
+ Run getMastraExports with package="${args.package}" to see all available exports.`;
1298
+ }
1299
+ const packageInfo = await getPackageRootPath(args.package, args.projectPath);
1300
+ if (!packageInfo) return `Package ${args.package} not found. Make sure it's installed.`;
1301
+ const output = [`# ${args.exportName} (${args.package})`, ""];
1302
+ if (args.includeTypes !== false) try {
1303
+ const typesPath = path.join(packageInfo.rootPath, exportInfo.types);
1304
+ const typesContent = await fs.readFile(typesPath, "utf-8");
1305
+ output.push("## Type Definition", "", `\`${exportInfo.types}\``, "", "```typescript");
1306
+ const lines = typesContent.split("\n");
1307
+ let startLine = lines.findIndex((line) => line.includes(args.exportName));
1308
+ if (startLine === -1) output.push(typesContent.slice(0, 2e3));
1309
+ else {
1310
+ startLine = Math.max(0, startLine - 2);
1311
+ let endLine = Math.min(lines.length, startLine + 50);
1312
+ output.push(lines.slice(startLine, endLine).join("\n"));
1313
+ }
1314
+ output.push("```", "");
1315
+ } catch {
1316
+ output.push("## Type Definition", "", `Could not read: ${exportInfo.types}`, "");
1317
+ }
1318
+ if (args.includeImplementation) try {
1319
+ const implPath = path.join(packageInfo.rootPath, exportInfo.implementation);
1320
+ const lines = (await fs.readFile(implPath, "utf-8")).split("\n");
1321
+ const numLines = args.implementationLines || 50;
1322
+ output.push("## Implementation", "");
1323
+ output.push(`\`${exportInfo.implementation}\`${exportInfo.line ? ` (line ${exportInfo.line})` : ""}`);
1324
+ output.push("", "```javascript");
1325
+ const startLine = exportInfo.line ? Math.max(0, exportInfo.line - 1) : 0;
1326
+ const endLine = Math.min(lines.length, startLine + numLines);
1327
+ output.push(lines.slice(startLine, endLine).join("\n"));
1328
+ if (endLine < lines.length) output.push(`// ... ${lines.length - endLine} more lines`);
1329
+ output.push("```", "");
1330
+ } catch {
1331
+ output.push("## Implementation", "", `Could not read: ${exportInfo.implementation}`, "");
1332
+ }
1333
+ output.push("## Next Steps", "", "- Use **readMastraDocs** to see practical guides and examples", "- Use **searchMastraDocs** to find usage patterns and best practices", "- Use **getMastraExports** to explore related APIs");
1334
+ return output.join("\n");
1335
+ }
1336
+ },
1337
+ readMastraDocs: {
1338
+ name: "readMastraDocs",
1339
+ description: `[📦 LOCAL PACKAGES] Read comprehensive guides and documentation on Mastra concepts, patterns, and implementation examples.
1340
+
1341
+ Use this when you need to:
1342
+ - Learn how to implement Mastra features (agents, tools, workflows, memory, RAG, etc.)
1343
+ - Understand Mastra architecture and design patterns
1344
+ - See practical code examples and tutorials
1345
+ - Read getting started guides and best practices
1346
+ - Understand how different components work together
1347
+
1348
+ Returns: Topic-based documentation with explanations, examples, and usage patterns.
1349
+ Available topics: agents, tools, workflows, memory, rag, integrations, deployment, and more.`,
1350
+ parameters: z.object({
1351
+ package: z.string().describe("Package name to read docs from (e.g., \"@mastra/core\", \"@mastra/memory\")"),
1352
+ topic: z.string().optional().describe("Optional: topic folder to read (e.g., \"agents\", \"tools\", \"workflows\"). Omit to list all available topics."),
1353
+ file: z.string().optional().describe("Optional: specific documentation file within the topic (e.g., \"01-overview.md\")"),
1354
+ projectPath: z.string().describe("Absolute path to your project root (we will search upward for node_modules)")
1355
+ }),
1356
+ execute: async (args) => {
1357
+ logger.debug("Executing readMastraEmbeddedDocs tool", { args });
1358
+ const packageInfo = await getPackageRootPath(args.package, args.projectPath);
1359
+ if (!packageInfo) return `Package ${args.package} not found. Make sure it's installed.`;
1360
+ const docsPath = path.join(packageInfo.rootPath, "dist", "docs");
1361
+ try {
1362
+ await fs.stat(docsPath);
1363
+ } catch {
1364
+ return `No embedded docs found for ${args.package}.
1365
+
1366
+ Make sure the package is installed and has documentation generated.`;
1367
+ }
1368
+ if (!args.topic) {
1369
+ const entries = await fs.readdir(docsPath, { withFileTypes: true });
1370
+ const topics = entries.filter((e) => e.isDirectory()).map((e) => e.name);
1371
+ const files = entries.filter((e) => e.isFile()).map((e) => e.name);
1372
+ return [
1373
+ `# ${args.package} - Available Documentation`,
1374
+ "",
1375
+ "## Root Files",
1376
+ ...files.map((f) => `- ${f}`),
1377
+ "",
1378
+ "## Documentation Topics",
1379
+ ...topics.map((t) => `- **${t}/** - Run readMastraDocs with topic="${t}" to read`),
1380
+ "",
1381
+ "## Next Steps",
1382
+ "",
1383
+ "- Choose a topic and run **readMastraDocs** with the topic parameter",
1384
+ "- Use **searchMastraDocs** to search for specific information",
1385
+ "- Use **getMastraExports** to see available APIs"
1386
+ ].join("\n");
1387
+ }
1388
+ const topicPath = path.join(docsPath, args.topic);
1389
+ if (args.file) try {
1390
+ const content = await fs.readFile(path.join(topicPath, args.file), "utf-8");
1391
+ return `# ${args.package}/${args.topic}/${args.file}
1392
+
1393
+ ${content}
1394
+
1395
+ ## Next Steps
1396
+
1397
+ - Use **getMastraExportDetails** to see API references for specific classes/functions mentioned
1398
+ - Use **searchMastraDocs** to find related topics
1399
+ - Use **getMastraExports** to explore available APIs`;
1400
+ } catch {
1401
+ return `File not found: ${args.topic}/${args.file}
1402
+
1403
+ Run readMastraDocs with package="${args.package}" and topic="${args.topic}" (without file parameter) to see available files.`;
1404
+ }
1405
+ try {
1406
+ const files = (await fs.readdir(topicPath, { withFileTypes: true })).filter((e) => e.isFile() && e.name.endsWith(".md")).sort();
1407
+ if (files.length === 0) return `No markdown files in ${args.topic}/
1408
+
1409
+ Run readMastraDocs with package="${args.package}" (without topic parameter) to see available topics.`;
1410
+ const contents = [`# ${args.package} - ${args.topic}`, ""];
1411
+ for (const file of files) {
1412
+ const content = await fs.readFile(path.join(topicPath, file.name), "utf-8");
1413
+ contents.push(`## ${file.name}`, "", content, "", "---", "");
1414
+ }
1415
+ contents.push("", "## Next Steps", "", "- Use **getMastraExportDetails** to see API references for specific classes/functions mentioned above", "- Use **searchMastraDocs** to find related information", "- Use **getMastraExports** to explore the complete API surface");
1416
+ return contents.join("\n");
1417
+ } catch {
1418
+ return `Topic not found: ${args.topic}
1419
+
1420
+ Run readMastraDocs with package="${args.package}" (without topic parameter) to see available topics.`;
1421
+ }
1422
+ }
1423
+ },
1424
+ searchMastraDocs: {
1425
+ name: "searchMastraDocs",
1426
+ description: `[📦 LOCAL PACKAGES] Search across all Mastra documentation to find specific information, patterns, or examples.
1427
+
1428
+ Use this when you need to:
1429
+ - Find specific topics or concepts quickly (e.g., "memory processors", "tool composition")
1430
+ - Locate examples of specific features or patterns
1431
+ - Search for error messages or troubleshooting info
1432
+ - Find mentions of specific APIs or configuration options
1433
+ - Discover where a feature is documented
1434
+
1435
+ Returns: Relevant documentation excerpts with file paths, ranked by relevance.
1436
+ Tip: Use specific terms for better results (e.g., "agent memory" vs "memory").`,
1437
+ parameters: z.object({
1438
+ query: z.string().describe("What to search for (case-insensitive, e.g., \"workflow steps\", \"vector store\", \"authentication\")"),
1439
+ package: z.string().optional().describe("Optional: limit search to a specific package (e.g., \"@mastra/core\"). Omit to search all packages."),
1440
+ maxResults: z.number().optional().default(10).describe("Optional: maximum number of results to return (default: 10)"),
1441
+ projectPath: z.string().describe("Absolute path to your project root (we will search upward for node_modules)")
1442
+ }),
1443
+ execute: async (args) => {
1444
+ logger.debug("Executing searchMastraEmbeddedDocs tool", { args });
1445
+ const packages = args.package ? [args.package] : await getInstalledMastraPackages(args.projectPath);
1446
+ if (packages.length === 0) return "No Mastra packages found.";
1447
+ const queryLower = args.query.toLowerCase();
1448
+ const results = [];
1449
+ for (const pkg of packages) {
1450
+ const packageInfo = await getPackageRootPath(pkg, args.projectPath);
1451
+ if (!packageInfo) continue;
1452
+ const docsPath = path.join(packageInfo.rootPath, "dist", "docs");
1453
+ try {
1454
+ const findFiles = async (dir) => {
1455
+ const entries = await fs.readdir(dir, { withFileTypes: true });
1456
+ const files = [];
1457
+ for (const entry of entries) {
1458
+ const fullPath = path.join(dir, entry.name);
1459
+ if (entry.isDirectory()) files.push(...await findFiles(fullPath));
1460
+ else if (entry.name.endsWith(".md")) files.push(fullPath);
1461
+ }
1462
+ return files;
1463
+ };
1464
+ for (const file of await findFiles(docsPath)) {
1465
+ const content = await fs.readFile(file, "utf-8");
1466
+ if (!content.toLowerCase().includes(queryLower)) continue;
1467
+ const lines = content.split("\n");
1468
+ for (let i = 0; i < lines.length; i++) if (lines[i]?.toLowerCase().includes(queryLower)) {
1469
+ const start = Math.max(0, i - 1);
1470
+ const end = Math.min(lines.length, i + 3);
1471
+ const excerpt = lines.slice(start, end).join("\n").slice(0, 300);
1472
+ const occurrences = content.toLowerCase().split(queryLower).length - 1;
1473
+ results.push({
1474
+ pkg,
1475
+ file: path.relative(docsPath, file),
1476
+ excerpt,
1477
+ score: occurrences
1478
+ });
1479
+ break;
1480
+ }
1481
+ }
1482
+ } catch {}
1483
+ }
1484
+ results.sort((a, b) => b.score - a.score);
1485
+ const topResults = results.slice(0, args.maxResults || 10);
1486
+ if (topResults.length === 0) return `No results found for "${args.query}".
1487
+
1488
+ Try:
1489
+ - Using different search terms
1490
+ - Searching for broader topics
1491
+ - Using **listMastraPackages** to see available packages
1492
+ - Using **readMastraDocs** to browse documentation by topic`;
1493
+ return [
1494
+ `# Search Results: "${args.query}"`,
1495
+ "",
1496
+ `Found ${results.length} result(s), showing top ${topResults.length}:`,
1497
+ "",
1498
+ ...topResults.map((r, i) => `## ${i + 1}. ${r.pkg} - ${r.file}\n\n\`\`\`\n${r.excerpt}\n\`\`\`\n`),
1499
+ "",
1500
+ "## Next Steps",
1501
+ "",
1502
+ "- Use **readMastraDocs** with a package and topic to read full documentation",
1503
+ "- Use **getMastraExportDetails** to see API details for mentioned classes/functions",
1504
+ "- Refine your search with more specific terms if needed"
1505
+ ].join("\n");
1506
+ }
1507
+ }
1508
+ };
1509
+ //#endregion
1510
+ //#region src/tools/migration.ts
1511
+ const migrationsBaseDir = fromPackageRoot(".docs/guides/migrations");
1512
+ function parseSections(content) {
1513
+ const lines = content.split("\n");
1514
+ const sections = [];
1515
+ let currentSection = null;
1516
+ let inFrontmatter = false;
1517
+ let contentStarted = false;
1518
+ for (let index = 0; index < lines.length; index++) {
1519
+ const line = lines[index];
1520
+ if (index === 0 && line === "---") {
1521
+ inFrontmatter = true;
1522
+ continue;
1523
+ }
1524
+ if (inFrontmatter && line === "---") {
1525
+ inFrontmatter = false;
1526
+ continue;
1527
+ }
1528
+ if (inFrontmatter) continue;
1529
+ contentStarted = true;
1530
+ const headingMatch = line?.match(/^(#{2,3})\s+(.+)$/);
1531
+ if (headingMatch && contentStarted) {
1532
+ if (currentSection) {
1533
+ currentSection.endLine = index - 1;
1534
+ sections.push(currentSection);
1535
+ }
1536
+ const level = headingMatch[1]?.length ?? 0;
1537
+ currentSection = {
1538
+ title: headingMatch[2] || "Untitled",
1539
+ level,
1540
+ content: line + "\n",
1541
+ startLine: index,
1542
+ endLine: index
1543
+ };
1544
+ } else if (currentSection) currentSection.content += line + "\n";
1545
+ }
1546
+ if (currentSection) {
1547
+ currentSection.endLine = lines.length - 1;
1548
+ sections.push(currentSection);
1549
+ }
1550
+ return sections;
1551
+ }
1552
+ async function discoverMigrations(baseDir, relativePath = "") {
1553
+ const migrations = [];
1554
+ const fullPath = path.join(baseDir, relativePath);
1555
+ try {
1556
+ const entries = await fs.readdir(fullPath, { withFileTypes: true });
1557
+ for (const entry of entries) {
1558
+ const entryRelativePath = path.join(relativePath, entry.name);
1559
+ if (entry.isDirectory()) {
1560
+ migrations.push({
1561
+ path: entryRelativePath,
1562
+ type: "directory"
1563
+ });
1564
+ const subMigrations = await discoverMigrations(baseDir, entryRelativePath);
1565
+ migrations.push(...subMigrations);
1566
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
1567
+ const cleanName = entry.name.replace(/\.md$/, "");
1568
+ migrations.push({
1569
+ path: relativePath ? path.join(relativePath, cleanName) : cleanName,
1570
+ type: "file"
1571
+ });
1572
+ }
1573
+ }
1574
+ } catch (error) {
1575
+ logger.error("Failed to discover migrations", {
1576
+ path: fullPath,
1577
+ error
1578
+ });
1579
+ }
1580
+ return migrations;
1581
+ }
1582
+ async function listDirectoryContents(dirPath = "") {
1583
+ try {
1584
+ const fullPath = path.join(migrationsBaseDir, dirPath);
1585
+ const resolvedPath = path.resolve(fullPath);
1586
+ const resolvedBaseDir = path.resolve(migrationsBaseDir);
1587
+ if (!resolvedPath.startsWith(resolvedBaseDir)) return "Invalid path";
1588
+ const entries = await fs.readdir(fullPath, { withFileTypes: true });
1589
+ const directories = [];
1590
+ const files = [];
1591
+ for (const entry of entries) if (entry.isDirectory()) directories.push(entry.name);
1592
+ else if (entry.isFile() && entry.name.endsWith(".md")) files.push(entry.name.replace(/\.md$/, ""));
1593
+ const output = [];
1594
+ const currentPath = dirPath || "migrations";
1595
+ output.push(`# ${currentPath}`);
1596
+ output.push("");
1597
+ if (directories.length > 0) {
1598
+ output.push("**Directories:**");
1599
+ directories.sort().forEach((dir) => {
1600
+ const nextPath = dirPath ? `${dirPath}/${dir}` : dir;
1601
+ output.push(`- **${dir}/** - Explore with \`{ path: "${nextPath}/" }\``);
1602
+ });
1603
+ output.push("");
1604
+ }
1605
+ if (files.length > 0) {
1606
+ output.push("**Migration Guides:**");
1607
+ files.sort().forEach((file) => {
1608
+ const filePath = dirPath ? `${dirPath}/${file}` : file;
1609
+ output.push(`- **${file}** - Get with \`{ path: "${filePath}" }\``);
1610
+ });
1611
+ output.push("");
1612
+ }
1613
+ if (directories.length === 0 && files.length === 0) output.push("No migrations found in this directory.");
1614
+ output.push("---");
1615
+ output.push("");
1616
+ output.push("**Actions:**");
1617
+ output.push("- Navigate to a directory by setting `path` to directory name with trailing `/`");
1618
+ output.push("- View a migration guide by setting `path` to the guide name");
1619
+ output.push("- List sections in a guide with `listSections: true`");
1620
+ output.push("- Search all guides with `queryKeywords`");
1621
+ return output.join("\n");
1622
+ } catch (error) {
1623
+ if (error.code === "ENOENT") return `Directory "${dirPath}" not found. Use \`{}\` to see top-level migrations.`;
1624
+ throw error;
1625
+ }
1626
+ }
1627
+ async function readMigrationContent(migrationPath) {
1628
+ try {
1629
+ const cleanPath = migrationPath.replace(/\.(mdx|md)$/, "");
1630
+ const filePath = path.join(migrationsBaseDir, cleanPath + ".md");
1631
+ const resolvedPath = path.resolve(filePath);
1632
+ const resolvedBaseDir = path.resolve(migrationsBaseDir);
1633
+ if (!resolvedPath.startsWith(resolvedBaseDir)) {
1634
+ logger.error("Path traversal attempt detected");
1635
+ return null;
1636
+ }
1637
+ return await fs.readFile(filePath, "utf-8");
1638
+ } catch (error) {
1639
+ logger.error("Failed to read migration", {
1640
+ path: migrationPath,
1641
+ error
1642
+ });
1643
+ return null;
1644
+ }
1645
+ }
1646
+ async function getSectionHeaders(migrationPath) {
1647
+ const content = await readMigrationContent(migrationPath);
1648
+ if (!content) return [];
1649
+ return parseSections(content).map((s) => ({
1650
+ title: s.title,
1651
+ level: s.level
1652
+ }));
1653
+ }
1654
+ async function getSections(migrationPath, sectionTitles) {
1655
+ const content = await readMigrationContent(migrationPath);
1656
+ if (!content) return `Migration "${migrationPath}" not found.\n\nAvailable migrations:\n${(await discoverMigrations(migrationsBaseDir)).filter((m) => m.type === "file").map((m) => `- ${m.path}`).join("\n")}`;
1657
+ if (!sectionTitles || sectionTitles.length === 0) return content;
1658
+ const sections = parseSections(content);
1659
+ const requestedSections = sections.filter((s) => sectionTitles.some((title) => s.title.toLowerCase().includes(title.toLowerCase())));
1660
+ if (requestedSections.length === 0) return `Requested sections not found in "${migrationPath}".\n\nAvailable sections:\n${sections.map((s) => `${"#".repeat(s.level)} ${s.title}`).join("\n")}`;
1661
+ return requestedSections.map((s) => s.content).join("\n---\n\n");
1662
+ }
1663
+ const migrationFiles = (await discoverMigrations(migrationsBaseDir)).filter((m) => m.type === "file");
1664
+ const migrationsListing = migrationFiles.length > 0 ? "\n\nExample migration paths:\n" + migrationFiles.slice(0, 5).map((m) => `- ${m.path}`).join("\n") + "\n..." : "\n\nNo migrations available. Run the documentation preparation script first.";
1665
+ const migrationTool = {
1666
+ name: "mastraMigration",
1667
+ description: `[🌐 REMOTE] Get migration guidance for Mastra version upgrades and breaking changes.
1668
+
1669
+ This tool works like a file browser - navigate through directories to find migration guides:
1670
+
1671
+ **Step 1: List top-level migrations**
1672
+ - Call with no parameters: \`{}\`
1673
+ - Shows all top-level migration guides and directories
1674
+
1675
+ **Step 2: Navigate into a directory**
1676
+ - Add trailing slash to explore: \`{ path: "upgrade-to-v1/" }\`
1677
+ - Lists all migration guides in that directory
1678
+
1679
+ **Step 3: View a migration guide**
1680
+ - Without trailing slash: \`{ path: "upgrade-to-v1/agent" }\`
1681
+ - Returns the full migration guide content
1682
+
1683
+ **Step 4: Explore guide sections (optional)**
1684
+ - List sections: \`{ path: "upgrade-to-v1/agent", listSections: true }\`
1685
+ - Get specific sections: \`{ path: "upgrade-to-v1/agent", sections: ["Voice methods"] }\`
1686
+
1687
+ **Alternative: Search by keywords**
1688
+ - \`{ queryKeywords: ["RuntimeContext", "pagination"] }\`
1689
+
1690
+ **Examples:**
1691
+ 1. List top-level: \`{}\`
1692
+ 2. Navigate to upgrade-to-v1: \`{ path: "upgrade-to-v1/" }\`
1693
+ 3. Get agent guide: \`{ path: "upgrade-to-v1/agent" }\`
1694
+ 4. List guide sections: \`{ path: "upgrade-to-v1/agent", listSections: true }\`
1695
+ 5. Search: \`{ queryKeywords: ["RuntimeContext"] }\`
1696
+
1697
+ **Tip:** Paths ending with \`/\` list directory contents. Paths without \`/\` fetch the migration guide.`,
1698
+ parameters: z.object({
1699
+ path: z.string().optional().describe("Path to the migration guide (e.g., \"upgrade-to-v1/agent\", \"agentnetwork\"). If not provided, lists all available migrations." + migrationsListing),
1700
+ sections: z.array(z.string()).optional().describe("Specific section titles to fetch from the migration guide. If not provided, returns the entire guide. Use this after exploring section headers."),
1701
+ listSections: z.boolean().optional().describe("Set to true to list all section headers in a migration guide without fetching full content."),
1702
+ queryKeywords: z.array(z.string()).optional().describe("Keywords to search across all migration guides. Use this to find guides related to specific topics.")
1703
+ }),
1704
+ execute: async (args) => {
1705
+ logger.debug("Executing mastraMigration tool", { args });
1706
+ try {
1707
+ if (args.queryKeywords && args.queryKeywords.length > 0) return [
1708
+ "# Migration Guide Search Results",
1709
+ "",
1710
+ await getMatchingPaths("", args.queryKeywords, migrationsBaseDir) || "No migration guides found matching your keywords.",
1711
+ "",
1712
+ "---",
1713
+ "",
1714
+ "To see all available migrations, call with no parameters."
1715
+ ].join("\n");
1716
+ if (args.path) {
1717
+ if (args.path.endsWith("/")) return await listDirectoryContents(args.path.slice(0, -1));
1718
+ if (args.listSections) {
1719
+ const headers = await getSectionHeaders(args.path);
1720
+ if (headers.length === 0) return await listDirectoryContents();
1721
+ return [
1722
+ `# ${args.path} - Section Headers`,
1723
+ "",
1724
+ "Available sections in this migration guide:",
1725
+ "",
1726
+ ...headers.map((h) => `${"#".repeat(h.level)} ${h.title}`),
1727
+ "",
1728
+ "---",
1729
+ "",
1730
+ "To get specific sections, provide their titles in the \"sections\" parameter."
1731
+ ].join("\n");
1732
+ }
1733
+ const content = await getSections(args.path, args.sections);
1734
+ return `# ${args.path}\n\n${content}`;
1735
+ }
1736
+ return await listDirectoryContents();
1737
+ } catch (error) {
1738
+ logger.error("Failed to execute mastraMigration tool", error);
1739
+ throw error;
1740
+ }
1741
+ }
1742
+ };
1743
+ //#endregion
1744
+ //#region src/index.ts
1745
+ let server;
1746
+ server = new MCPServer({
1747
+ name: "Mastra Documentation Server",
1748
+ version: JSON.parse(await fs.readFile(fromPackageRoot(`package.json`), "utf8")).version,
1749
+ tools: {
1750
+ mastraDocs: docsTool,
1751
+ mastraMigration: migrationTool,
1752
+ startMastraCourse,
1753
+ getMastraCourseStatus,
1754
+ startMastraCourseLesson,
1755
+ nextMastraCourseStep,
1756
+ clearMastraCourseHistory,
1757
+ ...embeddedDocsTools
1758
+ },
1759
+ prompts: migrationPromptMessages
1760
+ });
1761
+ Object.assign(logger, createLogger(server));
1762
+ async function runServer() {
1763
+ try {
1764
+ await server.startStdio();
1765
+ logger.info("Started Mastra Docs MCP Server");
1766
+ } catch (error) {
1767
+ logger.error("Failed to start server", error);
1768
+ process.exit(1);
1769
+ }
1770
+ }
1771
+ //#endregion
1772
+ export { writeErrorLog as i, server as n, setLogLevel as r, runServer as t };
1773
+
1774
+ //# sourceMappingURL=src-BZcgzbk9.js.map