@mandujs/mcp 0.3.3 → 0.4.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.
- package/package.json +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
|
+
"version": "0.4.0",
|
|
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.
|
|
35
|
+
"@mandujs/core": "^0.4.0",
|
|
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 {
|
|
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:
|
|
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 {
|
|
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
|
-
//
|
|
127
|
-
|
|
184
|
+
// 1. 초기 검증
|
|
185
|
+
const initialValidation = validateSlotContent(content);
|
|
186
|
+
|
|
187
|
+
// validateOnly 모드면 검증 결과만 반환
|
|
188
|
+
if (validateOnly) {
|
|
128
189
|
return {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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,
|
|
265
|
+
await Bun.write(slotPath, finalContent);
|
|
151
266
|
|
|
152
|
-
|
|
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:
|
|
157
|
-
previousLineCount: previousContent
|
|
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
|
}
|