@mandujs/mcp 0.9.0 → 0.9.2

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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/tools/brain.ts +211 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/mcp",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "Mandu MCP Server - Agent-native interface for Mandu framework operations",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -32,7 +32,7 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@mandujs/core": "^0.9.0",
35
+ "@mandujs/core": "^0.9.2",
36
36
  "@modelcontextprotocol/sdk": "^1.25.3"
37
37
  },
38
38
  "engines": {
@@ -5,6 +5,9 @@
5
5
  * - mandu_doctor: Guard failure analysis + patch suggestions
6
6
  * - mandu_watch_start: Start file watching
7
7
  * - mandu_watch_status: Get watch status
8
+ * - mandu_check_location: Check if file location is valid (v0.2)
9
+ * - mandu_check_import: Check if imports are valid (v0.2)
10
+ * - mandu_get_architecture: Get project architecture rules (v0.2)
8
11
  */
9
12
 
10
13
  import type { Tool } from "@modelcontextprotocol/sdk/types.js";
@@ -19,6 +22,8 @@ import {
19
22
  stopWatcher,
20
23
  getWatcher,
21
24
  generateJsonStatus,
25
+ initializeArchitectureAnalyzer,
26
+ getArchitectureAnalyzer,
22
27
  } from "../../../core/src/index.js";
23
28
  import { getProjectPaths } from "../utils/project.js";
24
29
 
@@ -64,6 +69,61 @@ export const brainToolDefinitions: Tool[] = [
64
69
  required: [],
65
70
  },
66
71
  },
72
+ // Architecture tools (v0.2)
73
+ {
74
+ name: "mandu_check_location",
75
+ description:
76
+ "Check if a file location follows project architecture rules. Call this BEFORE creating or moving files to ensure proper placement.",
77
+ inputSchema: {
78
+ type: "object",
79
+ properties: {
80
+ path: {
81
+ type: "string",
82
+ description: "File path to check (relative to project root)",
83
+ },
84
+ content: {
85
+ type: "string",
86
+ description: "Optional file content for import validation",
87
+ },
88
+ },
89
+ required: ["path"],
90
+ },
91
+ },
92
+ {
93
+ name: "mandu_check_import",
94
+ description:
95
+ "Check if imports in a file follow architecture rules. Call this to validate imports before adding them.",
96
+ inputSchema: {
97
+ type: "object",
98
+ properties: {
99
+ sourceFile: {
100
+ type: "string",
101
+ description: "Source file path (relative to project root)",
102
+ },
103
+ imports: {
104
+ type: "array",
105
+ items: { type: "string" },
106
+ description: "List of import paths to check",
107
+ },
108
+ },
109
+ required: ["sourceFile", "imports"],
110
+ },
111
+ },
112
+ {
113
+ name: "mandu_get_architecture",
114
+ description:
115
+ "Get the project architecture rules and folder structure. Use this to understand where to place new files.",
116
+ inputSchema: {
117
+ type: "object",
118
+ properties: {
119
+ includeStructure: {
120
+ type: "boolean",
121
+ description: "Include folder tree in response (default: true)",
122
+ },
123
+ },
124
+ required: [],
125
+ },
126
+ },
67
127
  ];
68
128
 
69
129
  export function brainTools(projectRoot: string) {
@@ -227,5 +287,156 @@ export function brainTools(projectRoot: string) {
227
287
  };
228
288
  }
229
289
  },
