@mandujs/mcp 0.18.1 → 0.18.3
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/package.json +1 -1
- package/src/tools/ate.ts +132 -51
- package/src/tools/contract.ts +56 -17
- package/src/tools/generate.ts +18 -4
- package/src/tools/guard.ts +3 -3
- package/src/tools/hydration.ts +9 -1
- package/src/tools/runtime.ts +69 -58
- package/src/tools/slot.ts +21 -7
- package/src/tools/spec.ts +39 -10
package/package.json
CHANGED
package/src/tools/ate.ts
CHANGED
|
@@ -14,61 +14,110 @@ import {
|
|
|
14
14
|
export const ateToolDefinitions: Tool[] = [
|
|
15
15
|
{
|
|
16
16
|
name: "mandu.ate.extract",
|
|
17
|
-
description:
|
|
17
|
+
description:
|
|
18
|
+
"ATE Step 1 — Extract: Statically analyze the Mandu project's AST to build an interaction graph of routes, slots, contracts, and data flow. " +
|
|
19
|
+
"Identifies all testable interactions without running the server. " +
|
|
20
|
+
"Output is stored in .mandu/ate/extract/ and used by mandu.ate.generate.",
|
|
18
21
|
inputSchema: {
|
|
19
22
|
type: "object",
|
|
20
23
|
properties: {
|
|
21
|
-
repoRoot: { type: "string" },
|
|
22
|
-
tsconfigPath: { type: "string" },
|
|
23
|
-
routeGlobs: {
|
|
24
|
-
|
|
24
|
+
repoRoot: { type: "string", description: "Absolute path to the Mandu project root" },
|
|
25
|
+
tsconfigPath: { type: "string", description: "Path to tsconfig.json (default: tsconfig.json in repoRoot)" },
|
|
26
|
+
routeGlobs: {
|
|
27
|
+
type: "array",
|
|
28
|
+
items: { type: "string" },
|
|
29
|
+
description: "Glob patterns to limit which routes are analyzed (e.g. ['app/api/**', 'app/blog/**']). Omit for all routes.",
|
|
30
|
+
},
|
|
31
|
+
buildSalt: {
|
|
32
|
+
type: "string",
|
|
33
|
+
description: "Cache invalidation salt — change this to force re-extraction even if source hasn't changed",
|
|
34
|
+
},
|
|
25
35
|
},
|
|
26
36
|
required: ["repoRoot"],
|
|
27
37
|
},
|
|
28
38
|
},
|
|
29
39
|
{
|
|
30
40
|
name: "mandu.ate.generate",
|
|
31
|
-
description:
|
|
41
|
+
description:
|
|
42
|
+
"ATE Step 2 — Generate: Create Playwright test scenarios from the interaction graph produced by mandu.ate.extract. " +
|
|
43
|
+
"Oracle level controls assertion depth: " +
|
|
44
|
+
"L0 = no assertions (smoke test only), " +
|
|
45
|
+
"L1 = basic HTTP status checks, " +
|
|
46
|
+
"L2 = contract schema validation (response shape matches Zod contract), " +
|
|
47
|
+
"L3 = full behavioral contract (side effects, state changes, error paths). " +
|
|
48
|
+
"Output: .mandu/ate/tests/*.spec.ts ready to run with Playwright.",
|
|
32
49
|
inputSchema: {
|
|
33
50
|
type: "object",
|
|
34
51
|
properties: {
|
|
35
|
-
repoRoot: { type: "string" },
|
|
36
|
-
oracleLevel: {
|
|
37
|
-
|
|
52
|
+
repoRoot: { type: "string", description: "Absolute path to the Mandu project root" },
|
|
53
|
+
oracleLevel: {
|
|
54
|
+
type: "string",
|
|
55
|
+
enum: ["L0", "L1", "L2", "L3"],
|
|
56
|
+
description: "Assertion depth: L0=smoke, L1=HTTP status, L2=contract schema, L3=full behavioral",
|
|
57
|
+
},
|
|
58
|
+
onlyRoutes: {
|
|
59
|
+
type: "array",
|
|
60
|
+
items: { type: "string" },
|
|
61
|
+
description: "Limit test generation to specific routeIds (e.g. ['api-users', 'blog-slug']). Omit for all routes.",
|
|
62
|
+
},
|
|
38
63
|
},
|
|
39
64
|
required: ["repoRoot"],
|
|
40
65
|
},
|
|
41
66
|
},
|
|
42
67
|
{
|
|
43
68
|
name: "mandu.ate.run",
|
|
44
|
-
description:
|
|
69
|
+
description:
|
|
70
|
+
"ATE Step 3 — Run: Execute the generated Playwright specs against a running Mandu dev server. " +
|
|
71
|
+
"Collects test artifacts (screenshots, traces, results) in .mandu/ate/runs/{runId}/. " +
|
|
72
|
+
"Requires the Mandu dev server to be running (use mandu_dev_start first). " +
|
|
73
|
+
"Returns a runId for use with mandu.ate.report and mandu.ate.heal.",
|
|
45
74
|
inputSchema: {
|
|
46
75
|
type: "object",
|
|
47
76
|
properties: {
|
|
48
|
-
repoRoot: { type: "string" },
|
|
49
|
-
baseURL: {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
77
|
+
repoRoot: { type: "string", description: "Absolute path to the Mandu project root" },
|
|
78
|
+
baseURL: {
|
|
79
|
+
type: "string",
|
|
80
|
+
description: "Dev server URL (default: http://localhost:3333). Must match the running mandu dev server.",
|
|
81
|
+
},
|
|
82
|
+
ci: { type: "boolean", description: "CI mode: stricter timeouts, no interactive prompts" },
|
|
83
|
+
headless: { type: "boolean", description: "Run browsers headlessly (default: true)" },
|
|
84
|
+
browsers: {
|
|
85
|
+
type: "array",
|
|
86
|
+
items: { type: "string", enum: ["chromium", "firefox", "webkit"] },
|
|
87
|
+
description: "Browsers to test against (default: ['chromium'])",
|
|
88
|
+
},
|
|
53
89
|
},
|
|
54
90
|
required: ["repoRoot"],
|
|
55
91
|
},
|
|
56
92
|
},
|
|
57
93
|
{
|
|
58
94
|
name: "mandu.ate.report",
|
|
59
|
-
description:
|
|
95
|
+
description:
|
|
96
|
+
"ATE Step 4 — Report: Generate a test report from run artifacts. " +
|
|
97
|
+
"Produces pass/fail summary, coverage by route, and failure details. " +
|
|
98
|
+
"Use the runId returned by mandu.ate.run. " +
|
|
99
|
+
"If tests failed, follow up with mandu.ate.heal to get fix suggestions.",
|
|
60
100
|
inputSchema: {
|
|
61
101
|
type: "object",
|
|
62
102
|
properties: {
|
|
63
|
-
repoRoot: { type: "string" },
|
|
64
|
-
runId: { type: "string" },
|
|
65
|
-
startedAt: { type: "string" },
|
|
66
|
-
finishedAt: { type: "string" },
|
|
67
|
-
exitCode: { type: "number" },
|
|
68
|
-
oracleLevel: {
|
|
69
|
-
|
|
103
|
+
repoRoot: { type: "string", description: "Absolute path to the Mandu project root" },
|
|
104
|
+
runId: { type: "string", description: "Run ID returned by mandu.ate.run" },
|
|
105
|
+
startedAt: { type: "string", description: "ISO timestamp when run started" },
|
|
106
|
+
finishedAt: { type: "string", description: "ISO timestamp when run finished" },
|
|
107
|
+
exitCode: { type: "number", description: "Playwright process exit code (0=pass, non-zero=fail)" },
|
|
108
|
+
oracleLevel: {
|
|
109
|
+
type: "string",
|
|
110
|
+
enum: ["L0", "L1", "L2", "L3"],
|
|
111
|
+
description: "Oracle level used during generation (for report context)",
|
|
112
|
+
},
|
|
113
|
+
format: {
|
|
114
|
+
type: "string",
|
|
115
|
+
enum: ["json", "html", "both"],
|
|
116
|
+
description: "Report format: json (machine-readable), html (visual), both (default)",
|
|
117
|
+
},
|
|
70
118
|
impact: {
|
|
71
119
|
type: "object",
|
|
120
|
+
description: "Impact analysis context — set if tests were run on a subset of routes via mandu.ate.impact",
|
|
72
121
|
properties: {
|
|
73
122
|
mode: { type: "string", enum: ["full", "subset"] },
|
|
74
123
|
changedFiles: { type: "array", items: { type: "string" } },
|
|
@@ -82,61 +131,89 @@ export const ateToolDefinitions: Tool[] = [
|
|
|
82
131
|
},
|
|
83
132
|
{
|
|
84
133
|
name: "mandu.ate.heal",
|
|
85
|
-
description:
|
|
134
|
+
description:
|
|
135
|
+
"ATE Step 5 — Heal: Analyze test failures from a run and generate safe diff suggestions for fixing the code. " +
|
|
136
|
+
"Classifies failures by root cause (schema mismatch, missing handler, wrong status, selector stale, etc.) " +
|
|
137
|
+
"and produces reviewable diffs — never auto-commits or overwrites files. " +
|
|
138
|
+
"Use mandu.ate.apply_heal to apply a specific suggestion after review. " +
|
|
139
|
+
"Supports rollback via mandu_rollback if applied changes cause regressions.",
|
|
86
140
|
inputSchema: {
|
|
87
141
|
type: "object",
|
|
88
142
|
properties: {
|
|
89
|
-
repoRoot: { type: "string" },
|
|
90
|
-
runId: { type: "string" },
|
|
143
|
+
repoRoot: { type: "string", description: "Absolute path to the Mandu project root" },
|
|
144
|
+
runId: { type: "string", description: "Run ID from mandu.ate.run with failures to analyze" },
|
|
91
145
|
},
|
|
92
146
|
required: ["repoRoot", "runId"],
|
|
93
147
|
},
|
|
94
148
|
},
|
|
95
149
|
{
|
|
96
150
|
name: "mandu.ate.impact",
|
|
97
|
-
description:
|
|
151
|
+
description:
|
|
152
|
+
"ATE Optimization — Impact Analysis: Calculate the minimal subset of routes affected by changed files using git diff. " +
|
|
153
|
+
"Avoids running the full test suite when only part of the codebase changed. " +
|
|
154
|
+
"Returns selectedRoutes to pass to mandu.ate.generate (onlyRoutes) or mandu.ate.run. " +
|
|
155
|
+
"Typical use: run after `git commit` to test only affected routes in CI.",
|
|
98
156
|
inputSchema: {
|
|
99
157
|
type: "object",
|
|
100
158
|
properties: {
|
|
101
|
-
repoRoot: { type: "string" },
|
|
102
|
-
base: { type: "string" },
|
|
103
|
-
head: { type: "string" },
|
|
159
|
+
repoRoot: { type: "string", description: "Absolute path to the Mandu project root" },
|
|
160
|
+
base: { type: "string", description: "Git base ref for diff (default: HEAD~1 or main branch)" },
|
|
161
|
+
head: { type: "string", description: "Git head ref for diff (default: current working tree)" },
|
|
104
162
|
},
|
|
105
163
|
required: ["repoRoot"],
|
|
106
164
|
},
|
|
107
165
|
},
|
|
108
166
|
{
|
|
109
167
|
name: "mandu.ate.auto_pipeline",
|
|
110
|
-
description:
|
|
168
|
+
description:
|
|
169
|
+
"ATE Full Pipeline — Run the complete ATE cycle in one call: " +
|
|
170
|
+
"Extract AST → Generate Playwright specs → Run tests → Create report → Suggest heals. " +
|
|
171
|
+
"Recommended for: initial setup, scheduled CI runs, and full regression testing. " +
|
|
172
|
+
"For incremental development, prefer individual steps (extract → generate → run → report → heal). " +
|
|
173
|
+
"Set useImpactAnalysis=true to automatically limit tests to changed routes.",
|
|
111
174
|
inputSchema: {
|
|
112
175
|
type: "object",
|
|
113
176
|
properties: {
|
|
114
|
-
repoRoot: { type: "string" },
|
|
115
|
-
baseURL: { type: "string" },
|
|
116
|
-
oracleLevel: {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
177
|
+
repoRoot: { type: "string", description: "Absolute path to the Mandu project root" },
|
|
178
|
+
baseURL: { type: "string", description: "Dev server URL (default: http://localhost:3333)" },
|
|
179
|
+
oracleLevel: {
|
|
180
|
+
type: "string",
|
|
181
|
+
enum: ["L0", "L1", "L2", "L3"],
|
|
182
|
+
description: "Assertion depth: L0=smoke, L1=HTTP status, L2=contract schema, L3=full behavioral",
|
|
183
|
+
},
|
|
184
|
+
ci: { type: "boolean", description: "CI mode: stricter timeouts" },
|
|
185
|
+
useImpactAnalysis: {
|
|
186
|
+
type: "boolean",
|
|
187
|
+
description: "Run impact analysis first and only test changed routes (faster in CI)",
|
|
188
|
+
},
|
|
189
|
+
base: { type: "string", description: "Git base ref for impact analysis" },
|
|
190
|
+
head: { type: "string", description: "Git head ref for impact analysis" },
|
|
191
|
+
autoHeal: {
|
|
192
|
+
type: "boolean",
|
|
193
|
+
description: "Automatically run heal analysis after failures (produces diff suggestions, never auto-applies)",
|
|
194
|
+
},
|
|
195
|
+
tsconfigPath: { type: "string", description: "Path to tsconfig.json" },
|
|
196
|
+
routeGlobs: { type: "array", items: { type: "string" }, description: "Limit extraction to specific route patterns" },
|
|
197
|
+
buildSalt: { type: "string", description: "Cache invalidation salt for extraction" },
|
|
125
198
|
},
|
|
126
199
|
required: ["repoRoot"],
|
|
127
200
|
},
|
|
128
201
|
},
|
|
129
202
|
{
|
|
130
203
|
name: "mandu.ate.feedback",
|
|
131
|
-
description:
|
|
204
|
+
description:
|
|
205
|
+
"ATE Feedback — Evaluate heal suggestions from a failed run and classify which fixes are safe to auto-apply. " +
|
|
206
|
+
"Safe-to-auto-apply: selector-map updates (CSS selector changes). " +
|
|
207
|
+
"Requires human review: contract schema changes, handler logic, route restructuring. " +
|
|
208
|
+
"Returns priority ranking of suggestions to guide review order.",
|
|
132
209
|
inputSchema: {
|
|
133
210
|
type: "object",
|
|
134
211
|
properties: {
|
|
135
|
-
repoRoot: { type: "string", description: "
|
|
136
|
-
runId: { type: "string", description: "
|
|
212
|
+
repoRoot: { type: "string", description: "Absolute path to the Mandu project root" },
|
|
213
|
+
runId: { type: "string", description: "Run ID with heal suggestions to evaluate" },
|
|
137
214
|
autoApply: {
|
|
138
215
|
type: "boolean",
|
|
139
|
-
description: "
|
|
216
|
+
description: "If true, auto-apply only selector-map changes (CSS selectors) — other changes always require review",
|
|
140
217
|
},
|
|
141
218
|
},
|
|
142
219
|
required: ["repoRoot", "runId"],
|
|
@@ -144,19 +221,23 @@ export const ateToolDefinitions: Tool[] = [
|
|
|
144
221
|
},
|
|
145
222
|
{
|
|
146
223
|
name: "mandu.ate.apply_heal",
|
|
147
|
-
description:
|
|
224
|
+
description:
|
|
225
|
+
"ATE Apply — Apply a specific heal suggestion diff to the codebase. " +
|
|
226
|
+
"Always creates a backup snapshot first (use mandu_rollback to undo). " +
|
|
227
|
+
"Run mandu.ate.feedback first to get the healIndex and confirm the fix is safe. " +
|
|
228
|
+
"After applying, re-run mandu.ate.run to verify the fix resolved the failure.",
|
|
148
229
|
inputSchema: {
|
|
149
230
|
type: "object",
|
|
150
231
|
properties: {
|
|
151
|
-
repoRoot: { type: "string", description: "
|
|
152
|
-
runId: { type: "string", description: "
|
|
232
|
+
repoRoot: { type: "string", description: "Absolute path to the Mandu project root" },
|
|
233
|
+
runId: { type: "string", description: "Run ID containing the heal suggestion to apply" },
|
|
153
234
|
healIndex: {
|
|
154
235
|
type: "number",
|
|
155
|
-
description: "
|
|
236
|
+
description: "0-based index of the heal suggestion to apply (from mandu.ate.heal or mandu.ate.feedback results)",
|
|
156
237
|
},
|
|
157
238
|
createBackup: {
|
|
158
239
|
type: "boolean",
|
|
159
|
-
description: "
|
|
240
|
+
description: "Create a snapshot before applying (default: true, strongly recommended — enables mandu_rollback)",
|
|
160
241
|
},
|
|
161
242
|
},
|
|
162
243
|
required: ["repoRoot", "runId", "healIndex"],
|
package/src/tools/contract.ts
CHANGED
|
@@ -16,7 +16,15 @@ import fs from "fs/promises";
|
|
|
16
16
|
export const contractToolDefinitions: Tool[] = [
|
|
17
17
|
{
|
|
18
18
|
name: "mandu_list_contracts",
|
|
19
|
-
description:
|
|
19
|
+
description:
|
|
20
|
+
"List all routes that have a contract module defined. " +
|
|
21
|
+
"In Mandu, a 'contract' is a Zod-based schema file (spec/contracts/{routeId}.contract.ts) that defines " +
|
|
22
|
+
"the request/response shape for an API route. Contracts enable: " +
|
|
23
|
+
"(1) automatic input validation and sanitization (strip/strict/passthrough normalize modes), " +
|
|
24
|
+
"(2) TypeScript type inference for slot handlers, " +
|
|
25
|
+
"(3) OpenAPI 3.0 spec generation via mandu_generate_openapi, " +
|
|
26
|
+
"(4) ATE L2 (schema validation) and L3 (full behavioral) test generation. " +
|
|
27
|
+
"Also returns a list of API routes that are missing contracts.",
|
|
20
28
|
inputSchema: {
|
|
21
29
|
type: "object",
|
|
22
30
|
properties: {},
|
|
@@ -25,13 +33,17 @@ export const contractToolDefinitions: Tool[] = [
|
|
|
25
33
|
},
|
|
26
34
|
{
|
|
27
35
|
name: "mandu_get_contract",
|
|
28
|
-
description:
|
|
36
|
+
description:
|
|
37
|
+
"Get the full TypeScript source of a contract file for a specific route. " +
|
|
38
|
+
"Returns the raw content of spec/contracts/{routeId}.contract.ts, " +
|
|
39
|
+
"which contains Zod schemas for each HTTP method's request body/query params and response shape. " +
|
|
40
|
+
"If the route has no contract, returns a suggestion to create one with mandu_create_contract.",
|
|
29
41
|
inputSchema: {
|
|
30
42
|
type: "object",
|
|
31
43
|
properties: {
|
|
32
44
|
routeId: {
|
|
33
45
|
type: "string",
|
|
34
|
-
description: "The route ID to retrieve contract for",
|
|
46
|
+
description: "The route ID to retrieve the contract for",
|
|
35
47
|
},
|
|
36
48
|
},
|
|
37
49
|
required: ["routeId"],
|
|
@@ -39,22 +51,27 @@ export const contractToolDefinitions: Tool[] = [
|
|
|
39
51
|
},
|
|
40
52
|
{
|
|
41
53
|
name: "mandu_create_contract",
|
|
42
|
-
description:
|
|
54
|
+
description:
|
|
55
|
+
"Create a new contract file for a route and link it in the manifest. " +
|
|
56
|
+
"Generates spec/contracts/{routeId}.contract.ts with Zod schema stubs for each HTTP method. " +
|
|
57
|
+
"Template includes: request schema (body for POST/PUT/PATCH, query for GET/DELETE) and response schema. " +
|
|
58
|
+
"After creation: edit the Zod schemas to match your actual API shape, " +
|
|
59
|
+
"then run mandu_generate to regenerate typed handlers with validation.",
|
|
43
60
|
inputSchema: {
|
|
44
61
|
type: "object",
|
|
45
62
|
properties: {
|
|
46
63
|
routeId: {
|
|
47
64
|
type: "string",
|
|
48
|
-
description: "The route ID to create contract for",
|
|
65
|
+
description: "The route ID to create a contract for",
|
|
49
66
|
},
|
|
50
67
|
description: {
|
|
51
68
|
type: "string",
|
|
52
|
-
description: "API description
|
|
69
|
+
description: "Human-readable API description added as a comment in the contract file",
|
|
53
70
|
},
|
|
54
71
|
methods: {
|
|
55
72
|
type: "array",
|
|
56
73
|
items: { type: "string" },
|
|
57
|
-
description: "HTTP methods to
|
|
74
|
+
description: "HTTP methods to generate schemas for (default: route's declared methods or ['GET', 'POST'])",
|
|
58
75
|
},
|
|
59
76
|
},
|
|
60
77
|
required: ["routeId"],
|
|
@@ -62,7 +79,11 @@ export const contractToolDefinitions: Tool[] = [
|
|
|
62
79
|
},
|
|
63
80
|
{
|
|
64
81
|
name: "mandu_update_route_contract",
|
|
65
|
-
description:
|
|
82
|
+
description:
|
|
83
|
+
"Update the manifest to link an existing contract file to a route. " +
|
|
84
|
+
"Use this when the contract file already exists but the manifest's contractModule path needs updating, " +
|
|
85
|
+
"or when moving a contract file to a new location. " +
|
|
86
|
+
"Does not modify the contract file itself — only updates the manifest reference and lock file.",
|
|
66
87
|
inputSchema: {
|
|
67
88
|
type: "object",
|
|
68
89
|
properties: {
|
|
@@ -72,7 +93,7 @@ export const contractToolDefinitions: Tool[] = [
|
|
|
72
93
|
},
|
|
73
94
|
contractModule: {
|
|
74
95
|
type: "string",
|
|
75
|
-
description: "
|
|
96
|
+
description: "Relative path to the contract file from project root (e.g., spec/contracts/users.contract.ts)",
|
|
76
97
|
},
|
|
77
98
|
},
|
|
78
99
|
required: ["routeId", "contractModule"],
|
|
@@ -80,7 +101,12 @@ export const contractToolDefinitions: Tool[] = [
|
|
|
80
101
|
},
|
|
81
102
|
{
|
|
82
103
|
name: "mandu_validate_contracts",
|
|
83
|
-
description:
|
|
104
|
+
description:
|
|
105
|
+
"Validate all contracts against their linked slot implementations for consistency. " +
|
|
106
|
+
"Checks that every HTTP method defined in a contract has a matching handler in the slot, " +
|
|
107
|
+
"response types are compatible, and no orphaned contracts exist. " +
|
|
108
|
+
"Run this after modifying contracts or slots, or before running ATE tests to catch mismatches early. " +
|
|
109
|
+
"Returns a list of violations with file locations, descriptions, and fix suggestions.",
|
|
84
110
|
inputSchema: {
|
|
85
111
|
type: "object",
|
|
86
112
|
properties: {},
|
|
@@ -89,18 +115,27 @@ export const contractToolDefinitions: Tool[] = [
|
|
|
89
115
|
},
|
|
90
116
|
{
|
|
91
117
|
name: "mandu_sync_contract_slot",
|
|
92
|
-
description:
|
|
118
|
+
description:
|
|
119
|
+
"Detect and generate code to resolve HTTP method mismatches between a contract and its slot. " +
|
|
120
|
+
"'contract-to-slot': finds HTTP methods defined in the contract but missing from the slot — " +
|
|
121
|
+
"generates handler stub code to add to the slot file. " +
|
|
122
|
+
"'slot-to-contract': finds slot handlers not documented in the contract — " +
|
|
123
|
+
"generates Zod schema stubs to add to the contract. " +
|
|
124
|
+
"Returns generated code snippets to review — does NOT auto-write files. " +
|
|
125
|
+
"Copy the output and use the Edit tool to apply the changes.",
|
|
93
126
|
inputSchema: {
|
|
94
127
|
type: "object",
|
|
95
128
|
properties: {
|
|
96
129
|
routeId: {
|
|
97
130
|
type: "string",
|
|
98
|
-
description: "The route ID to sync",
|
|
131
|
+
description: "The route ID to sync (must have both contractModule and slotModule)",
|
|
99
132
|
},
|
|
100
133
|
direction: {
|
|
101
134
|
type: "string",
|
|
102
135
|
enum: ["contract-to-slot", "slot-to-contract"],
|
|
103
|
-
description:
|
|
136
|
+
description:
|
|
137
|
+
"'contract-to-slot': generate slot handler stubs for undocumented contract methods. " +
|
|
138
|
+
"'slot-to-contract': generate contract schema stubs for undocumented slot handlers.",
|
|
104
139
|
},
|
|
105
140
|
},
|
|
106
141
|
required: ["routeId", "direction"],
|
|
@@ -108,21 +143,25 @@ export const contractToolDefinitions: Tool[] = [
|
|
|
108
143
|
},
|
|
109
144
|
{
|
|
110
145
|
name: "mandu_generate_openapi",
|
|
111
|
-
description:
|
|
146
|
+
description:
|
|
147
|
+
"Generate an OpenAPI 3.0 specification JSON file from all routes with contract modules. " +
|
|
148
|
+
"Only routes with a defined contractModule are included — add contracts with mandu_create_contract first. " +
|
|
149
|
+
"Output is written to openapi.json (or a custom path). " +
|
|
150
|
+
"The generated spec can be served with Swagger UI, Redoc, or imported into any OpenAPI-compatible tooling.",
|
|
112
151
|
inputSchema: {
|
|
113
152
|
type: "object",
|
|
114
153
|
properties: {
|
|
115
154
|
output: {
|
|
116
155
|
type: "string",
|
|
117
|
-
description: "Output file path (default: openapi.json)",
|
|
156
|
+
description: "Output file path relative to project root (default: openapi.json)",
|
|
118
157
|
},
|
|
119
158
|
title: {
|
|
120
159
|
type: "string",
|
|
121
|
-
description: "API title (default: Mandu API)",
|
|
160
|
+
description: "API title shown in the spec (default: 'Mandu API')",
|
|
122
161
|
},
|
|
123
162
|
version: {
|
|
124
163
|
type: "string",
|
|
125
|
-
description: "API version (default: from manifest)",
|
|
164
|
+
description: "API version string (default: taken from the manifest version)",
|
|
126
165
|
},
|
|
127
166
|
},
|
|
128
167
|
required: [],
|
package/src/tools/generate.ts
CHANGED
|
@@ -8,17 +8,27 @@ export const generateToolDefinitions: Tool[] = [
|
|
|
8
8
|
{
|
|
9
9
|
name: "mandu_generate",
|
|
10
10
|
description:
|
|
11
|
-
"Generate
|
|
11
|
+
"Generate all Mandu framework artifacts from the current routes manifest and resource schemas. " +
|
|
12
|
+
"Runs two generation steps: " +
|
|
13
|
+
"(1) Route generation: for every route in .mandu/routes.manifest.json, creates " +
|
|
14
|
+
"a server-side handler in .mandu/generated/server/{routeId}.route.ts and " +
|
|
15
|
+
"a web component in .mandu/generated/web/{routeId}.route.tsx. " +
|
|
16
|
+
"These generated files wire up slots and contracts automatically — do NOT edit them directly. " +
|
|
17
|
+
"Instead, edit source files in app/ (route definition) or spec/ (slots, contracts). " +
|
|
18
|
+
"(2) Resource generation (resources=true, default): scans spec/resources/{name}/schema.ts " +
|
|
19
|
+
"and generates CRUD boilerplate (repository, service, handlers) for each declared resource. " +
|
|
20
|
+
"Run this after adding routes, modifying slot/contract files, or changing resource schemas. " +
|
|
21
|
+
"Use dryRun=true to preview what would be created or overwritten without writing any files.",
|
|
12
22
|
inputSchema: {
|
|
13
23
|
type: "object",
|
|
14
24
|
properties: {
|
|
15
25
|
dryRun: {
|
|
16
26
|
type: "boolean",
|
|
17
|
-
description: "
|
|
27
|
+
description: "Preview what would be generated without writing files (default: false)",
|
|
18
28
|
},
|
|
19
29
|
resources: {
|
|
20
30
|
type: "boolean",
|
|
21
|
-
description: "Include resource artifact generation (default: true)",
|
|
31
|
+
description: "Include resource artifact generation from spec/resources/ (default: true)",
|
|
22
32
|
},
|
|
23
33
|
},
|
|
24
34
|
required: [],
|
|
@@ -26,7 +36,11 @@ export const generateToolDefinitions: Tool[] = [
|
|
|
26
36
|
},
|
|
27
37
|
{
|
|
28
38
|
name: "mandu_generate_status",
|
|
29
|
-
description:
|
|
39
|
+
description:
|
|
40
|
+
"Show the current state of all generated artifacts from .mandu/generated.map.json. " +
|
|
41
|
+
"Returns: generation timestamp, source spec version, total file count, " +
|
|
42
|
+
"and a list of generated files per route with their kinds (server handler, web component, slot stub). " +
|
|
43
|
+
"If no generated.map.json exists, prompts to run mandu_generate first.",
|
|
30
44
|
inputSchema: {
|
|
31
45
|
type: "object",
|
|
32
46
|
properties: {},
|
package/src/tools/guard.ts
CHANGED
|
@@ -82,8 +82,8 @@ export const guardToolDefinitions: Tool[] = [
|
|
|
82
82
|
properties: {
|
|
83
83
|
preset: {
|
|
84
84
|
type: "string",
|
|
85
|
-
enum: ["fsd", "clean", "hexagonal", "atomic", "mandu"],
|
|
86
|
-
description: "Architecture preset to use (default: from config or 'mandu')",
|
|
85
|
+
enum: ["fsd", "clean", "hexagonal", "atomic", "cqrs", "mandu"],
|
|
86
|
+
description: "Architecture preset to use (default: from config or 'mandu'). Use 'cqrs' for Command/Query separation.",
|
|
87
87
|
},
|
|
88
88
|
autoFix: {
|
|
89
89
|
type: "boolean",
|
|
@@ -120,7 +120,7 @@ export const guardToolDefinitions: Tool[] = [
|
|
|
120
120
|
},
|
|
121
121
|
preset: {
|
|
122
122
|
type: "string",
|
|
123
|
-
enum: ["fsd", "clean", "hexagonal", "atomic", "mandu"],
|
|
123
|
+
enum: ["fsd", "clean", "hexagonal", "atomic", "cqrs", "mandu"],
|
|
124
124
|
description: "Architecture preset for context",
|
|
125
125
|
},
|
|
126
126
|
},
|
package/src/tools/hydration.ts
CHANGED
|
@@ -28,6 +28,12 @@ export const hydrationToolDefinitions: Tool[] = [
|
|
|
28
28
|
type: "boolean",
|
|
29
29
|
description: "Generate source maps for debugging",
|
|
30
30
|
},
|
|
31
|
+
targetRouteIds: {
|
|
32
|
+
type: "array",
|
|
33
|
+
items: { type: "string" },
|
|
34
|
+
description:
|
|
35
|
+
"Only rebuild specific islands by routeId. Skips Runtime/Router/Vendor rebuild for faster incremental updates. Omit to rebuild everything.",
|
|
36
|
+
},
|
|
31
37
|
},
|
|
32
38
|
required: [],
|
|
33
39
|
},
|
|
@@ -115,9 +121,10 @@ export function hydrationTools(projectRoot: string) {
|
|
|
115
121
|
|
|
116
122
|
return {
|
|
117
123
|
mandu_build: async (args: Record<string, unknown>) => {
|
|
118
|
-
const { minify, sourcemap } = args as {
|
|
124
|
+
const { minify, sourcemap, targetRouteIds } = args as {
|
|
119
125
|
minify?: boolean;
|
|
120
126
|
sourcemap?: boolean;
|
|
127
|
+
targetRouteIds?: string[];
|
|
121
128
|
};
|
|
122
129
|
|
|
123
130
|
// Load manifest
|
|
@@ -130,6 +137,7 @@ export function hydrationTools(projectRoot: string) {
|
|
|
130
137
|
const result = await buildClientBundles(manifestResult.data, projectRoot, {
|
|
131
138
|
minify,
|
|
132
139
|
sourcemap,
|
|
140
|
+
targetRouteIds,
|
|
133
141
|
});
|
|
134
142
|
|
|
135
143
|
return {
|
package/src/tools/runtime.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Mandu MCP Runtime Tools
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* - Logger 설정 조회/변경
|
|
6
|
-
* - Normalize 설정 조회
|
|
7
|
-
* - Contract 옵션 확인
|
|
3
|
+
* Query and manage runtime configuration: logger settings and contract normalize options.
|
|
8
4
|
*/
|
|
9
5
|
|
|
10
6
|
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -17,8 +13,10 @@ export const runtimeToolDefinitions: Tool[] = [
|
|
|
17
13
|
{
|
|
18
14
|
name: "mandu_get_runtime_config",
|
|
19
15
|
description:
|
|
20
|
-
"Get
|
|
21
|
-
"Shows default values
|
|
16
|
+
"Get the Mandu runtime configuration defaults for logger and normalize settings. " +
|
|
17
|
+
"Shows default values for every configurable option along with usage examples. " +
|
|
18
|
+
"Use this to understand the runtime before calling mandu_set_contract_normalize " +
|
|
19
|
+
"or mandu_generate_logger_config.",
|
|
22
20
|
inputSchema: {
|
|
23
21
|
type: "object",
|
|
24
22
|
properties: {},
|
|
@@ -28,8 +26,11 @@ export const runtimeToolDefinitions: Tool[] = [
|
|
|
28
26
|
{
|
|
29
27
|
name: "mandu_get_contract_options",
|
|
30
28
|
description:
|
|
31
|
-
"
|
|
32
|
-
"These options control how request data is
|
|
29
|
+
"Read the normalize and coerceQueryParams options currently set in a specific contract file. " +
|
|
30
|
+
"These options control how incoming request data is validated and sanitized: " +
|
|
31
|
+
"'normalize' removes or blocks undefined fields (Mass Assignment protection), " +
|
|
32
|
+
"'coerceQueryParams' auto-converts URL query string values to their declared schema types (e.g., '123' → number). " +
|
|
33
|
+
"Returns the parsed values and their effect, or defaults if no explicit options are set.",
|
|
33
34
|
inputSchema: {
|
|
34
35
|
type: "object",
|
|
35
36
|
properties: {
|
|
@@ -44,8 +45,12 @@ export const runtimeToolDefinitions: Tool[] = [
|
|
|
44
45
|
{
|
|
45
46
|
name: "mandu_set_contract_normalize",
|
|
46
47
|
description:
|
|
47
|
-
"Set normalize mode
|
|
48
|
-
"
|
|
48
|
+
"Set the normalize mode (and optionally coerceQueryParams) in a route's contract file. " +
|
|
49
|
+
"Normalize modes: " +
|
|
50
|
+
"'strip' (default, recommended) — removes any request fields not defined in the schema, preventing Mass Assignment attacks. " +
|
|
51
|
+
"'strict' — returns HTTP 400 if the request contains any field not defined in the schema. " +
|
|
52
|
+
"'passthrough' — allows all fields through without filtering (validation only, no sanitization). " +
|
|
53
|
+
"coerceQueryParams: when true (default), auto-converts query string values to their declared schema types.",
|
|
49
54
|
inputSchema: {
|
|
50
55
|
type: "object",
|
|
51
56
|
properties: {
|
|
@@ -57,11 +62,12 @@ export const runtimeToolDefinitions: Tool[] = [
|
|
|
57
62
|
type: "string",
|
|
58
63
|
enum: ["strip", "strict", "passthrough"],
|
|
59
64
|
description:
|
|
60
|
-
"Normalize mode: strip (
|
|
65
|
+
"Normalize mode: 'strip' (remove undefined fields, prevents Mass Assignment), " +
|
|
66
|
+
"'strict' (return 400 on undefined fields), 'passthrough' (allow all fields through)",
|
|
61
67
|
},
|
|
62
68
|
coerceQueryParams: {
|
|
63
69
|
type: "boolean",
|
|
64
|
-
description: "
|
|
70
|
+
description: "Auto-convert URL query string values to schema-declared types (default: true)",
|
|
65
71
|
},
|
|
66
72
|
},
|
|
67
73
|
required: ["routeId"],
|
|
@@ -70,8 +76,10 @@ export const runtimeToolDefinitions: Tool[] = [
|
|
|
70
76
|
{
|
|
71
77
|
name: "mandu_list_logger_options",
|
|
72
78
|
description:
|
|
73
|
-
"List available logger configuration options and
|
|
74
|
-
"
|
|
79
|
+
"List all available logger configuration options with types, defaults, and descriptions. " +
|
|
80
|
+
"Covers: log format, level, header/body logging (security risk warnings), " +
|
|
81
|
+
"sampling rate, slow request threshold, redaction fields, custom sink, and skip patterns. " +
|
|
82
|
+
"Use this as a reference before calling mandu_generate_logger_config.",
|
|
75
83
|
inputSchema: {
|
|
76
84
|
type: "object",
|
|
77
85
|
properties: {},
|
|
@@ -81,33 +89,36 @@ export const runtimeToolDefinitions: Tool[] = [
|
|
|
81
89
|
{
|
|
82
90
|
name: "mandu_generate_logger_config",
|
|
83
91
|
description:
|
|
84
|
-
"Generate logger configuration code
|
|
85
|
-
"Returns
|
|
92
|
+
"Generate ready-to-use TypeScript logger configuration code for a specific environment. " +
|
|
93
|
+
"Returns an import statement and logger() call with environment-appropriate defaults: " +
|
|
94
|
+
"development: debug level, pretty format, higher verbosity; " +
|
|
95
|
+
"production: info level, JSON format, 10% sampling, no headers/body. " +
|
|
96
|
+
"Security note: includeHeaders and includeBody are forced to false in production regardless of input.",
|
|
86
97
|
inputSchema: {
|
|
87
98
|
type: "object",
|
|
88
99
|
properties: {
|
|
89
100
|
environment: {
|
|
90
101
|
type: "string",
|
|
91
102
|
enum: ["development", "production", "testing"],
|
|
92
|
-
description: "Target environment (default: development)",
|
|
103
|
+
description: "Target environment — determines default log level, format, and sampling rate (default: development)",
|
|
93
104
|
},
|
|
94
105
|
includeHeaders: {
|
|
95
106
|
type: "boolean",
|
|
96
|
-
description: "
|
|
107
|
+
description: "Log request headers — security risk, only recommended in development (default: false)",
|
|
97
108
|
},
|
|
98
109
|
includeBody: {
|
|
99
110
|
type: "boolean",
|
|
100
|
-
description: "
|
|
111
|
+
description: "Log request body — security risk, only recommended in development (default: false)",
|
|
101
112
|
},
|
|
102
113
|
format: {
|
|
103
114
|
type: "string",
|
|
104
115
|
enum: ["pretty", "json"],
|
|
105
|
-
description: "Log output format
|
|
116
|
+
description: "Log output format: 'pretty' (colored, human-readable) or 'json' (structured, for log aggregators)",
|
|
106
117
|
},
|
|
107
118
|
customRedact: {
|
|
108
119
|
type: "array",
|
|
109
120
|
items: { type: "string" },
|
|
110
|
-
description: "Additional
|
|
121
|
+
description: "Additional header or field names to redact/mask from logs",
|
|
111
122
|
},
|
|
112
123
|
},
|
|
113
124
|
required: [],
|
|
@@ -160,17 +171,17 @@ export function runtimeTools(projectRoot: string) {
|
|
|
160
171
|
logger: {
|
|
161
172
|
format: "Log output format: 'pretty' (colored, dev) or 'json' (structured, prod)",
|
|
162
173
|
level: "Minimum log level: 'debug' | 'info' | 'warn' | 'error'",
|
|
163
|
-
includeHeaders: "⚠️ Security risk if true
|
|
164
|
-
includeBody: "⚠️ Security risk if true
|
|
165
|
-
maxBodyBytes: "Maximum body
|
|
166
|
-
sampleRate: "Sampling rate 0
|
|
167
|
-
slowThresholdMs: "Requests
|
|
168
|
-
redact: "Header/field names to mask in logs",
|
|
174
|
+
includeHeaders: "⚠️ Security risk if true — logs all request headers including Authorization, Cookie",
|
|
175
|
+
includeBody: "⚠️ Security risk if true — logs raw request body; may expose PII",
|
|
176
|
+
maxBodyBytes: "Maximum body bytes to log (truncates larger bodies to avoid log bloat)",
|
|
177
|
+
sampleRate: "Sampling rate 0.0–1.0 (1.0 = 100% of requests logged)",
|
|
178
|
+
slowThresholdMs: "Requests exceeding this threshold (ms) are logged at warn level with details",
|
|
179
|
+
redact: "Header/field names to mask in logs (replaces value with '[REDACTED]')",
|
|
169
180
|
},
|
|
170
181
|
normalize: {
|
|
171
|
-
mode: "strip: remove undefined fields (Mass Assignment
|
|
172
|
-
coerceQueryParams: "Auto-convert query string '123' → number 123",
|
|
173
|
-
deep: "Apply normalization to nested objects",
|
|
182
|
+
mode: "strip: remove undefined fields (prevents Mass Assignment attacks), strict: return 400 on undefined fields, passthrough: allow all fields (validation only)",
|
|
183
|
+
coerceQueryParams: "Auto-convert URL query string '123' → number 123 based on schema type",
|
|
184
|
+
deep: "Apply normalization recursively to nested objects",
|
|
174
185
|
},
|
|
175
186
|
},
|
|
176
187
|
usage: {
|
|
@@ -242,11 +253,11 @@ export default Mandu.contract({
|
|
|
242
253
|
},
|
|
243
254
|
explanation: {
|
|
244
255
|
normalize: {
|
|
245
|
-
strip: "
|
|
246
|
-
strict: "
|
|
247
|
-
passthrough: "
|
|
256
|
+
strip: "Removes any request fields not defined in the schema — prevents Mass Assignment attacks (recommended default)",
|
|
257
|
+
strict: "Returns HTTP 400 if the request contains any field not defined in the schema",
|
|
258
|
+
passthrough: "Allows all fields through without filtering — validation only, no sanitization",
|
|
248
259
|
},
|
|
249
|
-
coerceQueryParams: "URL query
|
|
260
|
+
coerceQueryParams: "URL query strings are always plain strings; this option auto-converts them to the declared schema types (e.g., '42' → number, 'true' → boolean)",
|
|
250
261
|
},
|
|
251
262
|
};
|
|
252
263
|
},
|
|
@@ -341,10 +352,10 @@ export default Mandu.contract({
|
|
|
341
352
|
message: `Updated ${route.contractModule}`,
|
|
342
353
|
securityNote:
|
|
343
354
|
normalize === "passthrough"
|
|
344
|
-
? "⚠️ passthrough
|
|
355
|
+
? "⚠️ passthrough mode may be vulnerable to Mass Assignment attacks. Only use with trusted, fully-validated input."
|
|
345
356
|
: normalize === "strict"
|
|
346
|
-
? "strict
|
|
347
|
-
: "strip
|
|
357
|
+
? "strict mode returns HTTP 400 if the client sends any field not defined in the contract schema."
|
|
358
|
+
: "strip mode (recommended): fields not defined in the schema are automatically removed from the request.",
|
|
348
359
|
};
|
|
349
360
|
},
|
|
350
361
|
|
|
@@ -355,78 +366,78 @@ export default Mandu.contract({
|
|
|
355
366
|
name: "format",
|
|
356
367
|
type: '"pretty" | "json"',
|
|
357
368
|
default: "pretty",
|
|
358
|
-
description: "
|
|
369
|
+
description: "Log output format: 'pretty' (colored, human-readable for dev) or 'json' (structured, for log aggregators in prod)",
|
|
359
370
|
},
|
|
360
371
|
{
|
|
361
372
|
name: "level",
|
|
362
373
|
type: '"debug" | "info" | "warn" | "error"',
|
|
363
374
|
default: "info",
|
|
364
|
-
description: "
|
|
375
|
+
description: "Minimum log level: 'debug' (all requests with details), 'info' (standard), 'warn' (slow/suspicious only), 'error' (errors only)",
|
|
365
376
|
},
|
|
366
377
|
{
|
|
367
378
|
name: "includeHeaders",
|
|
368
379
|
type: "boolean",
|
|
369
380
|
default: false,
|
|
370
|
-
description: "⚠️
|
|
381
|
+
description: "⚠️ Security risk — logs all request headers including Authorization and Cookie. Only enable in development.",
|
|
371
382
|
},
|
|
372
383
|
{
|
|
373
384
|
name: "includeBody",
|
|
374
385
|
type: "boolean",
|
|
375
386
|
default: false,
|
|
376
|
-
description: "⚠️
|
|
387
|
+
description: "⚠️ Security risk — logs raw request body which may contain PII or credentials. Only enable in development.",
|
|
377
388
|
},
|
|
378
389
|
{
|
|
379
390
|
name: "maxBodyBytes",
|
|
380
391
|
type: "number",
|
|
381
392
|
default: 1024,
|
|
382
|
-
description: "
|
|
393
|
+
description: "Maximum bytes of request body to log (larger bodies are truncated to avoid log bloat)",
|
|
383
394
|
},
|
|
384
395
|
{
|
|
385
396
|
name: "redact",
|
|
386
397
|
type: "string[]",
|
|
387
398
|
default: '["authorization", "cookie", "password", ...]',
|
|
388
|
-
description: "
|
|
399
|
+
description: "Header or field names to mask in logs (values are replaced with '[REDACTED]')",
|
|
389
400
|
},
|
|
390
401
|
{
|
|
391
402
|
name: "requestId",
|
|
392
403
|
type: '"auto" | ((ctx) => string)',
|
|
393
404
|
default: "auto",
|
|
394
|
-
description: "
|
|
405
|
+
description: "Request ID generation strategy: 'auto' uses UUID or timestamp-based ID, or provide a custom function",
|
|
395
406
|
},
|
|
396
407
|
{
|
|
397
408
|
name: "sampleRate",
|
|
398
|
-
type: "number (0
|
|
409
|
+
type: "number (0.0–1.0)",
|
|
399
410
|
default: 1,
|
|
400
|
-
description: "
|
|
411
|
+
description: "Fraction of requests to log (1.0 = 100%, 0.1 = 10%). Reduce in production to control log volume.",
|
|
401
412
|
},
|
|
402
413
|
{
|
|
403
414
|
name: "slowThresholdMs",
|
|
404
415
|
type: "number",
|
|
405
416
|
default: 1000,
|
|
406
|
-
description: "
|
|
417
|
+
description: "Requests exceeding this duration (ms) are logged at warn level with full details",
|
|
407
418
|
},
|
|
408
419
|
{
|
|
409
420
|
name: "includeTraceOnSlow",
|
|
410
421
|
type: "boolean",
|
|
411
422
|
default: true,
|
|
412
|
-
description: "
|
|
423
|
+
description: "Include a timing trace report in the log entry for slow requests",
|
|
413
424
|
},
|
|
414
425
|
{
|
|
415
426
|
name: "sink",
|
|
416
427
|
type: "(entry: LogEntry) => void",
|
|
417
428
|
default: "console",
|
|
418
|
-
description: "
|
|
429
|
+
description: "Custom log output handler — use for integrating with Pino, CloudWatch, Datadog, etc.",
|
|
419
430
|
},
|
|
420
431
|
{
|
|
421
432
|
name: "skip",
|
|
422
433
|
type: "(string | RegExp)[]",
|
|
423
434
|
default: "[]",
|
|
424
|
-
description: '
|
|
435
|
+
description: 'URL path patterns to exclude from logging. Example: ["/health", /^\\/static\\//]',
|
|
425
436
|
},
|
|
426
437
|
],
|
|
427
438
|
presets: {
|
|
428
|
-
devLogger: "
|
|
429
|
-
prodLogger: "
|
|
439
|
+
devLogger: "Development preset: debug level, pretty format, detailed output",
|
|
440
|
+
prodLogger: "Production preset: info level, JSON format, no headers/body logging",
|
|
430
441
|
},
|
|
431
442
|
};
|
|
432
443
|
},
|
|
@@ -471,10 +482,10 @@ export const appLogger = logger(${JSON.stringify(config, null, 2)});
|
|
|
471
482
|
|
|
472
483
|
const warnings: string[] = [];
|
|
473
484
|
if (includeHeaders && isProd) {
|
|
474
|
-
warnings.push("⚠️ includeHeaders: true
|
|
485
|
+
warnings.push("⚠️ includeHeaders: true in production may expose sensitive Authorization, Cookie, and API key headers in logs.");
|
|
475
486
|
}
|
|
476
487
|
if (includeBody && isProd) {
|
|
477
|
-
warnings.push("⚠️ includeBody: true
|
|
488
|
+
warnings.push("⚠️ includeBody: true in production may expose PII, passwords, or credentials in logs.");
|
|
478
489
|
}
|
|
479
490
|
|
|
480
491
|
return {
|
|
@@ -483,9 +494,9 @@ export const appLogger = logger(${JSON.stringify(config, null, 2)});
|
|
|
483
494
|
code,
|
|
484
495
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
485
496
|
tips: [
|
|
486
|
-
"devLogger()
|
|
487
|
-
"sink
|
|
488
|
-
"skip
|
|
497
|
+
"You can also use the devLogger() or prodLogger() preset helpers for quick setup.",
|
|
498
|
+
"Use the 'sink' option to integrate with external systems like Pino, CloudWatch, or Datadog.",
|
|
499
|
+
"Use the 'skip' option to exclude health check and static asset paths (e.g., ['/health', '/metrics']).",
|
|
489
500
|
],
|
|
490
501
|
};
|
|
491
502
|
},
|
package/src/tools/slot.ts
CHANGED
|
@@ -11,13 +11,19 @@ import path from "path";
|
|
|
11
11
|
export const slotToolDefinitions: Tool[] = [
|
|
12
12
|
{
|
|
13
13
|
name: "mandu_read_slot",
|
|
14
|
-
description:
|
|
14
|
+
description:
|
|
15
|
+
"Read the TypeScript source of a route's slot file and validate its structure. " +
|
|
16
|
+
"In Mandu, a 'slot' is the server-side data loader for a route: " +
|
|
17
|
+
"it runs on every request before rendering and returns a typed object " +
|
|
18
|
+
"that is injected into the page component as props (for pages) or as handler context (for API routes). " +
|
|
19
|
+
"Slot files live at spec/slots/{routeId}.slot.ts and are auto-linked by generateManifest(). " +
|
|
20
|
+
"Returns the raw source, line count, and any structural validation issues.",
|
|
15
21
|
inputSchema: {
|
|
16
22
|
type: "object",
|
|
17
23
|
properties: {
|
|
18
24
|
routeId: {
|
|
19
25
|
type: "string",
|
|
20
|
-
description: "The route ID whose slot file to read",
|
|
26
|
+
description: "The route ID whose slot file to read (use mandu_list_routes to find IDs)",
|
|
21
27
|
},
|
|
22
28
|
},
|
|
23
29
|
required: ["routeId"],
|
|
@@ -26,13 +32,21 @@ export const slotToolDefinitions: Tool[] = [
|
|
|
26
32
|
{
|
|
27
33
|
name: "mandu_validate_slot",
|
|
28
34
|
description:
|
|
29
|
-
"Validate slot content without writing
|
|
35
|
+
"Validate TypeScript slot content against Mandu's structural rules — without writing any files. " +
|
|
36
|
+
"A valid slot must export a default function (or use the slot() builder) that accepts a Request " +
|
|
37
|
+
"and returns a plain serializable object (becomes the typed props injected into the page). " +
|
|
38
|
+
"Returns: " +
|
|
39
|
+
"errors (must fix before use), " +
|
|
40
|
+
"warnings (best-practice suggestions), " +
|
|
41
|
+
"autoFixable issues (with corrected code preview), " +
|
|
42
|
+
"manualFixRequired items (issues needing human review). " +
|
|
43
|
+
"Use this before writing a slot file with the Edit tool to catch structural problems early.",
|
|
30
44
|
inputSchema: {
|
|
31
45
|
type: "object",
|
|
32
46
|
properties: {
|
|
33
47
|
content: {
|
|
34
48
|
type: "string",
|
|
35
|
-
description: "The TypeScript
|
|
49
|
+
description: "The TypeScript slot source code to validate",
|
|
36
50
|
},
|
|
37
51
|
},
|
|
38
52
|
required: ["content"],
|
|
@@ -84,7 +98,7 @@ export function slotTools(projectRoot: string) {
|
|
|
84
98
|
|
|
85
99
|
const content = await file.text();
|
|
86
100
|
|
|
87
|
-
//
|
|
101
|
+
// Validate existing slot content structure
|
|
88
102
|
const validation = validateSlotContent(content);
|
|
89
103
|
|
|
90
104
|
return {
|
|
@@ -110,11 +124,11 @@ export function slotTools(projectRoot: string) {
|
|
|
110
124
|
|
|
111
125
|
const validation = validateSlotContent(content);
|
|
112
126
|
|
|
113
|
-
//
|
|
127
|
+
// Classify issues by whether they can be auto-fixed
|
|
114
128
|
const autoFixable = validation.issues.filter((i) => i.autoFixable);
|
|
115
129
|
const manualFix = validation.issues.filter((i) => !i.autoFixable);
|
|
116
130
|
|
|
117
|
-
//
|
|
131
|
+
// Generate correction preview for auto-fixable issues
|
|
118
132
|
let correctionPreview = null;
|
|
119
133
|
if (autoFixable.length > 0) {
|
|
120
134
|
const correction = correctSlotContent(content, validation.issues);
|
package/src/tools/spec.ts
CHANGED
|
@@ -15,7 +15,16 @@ import fs from "fs/promises";
|
|
|
15
15
|
export const specToolDefinitions: Tool[] = [
|
|
16
16
|
{
|
|
17
17
|
name: "mandu_list_routes",
|
|
18
|
-
description:
|
|
18
|
+
description:
|
|
19
|
+
"List all routes registered in the Mandu project, read from .mandu/routes.manifest.json. " +
|
|
20
|
+
"Route kinds: " +
|
|
21
|
+
"'api' (REST endpoint — app/**/route.ts, exports named GET/POST/PUT/PATCH/DELETE handler functions), " +
|
|
22
|
+
"'page' (SSR page — app/**/page.tsx, React component supporting client-side hydration islands). " +
|
|
23
|
+
"Special files auto-detected by the filesystem router (not user-created routes): " +
|
|
24
|
+
"layout.tsx (shared wrapper rendered around child routes), " +
|
|
25
|
+
"error.tsx (error boundary for the route subtree), " +
|
|
26
|
+
"loading.tsx (suspense fallback shown while page data loads). " +
|
|
27
|
+
"Each route may have an associated slotModule (server data loader) and contractModule (Zod API schema).",
|
|
19
28
|
inputSchema: {
|
|
20
29
|
type: "object",
|
|
21
30
|
properties: {},
|
|
@@ -24,13 +33,17 @@ export const specToolDefinitions: Tool[] = [
|
|
|
24
33
|
},
|
|
25
34
|
{
|
|
26
35
|
name: "mandu_get_route",
|
|
27
|
-
description:
|
|
36
|
+
description:
|
|
37
|
+
"Get full details of a specific route by its ID. " +
|
|
38
|
+
"Returns the complete route spec: kind, URL pattern, module paths (app, slot, contract, component), " +
|
|
39
|
+
"HTTP methods, and hydration configuration (for page routes with client islands). " +
|
|
40
|
+
"Use this before modifying a route to understand its current configuration.",
|
|
28
41
|
inputSchema: {
|
|
29
42
|
type: "object",
|
|
30
43
|
properties: {
|
|
31
44
|
routeId: {
|
|
32
45
|
type: "string",
|
|
33
|
-
description: "The route ID to retrieve",
|
|
46
|
+
description: "The route ID to retrieve (use mandu_list_routes to see all IDs)",
|
|
34
47
|
},
|
|
35
48
|
},
|
|
36
49
|
required: ["routeId"],
|
|
@@ -38,7 +51,15 @@ export const specToolDefinitions: Tool[] = [
|
|
|
38
51
|
},
|
|
39
52
|
{
|
|
40
53
|
name: "mandu_add_route",
|
|
41
|
-
description:
|
|
54
|
+
description:
|
|
55
|
+
"Scaffold a new route by creating source files in app/ and registering it in the manifest. " +
|
|
56
|
+
"For 'api' routes: creates app/{path}/route.ts with a GET handler stub. " +
|
|
57
|
+
"For 'page' routes: creates app/{path}/page.tsx with a React component stub. " +
|
|
58
|
+
"withSlot=true (default): also creates spec/slots/{routeId}.slot.ts — " +
|
|
59
|
+
"the server-side data loader that runs on every request before rendering and injects typed props into the page. " +
|
|
60
|
+
"withContract=true: also creates spec/contracts/{routeId}.contract.ts — " +
|
|
61
|
+
"Zod schemas for request/response validation, enabling typed handlers, OpenAPI generation, and ATE L2/L3 testing. " +
|
|
62
|
+
"Automatically runs generateManifest() after creation to link all files.",
|
|
42
63
|
inputSchema: {
|
|
43
64
|
type: "object",
|
|
44
65
|
properties: {
|
|
@@ -49,15 +70,15 @@ export const specToolDefinitions: Tool[] = [
|
|
|
49
70
|
kind: {
|
|
50
71
|
type: "string",
|
|
51
72
|
enum: ["api", "page"],
|
|
52
|
-
description: "Route type: api
|
|
73
|
+
description: "Route type: 'api' creates route.ts with HTTP handlers, 'page' creates page.tsx with a React component",
|
|
53
74
|
},
|
|
54
75
|
withSlot: {
|
|
55
76
|
type: "boolean",
|
|
56
|
-
description: "
|
|
77
|
+
description: "Also scaffold a server-side data loader at spec/slots/{routeId}.slot.ts (default: true)",
|
|
57
78
|
},
|
|
58
79
|
withContract: {
|
|
59
80
|
type: "boolean",
|
|
60
|
-
description: "
|
|
81
|
+
description: "Also scaffold a Zod contract at spec/contracts/{routeId}.contract.ts (default: false)",
|
|
61
82
|
},
|
|
62
83
|
},
|
|
63
84
|
required: ["path", "kind"],
|
|
@@ -65,13 +86,18 @@ export const specToolDefinitions: Tool[] = [
|
|
|
65
86
|
},
|
|
66
87
|
{
|
|
67
88
|
name: "mandu_delete_route",
|
|
68
|
-
description:
|
|
89
|
+
description:
|
|
90
|
+
"Delete a route's app/ source file and regenerate the manifest. " +
|
|
91
|
+
"Only removes the app/{path}/route.ts or page.tsx file — " +
|
|
92
|
+
"slot files (spec/slots/) and contract files (spec/contracts/) are intentionally preserved, " +
|
|
93
|
+
"as they may be reused when the route is recreated. " +
|
|
94
|
+
"Use mandu_list_routes before deleting to confirm the correct routeId.",
|
|
69
95
|
inputSchema: {
|
|
70
96
|
type: "object",
|
|
71
97
|
properties: {
|
|
72
98
|
routeId: {
|
|
73
99
|
type: "string",
|
|
74
|
-
description: "The route ID to delete",
|
|
100
|
+
description: "The route ID to delete (use mandu_list_routes to find it)",
|
|
75
101
|
},
|
|
76
102
|
},
|
|
77
103
|
required: ["routeId"],
|
|
@@ -79,7 +105,10 @@ export const specToolDefinitions: Tool[] = [
|
|
|
79
105
|
},
|
|
80
106
|
{
|
|
81
107
|
name: "mandu_validate_manifest",
|
|
82
|
-
description:
|
|
108
|
+
description:
|
|
109
|
+
"Validate the routes manifest (.mandu/routes.manifest.json) for structural integrity. " +
|
|
110
|
+
"Checks required fields, valid route kinds, correct module paths, and manifest schema version. " +
|
|
111
|
+
"Run this after manual manifest edits, after upgrading Mandu, or when routes behave unexpectedly.",
|
|
83
112
|
inputSchema: {
|
|
84
113
|
type: "object",
|
|
85
114
|
properties: {},
|