@musashishao/agent-kit 1.4.0 → 1.5.0

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.
@@ -3,10 +3,7 @@
3
3
  * Agent Kit MCP Gateway Server
4
4
  *
5
5
  * Provides AI agents with live access to project context, dependency graphs,
6
- * and semantic code search with automatic sync capabilities.
7
- *
8
- * Usage:
9
- * node dist/index.js [--project-root /path/to/project]
6
+ * and semantic search with automatic sync capabilities.
10
7
  */
11
8
 
12
9
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -31,14 +28,11 @@ const timestampChecker = new TimestampChecker(PROJECT_ROOT);
31
28
  // ============================================================================
32
29
 
33
30
  async function autoSyncIfNeeded(): Promise<{ synced: boolean; message: string }> {
34
- // Check if data is stale
35
31
  const status = timestampChecker.getSyncStatus();
36
-
37
32
  if (!status.needsSync) {
38
33
  return { synced: false, message: "Data is fresh" };
39
34
  }
40
35
 
41
- // Try to sync with debouncing
42
36
  const result = await debouncer.executeWithDebounce(async () => {
43
37
  return await syncer.syncAll();
44
38
  });
@@ -57,35 +51,17 @@ async function autoSyncIfNeeded(): Promise<{ synced: boolean; message: string }>
57
51
  function readJsonFile(filePath: string): any {
58
52
  try {
59
53
  const fullPath = path.resolve(PROJECT_ROOT, filePath);
60
- if (!fs.existsSync(fullPath)) {
61
- return null;
62
- }
54
+ if (!fs.existsSync(fullPath)) return null;
63
55
  return JSON.parse(fs.readFileSync(fullPath, "utf-8"));
64
- } catch (error) {
65
- return null;
66
- }
56
+ } catch { return null; }
67
57
  }
68
58
 
69
59
  function readTextFile(filePath: string): string | null {
70
60
  try {
71
61
  const fullPath = path.resolve(PROJECT_ROOT, filePath);
72
- if (!fs.existsSync(fullPath)) {
73
- return null;
74
- }
62
+ if (!fs.existsSync(fullPath)) return null;
75
63
  return fs.readFileSync(fullPath, "utf-8");
76
- } catch (error) {
77
- return null;
78
- }
79
- }
80
-
81
- function getFileModTime(filePath: string): number {
82
- try {
83
- const fullPath = path.resolve(PROJECT_ROOT, filePath);
84
- const stats = fs.statSync(fullPath);
85
- return stats.mtimeMs;
86
- } catch {
87
- return 0;
88
- }
64
+ } catch { return null; }
89
65
  }
90
66
 
91
67
  // ============================================================================
