@mandujs/mcp 0.19.3 β†’ 0.19.5

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/README.md CHANGED
@@ -34,18 +34,34 @@ Add to your MCP configuration (`.mcp.json` or `.claude.json`):
34
34
  "mcpServers": {
35
35
  "mandu": {
36
36
  "command": "bunx",
37
- "args": ["@mandujs/mcp"],
37
+ "args": ["mandu-mcp"],
38
38
  "cwd": "/path/to/your/project"
39
39
  }
40
40
  }
41
41
  }
42
42
  ```
43
43
 
44
+ > **Note**: Use `mandu-mcp` (not `@mandujs/mcp`) to avoid conflicts with Python's `mcp` CLI on PATH (#174).
45
+
44
46
  ### Direct Execution
45
47
 
46
48
  ```bash
47
49
  cd /path/to/project
48
- bunx @mandujs/mcp
50
+ bunx mandu-mcp
51
+ ```
52
+
53
+ ### Tool Profiles
54
+
55
+ Filter available tools via the `MANDU_MCP_PROFILE` env var:
56
+
57
+ | Profile | Tools | Use Case |
58
+ |---------|-------|----------|
59
+ | `minimal` | ~15 | Read-only operations, safe for autonomous agents |
60
+ | `standard` | ~50 | Default β€” most common operations |
61
+ | `full` | 85+ | All tools including destructive operations |
62
+
63
+ ```bash
64
+ MANDU_MCP_PROFILE=minimal bunx mandu-mcp
49
65
  ```
50
66
 
51
67
  ### Global Mode
@@ -64,7 +80,7 @@ bunx @mandujs/mcp --root /path/to/project
64
80
 
65
81
  ---
66
82
 
67
- ## Tools (35+)
83
+ ## Tools (85+)
68
84
 
69
85
  ### Spec Management
70
86
 
@@ -167,7 +183,7 @@ bunx @mandujs/mcp --root /path/to/project
167
183
  | `mandu_list_changes` | View change history |
168
184
  | `mandu_prune_history` | Clean old snapshots |
169
185
 
170
- ### ATE (Automation Test Engine) πŸ†•
186
+ ### ATE (Automation Test Engine) β€” 9 tools
171
187
 
172
188
  | Tool | Description |
173
189
  |------|-------------|
@@ -177,19 +193,40 @@ bunx @mandujs/mcp --root /path/to/project
177
193
  | `mandu.ate.report` | Generate test summary report |
178
194
  | `mandu.ate.heal` | Auto-suggest fixes for failed tests |
179
195
  | `mandu.ate.impact` | Compute affected routes (subset testing) |
196
+ | `mandu.ate.auto_pipeline` | Run full pipeline (extract β†’ generate β†’ run β†’ report β†’ heal) |
197
+ | `mandu.ate.feedback` | Analyze failures with 7-category classification |
198
+ | `mandu.ate.apply_heal` | Apply heal diffs safely (with backup) |
199
+
200
+ ### Test Selection (Phase 5) β€” 3 tools πŸ†•
201
+
202
+ | Tool | Description |
203
+ |------|-------------|
204
+ | `mandu.test.smart` | Smart test selection from git diff with priority scoring |
205
+ | `mandu.test.coverage` | Detect coverage gaps in interaction graph |
206
+ | `mandu.test.precommit` | Pre-commit hook: should we test before committing? |
207
+
208
+ ### Composite β€” 7 tools
209
+
210
+ | Tool | Description |
211
+ |------|-------------|
212
+ | `mandu.feature.create` | Scaffold full feature (route + contract + slot + island) |
213
+ | `mandu.diagnose` | Multi-aspect project health check |
214
+ | `mandu.island.add` | Add interactive island to route |
215
+ | `mandu.middleware.add` | Add middleware to route |
216
+ | `mandu.test.route` | Quick smoke test for a route |
217
+ | `mandu.deploy.check` | Pre-deploy production readiness check |
218
+ | `mandu.cache.manage` | Manage ISR/SWR cache (list, invalidate, stats) |
180
219
 
181
220
  ---
182
221
 
183
- ## Resources
222
+ ## Resources (4)
184
223
 
185
224
  | URI | Description |
186
225
  |-----|-------------|
187
- | `mandu://spec/manifest` | Current routes.manifest.json |
188
- | `mandu://generated/map` | Generated files mapping |
189
- | `mandu://transaction/active` | Active transaction state |
190
- | `mandu://slots/{routeId}` | Slot file content by route ID |
191
- | `mandu://watch/warnings` | Recent architecture violation warnings |
192
- | `mandu://watch/status` | Watcher status (active, uptime, count) |
226
+ | `mandu://routes` | Current routes manifest |
227
+ | `mandu://config` | Parsed `mandu.config.ts` settings |
228
+ | `mandu://errors` | Recent build and runtime errors |
229
+ | `mandu://activity` | **NEW**: Recent observability events + 5-minute stats from EventBus |
193
230
 
