@mandujs/mcp 0.19.4 β 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 +48 -11
- package/package.json +44 -44
- package/src/activity-monitor.ts +21 -9
- package/src/new-resources.ts +145 -145
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": ["
|
|
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
|
|
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 (
|
|
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://
|
|
188
|
-
| `mandu://
|
|
189
|
-
| `mandu://
|
|
190
|
-
| `mandu://
|
|
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.
|
|
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.
|
|
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
|
+
}
|
package/src/activity-monitor.ts
CHANGED
|
@@ -277,6 +277,8 @@ 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>();
|
|
280
282
|
// Phase 5-1: μμ΄μ νΈ μΈμ
μλ³ (MCP ν΄λΌμ΄μΈνΈλ³ μΆμ )
|
|
281
283
|
public sessionId: string = crypto.randomUUID();
|
|
282
284
|
|
|
@@ -382,17 +384,23 @@ export class ActivityMonitor {
|
|
|
382
384
|
fingerprint: `tool:error:${name}:${argsStr}`,
|
|
383
385
|
data: { tool: name, tag, args, argsSummary: argsStr, error },
|
|
384
386
|
});
|
|
385
|
-
// 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);
|
|
386
390
|
eventBus.emit({
|
|
387
391
|
type: "mcp",
|
|
388
392
|
severity: "error",
|
|
389
393
|
source: "mcp",
|
|
390
394
|
message: `${name} β ${error}`,
|
|
395
|
+
duration: startTime ? Date.now() - startTime : undefined,
|
|
391
396
|
data: { tool: name, args, error, sessionId: this.sessionId },
|
|
392
397
|
});
|
|
393
398
|
return;
|
|
394
399
|
}
|
|
395
400
|
|
|
401
|
+
// Phase 1-3: λꡬ νΈμΆ μμ μκ° κΈ°λ‘ (logResultμμ duration κ³μ°)
|
|
402
|
+
this.toolStartTimes.set(name, Date.now());
|
|
403
|
+
|
|
396
404
|
this.enqueue({
|
|
397
405
|
ts: new Date().toISOString(),
|
|
398
406
|
type: "tool.call",
|
|
@@ -400,14 +408,6 @@ export class ActivityMonitor {
|
|
|
400
408
|
source: "tool",
|
|
401
409
|
data: { tool: name, tag, args, argsSummary: argsStr },
|
|
402
410
|
});
|
|
403
|
-
// Phase 1-3: MCP λꡬ νΈμΆ β EventBus (Phase 5-1: sessionId ν¬ν¨)
|
|
404
|
-
eventBus.emit({
|
|
405
|
-
type: "mcp",
|
|
406
|
-
severity: "info",
|
|
407
|
-
source: "mcp",
|
|
408
|
-
message: `${name} β
`,
|
|
409
|
-
data: { tool: name, args, sessionId: this.sessionId },
|
|
410
|
-
});
|
|
411
411
|
}
|
|
412
412
|
|
|
413
413
|
/**
|
|
@@ -421,6 +421,18 @@ export class ActivityMonitor {
|
|
|
421
421
|
const summary = summarizeResult(result);
|
|
422
422
|
const tag = TOOL_ICONS[name] || name.replace("mandu_", "").toUpperCase();
|
|
423
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
|
+
|
|
424
436
|
if (summary) {
|
|
425
437
|
this.enqueue({
|
|
426
438
|
ts: new Date().toISOString(),
|
package/src/new-resources.ts
CHANGED
|
@@ -1,145 +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 { 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
|
-
}
|
|
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
|
+
}
|