@mandujs/mcp 0.12.2 → 0.16.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.
package/src/index.ts CHANGED
@@ -1,106 +1,106 @@
1
- #!/usr/bin/env bun
2
-
3
- /**
4
- * @mandujs/mcp - MCP Server for Mandu Framework
5
- *
6
- * DNA 기능 통합:
7
- * - DNA-001: 플러그인 기반 도구 등록
8
- * - DNA-006: 설정 핫 리로드
9
- * - DNA-007: 에러 추출 및 분류
10
- * - DNA-008: 구조화된 로깅
11
- * - DNA-016: Pre/Post 도구 훅
12
- */
13
-
14
- // Main exports
15
- export { ManduMcpServer, startServer } from "./server.js";
16
-
17
- // Registry exports (DNA-001)
18
- export {
19
- McpToolRegistry,
20
- mcpToolRegistry,
21
- type ToolRegistration,
22
- type RegistryEvent,
23
- type RegistryDump,
24
- } from "./registry/index.js";
25
-
26
- // Adapter exports
27
- export {
28
- toolToPlugin,
29
- pluginToTool,
30
- moduleToPlugins,
31
- pluginsToTools,
32
- pluginsToHandlers,
33
- monitorEventToRecord,
34
- recordToMonitorEvent,
35
- } from "./adapters/index.js";
36
-
37
- // Executor exports (DNA-007)
38
- export {
39
- formatMcpError,
40
- createToolResponse,
41
- isErrorResponse,
42
- extractErrorFromResponse,
43
- logToolError,
44
- ToolExecutor,
45
- createToolExecutor,
46
- type McpErrorResponse,
47
- type McpToolResponse,
48
- type ToolExecutorOptions,
49
- type ExecutionResult,
50
- } from "./executor/index.js";
51
-
52
- // Hook exports (DNA-016)
53
- export {
54
- mcpHookRegistry,
55
- registerDefaultMcpHooks,
56
- slowToolLoggingHook,
57
- statsCollectorHook,
58
- getToolStats,
59
- resetToolStats,
60
- createArgValidationHook,
61
- startMcpConfigWatcher,
62
- type McpToolContext,
63
- type McpPreToolHook,
64
- type McpPostToolHook,
65
- type McpConfigWatcherOptions,
66
- } from "./hooks/index.js";
67
-
68
- // Logging exports (DNA-008)
69
- export {
70
- createMcpActivityTransport,
71
- setupMcpLogging,
72
- teardownMcpLogging,
73
- dispatchMonitorEvent,
74
- createMcpLogRecord,
75
- MCP_TRANSPORT_ID,
76
- type McpTransportOptions,
77
- } from "./logging/index.js";
78
-
79
- // Tools exports
80
- export {
81
- registerBuiltinTools,
82
- getToolCounts,
83
- getToolsSummary,
84
- } from "./tools/index.js";
85
-
86
- // CLI entry point
87
- import { startServer } from "./server.js";
88
- import path from "path";
89
-
90
- // Start server if run directly
91
- if (import.meta.main) {
92
- const args = process.argv.slice(2);
93
- const globalMode = args.includes("--global");
94
- const rootIndex = args.indexOf("--root");
95
- const rootArg = rootIndex >= 0 ? args[rootIndex + 1] : undefined;
96
- const projectRoot = rootArg
97
- ? path.resolve(rootArg)
98
- : globalMode
99
- ? process.cwd()
100
- : undefined;
101
-
102
- startServer(projectRoot).catch((error) => {
103
- console.error("Failed to start Mandu MCP server:", error);
104
- process.exit(1);
105
- });
106
- }
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * @mandujs/mcp - MCP Server for Mandu Framework
5
+ *
6
+ * DNA 기능 통합:
7
+ * - DNA-001: 플러그인 기반 도구 등록
8
+ * - DNA-006: 설정 핫 리로드
9
+ * - DNA-007: 에러 추출 및 분류
10
+ * - DNA-008: 구조화된 로깅
11
+ * - DNA-016: Pre/Post 도구 훅
12
+ */
13
+
14
+ // Main exports
15
+ export { ManduMcpServer, startServer } from "./server.js";
16
+
17
+ // Registry exports (DNA-001)
18
+ export {
19
+ McpToolRegistry,
20
+ mcpToolRegistry,
21
+ type ToolRegistration,
22
+ type RegistryEvent,
23
+ type RegistryDump,
24
+ } from "./registry/index.js";
25
+
26
+ // Adapter exports
27
+ export {
28
+ toolToPlugin,
29
+ pluginToTool,
30
+ moduleToPlugins,
31
+ pluginsToTools,
32
+ pluginsToHandlers,
33
+ monitorEventToRecord,
34
+ recordToMonitorEvent,
35
+ } from "./adapters/index.js";
36
+
37
+ // Executor exports (DNA-007)
38
+ export {
39
+ formatMcpError,
40
+ createToolResponse,
41
+ isErrorResponse,
42
+ extractErrorFromResponse,
43
+ logToolError,
44
+ ToolExecutor,
45
+ createToolExecutor,
46
+ type McpErrorResponse,
47
+ type McpToolResponse,
48
+ type ToolExecutorOptions,
49
+ type ExecutionResult,
50
+ } from "./executor/index.js";
51
+
52
+ // Hook exports (DNA-016)
53
+ export {
54
+ mcpHookRegistry,
55
+ registerDefaultMcpHooks,
56
+ slowToolLoggingHook,
57
+ statsCollectorHook,
58
+ getToolStats,
59
+ resetToolStats,
60
+ createArgValidationHook,
61
+ startMcpConfigWatcher,
62
+ type McpToolContext,
63
+ type McpPreToolHook,
64
+ type McpPostToolHook,
65
+ type McpConfigWatcherOptions,
66
+ } from "./hooks/index.js";
67
+
68
+ // Logging exports (DNA-008)
69
+ export {
70
+ createMcpActivityTransport,
71
+ setupMcpLogging,
72
+ teardownMcpLogging,
73
+ dispatchMonitorEvent,
74
+ createMcpLogRecord,
75
+ MCP_TRANSPORT_ID,
76
+ type McpTransportOptions,
77
+ } from "./logging/index.js";
78
+
79
+ // Tools exports
80
+ export {
81
+ registerBuiltinTools,
82
+ getToolCounts,
83
+ getToolsSummary,
84
+ } from "./tools/index.js";
85
+
86
+ // CLI entry point
87
+ import { startServer } from "./server.js";
88
+ import path from "path";
89
+
90
+ // Start server if run directly
91
+ if (import.meta.main) {
92
+ const args = process.argv.slice(2);
93
+ const globalMode = args.includes("--global");
94
+ const rootIndex = args.indexOf("--root");
95
+ const rootArg = rootIndex >= 0 ? args[rootIndex + 1] : undefined;
96
+ const projectRoot = rootArg
97
+ ? path.resolve(rootArg)
98
+ : globalMode
99
+ ? process.cwd()
100
+ : undefined;
101
+
102
+ startServer(projectRoot).catch((error) => {
103
+ console.error("Failed to start Mandu MCP server:", error);
104
+ process.exit(1);
105
+ });
106
+ }
@@ -0,0 +1,129 @@
1
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ import { ateExtract, ateGenerate, ateRun, ateReport, ateHeal, ateImpact } from "@mandujs/ate";
3
+
4
+ export const ateToolDefinitions: Tool[] = [
5
+ {
6
+ name: "mandu.ate.extract",
7
+ description: "ATE: AST 기반 상호작용 그래프 추출",
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {
11
+ repoRoot: { type: "string" },
12
+ tsconfigPath: { type: "string" },
13
+ routeGlobs: { type: "array", items: { type: "string" } },
14
+ buildSalt: { type: "string" },
15
+ },
16
+ required: ["repoRoot"],
17
+ },
18
+ },
19
+ {
20
+ name: "mandu.ate.generate",
21
+ description: "ATE: 시나리오 생성 + Playwright spec codegen",
22
+ inputSchema: {
23
+ type: "object",
24
+ properties: {
25
+ repoRoot: { type: "string" },
26
+ oracleLevel: { type: "string", enum: ["L0", "L1", "L2", "L3"] },
27
+ onlyRoutes: { type: "array", items: { type: "string" } },
28
+ },
29
+ required: ["repoRoot"],
30
+ },
31
+ },
32
+ {
33
+ name: "mandu.ate.run",
34
+ description: "ATE: Playwright runner 실행(artifacts 수집)",
35
+ inputSchema: {
36
+ type: "object",
37
+ properties: {
38
+ repoRoot: { type: "string" },
39
+ baseURL: { type: "string" },
40
+ ci: { type: "boolean" },
41
+ headless: { type: "boolean" },
42
+ browsers: { type: "array", items: { type: "string", enum: ["chromium", "firefox", "webkit"] } },
43
+ },
44
+ required: ["repoRoot"],
45
+ },
46
+ },
47
+ {
48
+ name: "mandu.ate.report",
49
+ description: "ATE: summary.json 생성",
50
+ inputSchema: {
51
+ type: "object",
52
+ properties: {
53
+ repoRoot: { type: "string" },
54
+ runId: { type: "string" },
55
+ startedAt: { type: "string" },
56
+ finishedAt: { type: "string" },
57
+ exitCode: { type: "number" },
58
+ oracleLevel: { type: "string", enum: ["L0", "L1", "L2", "L3"] },
59
+ impact: {
60
+ type: "object",
61
+ properties: {
62
+ mode: { type: "string", enum: ["full", "subset"] },
63
+ changedFiles: { type: "array", items: { type: "string" } },
64
+ selectedRoutes: { type: "array", items: { type: "string" } },
65
+ },
66
+ required: ["mode", "changedFiles", "selectedRoutes"],
67
+ },
68
+ },
69
+ required: ["repoRoot", "runId", "startedAt", "finishedAt", "exitCode"],
70
+ },
71
+ },
72
+ {
73
+ name: "mandu.ate.heal",
74
+ description: "ATE: 실패 원인 분류 + 복구 제안(diff) 생성 (자동 커밋 금지)",
75
+ inputSchema: {
76
+ type: "object",
77
+ properties: {
78
+ repoRoot: { type: "string" },
79
+ runId: { type: "string" },
80
+ },
81
+ required: ["repoRoot", "runId"],
82
+ },
83
+ },
84
+ {
85
+ name: "mandu.ate.impact",
86
+ description: "ATE: git diff 기반 subset 계산",
87
+ inputSchema: {
88
+ type: "object",
89
+ properties: {
90
+ repoRoot: { type: "string" },
91
+ base: { type: "string" },
92
+ head: { type: "string" },
93
+ },
94
+ required: ["repoRoot"],
95
+ },
96
+ },
97
+ ];
98
+
99
+ export function ateTools(projectRoot: string) {
100
+ return {
101
+ "mandu.ate.extract": async (args: Record<string, unknown>) => {
102
+ return await ateExtract(args as any);
103
+ },
104
+ "mandu.ate.generate": async (args: Record<string, unknown>) => {
105
+ return ateGenerate(args as any);
106
+ },
107
+ "mandu.ate.run": async (args: Record<string, unknown>) => {
108
+ return await ateRun(args as any);
109
+ },
110
+ "mandu.ate.report": async (args: Record<string, unknown>) => {
111
+ const input = args as any;
112
+ return await ateReport({
113
+ repoRoot: input.repoRoot,
114
+ runId: input.runId,
115
+ startedAt: input.startedAt,
116
+ finishedAt: input.finishedAt,
117
+ exitCode: input.exitCode,
118
+ oracleLevel: input.oracleLevel ?? "L1",
119
+ impact: input.impact,
120
+ });
121
+ },
122
+ "mandu.ate.heal": async (args: Record<string, unknown>) => {
123
+ return ateHeal(args as any);
124
+ },
125
+ "mandu.ate.impact": async (args: Record<string, unknown>) => {
126
+ return ateImpact(args as any);
127
+ },
128
+ };
129
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
- import { loadManifest, generateRoutes, type GeneratedMap } from "@mandujs/core";
2
+ import { loadManifest, generateRoutes, generateManifest, GENERATED_RELATIVE_PATHS, type GeneratedMap } from "@mandujs/core";
3
3
  import { getProjectPaths, readJsonFile } from "../utils/project.js";
4
4
 
5
5
  export const generateToolDefinitions: Tool[] = [
@@ -36,7 +36,10 @@ export function generateTools(projectRoot: string) {
36
36
  mandu_generate: async (args: Record<string, unknown>) => {
37
37
  const { dryRun } = args as { dryRun?: boolean };
38
38
 
39
- // Load manifest
39
+ // Regenerate manifest from FS Routes first
40
+ const fsResult = await generateManifest(projectRoot);
41
+
42
+ // Load the freshly generated manifest
40
43
  const manifestResult = await loadManifest(paths.manifestPath);
41
44
  if (!manifestResult.success || !manifestResult.data) {
42
45
  return { error: manifestResult.errors };
@@ -50,11 +53,11 @@ export function generateTools(projectRoot: string) {
50
53
 
51
54
  for (const route of routes) {
52
55
  // Server handler
53
- wouldCreate.push(`apps/server/generated/routes/${route.id}.route.ts`);
56
+ wouldCreate.push(`${GENERATED_RELATIVE_PATHS.serverRoutes}/${route.id}.route.ts`);
54
57
 
55
58
  // Page component (for page kind)
56
59
  if (route.kind === "page") {
57
- wouldCreate.push(`apps/web/generated/routes/${route.id}.route.tsx`);
60
+ wouldCreate.push(`${GENERATED_RELATIVE_PATHS.webRoutes}/${route.id}.route.tsx`);
58
61
  }
59
62
 
60
63
  // Slot file (only if not exists)
@@ -303,6 +303,11 @@ export const guardToolDefinitions: Tool[] = [
303
303
  enum: ["auth", "crud", "api", "ui", "integration", "data", "util", "config", "other"],
304
304
  description: "Feature category (auto-detected if not specified)",
305
305
  },
306
+ preset: {
307
+ type: "string",
308
+ enum: ["fsd", "clean", "hexagonal", "atomic", "cqrs", "mandu"],
309
+ description: "Architecture preset (default: mandu). Use 'cqrs' for Command/Query separation.",
310
+ },
306
311
  },
307
312
  required: ["intent"],
308
313
  },
@@ -332,6 +337,11 @@ export const guardToolDefinitions: Tool[] = [
332
337
  type: "boolean",
333
338
  description: "If true, overwrite existing files (default: false)",
334
339
  },
340
+ preset: {
341
+ type: "string",
342
+ enum: ["fsd", "clean", "hexagonal", "atomic", "cqrs", "mandu"],
343
+ description: "Architecture preset (default: mandu)",
344
+ },
335
345
  },
336
346
  required: ["intent"],
337
347
  },
@@ -437,7 +447,7 @@ export function guardTools(projectRoot: string) {
437
447
  switch (error.errorType) {
438
448
  case "SPEC_ERROR":
439
449
  analysis.category = "Specification Error";
440
- analysis.fixLocation = error.fix?.file || "spec/routes.manifest.json";
450
+ analysis.fixLocation = error.fix?.file || ".mandu/routes.manifest.json";
441
451
  analysis.actions = [
442
452
  "Check the spec file for JSON syntax errors",
443
453
  "Validate route IDs are unique",
@@ -966,11 +976,12 @@ Mandu.filling()
966
976
  // ═══════════════════════════════════════════════════════════════════════════
967
977
 
968
978
  mandu_negotiate: async (args: Record<string, unknown>) => {
969
- const { intent, requirements, constraints, category } = args as {
979
+ const { intent, requirements, constraints, category, preset } = args as {
970
980
  intent: string;
971
981
  requirements?: string[];
972
982
  constraints?: string[];
973
983
  category?: FeatureCategory;
984
+ preset?: GuardPreset;
974
985
  };
975
986
 
976
987
  if (!intent) {
@@ -985,6 +996,7 @@ Mandu.filling()
985
996
  requirements,
986
997
  constraints,
987
998
  category,
999
+ preset,
988
1000
  };
989
1001
 
990
1002
  const result = await negotiate(request, projectRoot);
@@ -1029,11 +1041,12 @@ Mandu.filling()
1029
1041
  },
1030
1042
 
1031
1043
  mandu_generate_scaffold: async (args: Record<string, unknown>) => {
1032
- const { intent, category, dryRun = false, overwrite = false } = args as {
1044
+ const { intent, category, dryRun = false, overwrite = false, preset } = args as {
1033
1045
  intent: string;
1034
1046
  category?: FeatureCategory;
1035
1047
  dryRun?: boolean;
1036
1048
  overwrite?: boolean;
1049
+ preset?: GuardPreset;
1037
1050
  };
1038
1051
 
1039
1052
  if (!intent) {
@@ -1044,7 +1057,7 @@ Mandu.filling()
1044
1057
  }
1045
1058
 
1046
1059
  // 먼저 협상하여 구조 계획 얻기
1047
- const plan = await negotiate({ intent, category }, projectRoot);
1060
+ const plan = await negotiate({ intent, category, preset }, projectRoot);
1048
1061
 
1049
1062
  if (!plan.approved) {
1050
1063
  return {
@@ -318,8 +318,8 @@ export function hydrationTools(projectRoot: string) {
318
318
  };
319
319
  }
320
320
 
321
- // Create client slot file in apps/web/components/ (not spec/slots/)
322
- const clientModulePath = `apps/web/components/${routeId}.client.tsx`;
321
+ // Create client slot file in spec/slots/
322
+ const clientModulePath = `spec/slots/${routeId}.client.tsx`;
323
323
  const clientFilePath = path.join(projectRoot, clientModulePath);
324
324
 
325
325
  // Check if file already exists
@@ -378,19 +378,19 @@ function generateClientSlotTemplate(routeId: string, slotModule?: string): strin
378
378
  .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
379
379
  .join("");
380
380
 
381
- const typeImport = slotModule
382
- ? `// Import types from server slot if needed (adjust path to your project)
383
- // import type { LoaderData } from "../../../spec/slots/${routeId}.slot";
384
-
385
- `
386
- : "";
381
+ const typeImport = slotModule
382
+ ? `// Import types from server slot if needed (adjust path to your project)
383
+ // import type { LoaderData } from "../../../spec/slots/${routeId}.slot";
384
+
385
+ `
386
+ : "";
387
387
 
388
388
  return `/**
389
389
  * ${pascalCase} Client Slot
390
390
  * 브라우저에서 실행되는 클라이언트 로직
391
391
  */
392
392
 
393
- import { ManduClient } from "@mandujs/core/client";
393
+ import { ManduClient } from "@mandujs/core/client";
394
394
  import { useState, useCallback } from "react";
395
395
 
396
396
  ${typeImport}// 서버에서 전달받는 데이터 타입
@@ -399,7 +399,7 @@ interface ServerData {
399
399
  [key: string]: unknown;
400
400
  }
401
401
 
402
- export default ManduClient.island<ServerData>({
402
+ export default ManduClient.island<ServerData>({
403
403
  /**
404
404
  * Setup Phase
405
405
  * - 서버 데이터를 받아 클라이언트 상태 초기화
@@ -22,6 +22,7 @@ export { brainTools, brainToolDefinitions } from "./brain.js";
22
22
  export { runtimeTools, runtimeToolDefinitions } from "./runtime.js";
23
23
  export { seoTools, seoToolDefinitions } from "./seo.js";
24
24
  export { projectTools, projectToolDefinitions } from "./project.js";
25
+ export { ateTools, ateToolDefinitions } from "./ate.js";
25
26
 
26
27
  // 도구 모듈 import (등록용)
27
28
  import { specTools, specToolDefinitions } from "./spec.js";
@@ -36,13 +37,14 @@ import { brainTools, brainToolDefinitions } from "./brain.js";
36
37
  import { runtimeTools, runtimeToolDefinitions } from "./runtime.js";
37
38
  import { seoTools, seoToolDefinitions } from "./seo.js";
38
39
  import { projectTools, projectToolDefinitions } from "./project.js";
40
+ import { ateTools, ateToolDefinitions } from "./ate.js";
39
41
 
40
42
  /**
41
43
  * 도구 모듈 정보
42
44
  */
43
45
  interface ToolModule {
44
46
  category: string;
45
- definitions: typeof specToolDefinitions;
47
+ definitions: any;
46
48
  handlers: (
47
49
  projectRoot: string,
48
50
  server?: Server,
@@ -67,6 +69,7 @@ const TOOL_MODULES: ToolModule[] = [
67
69
  { category: "runtime", definitions: runtimeToolDefinitions, handlers: runtimeTools },
68
70
  { category: "seo", definitions: seoToolDefinitions, handlers: seoTools },
69
71
  { category: "project", definitions: projectToolDefinitions, handlers: projectTools as ToolModule["handlers"], requiresServer: true },
72
+ { category: "ate", definitions: ateToolDefinitions as any, handlers: ateTools as any },
70
73
  ];
71
74
 
72
75
  /**