sad-mcp 0.1.6 → 0.1.8
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/dist/index.js +1 -1
- package/dist/tools.js +129 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { trackServerStart } from "./tracking.js";
|
|
|
7
7
|
async function main() {
|
|
8
8
|
// Connect to Claude Desktop IMMEDIATELY — no blocking auth here
|
|
9
9
|
// Auth happens lazily on first Drive API call
|
|
10
|
-
const server = new Server({ name: "sad-mcp", version: "0.1.
|
|
10
|
+
const server = new Server({ name: "sad-mcp", version: "0.1.7" }, {
|
|
11
11
|
capabilities: {
|
|
12
12
|
tools: {},
|
|
13
13
|
prompts: {},
|
package/dist/tools.js
CHANGED
|
@@ -46,7 +46,7 @@ export function registerToolHandlers(server) {
|
|
|
46
46
|
tools: [
|
|
47
47
|
{
|
|
48
48
|
name: "search_materials",
|
|
49
|
-
description: "Search across all course materials (
|
|
49
|
+
description: "Search across all course materials for a topic. Returns a SHORT summary list of matching files (name, category, match count). To read the actual content, use get_material on the most relevant file(s) from the results.",
|
|
50
50
|
inputSchema: {
|
|
51
51
|
type: "object",
|
|
52
52
|
properties: {
|
|
@@ -58,6 +58,20 @@ export function registerToolHandlers(server) {
|
|
|
58
58
|
required: ["query"],
|
|
59
59
|
},
|
|
60
60
|
},
|
|
61
|
+
{
|
|
62
|
+
name: "get_material",
|
|
63
|
+
description: "Get the full text content of a specific course material file. Use this AFTER search_materials to read the content of a relevant file.",
|
|
64
|
+
inputSchema: {
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {
|
|
67
|
+
name: {
|
|
68
|
+
type: "string",
|
|
69
|
+
description: "The file name (or partial name) to retrieve. Matched against file names from search_materials or list_materials results.",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
required: ["name"],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
61
75
|
{
|
|
62
76
|
name: "list_materials",
|
|
63
77
|
description: "List all available course materials, optionally filtered by category.",
|
|
@@ -72,6 +86,24 @@ export function registerToolHandlers(server) {
|
|
|
72
86
|
},
|
|
73
87
|
},
|
|
74
88
|
},
|
|
89
|
+
{
|
|
90
|
+
name: "quiz",
|
|
91
|
+
description: "Generate a practice quiz on a course topic. Searches course materials for relevant content and returns it with instructions for Claude to create quiz questions. Use when a student wants to test their knowledge.",
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
topic: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: "The topic to quiz on (e.g., 'use case diagrams', 'BPMN', 'normalization', 'state diagrams')",
|
|
98
|
+
},
|
|
99
|
+
num_questions: {
|
|
100
|
+
type: "number",
|
|
101
|
+
description: "Number of questions to generate. Defaults to 5.",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
required: ["topic"],
|
|
105
|
+
},
|
|
106
|
+
},
|
|
75
107
|
],
|
|
76
108
|
}));
|
|
77
109
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -91,12 +123,14 @@ export function registerToolHandlers(server) {
|
|
|
91
123
|
if (matches.length > 0) {
|
|
92
124
|
results.push({
|
|
93
125
|
fileName: file.name,
|
|
94
|
-
path: file.path,
|
|
95
126
|
category: categorizeFile(file),
|
|
96
|
-
|
|
127
|
+
matchCount: matches.length,
|
|
128
|
+
preview: matches[0].line.trim().substring(0, 120),
|
|
97
129
|
});
|
|
98
130
|
}
|
|
99
131
|
}
|
|
132
|
+
// Sort by match count descending (most relevant first)
|
|
133
|
+
results.sort((a, b) => b.matchCount - a.matchCount);
|
|
100
134
|
if (results.length === 0) {
|
|
101
135
|
return {
|
|
102
136
|
content: [
|
|
@@ -108,18 +142,53 @@ export function registerToolHandlers(server) {
|
|
|
108
142
|
};
|
|
109
143
|
}
|
|
110
144
|
const formatted = results
|
|
111
|
-
.map((r) => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
145
|
+
.map((r) => `- ${r.fileName} [${r.category}] (${r.matchCount} matches) — "${r.preview}"`)
|
|
146
|
+
.join("\n");
|
|
147
|
+
return {
|
|
148
|
+
content: [
|
|
149
|
+
{
|
|
150
|
+
type: "text",
|
|
151
|
+
text: `Found "${query}" in ${results.length} file(s). Use get_material to read the most relevant one(s):\n\n${formatted}`,
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (name === "get_material") {
|
|
157
|
+
const queryName = args.name;
|
|
158
|
+
if (!queryName) {
|
|
159
|
+
return {
|
|
160
|
+
content: [{ type: "text", text: "Error: name parameter is required" }],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
await ensureTextCache();
|
|
164
|
+
const queryLower = queryName.toLowerCase();
|
|
165
|
+
let bestMatch = null;
|
|
166
|
+
for (const [, entry] of textCache) {
|
|
167
|
+
if (entry.file.name.toLowerCase().includes(queryLower)) {
|
|
168
|
+
bestMatch = entry;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (!bestMatch) {
|
|
173
|
+
return {
|
|
174
|
+
content: [
|
|
175
|
+
{
|
|
176
|
+
type: "text",
|
|
177
|
+
text: `No file found matching "${queryName}". Use search_materials or list_materials to find available files.`,
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// Truncate very large files
|
|
183
|
+
const maxLen = 30000;
|
|
184
|
+
const text = bestMatch.text.length > maxLen
|
|
185
|
+
? bestMatch.text.substring(0, maxLen) + "\n...[truncated]"
|
|
186
|
+
: bestMatch.text;
|
|
118
187
|
return {
|
|
119
188
|
content: [
|
|
120
189
|
{
|
|
121
190
|
type: "text",
|
|
122
|
-
text:
|
|
191
|
+
text: `📄 ${bestMatch.file.name} [${categorizeFile(bestMatch.file)}]\n\n${text}`,
|
|
123
192
|
},
|
|
124
193
|
],
|
|
125
194
|
};
|
|
@@ -156,6 +225,55 @@ export function registerToolHandlers(server) {
|
|
|
156
225
|
],
|
|
157
226
|
};
|
|
158
227
|
}
|
|
228
|
+
if (name === "quiz") {
|
|
229
|
+
const topic = args.topic;
|
|
230
|
+
const numQuestions = args.num_questions || 5;
|
|
231
|
+
if (!topic) {
|
|
232
|
+
return {
|
|
233
|
+
content: [{ type: "text", text: "Error: topic parameter is required" }],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
await ensureTextCache();
|
|
237
|
+
// Gather relevant material for the topic
|
|
238
|
+
const relevantContent = [];
|
|
239
|
+
for (const [, { file, text }] of textCache) {
|
|
240
|
+
const matches = searchInText(text, topic);
|
|
241
|
+
if (matches.length > 0) {
|
|
242
|
+
// Include surrounding context (5 lines around each match)
|
|
243
|
+
const lines = text.split("\n");
|
|
244
|
+
const snippets = [];
|
|
245
|
+
for (const match of matches.slice(0, 10)) {
|
|
246
|
+
const start = Math.max(0, match.lineNumber - 6);
|
|
247
|
+
const end = Math.min(lines.length, match.lineNumber + 4);
|
|
248
|
+
snippets.push(lines.slice(start, end).join("\n"));
|
|
249
|
+
}
|
|
250
|
+
relevantContent.push(`--- ${file.name} ---\n${snippets.join("\n...\n")}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (relevantContent.length === 0) {
|
|
254
|
+
return {
|
|
255
|
+
content: [
|
|
256
|
+
{
|
|
257
|
+
type: "text",
|
|
258
|
+
text: `No course material found on "${topic}". Try a different topic or check available materials with list_materials.`,
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
const materialText = relevantContent.join("\n\n");
|
|
264
|
+
// Truncate if too large
|
|
265
|
+
const truncated = materialText.length > 15000
|
|
266
|
+
? materialText.substring(0, 15000) + "\n...[truncated]"
|
|
267
|
+
: materialText;
|
|
268
|
+
return {
|
|
269
|
+
content: [
|
|
270
|
+
{
|
|
271
|
+
type: "text",
|
|
272
|
+
text: `QUIZ REQUEST: Generate ${numQuestions} practice questions on "${topic}" based ONLY on the following course material. Mix question types: multiple choice, true/false, and short answer. For each question, provide the answer and a brief explanation referencing the source material. Write questions and answers in Hebrew.\n\n=== COURSE MATERIAL ===\n${truncated}`,
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
};
|
|
276
|
+
}
|
|
159
277
|
throw new Error(`Unknown tool: ${name}`);
|
|
160
278
|
});
|
|
161
279
|
}
|