@mandujs/mcp 0.3.3 → 0.3.4

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 (2) hide show
  1. package/package.json +2 -2
  2. package/src/tools/slot.ts +198 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/mcp",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "Mandu MCP Server - Agent-native interface for Mandu framework operations",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -32,7 +32,7 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@mandujs/core": "^0.3.3",
35
+ "@mandujs/core": "^0.3.4",
36
36
  "@modelcontextprotocol/sdk": "^1.25.3"
37
37
  },
38
38
  "engines": {
package/src/tools/slot.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
- import { loadManifest } from "@mandujs/core";
2
+ import {
3
+ loadManifest,
4
+ validateSlotContent,
5
+ correctSlotContent,
6
+ runSlotCorrection,
7
+ summarizeValidationIssues,
8
+ } from "@mandujs/core";
3
9
  import { getProjectPaths, isInsideProject } from "../utils/project.js";
4
10
  import path from "path";
5
11
  import fs from "fs/promises";
@@ -21,7 +27,8 @@ export const slotToolDefinitions: Tool[] = [
21
27
  },
22
28
  {
23
29
  name: "mandu_write_slot",
24
- description: "Write or update the contents of a slot file",
30
+ description:
31
+ "Write or update the contents of a slot file with optional auto-correction",
25
32
  inputSchema: {
26
33
  type: "object",
27
34
  properties: {
@@ -33,10 +40,40 @@ export const slotToolDefinitions: Tool[] = [
33
40
  type: "string",
34
41
  description: "The TypeScript content to write to the slot file",
35
42
  },
43
+ autoCorrect: {
44
+ type: "boolean",
45
+ description:
46
+ "If true, automatically fix correctable issues (default: false)",
47
+ },
48
+ maxRetries: {
49
+ type: "number",
50
+ description:
51
+ "Maximum correction attempts when autoCorrect is true (default: 3)",
52
+ },
53
+ validateOnly: {
54
+ type: "boolean",
55
+ description:
56
+ "If true, only validate without writing (default: false)",
57
+ },
36
58
  },
37
59
  required: ["routeId", "content"],
38
60
  },
39
61
  },
62
+ {
63
+ name: "mandu_validate_slot",
64
+ description:
65
+ "Validate slot content without writing, get issues and suggestions",
66
+ inputSchema: {
67
+ type: "object",
68
+ properties: {
69
+ content: {
70
+ type: "string",
71
+ description: "The TypeScript content to validate",
72
+ },
73
+ },
74
+ required: ["content"],
75
+ },
76
+ },
40
77
  ];
41
78
 
