@mandujs/mcp 0.38.6 → 0.38.7

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 (42) hide show
  1. package/README.md +3 -3
  2. package/package.json +2 -2
  3. package/src/executor/error-handler.ts +76 -2
  4. package/src/index.ts +5 -5
  5. package/src/prompts.ts +4 -4
  6. package/src/resources/handlers.ts +4 -6
  7. package/src/resources/skills/guides.ts +49 -49
  8. package/src/resources/skills/loader.ts +8 -8
  9. package/src/resources/skills/mandu-agent-workflow/SKILL.md +124 -124
  10. package/src/resources/skills/mandu-agent-workflow/metadata.json +7 -7
  11. package/src/resources/skills/mandu-composition/SKILL.md +47 -47
  12. package/src/resources/skills/mandu-deployment/SKILL.md +53 -53
  13. package/src/resources/skills/mandu-deployment/rules/db-provider-supabase.md +3 -3
  14. package/src/resources/skills/mandu-fs-routes/SKILL.md +47 -47
  15. package/src/resources/skills/mandu-guard/SKILL.md +58 -58
  16. package/src/resources/skills/mandu-hydration/SKILL.md +67 -67
  17. package/src/resources/skills/mandu-hydration/rules/hydration-island-setup.md +54 -54
  18. package/src/resources/skills/mandu-hydration/rules/hydration-priority-visible.md +60 -60
  19. package/src/resources/skills/mandu-performance/SKILL.md +47 -47
  20. package/src/resources/skills/mandu-security/SKILL.md +47 -47
  21. package/src/resources/skills/mandu-slot/SKILL.md +48 -48
  22. package/src/resources/skills/mandu-styling/SKILL.md +52 -52
  23. package/src/resources/skills/mandu-testing/SKILL.md +55 -55
  24. package/src/resources/skills/mandu-ui/SKILL.md +52 -52
  25. package/src/resources/skills/recipes.ts +28 -28
  26. package/src/server.ts +2 -1
  27. package/src/tools/agent.ts +443 -443
  28. package/src/tools/ate.ts +37 -37
  29. package/src/tools/brain.ts +12 -11
  30. package/src/tools/composite.ts +13 -13
  31. package/src/tools/contract.ts +1 -1
  32. package/src/tools/deploy-plan.ts +1 -1
  33. package/src/tools/generate.ts +3 -1
  34. package/src/tools/guard.ts +36 -1
  35. package/src/tools/history.ts +1 -1
  36. package/src/tools/hydration.ts +1 -3
  37. package/src/tools/index.ts +14 -0
  38. package/src/tools/kitchen.ts +72 -72
  39. package/src/tools/runtime.ts +1 -1
  40. package/src/tools/slot-validation.ts +19 -19
  41. package/src/tools/transaction.ts +19 -5
  42. package/src/utils/withWarnings.ts +1 -1
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 {
@@ -13,19 +13,20 @@
13
13
  import type { Tool } from "@modelcontextprotocol/sdk/types.js";
14
14
  import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
15
15
  import type { ActivityMonitor } from "../activity-monitor.js";
16
+ import { loadManifest, runGuardCheck } from "@mandujs/core";
16
17
  import {
17
- loadManifest,
18
- runGuardCheck,
19
18
  analyzeViolations,
20
19
  initializeBrain,
21
20
  getBrain,
22
- startWatcher,
23
- stopWatcher,
24
- getWatcher,
25
- generateJsonStatus,
26
21
  initializeArchitectureAnalyzer,
27
22
  getArchitectureAnalyzer,
28
- } from "@mandujs/core";
23
+ } from "@mandujs/core/brain";
24
+ import {
25
+ generateJsonStatus,
26
+ getWatcher,
27
+ startWatcher,
28
+ stopWatcher,
29
+ } from "@mandujs/core/watcher";
29
30
  import { getProjectPaths } from "../utils/project.js";
30
31
 
