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