42
79
  export function slotTools(projectRoot: string) {
@@ -82,11 +119,20 @@ export function slotTools(projectRoot: string) {
82
119
  }
83
120
 
84
121
  const content = await file.text();
122
+
123
+ // 기존 슬롯 내용도 검증
124
+ const validation = validateSlotContent(content);
125
+
85
126
  return {
86
127
  exists: true,
87
128
  slotPath: route.slotModule,
88
129
  content,
89
130
  lineCount: content.split("\n").length,
131
+ validation: {
132
+ valid: validation.valid,
133
+ summary: summarizeValidationIssues(validation.issues),
134
+ issues: validation.issues,
135
+ },
90
136
  };
91
137
  } catch (error) {
92
138
  return {
@@ -96,7 +142,19 @@ export function slotTools(projectRoot: string) {
96
142
  },
97
143
 
98
144
  mandu_write_slot: async (args: Record<string, unknown>) => {
99
- const { routeId, content } = args as { routeId: string; content: string };
145
+ const {
146
+ routeId,
147
+ content,
148
+ autoCorrect = false,
149
+ maxRetries = 3,
150
+ validateOnly = false,
151
+ } = args as {
152
+ routeId: string;
153
+ content: string;
154
+ autoCorrect?: boolean;
155
+ maxRetries?: number;
156
+ validateOnly?: boolean;
157
+ };
100
158
 
101
159
  // Load manifest to find the route
102
160
  const manifestResult = await loadManifest(paths.manifestPath);
@@ -123,15 +181,72 @@ export function slotTools(projectRoot: string) {
123
181
  return { error: "Slot path is outside project directory" };
124
182
  }
125
183
 
126
- // Basic validation - check for Mandu.filling() pattern
127
- if (!content.includes("Mandu") && !content.includes("filling")) {
184
+ // 1. 초기 검증
185
+ const initialValidation = validateSlotContent(content);
186
+
187
+ // validateOnly 모드면 검증 결과만 반환
188
+ if (validateOnly) {
128
189
  return {
129
- warning: "Slot content doesn't appear to use Mandu.filling() pattern",
130
- tip: "Slot files should export a default using Mandu.filling()",
131
- proceeding: true,
190
+ validateOnly: true,
191
+ valid: initialValidation.valid,
192
+ summary: summarizeValidationIssues(initialValidation.issues),
193
+ issues: initialValidation.issues,
194
+ tip: initialValidation.valid
195
+ ? "Content is valid and ready to write"
196
+ : "Fix the issues and try again, or use autoCorrect: true",
132
197
  };
133
198
  }
134
199
 
200
+ // 2. autoCorrect 모드
201
+ let finalContent = content;
202
+ let correctionResult = null;
203
+
204
+ if (autoCorrect && !initialValidation.valid) {
205
+ correctionResult = await runSlotCorrection(
206
+ content,
207
+ validateSlotContent,
208
+ maxRetries
209
+ );
210
+ finalContent = correctionResult.finalContent;
211
+
212
+ // 여전히 에러가 있으면 쓰기 거부
213
+ if (!correctionResult.success) {
214
+ const errors = correctionResult.remainingIssues.filter(
215
+ (i) => i.severity === "error"
216
+ );
217
+ if (errors.length > 0) {
218
+ return {
219
+ success: false,
220
+ autoCorrectAttempted: true,
221
+ attempts: correctionResult.attempts,
222
+ appliedFixes: correctionResult.allFixes,
223
+ remainingErrors: errors,
224
+ summary: `${correctionResult.allFixes.length}개 문제 수정, ${errors.length}개 에러 남음`,
225
+ tip: "수동으로 수정이 필요한 에러가 있습니다",
226
+ suggestedContent: finalContent, // 부분적으로 수정된 내용 제공
227
+ };
228
+ }
229
+ }
230
+ } else if (!autoCorrect && !initialValidation.valid) {
231
+ // autoCorrect 없이 에러가 있으면 경고와 함께 진행 여부 결정
232
+ const errors = initialValidation.issues.filter(
233
+ (i) => i.severity === "error"
234
+ );
235
+ if (errors.length > 0) {
236
+ return {
237
+ success: false,
238
+ valid: false,
239
+ errors,
240
+ summary: summarizeValidationIssues(initialValidation.issues),
241
+ tip: "Use autoCorrect: true to attempt automatic fixes, or fix manually",
242
+ autoFixable: initialValidation.issues
243
+ .filter((i) => i.autoFixable)
244
+ .map((i) => i.code),
245
+ };
246
+ }
247
+ }
248
+
249
+ // 3. 파일 쓰기
135
250
  try {
136
251
  // Ensure directory exists
137
252
  const slotDir = path.dirname(slotPath);
@@ -147,20 +262,91 @@ export function slotTools(projectRoot: string) {
147
262
  }
148
263
 
149
264
  // Write the new content
150
- await Bun.write(slotPath, content);
265
+ await Bun.write(slotPath, finalContent);
151
266
 
152
- return {
267
+ // 최종 검증
268
+ const finalValidation = validateSlotContent(finalContent);
269
+
270
+ const result: Record<string, unknown> = {
153
271
  success: true,
154
272
  slotPath: route.slotModule,
155
273
  action: existed ? "updated" : "created",
156
- lineCount: content.split("\n").length,
157
- previousLineCount: previousContent ? previousContent.split("\n").length : null,
274
+ lineCount: finalContent.split("\n").length,
275
+ previousLineCount: previousContent
276
+ ? previousContent.split("\n").length
277
+ : null,
278
+ validation: {
279
+ valid: finalValidation.valid,
280
+ summary: summarizeValidationIssues(finalValidation.issues),
281
+ warnings: finalValidation.issues.filter(
282
+ (i) => i.severity === "warning"
283
+ ),
284
+ },
158
285
  };
286
+
287
+ // autoCorrect 결과 추가
288
+ if (correctionResult) {
289
+ result.autoCorrection = {
290
+ applied: true,
291
+ attempts: correctionResult.attempts,
292
+ fixes: correctionResult.allFixes,
293
+ };
294
+ }
295
+
296
+ return result;
159
297
  } catch (error) {
160
298
  return {
161
299
  error: `Failed to write slot file: ${error instanceof Error ? error.message : String(error)}`,
162
300
  };
163
301
  }
164
302
  },
303
+
304
+ mandu_validate_slot: async (args: Record<string, unknown>) => {
305
+ const { content } = args as { content: string };
306
+
307
+ const validation = validateSlotContent(content);
308
+
309
+ // 자동 수정 가능한 항목 분류
310
+ const autoFixable = validation.issues.filter((i) => i.autoFixable);
311
+ const manualFix = validation.issues.filter((i) => !i.autoFixable);
312
+
313
+ // 수정 미리보기 제공
314
+ let correctionPreview = null;
315
+ if (autoFixable.length > 0) {
316
+ const correction = correctSlotContent(content, validation.issues);
317
+ correctionPreview = {
318
+ wouldFix: correction.appliedFixes.length,
319
+ fixes: correction.appliedFixes,
320
+ resultingContent:
321
+ correction.corrected
322
+ ? `(${correction.content.split("\n").length} lines after correction)`
323
+ : null,
324
+ };
325
+ }
326
+
327
+ return {
328
+ valid: validation.valid,
329
+ summary: summarizeValidationIssues(validation.issues),
330
+ errors: validation.issues.filter((i) => i.severity === "error"),
331
+ warnings: validation.issues.filter((i) => i.severity === "warning"),
332
+ autoFixable: autoFixable.map((i) => ({
333
+ code: i.code,
334
+ message: i.message,
335
+ line: i.line,
336
+ })),
337
+ manualFixRequired: manualFix.map((i) => ({
338
+ code: i.code,
339
+ message: i.message,
340
+ suggestion: i.suggestion,
341
+ line: i.line,
342
+ })),
343
+ correctionPreview,
344
+ tip: validation.valid
345
+ ? "Content is valid"
346
+ : autoFixable.length > 0
347
+ ? "Use mandu_write_slot with autoCorrect: true to auto-fix"
348
+ : "Manual fixes required before writing",
349
+ };
350
+ },
165
351
  };
166
352
  }