31
32
  export const brainToolDefinitions: Tool[] = [
@@ -664,7 +665,7 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
664
665
 
665
666
  // #235 followup — brain auth tools (status / login / logout).
666
667
  handlers["mandu.brain.status"] = async () => {
667
- const core = await import("@mandujs/core");
668
+ const core = await import("@mandujs/core/brain");
668
669
  assertBrainAuthSurface(core);
669
670
  const store = core.getCredentialStore();
670
671
  const resolution = await core.resolveBrainAdapter({
@@ -744,7 +745,7 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
744
745
  };
745
746
 
746
747
  if (provider === "openai") {
747
- const core = await import("@mandujs/core");
748
+ const core = await import("@mandujs/core/brain");
748
749
  assertBrainAuthSurface(core);
749
750
  const auth = new core.ChatGPTAuth();
750
751
  const existing = auth.locateAuthFile();
@@ -835,7 +836,7 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
835
836
  }
836
837
 
837
838
  // Anthropic — Mandu-managed OAuth loopback flow.
838
- const core = await import("@mandujs/core");
839
+ const core = await import("@mandujs/core/brain");
839
840
  assertBrainAuthSurface(core);
840
841
  try {
841
842
  const adapter = new core.AnthropicOAuthAdapter({
@@ -888,7 +889,7 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
888
889
  const { provider = "all" } = args as {
889
890
  provider?: "openai" | "anthropic" | "all";
890
891
  };
891
- const core = await import("@mandujs/core");
892
+ const core = await import("@mandujs/core/brain");
892
893
  assertBrainAuthSurface(core);
893
894
  const store = core.getCredentialStore();
894
895
  const targets =
@@ -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
+ }
@@ -2,12 +2,12 @@ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
2
  import {
3
3
  loadManifest,
4
4
  runContractGuardCheck,
5
- generateContractTemplate,
6
5
  generateOpenAPIDocument,
7
6
  openAPIToJSON,
8
7
  type RouteSpec,
9
8
  type SpecHttpMethod,
10
9
  } from "@mandujs/core";
10
+ import { generateContractTemplate } from "@mandujs/core/generator";
11
11
  import { getProjectPaths, writeJsonFile } from "../utils/project.js";
12
12
  import path from "path";
13
13
  import fs from "fs/promises";
@@ -43,9 +43,9 @@ import {
43
43
  import {
44
44
  generateManifest,
45
45
  loadManifest,
46
- resolveBrainAdapter,
47
46
  type RoutesManifest,
48
47
  } from "@mandujs/core";
48
+ import { resolveBrainAdapter } from "@mandujs/core/brain";
49
49
  import path from "node:path";
50
50
 
51
51
  /**
@@ -1,5 +1,7 @@
1
1
  import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
- import { loadManifest, generateRoutes, generateManifest, GENERATED_RELATIVE_PATHS, type GeneratedMap, parseResourceSchema, generateResourceArtifacts } from "@mandujs/core";
2
+ import { generateManifest, loadManifest, parseResourceSchema, generateResourceArtifacts } from "@mandujs/core";
3
+ import { generateRoutes, type GeneratedMap } from "@mandujs/core/generator";
4
+ import { GENERATED_RELATIVE_PATHS } from "@mandujs/core/paths";
3
5
  import { getProjectPaths, readJsonFile } from "../utils/project.js";
4
6
  import path from "path";
5
7
  import fs from "fs/promises";
@@ -7,7 +7,6 @@ import {
7
7
  loadManifest,
8
8
  runGuardCheck,
9
9
  runAutoCorrect,
10
- type GeneratedMap,
11
10
  // Self-Healing Guard imports
12
11
  checkWithHealing,
13
12
  healAll,
@@ -19,6 +18,7 @@ import {
19
18
  type GuardPreset,
20
19
  type Violation,
21
20
  } from "@mandujs/core";
21
+ import type { GeneratedMap } from "@mandujs/core/generator";
22
22
  import { getProjectPaths, readJsonFile, readConfig } from "../utils/project.js";
23
23
  import fs from "fs/promises";
24
24
  import path from "path";
@@ -138,7 +138,25 @@ export function guardTools(projectRoot: string) {
138
138
  }
139
139
  };
140
140
 
141
+ const guardViolationCode = (ruleId: string | undefined, type?: string) =>
142
+ `MANDU_GUARD_${(ruleId || type || "VIOLATION").toUpperCase().replace(/[^A-Z0-9]+/g, "_")}`;
143
+
144
+ const explainViolation = (input: {
145
+ type?: string;
146
+ ruleId?: string;
147
+ fromLayer?: string;
148
+ toLayer?: string;
149
+ suggestion?: string;
150
+ }): string => {
151
+ const source = input.fromLayer ? ` from ${input.fromLayer}` : "";
152
+ const target = input.toLayer ? ` to ${input.toLayer}` : "";
153
+ const rule = input.ruleId ?? input.type ?? "architecture rule";
154
+ const fix = input.suggestion ? ` Suggested fix: ${input.suggestion}` : "";
155
+ return `Violation of ${rule}${source}${target}.${fix}`.trim();
156
+ };
157
+
141
158
  const summarizeArchitectureViolation = (violation: Violation) => ({
159
+ code: guardViolationCode(violation.ruleName, violation.type),
142
160
  ruleId: violation.ruleName,
143
161
  type: violation.type,
144
162
  file: path.relative(projectRoot, violation.filePath).replace(/\\/g, "/") || violation.filePath,
@@ -146,16 +164,25 @@ export function guardTools(projectRoot: string) {
146
164
  column: violation.column,
147
165
  message: violation.ruleDescription,
148
166
  suggestion: violation.suggestions[0],
167
+ explanation: explainViolation({
168
+ type: violation.type,
169
+ ruleId: violation.ruleName,
170
+ fromLayer: violation.fromLayer,
171
+ toLayer: violation.toLayer,
172
+ suggestion: violation.suggestions[0],
173
+ }),
149
174
  fromLayer: violation.fromLayer,
150
175
  toLayer: violation.toLayer,
151
176
  importStatement: violation.importStatement,
152
177
  });
153
178
 
154
179
  const summarizeLegacyViolation = (v: Awaited<ReturnType<typeof runGuardCheck>>["violations"][number]) => ({
180
+ code: guardViolationCode(v.ruleId),
155
181
  ruleId: v.ruleId,
156
182
  file: v.file,
157
183
  message: v.message,
158
184
  suggestion: v.suggestion,
185
+ explanation: explainViolation({ ruleId: v.ruleId, suggestion: v.suggestion }),
159
186
  });
160
187
 
161
188
  const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
@@ -521,10 +548,18 @@ export function guardTools(projectRoot: string) {
521
548
  preset: config.preset,
522
549
  violations: items.map((item) => ({
523
550
  // Violation info
551
+ code: guardViolationCode(item.violation.ruleName, item.violation.type),
524
552
  type: item.violation.type,
525
553
  file: item.violation.filePath,
526
554
  line: item.violation.line,
527
555
  message: item.violation.ruleDescription,
556
+ explanation: explainViolation({
557
+ type: item.violation.type,
558
+ ruleId: item.violation.ruleName,
559
+ fromLayer: item.violation.fromLayer,
560
+ toLayer: item.violation.toLayer,
561
+ suggestion: item.healing.primary.explanation,
562
+ }),
528
563
  fromLayer: item.violation.fromLayer,
529
564
  toLayer: item.violation.toLayer,
530
565
  importStatement: item.violation.importStatement,
@@ -4,7 +4,7 @@ import {
4
4
  pruneHistory,
5
5
  getChangeStats,
6
6
  readSnapshotById,
7
- } from "@mandujs/core";
7
+ } from "@mandujs/core/change";
8
8
 
9
9
  export const historyToolDefinitions: Tool[] = [
10
10
  {
@@ -1,15 +1,13 @@
1
1
  import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
2
  import {
3
3
  loadManifest,
4
- buildClientBundles,
5
- formatSize,
6
4
  needsHydration,
7
5
  getRouteHydration,
8
- type BundleManifest,
9
6
  type SpecHydrationStrategy,
10
7
  type HydrationPriority,
11
8
  type HydrationConfig,
12
9
  } from "@mandujs/core";
10
+ import { buildClientBundles, formatSize, type BundleManifest } from "@mandujs/core/bundler";
13
11
  import { getProjectPaths, readJsonFile, writeJsonFile } from "../utils/project.js";
14
12
  import path from "path";
15
13
 
@@ -293,6 +293,7 @@ export function validateBuiltinToolModules(
293
293
  const issues: string[] = [];
294
294
  const categories = new Set<string>();
295
295
  const toolNames = new Map<string, string>();
296
+ const descriptions = new Map<string, string>();
296
297
 
297
298
  for (const module of modules) {
298
299
  if (categories.has(module.category)) {
@@ -305,6 +306,19 @@ export function validateBuiltinToolModules(
305
306
  }
306
307
 
307
308
  for (const definition of module.definitions) {
309
+ if (!definition.description?.trim()) {
310
+ issues.push(`tool definition is missing description: ${definition.name} in ${module.category}`);
311
+ }
312
+ const normalizedDescription = definition.description?.trim().replace(/\s+/g, " ").toLowerCase();
313
+ if (normalizedDescription) {
314
+ const previousTool = descriptions.get(normalizedDescription);
315
+ if (previousTool) {
316
+ issues.push(
317
+ `duplicate tool description: ${definition.name} and ${previousTool} both describe the same action`
318
+ );
319
+ }
320
+ descriptions.set(normalizedDescription, definition.name);
321
+ }
308
322
  const previousCategory = toolNames.get(definition.name);
309
323
  if (previousCategory) {
310
324
  issues.push(
@@ -4,10 +4,10 @@
4
4
  * Enables any MCP-compatible agent to read client-side errors in real-time.
5
5
  */
6
6
 
7
- import type { Tool } from "@modelcontextprotocol/sdk/types.js";
8
- import { loadManduConfig } from "@mandujs/core";
9
- import { getDevServerState } from "./project.js";
10
- import { readRuntimeControl } from "../utils/runtime-control.js";
7
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
8
+ import { loadManduConfig } from "@mandujs/core";
9
+ import { getDevServerState } from "./project.js";
10
+ import { readRuntimeControl } from "../utils/runtime-control.js";
11
11
 
12
12
  export const kitchenToolDefinitions: Tool[] = [
13
13
  {
@@ -20,18 +20,18 @@ export const kitchenToolDefinitions: Tool[] = [
20
20
  inputSchema: {
21
21
  type: "object",
22
22
  properties: {
23
- clear: {
24
- type: "boolean",
25
- description: "Clear errors after reading (default: false)",
26
- },
27
- baseURL: {
28
- type: "string",
29
- description: "Explicit dev server URL. Overrides runtime-control/config discovery.",
30
- },
31
- port: {
32
- type: "number",
33
- description: "Explicit dev server port. Overrides runtime-control/config discovery.",
34
- },
23
+ clear: {
24
+ type: "boolean",
25
+ description: "Clear errors after reading (default: false)",
26
+ },
27
+ baseURL: {
28
+ type: "string",
29
+ description: "Explicit dev server URL. Overrides runtime-control/config discovery.",
30
+ },
31
+ port: {
32
+ type: "number",
33
+ description: "Explicit dev server port. Overrides runtime-control/config discovery.",
34
+ },
35
35
  },
36
36
  required: [],
37
37
  },
@@ -56,19 +56,19 @@ export const kitchenToolDefinitions: Tool[] = [
56
56
  description:
57
57
  "Include the extended diagnose report. Default true. Set false to lower latency when a11y_hints / package_export_gaps are noisy.",
58
58
  },
59
- includeDiff: {
60
- type: "boolean",
61
- description:
62
- "Include git diff against MANDU_DIFF_BASE (default HEAD). Default true. Set false to skip when git is unavailable.",
63
- },
64
- baseURL: {
65
- type: "string",
66
- description: "Explicit dev server URL. Overrides runtime-control/config discovery.",
67
- },
68
- port: {
69
- type: "number",
70
- description: "Explicit dev server port. Overrides runtime-control/config discovery.",
71
- },
59
+ includeDiff: {
60
+ type: "boolean",
61
+ description:
62
+ "Include git diff against MANDU_DIFF_BASE (default HEAD). Default true. Set false to skip when git is unavailable.",
63
+ },
64
+ baseURL: {
65
+ type: "string",
66
+ description: "Explicit dev server URL. Overrides runtime-control/config discovery.",
67
+ },
68
+ port: {
69
+ type: "number",
70
+ description: "Explicit dev server port. Overrides runtime-control/config discovery.",
71
+ },
72
72
  },
73
73
  required: [],
74
74
  },
@@ -76,32 +76,32 @@ export const kitchenToolDefinitions: Tool[] = [
76
76
  ];
77
77
 
78
78
  /**
79
- * Resolve the dev server base URL. Prefers explicit args, then
80
- * `.mandu/runtime-control.json` from the running dev/start process, then
81
- * captured stdout, then config/default 3333. Shared by every Kitchen-backed
82
- * MCP tool so they all agree on where to fetch from.
79
+ * Resolve the dev server base URL. Prefers explicit args, then
80
+ * `.mandu/runtime-control.json` from the running dev/start process, then
81
+ * captured stdout, then config/default 3333. Shared by every Kitchen-backed
82
+ * MCP tool so they all agree on where to fetch from.
83
83
  */
84
- async function resolveDevServerBaseUrl(
85
- projectRoot: string,
86
- args: { baseURL?: unknown; port?: unknown } = {},
87
- ): Promise<string> {
88
- if (typeof args.baseURL === "string" && args.baseURL.trim()) {
89
- return args.baseURL.trim().replace(/\/+$/, "");
90
- }
91
-
92
- const explicitPort = normalizePort(args.port);
93
- if (explicitPort) {
94
- return `http://localhost:${explicitPort}`;
95
- }
96
-
97
- const control = await readRuntimeControl(projectRoot);
98
- if (control?.baseUrl) {
99
- return control.baseUrl.replace(/\/+$/, "");
100
- }
101
-
102
- let port: number | undefined;
103
-
104
- const serverState = getDevServerState();
84
+ async function resolveDevServerBaseUrl(
85
+ projectRoot: string,
86
+ args: { baseURL?: unknown; port?: unknown } = {},
87
+ ): Promise<string> {
88
+ if (typeof args.baseURL === "string" && args.baseURL.trim()) {
89
+ return args.baseURL.trim().replace(/\/+$/, "");
90
+ }
91
+
92
+ const explicitPort = normalizePort(args.port);
93
+ if (explicitPort) {
94
+ return `http://localhost:${explicitPort}`;
95
+ }
96
+
97
+ const control = await readRuntimeControl(projectRoot);
98
+ if (control?.baseUrl) {
99
+ return control.baseUrl.replace(/\/+$/, "");
100
+ }
101
+
102
+ let port: number | undefined;
103
+
104
+ const serverState = getDevServerState();
105
105
  if (serverState) {
106
106
  for (const line of serverState.output) {
107
107
  const portMatch = line.match(/https?:\/\/localhost:(\d+)/);
@@ -115,21 +115,21 @@ async function resolveDevServerBaseUrl(
115
115
  const config = await loadManduConfig(projectRoot);
116
116
  port = config.server?.port ?? 3333;
117
117
  }
118
-
119
- return `http://localhost:${port}`;
120
- }
121
-
122
- function normalizePort(value: unknown): number | undefined {
123
- const raw = typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value, 10) : NaN;
124
- if (!Number.isInteger(raw) || raw < 1 || raw > 65535) return undefined;
125
- return raw;
126
- }
118
+
119
+ return `http://localhost:${port}`;
120
+ }
121
+
122
+ function normalizePort(value: unknown): number | undefined {
123
+ const raw = typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value, 10) : NaN;
124
+ if (!Number.isInteger(raw) || raw < 1 || raw > 65535) return undefined;
125
+ return raw;
126
+ }
127
127
 
128
128
  export function kitchenTools(projectRoot: string) {
129
129
  const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
130
- "mandu.kitchen.errors": async (args: Record<string, unknown>) => {
131
- const { clear = false } = args as { clear?: boolean };
132
- const baseUrl = await resolveDevServerBaseUrl(projectRoot, args);
130
+ "mandu.kitchen.errors": async (args: Record<string, unknown>) => {
131
+ const { clear = false } = args as { clear?: boolean };
132
+ const baseUrl = await resolveDevServerBaseUrl(projectRoot, args);
133
133
 
134
134
  try {
135
135
  // Fetch errors from Kitchen API
@@ -185,12 +185,12 @@ export function kitchenTools(projectRoot: string) {
185
185
  */
186
186
  "mandu.devtools.context": async (args: Record<string, unknown>) => {
187
187
  const {
188
- includeBundle = true,
189
- includeDiagnose = true,
190
- includeDiff = true,
191
- } = args as { includeBundle?: boolean; includeDiagnose?: boolean; includeDiff?: boolean };
192
-
193
- const baseUrl = await resolveDevServerBaseUrl(projectRoot, args);
188
+ includeBundle = true,
189
+ includeDiagnose = true,
190
+ includeDiff = true,
191
+ } = args as { includeBundle?: boolean; includeDiagnose?: boolean; includeDiff?: boolean };
192
+
193
+ const baseUrl = await resolveDevServerBaseUrl(projectRoot, args);
194
194
  const params = new URLSearchParams();
195
195
  if (!includeBundle) params.set("bundle", "0");
196
196
  if (!includeDiagnose) params.set("diagnose", "0");
@@ -10,8 +10,8 @@ import {
10
10
  loadManduConfig,
11
11
  loadManifest,
12
12
  needsHydration,
13
- type BundleManifest,
14
13
  } from "@mandujs/core";
14
+ import type { BundleManifest } from "@mandujs/core/bundler";
15
15
  import { getDevServerState } from "./project.js";
16
16
  import { readRuntimeControl } from "../utils/runtime-control.js";
17
17
  import path from "path";
@@ -1,13 +1,13 @@
1
- import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
- import {
3
- DEFAULT_SLOT_CONSTRAINTS,
4
- API_SLOT_CONSTRAINTS,
5
- READONLY_SLOT_CONSTRAINTS,
6
- } from "@mandujs/core";
7
-
8
- export const slotValidationToolDefinitions: Tool[] = [
9
- {
10
- name: "mandu.slot.constraints",
1
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ import {
3
+ DEFAULT_SLOT_CONSTRAINTS,
4
+ API_SLOT_CONSTRAINTS,
5
+ READONLY_SLOT_CONSTRAINTS,
6
+ } from "@mandujs/core";
7
+
8
+ export const slotValidationToolDefinitions: Tool[] = [
9
+ {
10
+ name: "mandu.slot.constraints",
11
11
  description:
12
12
  "Get recommended slot constraint presets (default, api, readonly).",
13
13
  annotations: {
@@ -27,10 +27,10 @@ export const slotValidationToolDefinitions: Tool[] = [
27
27
  },
28
28
  ];
29
29
 
30
- export function slotValidationTools(projectRoot: string) {
31
- void projectRoot;
32
- const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
33
- "mandu.slot.constraints": async (args: Record<string, unknown>) => {
30
+ export function slotValidationTools(projectRoot: string) {
31
+ void projectRoot;
32
+ const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
33
+ "mandu.slot.constraints": async (args: Record<string, unknown>) => {
34
34
  const { preset } = args as { preset?: "default" | "api" | "readonly" };
35
35
 
36
36
  const presets = {
@@ -84,10 +84,10 @@ Mandu.filling()
84
84
  `.trim(),
85
85
  };
86
86
  },
87
- };
88
-
89
- // Backward-compatible aliases
90
- handlers["mandu_get_slot_constraints"] = handlers["mandu.slot.constraints"];
91
-
87
+ };
88
+
89
+ // Backward-compatible aliases
90
+ handlers["mandu_get_slot_constraints"] = handlers["mandu.slot.constraints"];
91
+
92
92
  return handlers;
93
93
  }