@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.
- package/.docs/docs/mastra-platform/overview.md +4 -2
- package/.docs/docs/mastra-platform/trace-intelligence.md +123 -0
- package/.docs/docs/rag/vector-databases.md +17 -0
- package/.docs/docs/voice/overview.md +5 -0
- package/.docs/docs/voice/{livekit.md → realtime-voice.md} +5 -3
- package/.docs/docs/workspace/filesystem.md +1 -0
- package/.docs/docs/workspace/sandbox.md +5 -0
- package/.docs/guides/deployment/netlify.md +1 -1
- package/.docs/models/environment-variables.md +1 -0
- package/.docs/models/gateways/openrouter.md +2 -3
- package/.docs/models/index.md +1 -1
- package/.docs/models/providers/abliteration-ai.md +6 -5
- package/.docs/models/providers/aiand.md +2 -1
- package/.docs/models/providers/crof.md +2 -1
- package/.docs/models/providers/hyper.md +92 -0
- package/.docs/models/providers/nvidia.md +23 -9
- package/.docs/models/providers/synthetic.md +2 -1
- package/.docs/models/providers.md +1 -0
- package/.docs/reference/vectors/mongodb.md +185 -4
- package/.docs/reference/voice/livekit.md +3 -3
- package/CHANGELOG.md +7 -0
- package/dist/index.js +2 -3
- package/dist/src-BZcgzbk9.js +1774 -0
- package/dist/src-BZcgzbk9.js.map +1 -0
- package/dist/stdio.js +28 -30
- package/dist/stdio.js.map +1 -1
- package/package.json +6 -6
- package/dist/chunk-GLPCVXXO.js +0 -2075
- package/dist/chunk-GLPCVXXO.js.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -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
|