194
231
  ---
195
232
 
package/package.json CHANGED
@@ -1,44 +1,44 @@
1
- {
2
- "name": "@mandujs/mcp",
3
- "version": "0.19.3",
4
- "description": "Mandu MCP Server - Agent-native interface for Mandu framework operations",
5
- "type": "module",
6
- "main": "./src/index.ts",
7
- "bin": {
8
- "mandu-mcp": "./src/index.ts",
9
- "mandujs-mcp": "./src/index.ts",
10
- "mcp": "./src/index.ts"
11
- },
12
- "exports": {
13
- ".": "./src/index.ts"
14
- },
15
- "files": [
16
- "src/**/*"
17
- ],
18
- "keywords": [
19
- "mandu",
20
- "mcp",
21
- "model-context-protocol",
22
- "ai",
23
- "agent",
24
- "code-generation"
25
- ],
26
- "repository": {
27
- "type": "git",
28
- "url": "https://github.com/konamgil/mandu.git",
29
- "directory": "packages/mcp"
30
- },
31
- "author": "konamgil",
32
- "license": "MPL-2.0",
33
- "publishConfig": {
34
- "access": "public"
35
- },
36
- "dependencies": {
37
- "@mandujs/core": "^0.20.0",
38
- "@mandujs/ate": "^0.18.0",
39
- "@modelcontextprotocol/sdk": "^1.25.3"
40
- },
41
- "engines": {
42
- "bun": ">=1.0.0"
43
- }
44
- }
1
+ {
2
+ "name": "@mandujs/mcp",
3
+ "version": "0.19.5",
4
+ "description": "Mandu MCP Server - Agent-native interface for Mandu framework operations",
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "bin": {
8
+ "mandu-mcp": "./src/index.ts",
9
+ "mandujs-mcp": "./src/index.ts",
10
+ "mcp": "./src/index.ts"
11
+ },
12
+ "exports": {
13
+ ".": "./src/index.ts"
14
+ },
15
+ "files": [
16
+ "src/**/*"
17
+ ],
18
+ "keywords": [
19
+ "mandu",
20
+ "mcp",
21
+ "model-context-protocol",
22
+ "ai",
23
+ "agent",
24
+ "code-generation"
25
+ ],
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/konamgil/mandu.git",
29
+ "directory": "packages/mcp"
30
+ },
31
+ "author": "konamgil",
32
+ "license": "MPL-2.0",
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "dependencies": {
37
+ "@mandujs/core": "^0.21.0",
38
+ "@mandujs/ate": "^0.18.0",
39
+ "@modelcontextprotocol/sdk": "^1.25.3"
40
+ },
41
+ "engines": {
42
+ "bun": ">=1.0.0"
43
+ }
44
+ }
@@ -277,6 +277,10 @@ export class ActivityMonitor {
277
277
  private summaryTimer: NodeJS.Timeout | null = null;
278
278
  private summaryCounts = { total: 0, info: 0, warn: 0, error: 0 };
279
279
  private lastToolArgs = new Map<string, Record<string, unknown> | null>();
280
+ // Phase 1-3: 도ꡬ 호좜 μ‹œμž‘ μ‹œκ°„ 좔적 (duration κ³„μ‚°μš©)
281
+ private toolStartTimes = new Map<string, number>();
282
+ // Phase 5-1: μ—μ΄μ „νŠΈ μ„Έμ…˜ 식별 (MCP ν΄λΌμ΄μ–ΈνŠΈλ³„ 좔적)
283
+ public sessionId: string = crypto.randomUUID();
280
284
 
281
285
  constructor(projectRoot: string) {
282
286
  this.projectRoot = projectRoot;
@@ -380,17 +384,23 @@ export class ActivityMonitor {
380
384
  fingerprint: `tool:error:${name}:${argsStr}`,
381
385
  data: { tool: name, tag, args, argsSummary: argsStr, error },
382
386
  });
383
- // Phase 1-3: MCP 도ꡬ μ—λŸ¬ β†’ EventBus
387
+ // Phase 1-3: MCP 도ꡬ μ—λŸ¬ β†’ EventBus (sessionId + duration 포함)
388
+ const startTime = this.toolStartTimes.get(name);
389
+ this.toolStartTimes.delete(name);
384
390
  eventBus.emit({
385
391
  type: "mcp",
386
392
  severity: "error",
387
393
  source: "mcp",
388
394
  message: `${name} ❌ ${error}`,
389
- data: { tool: name, args, error },
395
+ duration: startTime ? Date.now() - startTime : undefined,
396
+ data: { tool: name, args, error, sessionId: this.sessionId },
390
397
  });
391
398
  return;
392
399
  }
