@mandujs/mcp 0.37.3 → 0.38.0

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.
@@ -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,50 +39,53 @@ 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
- },
46
- },
42
+ },
43
+ },
44
+ required: [],
45
+ additionalProperties: false,
46
+ },
47
+ },
47
48
  {
48
49
  name: "mandu.build.status",
49
50
  description:
50
51
  "Get the current build status, bundle manifest, and statistics for client bundles.",
51
- annotations: {
52
- readOnlyHint: true,
53
- },
54
- inputSchema: {
55
- type: "object",
56
- properties: {},
57
- required: [],
58
- },
59
- },
60
- {
61
- name: "mandu.island.list",
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",
62
64
  description:
63
65
  "List all routes that have client-side hydration (islands). Shows hydration strategy and priority for each.",
64
- annotations: {
65
- readOnlyHint: true,
66
- },
67
- inputSchema: {
68
- type: "object",
69
- properties: {},
70
- required: [],
71
- },
72
- },
73
- {
74
- name: "mandu.hydration.set",
66
+ annotations: {
67
+ readOnlyHint: true,
68
+ },
69
+ inputSchema: {
70
+ type: "object",
71
+ properties: {},
72
+ required: [],
73
+ additionalProperties: false,
74
+ },
75
+ },
76
+ {
77
+ name: "mandu.hydration.set",
75
78
  description:
76
79
  "Set hydration configuration for a specific route. Updates the route's hydration strategy and priority.",
77
- annotations: {
78
- readOnlyHint: false,
79
- },
80
- inputSchema: {
81
- type: "object",
82
- properties: {
83
- routeId: {
84
- type: "string",
85
- description: "The route ID to configure",
80
+ annotations: {
81
+ readOnlyHint: false,
82
+ },
83
+ inputSchema: {
84
+ type: "object",
85
+ properties: {
86
+ routeId: {
87
+ type: "string",
88
+ description: "The route ID to configure",
86
89
  },
87
90
  strategy: {
88
91
  type: "string",
@@ -100,24 +103,25 @@ export const hydrationToolDefinitions: Tool[] = [
100
103
  type: "boolean",
101
104
  description: "Whether to preload the bundle with modulepreload",
102
105
  },
103
- },
104
- required: ["routeId"],
105
- },
106
- },
107
- {
106
+ },
107
+ required: ["routeId"],
108
+ additionalProperties: false,
109
+ },
110
+ },
111
+ {
108
112
  name: "mandu.hydration.addClientSlot",
109
113
  description:
110
114
  "Add a client slot file for a route to enable hydration. Creates the .client.ts file and updates the manifest.",
111
- annotations: {
112
- destructiveHint: false,
113
- readOnlyHint: false,
114
- },
115
- inputSchema: {
116
- type: "object",
117
- properties: {
118
- routeId: {
119
- type: "string",
120
- description: "The route ID to add client slot for",
115
+ annotations: {
116
+ destructiveHint: false,
117
+ readOnlyHint: false,
118
+ },
119
+ inputSchema: {
120
+ type: "object",
121
+ properties: {
122
+ routeId: {
123
+ type: "string",
124
+ description: "The route ID to add client slot for",
121
125
  },
122
126
  strategy: {
123
127
  type: "string",
@@ -129,13 +133,14 @@ export const hydrationToolDefinitions: Tool[] = [
129
133
  enum: ["immediate", "visible", "idle", "interaction"],
130
134
  description: "Hydration priority (default: visible)",
131
135
  },
132
- },
133
- required: ["routeId"],
134
- },
135
- },
136
- ];
137
-
138
- export function hydrationTools(projectRoot: string) {
136
+ },
137
+ required: ["routeId"],
138
+ additionalProperties: false,
139
+ },
140
+ },
141
+ ];
142
+
143
+ export function hydrationTools(projectRoot: string) {
139
144
  const paths = getProjectPaths(projectRoot);
140
145
 
141
146
  const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
@@ -223,20 +228,25 @@ export function hydrationTools(projectRoot: string) {
223
228
  }
224
229
 
225
230
  const islands = manifestResult.data.routes
226
- .filter((route) => route.kind === "page")
227
- .map((route) => {
228
- const hydration = getRouteHydration(route);
229
- const isIsland = needsHydration(route);
230
-
231
- return {
232
- routeId: route.id,
233
- pattern: route.pattern,
234
- hasClientModule: !!route.clientModule,
235
- clientModule: route.clientModule || null,
236
- isIsland,
237
- hydration: {
238
- strategy: hydration.strategy,
239
- priority: hydration.priority,
231
+ .filter((route) => route.kind === "page")
232
+ .map((route) => {
233
+ const hydration = getRouteHydration(route);
234
+ const isIsland = needsHydration(route);
235
+ const warning = isIsland && !route.clientModule
236
+ ? `Route has hydration strategy '${hydration.strategy}' but no clientModule; build would emit no route bundle.`
237
+ : null;
238
+
239
+ return {
240
+ routeId: route.id,
241
+ pattern: route.pattern,
242
+ hasClientModule: !!route.clientModule,
243
+ clientModule: route.clientModule || null,
244
+ isIsland,
245
+ status: isIsland ? (route.clientModule ? "ready" : "broken") : "static",
246
+ warning,
247
+ hydration: {
248
+ strategy: hydration.strategy,
249
+ priority: hydration.priority,
240
250
  preload: hydration.preload,
241
251
  },
242
252
  };
@@ -252,12 +262,19 @@ export function hydrationTools(projectRoot: string) {
252
262
  islands: islands.filter((i) => i.isIsland),
253
263
  staticPages: islands.filter((i) => !i.isIsland),
254
264
  };
255
- },
256
-
257
- "mandu.hydration.set": async (args: Record<string, unknown>) => {
258
- const { routeId, strategy, priority, preload } = args as {
259
- routeId: string;
260
- strategy?: SpecHydrationStrategy;
265
+ },
266
+
267
+ "mandu.hydration.set": async (args: Record<string, unknown>) => {
268
+ const validationError = validateRouteIdArgs(
269
+ args,
270
+ "mandu.hydration.set",
271
+ ["routeId", "strategy", "priority", "preload"],
272
+ );
273
+ if (validationError) return { error: validationError };
274
+
275
+ const { routeId, strategy, priority, preload } = args as {
276
+ routeId: string;
277
+ strategy?: SpecHydrationStrategy;
261
278
  priority?: HydrationPriority;
262
279
  preload?: boolean;
263
280
  };
@@ -311,12 +328,19 @@ export function hydrationTools(projectRoot: string) {
311
328
  newHydration,
312
329
  message: `Updated hydration config for ${routeId}`,
313
330
  };
314
- },
315
-
316
- "mandu.hydration.addClientSlot": async (args: Record<string, unknown>) => {
317
- const { routeId, strategy = "island", priority = "visible" } = args as {
318
- routeId: string;
319
- strategy?: SpecHydrationStrategy;
331
+ },
332
+
333
+ "mandu.hydration.addClientSlot": async (args: Record<string, unknown>) => {
334
+ const validationError = validateRouteIdArgs(
335
+ args,
336
+ "mandu.hydration.addClientSlot",
337
+ ["routeId", "strategy", "priority"],
338
+ );
339
+ if (validationError) return { error: validationError };
340
+
341
+ const { routeId, strategy = "island", priority = "visible" } = args as {
342
+ routeId: string;
343
+ strategy?: SpecHydrationStrategy;
320
344
  priority?: HydrationPriority;
321
345
  };
322
346
 
@@ -396,14 +420,30 @@ export function hydrationTools(projectRoot: string) {
396
420
  };
397
421
 
398
422
  // Backward-compatible aliases (deprecated)
399
- handlers["mandu_build"] = handlers["mandu.build"];
400
- handlers["mandu_build_status"] = handlers["mandu.build.status"];
401
- handlers["mandu_list_islands"] = handlers["mandu.island.list"];
402
- handlers["mandu_set_hydration"] = handlers["mandu.hydration.set"];
403
- handlers["mandu_add_client_slot"] = handlers["mandu.hydration.addClientSlot"];
404
-
405
- return handlers;
406
- }
423
+ handlers["mandu_build"] = handlers["mandu.build"];
424
+ handlers["mandu_build_status"] = handlers["mandu.build.status"];
425
+ handlers["mandu_list_islands"] = handlers["mandu.island.list"];
426
+ handlers["mandu_set_hydration"] = handlers["mandu.hydration.set"];
427
+ handlers["mandu_hydration_set"] = handlers["mandu.hydration.set"];
428
+ handlers["mandu_add_client_slot"] = handlers["mandu.hydration.addClientSlot"];
429
+ handlers["mandu_hydration_add_client_slot"] = handlers["mandu.hydration.addClientSlot"];
430
+
431
+ return handlers;
432
+ }
433
+
434
+ function validateRouteIdArgs(
435
+ args: Record<string, unknown>,
436
+ toolName: string,
437
+ allowedKeys: readonly string[],
438
+ ): string | null {
439
+ if (typeof args.routeId !== "string" || args.routeId.trim().length === 0) {
440
+ const allowed = new Set(allowedKeys);
441
+ const unknownKeys = Object.keys(args).filter((key) => !allowed.has(key));
442
+ const got = unknownKeys.length > 0 ? ` (got unknown key${unknownKeys.length === 1 ? "" : "s"} '${unknownKeys.join("', '")}')` : "";
443
+ return `${toolName}: missing required parameter 'routeId'${got}`;
444
+ }
445
+ return null;
446
+ }
407
447
 
408
448
  /**
409
449
  * Generate a client slot template
@@ -11,8 +11,9 @@ import { mcpToolRegistry } from "../registry/mcp-tool-registry.js";
11
11
  import { moduleToPlugins } from "../adapters/tool-adapter.js";
12
12
  import { type McpProfile, getProfileCategories } from "../profiles.js";
13
13
 
14
- // 도구 모듈 export
15
- export { specTools, specToolDefinitions } from "./spec.js";
14
+ // 도구 모듈 export
15
+ export { agentTools, agentToolDefinitions } from "./agent.js";
16
+ export { specTools, specToolDefinitions } from "./spec.js";
16
17
  export { generateTools, generateToolDefinitions } from "./generate.js";
17
18
  export { transactionTools, transactionToolDefinitions } from "./transaction.js";
18
19
  export { historyTools, historyToolDefinitions } from "./history.js";
@@ -85,8 +86,9 @@ export {
85
86
  extractContractToolDefinitions,
86
87
  } from "./extract-contract.js";
87
88
 
88
- // 도구 모듈 import (등록용)
89
- import { specTools, specToolDefinitions } from "./spec.js";
89
+ // 도구 모듈 import (등록용)
90
+ import { agentTools, agentToolDefinitions } from "./agent.js";
91
+ import { specTools, specToolDefinitions } from "./spec.js";
90
92
  import { generateTools, generateToolDefinitions } from "./generate.js";
91
93
  import { transactionTools, transactionToolDefinitions } from "./transaction.js";
92
94
  import { historyTools, historyToolDefinitions } from "./history.js";
@@ -189,7 +191,8 @@ export interface ToolModule {
189
191
  * 빌트인 도구 모듈 목록
190
192
  */
191
193
  export const TOOL_MODULES: ToolModule[] = [
192
- { category: "spec", definitions: specToolDefinitions, handlers: specTools },
194
+ { category: "agent", definitions: agentToolDefinitions, handlers: agentTools },
195
+ { category: "spec", definitions: specToolDefinitions, handlers: specTools },
193
196
  { category: "generate", definitions: generateToolDefinitions, handlers: generateTools },
194
197
  { category: "transaction", definitions: transactionToolDefinitions, handlers: transactionTools },
195
198
  { category: "history", definitions: historyToolDefinitions, handlers: historyTools },