@mandujs/mcp 0.38.3 → 0.38.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.
Files changed (32) hide show
  1. package/README.md +3 -3
  2. package/package.json +2 -2
  3. package/src/index.ts +5 -5
  4. package/src/profiles.ts +112 -50
  5. package/src/prompts.ts +4 -4
  6. package/src/resources/skills/guides.ts +49 -49
  7. package/src/resources/skills/loader.ts +8 -8
  8. package/src/resources/skills/mandu-agent-workflow/SKILL.md +124 -124
  9. package/src/resources/skills/mandu-agent-workflow/metadata.json +7 -7
  10. package/src/resources/skills/mandu-composition/SKILL.md +47 -47
  11. package/src/resources/skills/mandu-deployment/SKILL.md +53 -53
  12. package/src/resources/skills/mandu-deployment/rules/db-provider-supabase.md +3 -3
  13. package/src/resources/skills/mandu-fs-routes/SKILL.md +47 -47
  14. package/src/resources/skills/mandu-guard/SKILL.md +58 -58
  15. package/src/resources/skills/mandu-hydration/SKILL.md +67 -67
  16. package/src/resources/skills/mandu-hydration/rules/hydration-island-setup.md +54 -54
  17. package/src/resources/skills/mandu-hydration/rules/hydration-priority-visible.md +60 -60
  18. package/src/resources/skills/mandu-performance/SKILL.md +47 -47
  19. package/src/resources/skills/mandu-security/SKILL.md +47 -47
  20. package/src/resources/skills/mandu-slot/SKILL.md +48 -48
  21. package/src/resources/skills/mandu-styling/SKILL.md +52 -52
  22. package/src/resources/skills/mandu-testing/SKILL.md +55 -55
  23. package/src/resources/skills/mandu-ui/SKILL.md +52 -52
  24. package/src/resources/skills/recipes.ts +28 -28
  25. package/src/server.ts +3 -3
  26. package/src/tools/agent.ts +443 -443
  27. package/src/tools/ate.ts +37 -37
  28. package/src/tools/composite.ts +13 -13
  29. package/src/tools/hydration.ts +197 -197
  30. package/src/tools/kitchen.ts +72 -72
  31. package/src/tools/runtime.ts +579 -579
  32. package/src/tools/slot-validation.ts +19 -19
package/src/tools/ate.ts CHANGED
@@ -105,11 +105,11 @@ export const ateToolDefinitions: Tool[] = [
105
105
  type: "string",
106
106
  description: "Dev server URL (default: http://localhost:3333). Must match the running mandu dev server.",
107
107
  },
108
- ci: { type: "boolean", description: "CI mode: stricter timeouts, no interactive prompts" },
109
- timeoutMs: {
110
- type: "number",
111
- description: "Hard timeout for the whole Playwright process in milliseconds (default: 600000).",
112
- },
108
+ ci: { type: "boolean", description: "CI mode: stricter timeouts, no interactive prompts" },
109
+ timeoutMs: {
110
+ type: "number",
111
+ description: "Hard timeout for the whole Playwright process in milliseconds (default: 600000).",
112
+ },
113
113
  headless: { type: "boolean", description: "Run browsers headlessly (default: true)" },