290
+
291
+ // Architecture tools (v0.2)
292
+ mandu_check_location: async (args: Record<string, unknown>) => {
293
+ const { path: filePath, content } = args as {
294
+ path: string;
295
+ content?: string;
296
+ };
297
+
298
+ try {
299
+ // Initialize analyzer if needed
300
+ let analyzer = getArchitectureAnalyzer();
301
+ if (!analyzer) {
302
+ await initializeBrain();
303
+ analyzer = initializeArchitectureAnalyzer(projectRoot);
304
+ }
305
+
306
+ const result = await analyzer.checkLocation({
307
+ path: filePath,
308
+ content,
309
+ });
310
+
311
+ if (result.allowed) {
312
+ return {
313
+ allowed: true,
314
+ message: `✅ '${filePath}'는 올바른 위치입니다`,
315
+ tip: "파일을 생성해도 됩니다",
316
+ };
317
+ }
318
+
319
+ return {
320
+ allowed: false,
321
+ violations: result.violations.map((v) => ({
322
+ rule: v.ruleId,
323
+ message: v.message,
324
+ severity: v.severity,
325
+ })),
326
+ suggestion: result.suggestion,
327
+ recommendedPath: result.recommendedPath,
328
+ tip: result.recommendedPath
329
+ ? `권장 경로: ${result.recommendedPath}`
330
+ : "위반 사항을 확인하고 적절한 위치에 파일을 생성하세요",
331
+ };
332
+ } catch (error) {
333
+ return {
334
+ error: "Location check failed",
335
+ details: error instanceof Error ? error.message : "Unknown error",
336
+ };
337
+ }
338
+ },
339
+
340
+ mandu_check_import: async (args: Record<string, unknown>) => {
341
+ const { sourceFile, imports } = args as {
342
+ sourceFile: string;
343
+ imports: string[];
344
+ };
345
+
346
+ try {
347
+ let analyzer = getArchitectureAnalyzer();
348
+ if (!analyzer) {
349
+ await initializeBrain();
350
+ analyzer = initializeArchitectureAnalyzer(projectRoot);
351
+ }
352
+
353
+ const result = await analyzer.checkImports({
354
+ sourceFile,
355
+ imports,
356
+ });
357
+
358
+ if (result.allowed) {
359
+ return {
360
+ allowed: true,
361
+ message: "✅ 모든 import가 허용됩니다",
362
+ checkedImports: imports,
363
+ };
364
+ }
365
+
366
+ return {
367
+ allowed: false,
368
+ violations: result.violations.map((v) => ({
369
+ import: v.import,
370
+ reason: v.reason,
371
+ suggestion: v.suggestion,
372
+ })),
373
+ tip: "금지된 import를 제거하거나 대안을 사용하세요",
374
+ };
375
+ } catch (error) {
376
+ return {
377
+ error: "Import check failed",
378
+ details: error instanceof Error ? error.message : "Unknown error",
379
+ };
380
+ }
381
+ },
382
+
383
+ mandu_get_architecture: async (args: Record<string, unknown>) => {
384
+ const { includeStructure = true } = args as {
385
+ includeStructure?: boolean;
386
+ };
387
+
388
+ try {
389
+ let analyzer = getArchitectureAnalyzer();
390
+ if (!analyzer) {
391
+ await initializeBrain();
392
+ analyzer = initializeArchitectureAnalyzer(projectRoot);
393
+ }
394
+
395
+ const config = analyzer.getConfig();
396
+ const structure = includeStructure
397
+ ? await analyzer.getProjectStructure()
398
+ : null;
399
+
400
+ return {
401
+ folders: Object.entries(config.folders || {}).map(([key, rule]) => {
402
+ const folderRule =
403
+ typeof rule === "string"
404
+ ? { pattern: key, description: rule }
405
+ : rule;
406
+ return {
407
+ pattern: folderRule.pattern,
408
+ description: folderRule.description,
409
+ readonly: folderRule.readonly || false,
410
+ allowedFiles: folderRule.allowedFiles,
411
+ };
412
+ }),
413
+ importRules: (config.imports || []).map((rule) => ({
414
+ source: rule.source,
415
+ forbid: rule.forbid,
416
+ allow: rule.allow,
417
+ reason: rule.reason,
418
+ })),
419
+ namingRules: (config.naming || []).map((rule) => ({
420
+ folder: rule.folder,
421
+ pattern: rule.filePattern,
422
+ description: rule.description,
423
+ examples: rule.examples,
424
+ })),
425
+ structure: structure
426
+ ? {
427
+ rootDir: structure.rootDir,
428
+ folders: structure.folders,
429
+ indexedAt: structure.indexedAt,
430
+ }
431
+ : null,
432
+ tip: "이 규칙을 따라 파일을 생성하세요. mandu_check_location으로 검증할 수 있습니다.",
433
+ };
434
+ } catch (error) {
435
+ return {
436
+ error: "Failed to get architecture",
437
+ details: error instanceof Error ? error.message : "Unknown error",
438
+ };
439
+ }
440
+ },
230
441
  };
231
442
  }