393
400
 
401
+ // Phase 1-3: 도ꡬ 호좜 μ‹œμž‘ μ‹œκ°„ 기둝 (logResultμ—μ„œ duration 계산)
402
+ this.toolStartTimes.set(name, Date.now());
403
+
394
404
  this.enqueue({
395
405
  ts: new Date().toISOString(),
396
406
  type: "tool.call",
@@ -398,14 +408,6 @@ export class ActivityMonitor {
398
408
  source: "tool",
399
409
  data: { tool: name, tag, args, argsSummary: argsStr },
400
410
  });
401
- // Phase 1-3: MCP 도ꡬ 호좜 β†’ EventBus
402
- eventBus.emit({
403
- type: "mcp",
404
- severity: "info",
405
- source: "mcp",
406
- message: `${name} βœ…`,
407
- data: { tool: name, args },
408
- });
409
411
  }
410
412
 
411
413
  /**
@@ -419,6 +421,18 @@ export class ActivityMonitor {
419
421
  const summary = summarizeResult(result);
420
422
  const tag = TOOL_ICONS[name] || name.replace("mandu_", "").toUpperCase();
421
423
 
424
+ // Phase 1-3: 도ꡬ μ™„λ£Œ μ‹œ duration 계산 및 EventBus emit (Phase 5-1: sessionId 포함)
425
+ const startTime = this.toolStartTimes.get(name);
426
+ this.toolStartTimes.delete(name);
427
+ eventBus.emit({
428
+ type: "mcp",
429
+ severity: "info",
430
+ source: "mcp",
431
+ message: `${name} βœ…`,
432
+ duration: startTime ? Date.now() - startTime : undefined,
433
+ data: { tool: name, args: lastArgs, sessionId: this.sessionId },
434
+ });
435
+
422
436
  if (summary) {
423
437
  this.enqueue({
424
438
  ts: new Date().toISOString(),
@@ -1,119 +1,145 @@
1
- /**
2
- * MCP Resources for Mandu Framework
3
- *
4
- * Project state data exposed via the MCP resource protocol.
5
- */
6
-
7
- import type { Resource } from "@modelcontextprotocol/sdk/types.js";
8
- import path from "path";
9
- import { readConfig, readJsonFile } from "./utils/project.js";
10
- import { loadManduConfig, loadManifest } from "@mandujs/core";
11
- import { getDevServerState } from "./tools/project.js";
12
-
13
- export const manduResourceDefinitions: Resource[] = [
14
- {
15
- uri: "mandu://routes",
16
- name: "Route Manifest",
17
- description: "Current project route manifest (JSON)",
18
- mimeType: "application/json",
19
- },
20
- {
21
- uri: "mandu://config",
22
- name: "Mandu Config",
23
- description: "Parsed mandu.config.ts settings",
24
- mimeType: "application/json",
25
- },
26
- {
27
- uri: "mandu://errors",
28
- name: "Recent Errors",
29
- description: "Recent build and runtime errors",
30
- mimeType: "application/json",
31
- },
32
- ];
33
-
34
- type ResourceReadResult = { uri: string; mimeType: string; text: string };
35
- type ResourceHandler = () => Promise<ResourceReadResult>;
36
-
37
- function jsonResult(uri: string, data: unknown): ResourceReadResult {
38
- return { uri, mimeType: "application/json", text: JSON.stringify(data, null, 2) };
39
- }
40
-
41
- export function manduResourceHandlers(projectRoot: string): Record<string, ResourceHandler> {
42
- const manifestPath = path.join(projectRoot, ".mandu", "routes.manifest.json");
43
-
44
- return {
45
- "mandu://routes": async () => {
46
- const result = await loadManifest(manifestPath);
47
- if (!result.success || !result.data) {
48
- return jsonResult("mandu://routes", {
49
- error: "Failed to load route manifest",
50
- details: result.errors,
51
- hint: "Run 'mandu generate' or 'mandu dev' to create the manifest.",
52
- });
53
- }
54
- return jsonResult("mandu://routes", {
55
- version: result.data.version,
56
- routeCount: result.data.routes.length,
57
- routes: result.data.routes.map((r) => ({
58
- id: r.id, pattern: r.pattern, kind: r.kind, module: r.module,
59
- slotModule: r.slotModule ?? null, clientModule: r.clientModule ?? null,
60
- })),
61
- });
62
- },
63
-
64
- "mandu://config": async () => {
65
- try {
66
- const config = await loadManduConfig(projectRoot);
67
- return jsonResult("mandu://config", {
68
- server: config.server ?? {},
69
- guard: config.guard ?? {},
70
- build: config.build ?? {},
71
- dev: config.dev ?? {},
72
- fsRoutes: config.fsRoutes ?? {},
73
- seo: config.seo ?? {},
74
- });
75
- } catch {
76
- const raw = await readConfig(projectRoot);
77
- return jsonResult("mandu://config", raw ?? {
78
- error: "No mandu.config.ts/js/json found",
79
- hint: "Create a mandu.config.ts in the project root.",
80
- });
81
- }
82
- },
83
-
84
- "mandu://errors": async () => {
85
- const errors: unknown[] = [];
86
-
87
- // Try Kitchen DevTools error log from running dev server
88
- let port: number | undefined;
89
- const serverState = getDevServerState();
90
- if (serverState) {
91
- for (const line of serverState.output) {
92
- const m = line.match(/https?:\/\/localhost:(\d+)/);
93
- if (m) port = parseInt(m[1], 10);
94
- }
95
- }
96
- if (port) {
97
- try {
98
- const res = await fetch(`http://localhost:${port}/__kitchen/api/errors`);
99
- if (res.ok) {
100
- const body = (await res.json()) as { errors: unknown[] };
101
- if (body.errors?.length) errors.push(...body.errors);
102
- }
103
- } catch { /* dev server not reachable */ }
104
- }
105
-
106
- // Read local error log file
107
- const loggedErrors = await readJsonFile<unknown[]>(
108
- path.join(projectRoot, ".mandu", "errors.json"),
109
- );
110
- if (Array.isArray(loggedErrors)) errors.push(...loggedErrors);
111
-
112
- return jsonResult("mandu://errors", {
113
- count: errors.length,
114
- errors,
115
- source: port ? "kitchen+log" : "log",
116
- });
117
- },
118
- };
119
- }
1
+ /**
2
+ * MCP Resources for Mandu Framework
3
+ *
4
+ * Project state data exposed via the MCP resource protocol.
5
+ */
6
+
7
+ import type { Resource } from "@modelcontextprotocol/sdk/types.js";
8
+ import path from "path";
9
+ import { readConfig, readJsonFile } from "./utils/project.js";
10
+ import { loadManduConfig, loadManifest } from "@mandujs/core";
11
+ import { eventBus } from "@mandujs/core/observability";
12
+ import { getDevServerState } from "./tools/project.js";
13
+
14
+ export const manduResourceDefinitions: Resource[] = [
15
+ {
16
+ uri: "mandu://routes",
17
+ name: "Route Manifest",
18
+ description: "Current project route manifest (JSON)",
19
+ mimeType: "application/json",
20
+ },
21
+ {
22
+ uri: "mandu://config",
23
+ name: "Mandu Config",
24
+ description: "Parsed mandu.config.ts settings",
25
+ mimeType: "application/json",
26
+ },
27
+ {
28
+ uri: "mandu://errors",
29
+ name: "Recent Errors",
30
+ description: "Recent build and runtime errors",
31
+ mimeType: "application/json",
32
+ },
33
+ {
34
+ uri: "mandu://activity",
35
+ name: "Recent Activity",
36
+ description: "Recent observability events (HTTP, MCP, Guard) from EventBus + 5-minute stats",
37
+ mimeType: "application/json",
38
+ },
39
+ ];
40
+
41
+ type ResourceReadResult = { uri: string; mimeType: string; text: string };
42
+ type ResourceHandler = () => Promise<ResourceReadResult>;
43
+
44
+ function jsonResult(uri: string, data: unknown): ResourceReadResult {
45
+ return { uri, mimeType: "application/json", text: JSON.stringify(data, null, 2) };
46
+ }
47
+
48
+ export function manduResourceHandlers(projectRoot: string): Record<string, ResourceHandler> {
49
+ const manifestPath = path.join(projectRoot, ".mandu", "routes.manifest.json");
50
+
51
+ return {
52
+ "mandu://routes": async () => {
53
+ const result = await loadManifest(manifestPath);
54
+ if (!result.success || !result.data) {
55
+ return jsonResult("mandu://routes", {
56
+ error: "Failed to load route manifest",
57
+ details: result.errors,
58
+ hint: "Run 'mandu generate' or 'mandu dev' to create the manifest.",
59
+ });
60
+ }
61
+ return jsonResult("mandu://routes", {
62
+ version: result.data.version,
63
+ routeCount: result.data.routes.length,
64
+ routes: result.data.routes.map((r) => ({
65
+ id: r.id, pattern: r.pattern, kind: r.kind, module: r.module,
66
+ slotModule: r.slotModule ?? null, clientModule: r.clientModule ?? null,
67
+ })),
68
+ });
69
+ },
70
+
71
+ "mandu://config": async () => {
72
+ try {
73
+ const config = await loadManduConfig(projectRoot);
74
+ return jsonResult("mandu://config", {
75
+ server: config.server ?? {},
76
+ guard: config.guard ?? {},
77
+ build: config.build ?? {},
78
+ dev: config.dev ?? {},
79
+ fsRoutes: config.fsRoutes ?? {},
80
+ seo: config.seo ?? {},
81
+ });
82
+ } catch {
83
+ const raw = await readConfig(projectRoot);
84
+ return jsonResult("mandu://config", raw ?? {
85
+ error: "No mandu.config.ts/js/json found",
86
+ hint: "Create a mandu.config.ts in the project root.",
87
+ });
88
+ }
89
+ },
90
+
91
+ "mandu://activity": async () => {
92
+ // Phase 5-3: AI μ—μ΄μ „νŠΈκ°€ EventBus ν™œλ™μ„ 직접 쑰회 κ°€λŠ₯
93
+ const recent = eventBus.getRecent(20);
94
+ const stats = eventBus.getStats(5 * 60 * 1000); // last 5 minutes
95
+ return jsonResult("mandu://activity", {
96
+ recent: recent.map((e) => ({
97
+ ts: new Date(e.timestamp).toISOString(),
98
+ type: e.type,
99
+ severity: e.severity,
100
+ source: e.source,
101
+ message: e.message,
102
+ duration: e.duration,
103
+ correlationId: e.correlationId,
104
+ })),
105
+ stats,
106
+ windowMs: 5 * 60 * 1000,
107
+ });
108
+ },
109
+
110
+ "mandu://errors": async () => {
111
+ const errors: unknown[] = [];
112
+
113
+ // Try Kitchen DevTools error log from running dev server
114
+ let port: number | undefined;
115
+ const serverState = getDevServerState();
116
+ if (serverState) {
117
+ for (const line of serverState.output) {
118
+ const m = line.match(/https?:\/\/localhost:(\d+)/);
119
+ if (m) port = parseInt(m[1], 10);
120
+ }
121
+ }
122
+ if (port) {
123
+ try {
124
+ const res = await fetch(`http://localhost:${port}/__kitchen/api/errors`);
125
+ if (res.ok) {
126
+ const body = (await res.json()) as { errors: unknown[] };
127
+ if (body.errors?.length) errors.push(...body.errors);
128
+ }
129
+ } catch { /* dev server not reachable */ }
130
+ }
131
+
132
+ // Read local error log file
133
+ const loggedErrors = await readJsonFile<unknown[]>(
134
+ path.join(projectRoot, ".mandu", "errors.json"),
135
+ );
136
+ if (Array.isArray(loggedErrors)) errors.push(...loggedErrors);
137
+
138
+ return jsonResult("mandu://errors", {
139
+ count: errors.length,
140
+ errors,
141
+ source: port ? "kitchen+log" : "log",
142
+ });
143
+ },
144
+ };
145
+ }