114
114
  browsers: {
115
115
  type: "array",
@@ -264,11 +264,11 @@ export const ateToolDefinitions: Tool[] = [
264
264
  enum: ["L0", "L1", "L2", "L3"],
265
265
  description: "Assertion depth: L0=smoke, L1=HTTP status, L2=contract schema, L3=full behavioral",
266
266
  },
267
- ci: { type: "boolean", description: "CI mode: stricter timeouts" },
268
- timeoutMs: {
269
- type: "number",
270
- description: "Hard timeout for the whole Playwright process in milliseconds (default: 600000).",
271
- },
267
+ ci: { type: "boolean", description: "CI mode: stricter timeouts" },
268
+ timeoutMs: {
269
+ type: "number",
270
+ description: "Hard timeout for the whole Playwright process in milliseconds (default: 600000).",
271
+ },
272
272
  useImpactAnalysis: {
273
273
  type: "boolean",
274
274
  description: "Run impact analysis first and only test changed routes (faster in CI)",
@@ -386,7 +386,7 @@ export function ateTools(projectRoot: string, server?: Server) {
386
386
  return await ateRun(input);
387
387
  } catch (err) {
388
388
  const message = err instanceof Error ? err.message : String(err);
389
- const isTimeout = /timeout|timed out|타임아웃/i.test(message);
389
+ const isTimeout = /timeout|timed out|타임아웃/i.test(message);
390
390
  const snap = tracker.snapshot();
391
391
  const partial: PartialRunResults = {
392
392
  runId: snap.runId ?? `unknown-${Date.now()}`,
@@ -447,21 +447,21 @@ export function ateTools(projectRoot: string, server?: Server) {
447
447
  browsers,
448
448
  onlyFiles,
449
449
  onlyRoutes,
450
- grep,
451
- timeoutMs,
452
- progressToken,
453
- } = args as {
450
+ grep,
451
+ timeoutMs,
452
+ progressToken,
453
+ } = args as {
454
454
  repoRoot: string;
455
455
  baseURL?: string;
456
456
  ci?: boolean;
457
457
  headless?: boolean;
458
458
  browsers?: ("chromium" | "firefox" | "webkit")[];
459
459
  onlyFiles?: string[];
460
- onlyRoutes?: string[];
461
- grep?: string;
462
- timeoutMs?: number;
463
- progressToken?: string | number;
464
- };
460
+ onlyRoutes?: string[];
461
+ grep?: string;
462
+ timeoutMs?: number;
463
+ progressToken?: string | number;
464
+ };
465
465
  return await runWithObservability(
466
466
  {
467
467
  repoRoot,
@@ -469,11 +469,11 @@ export function ateTools(projectRoot: string, server?: Server) {
469
469
  ci,
470
470
  headless,
471
471
  browsers,
472
- onlyFiles,
473
- onlyRoutes,
474
- grep,
475
- timeoutMs,
476
- },
472
+ onlyFiles,
473
+ onlyRoutes,
474
+ grep,
475
+ timeoutMs,
476
+ },
477
477
  { progressToken },
478
478
  );
479
479
  },
@@ -529,10 +529,10 @@ export function ateTools(projectRoot: string, server?: Server) {
529
529
  },
530
530
  "mandu.ate.auto_pipeline": async (args: Record<string, unknown>) => {
531
531
  const {
532
- repoRoot, baseURL, oracleLevel, ci, useImpactAnalysis,
533
- base, head, autoHeal, tsconfigPath, routeGlobs, buildSalt,
534
- timeoutMs,
535
- } = args as {
532
+ repoRoot, baseURL, oracleLevel, ci, useImpactAnalysis,
533
+ base, head, autoHeal, tsconfigPath, routeGlobs, buildSalt,
534
+ timeoutMs,
535
+ } = args as {
536
536
  repoRoot: string;
537
537
  baseURL?: string;
538
538
  oracleLevel?: OracleLevel;
@@ -542,14 +542,14 @@ export function ateTools(projectRoot: string, server?: Server) {
542
542
  head?: string;
543
543
  autoHeal?: boolean;
544
544
  tsconfigPath?: string;
545
- routeGlobs?: string[];
546
- buildSalt?: string;
547
- timeoutMs?: number;
548
- };
549
- return await runFullPipeline({
550
- repoRoot, baseURL, oracleLevel, ci, useImpactAnalysis,
551
- base, head, autoHeal, tsconfigPath, routeGlobs, buildSalt, timeoutMs,
552
- });
545
+ routeGlobs?: string[];
546
+ buildSalt?: string;
547
+ timeoutMs?: number;
548
+ };
549
+ return await runFullPipeline({
550
+ repoRoot, baseURL, oracleLevel, ci, useImpactAnalysis,
551
+ base, head, autoHeal, tsconfigPath, routeGlobs, buildSalt, timeoutMs,
552
+ });
553
553
  },
554
554
  "mandu.ate.feedback": async (args: Record<string, unknown>) => {
555
555
  const { repoRoot, runId, autoApply } = args as {
@@ -60,10 +60,10 @@ export const compositeToolDefinitions: Tool[] = [
60
60
  },
61
61
  },
62
62
  {
63
- name: "mandu.island.add",
64
- description:
65
- "Create an island component with correct @mandujs/core/client imports and hydration strategy. " +
66
- "Generates a .island.tsx file in app/{route}/ with the wrapComponent() island definition.",
63
+ name: "mandu.island.add",
64
+ description:
65
+ "Create an island component with correct @mandujs/core/client imports and hydration strategy. " +
66
+ "Generates a .island.tsx file in app/{route}/ with the wrapComponent() island definition.",
67
67
  annotations: {
68
68
  destructiveHint: true,
69
69
  readOnlyHint: false,
@@ -474,10 +474,10 @@ function generateMiddlewareSource(preset: string, options: Record<string, string
474
474
  return templates[preset] ?? templates.default;
475
475
  }
476
476
 
477
- function generateIslandSource(name: string, strategy: string): string {
478
- return `"use client";
479
- import { wrapComponent } from "@mandujs/core/client";
480
- import { useState } from "react";
477
+ function generateIslandSource(name: string, strategy: string): string {
478
+ return `"use client";
479
+ import { wrapComponent } from "@mandujs/core/client";
480
+ import { useState } from "react";
481
481
 
482
482
  interface ${name}Props {
483
483
  [key: string]: unknown;
@@ -493,8 +493,8 @@ function ${name}Inner(props: ${name}Props) {
493
493
  );
494
494
  }
495
495
 
496
- // Route hydration priority should be set on the page/manifest route.
497
- // Suggested priority for this island: "${strategy}".
498
- export default wrapComponent(${name}Inner);
499
- `;
500
- }
496
+ // Route hydration priority should be set on the page/manifest route.
497
+ // Suggested priority for this island: "${strategy}".
498
+ export default wrapComponent(${name}Inner);
499
+ `;
500
+ }
@@ -18,17 +18,17 @@ export const hydrationToolDefinitions: Tool[] = [
18
18
  name: "mandu.build",
19
19
  description:
20
20
  "Build client bundles for hydration. Compiles client slots (.client.ts) into browser-ready JavaScript bundles.",
21
- annotations: {
22
- destructiveHint: true,
23
- readOnlyHint: false,
24
- idempotentHint: true,
25
- },
26
- inputSchema: {
27
- type: "object",
28
- properties: {
29
- minify: {
30
- type: "boolean",
31
- description: "Minify the output bundles (default: true in production)",
21
+ annotations: {
22
+ destructiveHint: true,
23
+ readOnlyHint: false,
24
+ idempotentHint: true,
25
+ },
26
+ inputSchema: {
27
+ type: "object",
28
+ properties: {
29
+ minify: {
30
+ type: "boolean",
31
+ description: "Minify the output bundles (default: true in production)",
32
32
  },
33
33
  sourcemap: {
34
34
  type: "boolean",
@@ -39,67 +39,67 @@ export const hydrationToolDefinitions: Tool[] = [
39
39
  items: { type: "string" },
40
40
  description:
41
41
  "Only rebuild specific islands by routeId. Skips Runtime/Router/Vendor rebuild for faster incremental updates. Omit to rebuild everything.",
42
- },
43
- },
44
- required: [],
45
- additionalProperties: false,
46
- },
47
- },
42
+ },
43
+ },
44
+ required: [],
45
+ additionalProperties: false,
46
+ },
47
+ },
48
48
  {
49
49
  name: "mandu.build.status",
50
50
  description:
51
51
  "Get the current build status, bundle manifest, and statistics for client bundles.",
52
- annotations: {
53
- readOnlyHint: true,
54
- },
55
- inputSchema: {
56
- type: "object",
57
- properties: {},
58
- required: [],
59
- additionalProperties: false,
60
- },
61
- },
62
- {
63
- name: "mandu.island.list",
64
- description:
65
- "Legacy alias for page client mount diagnostics. Prefer mandu.pageClientMount.list for terminology that separates page-level hydration from nested islands.",
66
- annotations: {
67
- readOnlyHint: true,
68
- },
69
- inputSchema: {
70
- type: "object",
71
- properties: {},
72
- required: [],
73
- additionalProperties: false,
74
- },
75
- },
76
- {
77
- name: "mandu.pageClientMount.list",
78
- description:
79
- "List page-level client mounts: page routes that need a route-level clientModule and bundle. This is distinct from nested islands/partials.",
80
- annotations: {
81
- readOnlyHint: true,
82
- },
83
- inputSchema: {
84
- type: "object",
85
- properties: {},
86
- required: [],
87
- additionalProperties: false,
88
- },
89
- },
90
- {
91
- name: "mandu.hydration.set",
52
+ annotations: {
53
+ readOnlyHint: true,
54
+ },
55
+ inputSchema: {
56
+ type: "object",
57
+ properties: {},
58
+ required: [],
59
+ additionalProperties: false,
60
+ },
61
+ },
62
+ {
63
+ name: "mandu.island.list",
64
+ description:
65
+ "Legacy alias for page client mount diagnostics. Prefer mandu.pageClientMount.list for terminology that separates page-level hydration from nested islands.",
66
+ annotations: {
67
+ readOnlyHint: true,
68
+ },
69
+ inputSchema: {
70
+ type: "object",
71
+ properties: {},
72
+ required: [],
73
+ additionalProperties: false,
74
+ },
75
+ },
76
+ {
77
+ name: "mandu.pageClientMount.list",
78
+ description:
79
+ "List page-level client mounts: page routes that need a route-level clientModule and bundle. This is distinct from nested islands/partials.",
80
+ annotations: {
81
+ readOnlyHint: true,
82
+ },
83
+ inputSchema: {
84
+ type: "object",
85
+ properties: {},
86
+ required: [],
87
+ additionalProperties: false,
88
+ },
89
+ },
90
+ {
91
+ name: "mandu.hydration.set",
92
92
  description:
93
93
  "Set hydration configuration for a specific route. Updates the route's hydration strategy and priority.",
94
- annotations: {
95
- readOnlyHint: false,
96
- },
97
- inputSchema: {
98
- type: "object",
99
- properties: {
100
- routeId: {
101
- type: "string",
102
- description: "The route ID to configure",
94
+ annotations: {
95
+ readOnlyHint: false,
96
+ },
97
+ inputSchema: {
98
+ type: "object",
99
+ properties: {
100
+ routeId: {
101
+ type: "string",
102
+ description: "The route ID to configure",
103
103
  },
104
104
  strategy: {
105
105
  type: "string",
@@ -117,25 +117,25 @@ export const hydrationToolDefinitions: Tool[] = [
117
117
  type: "boolean",
118
118
  description: "Whether to preload the bundle with modulepreload",
119
119
  },
120
- },
121
- required: ["routeId"],
122
- additionalProperties: false,
123
- },
124
- },
125
- {
120
+ },
121
+ required: ["routeId"],
122
+ additionalProperties: false,
123
+ },
124
+ },
125
+ {
126
126
  name: "mandu.hydration.addClientSlot",
127
127
  description:
128
128
  "Add a client slot file for a route to enable hydration. Creates the .client.ts file and updates the manifest.",
129
- annotations: {
130
- destructiveHint: false,
131
- readOnlyHint: false,
132
- },
133
- inputSchema: {
134
- type: "object",
135
- properties: {
136
- routeId: {
137
- type: "string",
138
- description: "The route ID to add client slot for",
129
+ annotations: {
130
+ destructiveHint: false,
131
+ readOnlyHint: false,
132
+ },
133
+ inputSchema: {
134
+ type: "object",
135
+ properties: {
136
+ routeId: {
137
+ type: "string",
138
+ description: "The route ID to add client slot for",
139
139
  },
140
140
  strategy: {
141
141
  type: "string",
@@ -147,14 +147,14 @@ export const hydrationToolDefinitions: Tool[] = [
147
147
  enum: ["immediate", "visible", "idle", "interaction"],
148
148
  description: "Hydration priority (default: visible)",
149
149
  },
150
- },
151
- required: ["routeId"],
152
- additionalProperties: false,
153
- },
154
- },
155
- ];
156
-
157
- export function hydrationTools(projectRoot: string) {
150
+ },
151
+ required: ["routeId"],
152
+ additionalProperties: false,
153
+ },
154
+ },
155
+ ];
156
+
157
+ export function hydrationTools(projectRoot: string) {
158
158
  const paths = getProjectPaths(projectRoot);
159
159
 
160
160
  const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
@@ -234,76 +234,76 @@ export function hydrationTools(projectRoot: string) {
234
234
  };
235
235
  },
236
236
 
237
- "mandu.pageClientMount.list": async () => {
238
- // Load manifest
239
- const manifestResult = await loadManifest(paths.manifestPath);
240
- if (!manifestResult.success || !manifestResult.data) {
241
- return { error: manifestResult.errors };
242
- }
243
-
244
- const pageClientMounts = manifestResult.data.routes
245
- .filter((route) => route.kind === "page")
246
- .map((route) => {
247
- const hydration = getRouteHydration(route);
248
- const needsClientMount = needsHydration(route);
249
- const warning = needsClientMount && !route.clientModule
250
- ? `Route has hydration strategy '${hydration.strategy}' but no clientModule; build would emit no route bundle.`
251
- : null;
252
-
253
- return {
254
- routeId: route.id,
255
- pattern: route.pattern,
256
- hasClientModule: !!route.clientModule,
257
- clientModule: route.clientModule || null,
258
- needsClientMount,
259
- // Backward compatibility for older agents that read `isIsland`.
260
- isIsland: needsClientMount,
261
- status: needsClientMount ? (route.clientModule ? "ready" : "broken") : "static",
262
- warning,
263
- hydration: {
264
- strategy: hydration.strategy,
265
- priority: hydration.priority,
237
+ "mandu.pageClientMount.list": async () => {
238
+ // Load manifest
239
+ const manifestResult = await loadManifest(paths.manifestPath);
240
+ if (!manifestResult.success || !manifestResult.data) {
241
+ return { error: manifestResult.errors };
242
+ }
243
+
244
+ const pageClientMounts = manifestResult.data.routes
245
+ .filter((route) => route.kind === "page")
246
+ .map((route) => {
247
+ const hydration = getRouteHydration(route);
248
+ const needsClientMount = needsHydration(route);
249
+ const warning = needsClientMount && !route.clientModule
250
+ ? `Route has hydration strategy '${hydration.strategy}' but no clientModule; build would emit no route bundle.`
251
+ : null;
252
+
253
+ return {
254
+ routeId: route.id,
255
+ pattern: route.pattern,
256
+ hasClientModule: !!route.clientModule,
257
+ clientModule: route.clientModule || null,
258
+ needsClientMount,
259
+ // Backward compatibility for older agents that read `isIsland`.
260
+ isIsland: needsClientMount,
261
+ status: needsClientMount ? (route.clientModule ? "ready" : "broken") : "static",
262
+ warning,
263
+ hydration: {
264
+ strategy: hydration.strategy,
265
+ priority: hydration.priority,
266
266
  preload: hydration.preload,
267
- },
268
- };
269
- });
270
-
271
- const pageClientMountCount = pageClientMounts.filter((i) => i.needsClientMount).length;
272
- const staticCount = pageClientMounts.filter((i) => !i.needsClientMount).length;
273
- const activeMounts = pageClientMounts.filter((i) => i.needsClientMount);
274
- const staticPages = pageClientMounts.filter((i) => !i.needsClientMount);
275
-
276
- return {
277
- terminology: {
278
- pageClientMount:
279
- "A page route whose whole page is hydrated from a route-level clientModule and route bundle.",
280
- island:
281
- "A nested or route-local island bundle, distinct from page client mounts. Use mandu.runtime.status for both collections.",
282
- },
283
- totalPages: pageClientMounts.length,
284
- pageClientMountCount,
285
- staticCount,
286
- pageClientMounts: activeMounts,
287
- staticPages,
288
- // Backward compatibility for older clients.
289
- islandCount: pageClientMountCount,
290
- islands: activeMounts,
291
- };
292
- },
293
-
294
- "mandu.island.list": async () => handlers["mandu.pageClientMount.list"]({}),
295
-
296
- "mandu.hydration.set": async (args: Record<string, unknown>) => {
297
- const validationError = validateRouteIdArgs(
298
- args,
299
- "mandu.hydration.set",
300
- ["routeId", "strategy", "priority", "preload"],
301
- );
302
- if (validationError) return { error: validationError };
303
-
304
- const { routeId, strategy, priority, preload } = args as {
305
- routeId: string;
306
- strategy?: SpecHydrationStrategy;
267
+ },
268
+ };
269
+ });
270
+
271
+ const pageClientMountCount = pageClientMounts.filter((i) => i.needsClientMount).length;
272
+ const staticCount = pageClientMounts.filter((i) => !i.needsClientMount).length;
273
+ const activeMounts = pageClientMounts.filter((i) => i.needsClientMount);
274
+ const staticPages = pageClientMounts.filter((i) => !i.needsClientMount);
275
+
276
+ return {
277
+ terminology: {
278
+ pageClientMount:
279
+ "A page route whose whole page is hydrated from a route-level clientModule and route bundle.",
280
+ island:
281
+ "A nested or route-local island bundle, distinct from page client mounts. Use mandu.runtime.status for both collections.",
282
+ },
283
+ totalPages: pageClientMounts.length,
284
+ pageClientMountCount,
285
+ staticCount,
286
+ pageClientMounts: activeMounts,
287
+ staticPages,
288
+ // Backward compatibility for older clients.
289
+ islandCount: pageClientMountCount,
290
+ islands: activeMounts,
291
+ };
292
+ },
293
+
294
+ "mandu.island.list": async () => handlers["mandu.pageClientMount.list"]({}),
295
+
296
+ "mandu.hydration.set": async (args: Record<string, unknown>) => {
297
+ const validationError = validateRouteIdArgs(
298
+ args,
299
+ "mandu.hydration.set",
300
+ ["routeId", "strategy", "priority", "preload"],
301
+ );
302
+ if (validationError) return { error: validationError };
303
+
304
+ const { routeId, strategy, priority, preload } = args as {
305
+ routeId: string;
306
+ strategy?: SpecHydrationStrategy;
307
307
  priority?: HydrationPriority;
308
308
  preload?: boolean;
309
309
  };
@@ -357,19 +357,19 @@ export function hydrationTools(projectRoot: string) {
357
357
  newHydration,
358
358
  message: `Updated hydration config for ${routeId}`,
359
359
  };
360
- },
361
-
362
- "mandu.hydration.addClientSlot": async (args: Record<string, unknown>) => {
363
- const validationError = validateRouteIdArgs(
364
- args,
365
- "mandu.hydration.addClientSlot",
366
- ["routeId", "strategy", "priority"],
367
- );
368
- if (validationError) return { error: validationError };
369
-
370
- const { routeId, strategy = "island", priority = "visible" } = args as {
371
- routeId: string;
372
- strategy?: SpecHydrationStrategy;
360
+ },
361
+
362
+ "mandu.hydration.addClientSlot": async (args: Record<string, unknown>) => {
363
+ const validationError = validateRouteIdArgs(
364
+ args,
365
+ "mandu.hydration.addClientSlot",
366
+ ["routeId", "strategy", "priority"],
367
+ );
368
+ if (validationError) return { error: validationError };
369
+
370
+ const { routeId, strategy = "island", priority = "visible" } = args as {
371
+ routeId: string;
372
+ strategy?: SpecHydrationStrategy;
373
373
  priority?: HydrationPriority;
374
374
  };
375
375
 
@@ -449,31 +449,31 @@ export function hydrationTools(projectRoot: string) {
449
449
  };
450
450
 
451
451
  // Backward-compatible aliases (deprecated)
452
- handlers["mandu_build"] = handlers["mandu.build"];
453
- handlers["mandu_build_status"] = handlers["mandu.build.status"];
454
- handlers["mandu_list_islands"] = handlers["mandu.island.list"];
455
- handlers["mandu_page_client_mount_list"] = handlers["mandu.pageClientMount.list"];
456
- handlers["mandu_set_hydration"] = handlers["mandu.hydration.set"];
457
- handlers["mandu_hydration_set"] = handlers["mandu.hydration.set"];
458
- handlers["mandu_add_client_slot"] = handlers["mandu.hydration.addClientSlot"];
459
- handlers["mandu_hydration_add_client_slot"] = handlers["mandu.hydration.addClientSlot"];
460
-
461
- return handlers;
462
- }
463
-
464
- function validateRouteIdArgs(
465
- args: Record<string, unknown>,
466
- toolName: string,
467
- allowedKeys: readonly string[],
468
- ): string | null {
469
- if (typeof args.routeId !== "string" || args.routeId.trim().length === 0) {
470
- const allowed = new Set(allowedKeys);
471
- const unknownKeys = Object.keys(args).filter((key) => !allowed.has(key));
472
- const got = unknownKeys.length > 0 ? ` (got unknown key${unknownKeys.length === 1 ? "" : "s"} '${unknownKeys.join("', '")}')` : "";
473
- return `${toolName}: missing required parameter 'routeId'${got}`;
474
- }
475
- return null;
476
- }
452
+ handlers["mandu_build"] = handlers["mandu.build"];
453
+ handlers["mandu_build_status"] = handlers["mandu.build.status"];
454
+ handlers["mandu_list_islands"] = handlers["mandu.island.list"];
455
+ handlers["mandu_page_client_mount_list"] = handlers["mandu.pageClientMount.list"];
456
+ handlers["mandu_set_hydration"] = handlers["mandu.hydration.set"];
457
+ handlers["mandu_hydration_set"] = handlers["mandu.hydration.set"];
458
+ handlers["mandu_add_client_slot"] = handlers["mandu.hydration.addClientSlot"];
459
+ handlers["mandu_hydration_add_client_slot"] = handlers["mandu.hydration.addClientSlot"];
460
+
461
+ return handlers;
462
+ }
463
+
464
+ function validateRouteIdArgs(
465
+ args: Record<string, unknown>,
466
+ toolName: string,
467
+ allowedKeys: readonly string[],
468
+ ): string | null {
469
+ if (typeof args.routeId !== "string" || args.routeId.trim().length === 0) {
470
+ const allowed = new Set(allowedKeys);
471
+ const unknownKeys = Object.keys(args).filter((key) => !allowed.has(key));
472
+ const got = unknownKeys.length > 0 ? ` (got unknown key${unknownKeys.length === 1 ? "" : "s"} '${unknownKeys.join("', '")}')` : "";
473
+ return `${toolName}: missing required parameter 'routeId'${got}`;
474
+ }
475
+ return null;
476
+ }
477
477
 
478
478
  /**
479
479
  * Generate a client slot template