@@ -94,68 +70,43 @@ function getFileModTime(filePath: string): number {
94
70
 
95
71
  const server = new McpServer({
96
72
  name: "agent-kit-gateway",
97
- version: "1.0.0",
73
+ version: "1.1.0", // Universal Intelligence Version
98
74
  });
99
75
 
100
76
  // ============================================================================
101
- // Tool 1: get_project_context
77
+ // Tool: get_project_context
102
78
  // ============================================================================
103
79
 
104
80
  server.tool(
105
81
  "get_project_context",
106
82
  "Get project overview including tech stack, structure, and conventions from AGENTS.md",
107
83
  {
108
- section: z
109
- .enum(["all", "tech_stack", "structure", "conventions", "commands"])
110
- .optional()
111
- .default("all")
112
- .describe("Specific section to retrieve"),
84
+ section: z.enum(["all", "tech_stack", "structure", "conventions", "commands"]).optional().default("all")
113
85
  },
114
86
  async ({ section }) => {
115
87
  const agentsMd = readTextFile("AGENTS.md");
116
-
117
88
  if (!agentsMd) {
118
89
  return {
119
- content: [
120
- {
121
- type: "text",
122
- text: JSON.stringify({
123
- error: "AGENTS.md not found",
124
- suggestion: "Run 'ak init' to generate AI infrastructure",
125
- project_root: PROJECT_ROOT,
126
- }),
127
- },
128
- ],
90
+ content: [{ type: "text" as const, text: "AGENTS.md not found. Run 'ak init' first." }]
129
91
  };
130
92
  }
131
93
 
132
- // Parse sections from AGENTS.md
133
94
  const sections: Record<string, string> = {};
134
- const sectionRegex = /^## (.+)$/gm;
135
- let matches;
136
- let lastIndex = 0;
137
- let lastSection = "";
138
-
139
95
  const lines = agentsMd.split("\n");
140
96
  let currentSection = "";
141
97
  let currentContent: string[] = [];
142
98
 
143
99
  for (const line of lines) {
144
100
  if (line.startsWith("## ")) {
145
- if (currentSection) {
146
- sections[currentSection] = currentContent.join("\n").trim();
147
- }
101
+ if (currentSection) sections[currentSection] = currentContent.join("\n").trim();
148
102
  currentSection = line.replace("## ", "").trim();
149
103
  currentContent = [];
150
104
  } else {
151
105
  currentContent.push(line);
152
106
  }
153
107
  }
154
- if (currentSection) {
155
- sections[currentSection] = currentContent.join("\n").trim();
156
- }
108
+ if (currentSection) sections[currentSection] = currentContent.join("\n").trim();
157
109
 
158
- // Map section names to content
159
110
  const sectionMap: Record<string, string | undefined> = {
160
111
  tech_stack: sections["🛠️ Tech Stack"] || sections["Tech Stack"],
161
112
  structure: sections["📁 Directory Map"] || sections["Directory Map"],
@@ -164,427 +115,143 @@ server.tool(
164
115
  all: agentsMd,
165
116
  };
166
117
 
167
- const result = sectionMap[section] || agentsMd;
168
-
169
- return {
170
- content: [
171
- {
172
- type: "text",
173
- text: result,
174
- },
175
- ],
176
- };
118
+ return { content: [{ type: "text" as const, text: sectionMap[section] || agentsMd }] };
177
119
  }
178
120
  );
179
121
 
180
122
  // ============================================================================
181
- // Tool 2: analyze_dependencies
123
+ // Tool: get_project_intelligence
182
124
  // ============================================================================
183
125
 
184
126
  server.tool(
185
- "analyze_dependencies",
186
- "Get dependency graph for a file or module. Shows imports and files that import this file. Auto-syncs if data is stale.",
187
- {
188
- file_path: z.string().describe("File path to analyze (relative to project root)"),
189
- direction: z
190
- .enum(["imports", "imported_by", "both"])
191
- .optional()
192
- .default("both")
193
- .describe("Direction of dependencies to show"),
194
- depth: z.number().optional().default(2).describe("Depth of traversal"),
195
- },
196
- async ({ file_path, direction, depth }) => {
197
- // Auto-sync if data is stale
198
- const syncStatus = await autoSyncIfNeeded();
199
-
127
+ "get_project_intelligence",
128
+ "Get high-level project summary: Code vs Docs distribution, primary types, and impact analysis.",
129
+ {},
130
+ async () => {
200
131
  const graph = readJsonFile(".agent/graph.json");
201
-
202
- if (!graph) {
203
- return {
204
- content: [
205
- {
206
- type: "text",
207
- text: JSON.stringify({
208
- error: "Dependency graph not found",
209
- suggestion: "Run 'ak sync' to generate dependency graph",
210
- }),
211
- },
212
- ],
213
- };
214
- }
215
-
216
- // Normalize file path
217
- const normalizedPath = file_path.replace(/^\.\//, "");
218
-
219
- // Find the node
220
- const node = graph.nodes?.find(
221
- (n: any) => n.id === normalizedPath || n.path === normalizedPath
222
- );
223
-
224
- if (!node) {
225
- return {
226
- content: [
227
- {
228
- type: "text",
229
- text: JSON.stringify({
230
- error: `File not found in graph: ${file_path}`,
231
- available_files: graph.nodes?.slice(0, 10).map((n: any) => n.id),
232
- }),
233
- },
234
- ],
235
- };
236
- }
237
-
238
- // Build result based on direction
239
- const result: any = {
240
- file: normalizedPath,
241
- type: node.type,
132
+ if (!graph) return { content: [{ type: "text" as const, text: "Project data not generated." }] };
133
+
134
+ const stats = graph.metadata?.file_types || {};
135
+ const total = graph.metadata?.total_files || 1;
136
+ const docCount = (stats.documentation || 0) + (stats.document || 0) + (stats.readme || 0);
137
+ const codeCount = (stats.component || 0) + (stats.module || 0) + (stats.utility || 0);
138
+
139
+ const intelligence = {
140
+ project_name: path.basename(PROJECT_ROOT),
141
+ project_type: docCount > codeCount ? "Documentation-Heavy" : "Code-Centric",
142
+ distribution: { docs: `${Math.round((docCount / total) * 100)}%`, code: `${Math.round((codeCount / total) * 100)}%` },
143
+ top_files: graph.nodes?.slice(0, 5).map((n: any) => n.id)
242
144
  };
243
145
 
244
- if (direction === "imports" || direction === "both") {
245
- result.imports = node.imports || [];
246
- }
247
-
248
- if (direction === "imported_by" || direction === "both") {
249
- // Find files that import this file
250
- const importedBy = graph.edges
251
- ?.filter((e: any) => e.target === normalizedPath)
252
- .map((e: any) => e.source) || [];
253
- result.imported_by = importedBy;
254
- }
255
-
256
- // Calculate impact score
257
- result.impact_score = (result.imported_by?.length || 0);
258
-
259
- return {
260
- content: [
261
- {
262
- type: "text",
263
- text: JSON.stringify(result, null, 2),
264
- },
265
- ],
266
- };
146
+ return { content: [{ type: "text" as const, text: JSON.stringify(intelligence, null, 2) }] };
267
147
  }
268
148
  );
269
149
 
270
150
  // ============================================================================
271
- // Tool 3: search_code_logic
151
+ // Tool: analyze_dependencies
272
152
  // ============================================================================
273
153
 
274
154
  server.tool(
275
- "search_code_logic",
276
- "Semantic search through code chunks with context. Auto-syncs if data is stale.",
155
+ "analyze_dependencies",
156
+ "Analyze imports/references for a file.",
277
157
  {
278
- query: z.string().describe("Search query describing what you're looking for"),
279
- file_filter: z.string().optional().describe("Glob pattern to filter files (e.g., '*.ts')"),
280
- top_k: z.number().optional().default(10).describe("Number of results to return"),
158
+ file_path: z.string(),
159
+ direction: z.enum(["imports", "imported_by", "both"]).optional().default("both")
281
160
  },
282
- async ({ query, file_filter, top_k }) => {
283
- // Auto-sync if data is stale
284
- const syncStatus = await autoSyncIfNeeded();
285
-
286
- const chunks = readJsonFile(".agent/rag/chunks.json");
287
-
288
- if (!chunks || !chunks.chunks) {
289
- return {
290
- content: [
291
- {
292
- type: "text",
293
- text: JSON.stringify({
294
- error: "RAG chunks not found",
295
- suggestion: "Run 'ak sync' to generate code chunks",
296
- }),
297
- },
298
- ],
299
- };
300
- }
301
-
302
- // Simple keyword-based search (in production, use embeddings)
303
- const queryWords = query.toLowerCase().split(/\s+/);
161
+ async ({ file_path, direction }) => {
162
+ await autoSyncIfNeeded();
163
+ const graph = readJsonFile(".agent/graph.json");
164
+ if (!graph) return { content: [{ type: "text" as const, text: "Graph not found." }] };
304
165
 
305
- const scored = chunks.chunks.map((chunk: any) => {
306
- const content = (chunk.content || "").toLowerCase();
307
- const metadata = chunk.metadata || {};
166
+ const normalizedPath = file_path.replace(/^\.\//, "");
167
+ const node = graph.nodes?.find((n: any) => n.id === normalizedPath);
168
+ if (!node) return { content: [{ type: "text" as const, text: "File not found in graph." }] };
308
169
 
309
- // Apply file filter
310
- if (file_filter) {
311
- const pattern = file_filter.replace("*", ".*");
312
- if (!new RegExp(pattern).test(metadata.file_path || "")) {
313
- return { chunk, score: -1 };
314
- }
315
- }
170
+ const isDoc = ["documentation", "readme", "document"].includes(node.type);
171
+ const outLabel = isDoc ? "references" : "imports";
172
+ const inLabel = isDoc ? "referenced_by" : "imported_by";
316
173
 
317
- // Score based on keyword matches
318
- let score = 0;
319
- for (const word of queryWords) {
320
- if (content.includes(word)) {
321
- score += 1;
322
- }
323
- if ((metadata.name || "").toLowerCase().includes(word)) {
324
- score += 2; // Boost for name matches
325
- }
326
- }
174
+ const result: any = { file: normalizedPath, type: node.type };
175
+ if (direction !== "imported_by") result[outLabel] = node.imports || [];
176
+ if (direction !== "imports") {
177
+ result[inLabel] = graph.edges?.filter((e: any) => e.target === normalizedPath).map((e: any) => e.source) || [];
178
+ }
327
179
 
328
- return { chunk, score };
329
- });
330
-
331
- // Sort and take top k
332
- const results = scored
333
- .filter((s: any) => s.score > 0)
334
- .sort((a: any, b: any) => b.score - a.score)
335
- .slice(0, top_k)
336
- .map((s: any) => ({
337
- file: s.chunk.metadata?.file_path,
338
- type: s.chunk.metadata?.chunk_type,
339
- name: s.chunk.metadata?.name,
340
- lines: `${s.chunk.metadata?.start_line}-${s.chunk.metadata?.end_line}`,
341
- preview: s.chunk.content?.substring(0, 200) + "...",
342
- relevance_score: s.score,
343
- }));
344
-
345
- return {
346
- content: [
347
- {
348
- type: "text",
349
- text: JSON.stringify({
350
- query,
351
- total_results: results.length,
352
- results,
353
- }, null, 2),
354
- },
355
- ],
356
- };
180
+ return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
357
181
  }
358
182
  );
359
183
 
360
184
  // ============================================================================
361
- // Tool 4: get_impact_zone
185
+ // Tool: search_knowledge (Search Engine)
362
186
  // ============================================================================
363
187
 
188
+ async function handleSearch(args: { query: string, file_filter?: string, top_k: number }) {
189
+ await autoSyncIfNeeded();
190
+ const chunks = readJsonFile(".agent/rag/chunks.json");
191
+ if (!chunks?.chunks) return { content: [{ type: "text" as const, text: "No chunks found." }] };
192
+
193
+ const query = args.query.toLowerCase();
194
+ const results = chunks.chunks
195
+ .map((chunk: any) => {
196
+ let score = 0;
197
+ const content = chunk.content.toLowerCase();
198
+ if (content.includes(query)) score += 5;
199
+ if (chunk.metadata.name?.toLowerCase().includes(query)) score += 10;
200
+ return { chunk, score };
201
+ })
202
+ .filter((s: any) => s.score > 0)
203
+ .sort((a: any, b: any) => b.score - a.score)
204
+ .slice(0, args.top_k)
205
+ .map((s: any) => ({
206
+ file: s.chunk.metadata.file_path,
207
+ context: s.chunk.metadata.context_path || s.chunk.metadata.name,
208
+ preview: s.chunk.content.substring(0, 250) + "..."
209
+ }));
210
+
211
+ return { content: [{ type: "text" as const, text: JSON.stringify(results, null, 2) }] };
212
+ }
213
+
364
214
  server.tool(
365
- "get_impact_zone",
366
- "Get all files that would be affected if a specific file is modified. Auto-syncs if data is stale.",
215
+ "search_knowledge",
216
+ "Universal search through project knowledge (Code + Docs).",
367
217
  {
368
- file_path: z.string().describe("File path to analyze impact for"),
369
- depth: z.number().optional().default(3).describe("How many levels of dependencies to traverse"),
218
+ query: z.string(),
219
+ file_filter: z.string().optional(),
220
+ top_k: z.number().optional().default(10)
370
221
  },
371
- async ({ file_path, depth }) => {
372
- // Auto-sync if data is stale
373
- const syncStatus = await autoSyncIfNeeded();
374
-
375
- const graph = readJsonFile(".agent/graph.json");
376
-
377
- if (!graph) {
378
- return {
379
- content: [
380
- {
381
- type: "text",
382
- text: JSON.stringify({
383
- error: "Dependency graph not found",
384
- suggestion: "Run 'ak sync' to generate dependency graph",
385
- }),
386
- },
387
- ],
388
- };
389
- }
390
-
391
- const normalizedPath = file_path.replace(/^\.\//, "");
392
-
393
- // Build adjacency list for reverse dependencies (who imports this?)
394
- const reverseGraph: Record<string, string[]> = {};
395
- for (const edge of graph.edges || []) {
396
- if (!reverseGraph[edge.target]) {
397
- reverseGraph[edge.target] = [];
398
- }
399
- reverseGraph[edge.target].push(edge.source);
400
- }
401
-
402
- // BFS to find all affected files
403
- const visited = new Set<string>();
404
- const queue: { file: string; level: number }[] = [{ file: normalizedPath, level: 0 }];
405
- const impactByLevel: Record<number, string[]> = {};
406
-
407
- while (queue.length > 0) {
408
- const { file, level } = queue.shift()!;
409
-
410
- if (visited.has(file) || level > depth) {
411
- continue;
412
- }
413
- visited.add(file);
414
-
415
- if (level > 0) {
416
- if (!impactByLevel[level]) {
417
- impactByLevel[level] = [];
418
- }
419
- impactByLevel[level].push(file);
420
- }
421
-
422
- // Add files that import this file
423
- const dependents = reverseGraph[file] || [];
424
- for (const dep of dependents) {
425
- if (!visited.has(dep)) {
426
- queue.push({ file: dep, level: level + 1 });
427
- }
428
- }
429
- }
430
-
431
- // Flatten results
432
- const allAffected = Object.values(impactByLevel).flat();
433
-
434
- return {
435
- content: [
436
- {
437
- type: "text",
438
- text: JSON.stringify({
439
- source_file: normalizedPath,
440
- total_affected: allAffected.length,
441
- impact_by_level: impactByLevel,
442
- all_affected_files: allAffected,
443
- warning: allAffected.length > 10
444
- ? "High impact change! Review carefully."
445
- : null,
446
- }, null, 2),
447
- },
448
- ],
449
- };
450
- }
222
+ async (args) => await handleSearch(args)
451
223
  );
452
224
 
453
- // ============================================================================
454
- // Tool 5: force_sync
455
- // ============================================================================
456
-
457
225
  server.tool(
458
- "force_sync",
459
- "Force refresh all AI infrastructure data (AGENTS.md, graph, RAG chunks)",
226
+ "search_code_logic",
227
+ "Alias for search_knowledge.",
460
228
  {
461
- target: z
462
- .enum(["all", "graph", "rag", "agents_md"])
463
- .optional()
464
- .default("all")
465
- .describe("What to sync"),
229
+ query: z.string(),
230
+ file_filter: z.string().optional(),
231
+ top_k: z.number().optional().default(10)
466
232
  },
467
- async ({ target }) => {
468
- const { spawn } = await import("child_process");
469
- const results: Record<string, string> = {};
470
-
471
- const runScript = (scriptPath: string, args: string[]): Promise<string> => {
472
- return new Promise((resolve, reject) => {
473
- const proc = spawn("python", [scriptPath, ...args], {
474
- cwd: PROJECT_ROOT,
475
- });
476
-
477
- let output = "";
478
- let error = "";
479
-
480
- proc.stdout.on("data", (data) => {
481
- output += data.toString();
482
- });
483
-
484
- proc.stderr.on("data", (data) => {
485
- error += data.toString();
486
- });
487
-
488
- proc.on("close", (code) => {
489
- if (code === 0) {
490
- resolve(output);
491
- } else {
492
- resolve(`Error: ${error || output}`);
493
- }
494
- });
495
-
496
- proc.on("error", (err) => {
497
- resolve(`Failed to run: ${err.message}`);
498
- });
499
- });
500
- };
501
-
502
- // Determine script paths (relative to kit installation)
503
- const kitPath = process.env.AGENT_KIT_PATH || path.join(__dirname, "..", "..");
504
-
505
- if (target === "all" || target === "graph") {
506
- const graphScript = path.join(kitPath, "skills/graph-mapper/scripts/generate_graph.py");
507
- if (fs.existsSync(graphScript)) {
508
- results.graph = await runScript(graphScript, [
509
- "--src", path.join(PROJECT_ROOT, "src"),
510
- "--output", path.join(PROJECT_ROOT, ".agent/graph.json"),
511
- "--format", "both"
512
- ]);
513
- } else {
514
- results.graph = "Graph script not found";
515
- }
516
- }
517
-
518
- if (target === "all" || target === "rag") {
519
- const ragScript = path.join(kitPath, "skills/rag-engineering/scripts/chunk_code.py");
520
- if (fs.existsSync(ragScript)) {
521
- results.rag = await runScript(ragScript, [
522
- "--src", path.join(PROJECT_ROOT, "src"),
523
- "--output", path.join(PROJECT_ROOT, ".agent/rag/chunks.json")
524
- ]);
525
- } else {
526
- results.rag = "RAG script not found";
527
- }
528
- }
529
-
530
- if (target === "all" || target === "agents_md") {
531
- const infraScript = path.join(kitPath, "skills/app-builder/scripts/generate_ai_infra.py");
532
- if (fs.existsSync(infraScript)) {
533
- results.agents_md = await runScript(infraScript, [
534
- "--project-root", PROJECT_ROOT
535
- ]);
536
- } else {
537
- results.agents_md = "AI infra script not found";
538
- }
539
- }
540
-
541
- return {
542
- content: [
543
- {
544
- type: "text",
545
- text: JSON.stringify({
546
- status: "Sync completed",
547
- target,
548
- results,
549
- timestamp: new Date().toISOString(),
550
- }, null, 2),
551
- },
552
- ],
553
- };
554
- }
233
+ async (args) => await handleSearch(args)
555
234
  );
556
235
 
557
236
  // ============================================================================
558
- // Resource: Project Status
237
+ // Tool: get_impact_zone
559
238
  // ============================================================================
560
239
 
561
- server.resource(
562
- "project-status",
563
- "status://project",
564
- async (uri) => {
565
- const agentsMd = fs.existsSync(path.join(PROJECT_ROOT, "AGENTS.md"));
566
- const graphJson = fs.existsSync(path.join(PROJECT_ROOT, ".agent/graph.json"));
567
- const ragChunks = fs.existsSync(path.join(PROJECT_ROOT, ".agent/rag/chunks.json"));
568
-
569
- const status = {
570
- project_root: PROJECT_ROOT,
571
- infrastructure: {
572
- agents_md: agentsMd ? "✅ Present" : "❌ Missing",
573
- graph: graphJson ? "✅ Present" : "❌ Missing",
574
- rag: ragChunks ? "✅ Present" : "❌ Missing",
575
- },
576
- last_checked: new Date().toISOString(),
577
- };
240
+ server.tool(
241
+ "get_impact_zone",
242
+ "Find files affected by changing a file.",
243
+ { file_path: z.string() },
244
+ async ({ file_path }) => {
245
+ await autoSyncIfNeeded();
246
+ const graph = readJsonFile(".agent/graph.json");
247
+ if (!graph) return { content: [{ type: "text" as const, text: "Graph not found." }] };
578
248
 
579
- return {
580
- contents: [
581
- {
582
- uri: uri.href,
583
- mimeType: "application/json",
584
- text: JSON.stringify(status, null, 2),
585
- },
586
- ],
587
- };
249
+ const normalizedPath = file_path.replace(/^\.\//, "");
250
+ const affected = graph.edges
251
+ ?.filter((e: any) => e.target === normalizedPath)
252
+ .map((e: any) => e.source) || [];
253
+
254
+ return { content: [{ type: "text" as const, text: JSON.stringify({ file: normalizedPath, affected }, null, 2) }] };
588
255
  }
589
256
  );
590
257
 
@@ -593,16 +260,11 @@ server.resource(
593
260
  // ============================================================================
594
261
 
595
262
  async function main() {
596
- console.error(`Agent Kit MCP Gateway starting...`);
597
- console.error(`Project root: ${PROJECT_ROOT}`);
598
-
599
263
  const transport = new StdioServerTransport();
600
264
  await server.connect(transport);
601
-
602
- console.error("MCP Gateway ready and listening on stdio");
603
265
  }
604
266
 
605
- main().catch((error) => {
606
- console.error("Fatal error:", error);
267
+ main().catch(err => {
268
+ console.error(err);
607
269
  process.exit(1);
608
270
  });