@mandujs/mcp 0.18.9 โ 0.18.10
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/README.md +0 -1
- package/package.json +42 -42
- package/src/activity-monitor.ts +9 -8
- package/src/adapters/tool-adapter.ts +2 -0
- package/src/executor/error-handler.ts +268 -250
- package/src/index.ts +8 -0
- package/src/new-resources.ts +119 -0
- package/src/profiles.ts +34 -0
- package/src/prompts.ts +104 -0
- package/src/resources/handlers.ts +0 -23
- package/src/server.ts +78 -5
- package/src/tools/ate.ts +28 -0
- package/src/tools/brain.ts +56 -24
- package/src/tools/component.ts +194 -185
- package/src/tools/composite.ts +440 -0
- package/src/tools/contract.ts +58 -58
- package/src/tools/decisions.ts +270 -0
- package/src/tools/generate.ts +23 -21
- package/src/tools/guard.ts +32 -708
- package/src/tools/history.ts +24 -7
- package/src/tools/hydration.ts +40 -13
- package/src/tools/index.ts +28 -2
- package/src/tools/kitchen.ts +107 -0
- package/src/tools/negotiate.ts +263 -0
- package/src/tools/project.ts +464 -382
- package/src/tools/resource.ts +19 -2
- package/src/tools/runtime.ts +533 -508
- package/src/tools/seo.ts +446 -417
- package/src/tools/slot-validation.ts +200 -0
- package/src/tools/slot.ts +20 -25
- package/src/tools/spec.ts +45 -43
- package/src/tools/transaction.ts +55 -13
- package/src/tx-lock.ts +73 -0
- package/src/utils/project.ts +48 -9
- package/src/utils/runtime-control.ts +52 -0
- package/src/utils/withWarnings.ts +2 -1
package/README.md
CHANGED
|
@@ -185,7 +185,6 @@ bunx @mandujs/mcp --root /path/to/project
|
|
|
185
185
|
| URI | Description |
|
|
186
186
|
|-----|-------------|
|
|
187
187
|
| `mandu://spec/manifest` | Current routes.manifest.json |
|
|
188
|
-
| `mandu://spec/lock` | Current spec.lock.json with hash |
|
|
189
188
|
| `mandu://generated/map` | Generated files mapping |
|
|
190
189
|
| `mandu://transaction/active` | Active transaction state |
|
|
191
190
|
| `mandu://slots/{routeId}` | Slot file content by route ID |
|
package/package.json
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@mandujs/mcp",
|
|
3
|
-
"version": "0.18.
|
|
4
|
-
"description": "Mandu MCP Server - Agent-native interface for Mandu framework operations",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./src/index.ts",
|
|
7
|
-
"bin": {
|
|
8
|
-
"mandu-mcp": "./src/index.ts"
|
|
9
|
-
},
|
|
10
|
-
"exports": {
|
|
11
|
-
".": "./src/index.ts"
|
|
12
|
-
},
|
|
13
|
-
"files": [
|
|
14
|
-
"src/**/*"
|
|
15
|
-
],
|
|
16
|
-
"keywords": [
|
|
17
|
-
"mandu",
|
|
18
|
-
"mcp",
|
|
19
|
-
"model-context-protocol",
|
|
20
|
-
"ai",
|
|
21
|
-
"agent",
|
|
22
|
-
"code-generation"
|
|
23
|
-
],
|
|
24
|
-
"repository": {
|
|
25
|
-
"type": "git",
|
|
26
|
-
"url": "https://github.com/konamgil/mandu.git",
|
|
27
|
-
"directory": "packages/mcp"
|
|
28
|
-
},
|
|
29
|
-
"author": "konamgil",
|
|
30
|
-
"license": "MPL-2.0",
|
|
31
|
-
"publishConfig": {
|
|
32
|
-
"access": "public"
|
|
33
|
-
},
|
|
34
|
-
"dependencies": {
|
|
35
|
-
"@mandujs/core": "^0.19.
|
|
36
|
-
"@mandujs/ate": "^0.17.2",
|
|
37
|
-
"@modelcontextprotocol/sdk": "^1.25.3"
|
|
38
|
-
},
|
|
39
|
-
"engines": {
|
|
40
|
-
"bun": ">=1.0.0"
|
|
41
|
-
}
|
|
42
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@mandujs/mcp",
|
|
3
|
+
"version": "0.18.10",
|
|
4
|
+
"description": "Mandu MCP Server - Agent-native interface for Mandu framework operations",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mandu-mcp": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"src/**/*"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mandu",
|
|
18
|
+
"mcp",
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"ai",
|
|
21
|
+
"agent",
|
|
22
|
+
"code-generation"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/konamgil/mandu.git",
|
|
27
|
+
"directory": "packages/mcp"
|
|
28
|
+
},
|
|
29
|
+
"author": "konamgil",
|
|
30
|
+
"license": "MPL-2.0",
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@mandujs/core": "^0.19.1",
|
|
36
|
+
"@mandujs/ate": "^0.17.2",
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.25.3"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"bun": ">=1.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/activity-monitor.ts
CHANGED
|
@@ -18,14 +18,14 @@ const TOOL_ICONS: Record<string, string> = {
|
|
|
18
18
|
mandu_delete_route: "SPEC-",
|
|
19
19
|
mandu_validate_manifest: "SPEC?",
|
|
20
20
|
// Generate
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
"mandu.generate": "GEN",
|
|
22
|
+
"mandu.generate.status": "GEN?",
|
|
23
23
|
// Guard
|
|
24
24
|
mandu_guard_check: "GUARD",
|
|
25
25
|
// Slot
|
|
26
|
-
|
|
26
|
+
"mandu.slot.read": "SLOT",
|
|
27
27
|
mandu_write_slot: "SLOT~",
|
|
28
|
-
|
|
28
|
+
"mandu.slot.validate": "SLOT?",
|
|
29
29
|
// Contract
|
|
30
30
|
mandu_list_contracts: "CONTRACT",
|
|
31
31
|
mandu_get_contract: "CONTRACT",
|
|
@@ -281,7 +281,10 @@ export class ActivityMonitor {
|
|
|
281
281
|
this.projectRoot = projectRoot;
|
|
282
282
|
const userConfig = loadMonitorConfig(projectRoot);
|
|
283
283
|
this.config = mergeConfig(DEFAULT_CONFIG, userConfig);
|
|
284
|
-
|
|
284
|
+
// When openTerminal is enabled, force pretty format for human-readable terminal output
|
|
285
|
+
this.outputFormat = this.config.openTerminal
|
|
286
|
+
? "pretty"
|
|
287
|
+
: resolveOutputFormat(this.config.output);
|
|
285
288
|
}
|
|
286
289
|
|
|
287
290
|
start(): void {
|
|
@@ -327,9 +330,7 @@ export class ActivityMonitor {
|
|
|
327
330
|
);
|
|
328
331
|
}
|
|
329
332
|
|
|
330
|
-
|
|
331
|
-
this.config.openTerminal && this.outputFormat === "pretty";
|
|
332
|
-
if (openTerminal) {
|
|
333
|
+
if (this.config.openTerminal) {
|
|
333
334
|
this.openTerminal();
|
|
334
335
|
}
|
|
335
336
|
}
|
|
@@ -26,6 +26,7 @@ export function toolToPlugin(
|
|
|
26
26
|
name: definition.name,
|
|
27
27
|
description: definition.description ?? "",
|
|
28
28
|
inputSchema: definition.inputSchema as unknown as Record<string, unknown>,
|
|
29
|
+
...(definition.annotations && { annotations: definition.annotations as unknown as Record<string, unknown> }),
|
|
29
30
|
execute: handler as (input: unknown) => Promise<unknown>,
|
|
30
31
|
};
|
|
31
32
|
}
|
|
@@ -38,6 +39,7 @@ export function pluginToTool(plugin: McpToolPlugin): Tool {
|
|
|
38
39
|
name: plugin.name,
|
|
39
40
|
description: plugin.description,
|
|
40
41
|
inputSchema: plugin.inputSchema as Tool["inputSchema"],
|
|
42
|
+
...(plugin.annotations && { annotations: plugin.annotations as Tool["annotations"] }),
|
|
41
43
|
};
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -1,250 +1,268 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP Error Handler
|
|
3
|
-
*
|
|
4
|
-
* DNA-007 ์๋ฌ ์ถ์ถ ์์คํ
๊ธฐ๋ฐ MCP ์๋ฌ ์ฒ๋ฆฌ
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
extractErrorInfo,
|
|
9
|
-
classifyError,
|
|
10
|
-
serializeError,
|
|
11
|
-
isRetryableError,
|
|
12
|
-
type ErrorCategory,
|
|
13
|
-
type ExtractedErrorInfo,
|
|
14
|
-
} from "@mandujs/core";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* MCP ์๋ฌ ์๋ต ํ์
|
|
18
|
-
*/
|
|
19
|
-
export interface McpErrorResponse {
|
|
20
|
-
/** ์๋ฌ ๋ฉ์์ง */
|
|
21
|
-
error: string;
|
|
22
|
-
/** ์๋ฌ ์ฝ๋ (์๋ ๊ฒฝ์ฐ) */
|
|
23
|
-
code?: string;
|
|
24
|
-
/** ์๋ฌ ์นดํ
๊ณ ๋ฆฌ (DNA-007) */
|
|
25
|
-
category: ErrorCategory;
|
|
26
|
-
/** ์ฌ์๋ ๊ฐ๋ฅ ์ฌ๋ถ */
|
|
27
|
-
retryable: boolean;
|
|
28
|
-
/** ์ถ๊ฐ ์ปจํ
์คํธ */
|
|
29
|
-
context?: Record<string, unknown>;
|
|
30
|
-
/** ๋ณต๊ตฌ ์ ์ */
|
|
31
|
-
suggestion?: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* MCP ๋๊ตฌ ์๋ต ํ์
|
|
36
|
-
*/
|
|
37
|
-
export interface McpToolResponse {
|
|
38
|
-
content: Array<{ type: string; text: string }>;
|
|
39
|
-
isError?: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* ์๋ฌ๋ฅผ MCP ์๋ต ํ์์ผ๋ก ๋ณํ
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```ts
|
|
47
|
-
* try {
|
|
48
|
-
* await tool.execute(args);
|
|
49
|
-
* } catch (err) {
|
|
50
|
-
* const response = formatMcpError(err, "mandu_guard_check");
|
|
51
|
-
* // { error: "...", code: "...", category: "validation", ... }
|
|
52
|
-
* }
|
|
53
|
-
* ```
|
|
54
|
-
*/
|
|
55
|
-
export function formatMcpError(err: unknown, toolName?: string): McpErrorResponse {
|
|
56
|
-
const info = extractErrorInfo(err);
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
error: info.message,
|
|
60
|
-
code: info.code,
|
|
61
|
-
category: info.category,
|
|
62
|
-
retryable: isRetryableError(err),
|
|
63
|
-
context: {
|
|
64
|
-
...info.context,
|
|
65
|
-
toolName,
|
|
66
|
-
errorName: info.name,
|
|
67
|
-
},
|
|
68
|
-
suggestion: generateSuggestion(info, toolName),
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* ์๋ฌ ์นดํ
๊ณ ๋ฆฌ ๋ฐ ๋๊ตฌ๋ณ ๋ณต๊ตฌ ์ ์ ์์ฑ
|
|
74
|
-
*/
|
|
75
|
-
function generateSuggestion(info: ExtractedErrorInfo, toolName?: string): string | undefined {
|
|
76
|
-
// ๋๊ตฌ๋ณ ํนํ ์ ์
|
|
77
|
-
if (toolName) {
|
|
78
|
-
const toolSuggestion = getToolSpecificSuggestion(toolName, info);
|
|
79
|
-
if (toolSuggestion) return toolSuggestion;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ์นดํ
๊ณ ๋ฆฌ๋ณ ์ผ๋ฐ ์ ์
|
|
83
|
-
switch (info.category) {
|
|
84
|
-
case "network":
|
|
85
|
-
return "๋คํธ์ํฌ ์ฐ๊ฒฐ์ ํ์ธํ๊ณ ๋ค์ ์๋ํด์ฃผ์ธ์.";
|
|
86
|
-
|
|
87
|
-
case "timeout":
|
|
88
|
-
return "์์ฒญ ์๊ฐ์ด ์ด๊ณผ๋์์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.";
|
|
89
|
-
|
|
90
|
-
case "auth":
|
|
91
|
-
return "์ธ์ฆ ์ ๋ณด๋ฅผ ํ์ธํด์ฃผ์ธ์.";
|
|
92
|
-
|
|
93
|
-
case "validation":
|
|
94
|
-
return "์
๋ ฅ ๊ฐ์ ํ์ธํด์ฃผ์ธ์. ํ์ ํ๋ผ๋ฏธํฐ๊ฐ ๋๋ฝ๋์๊ฑฐ๋ ํ์์ด ์ฌ๋ฐ๋ฅด์ง ์์ ์ ์์ต๋๋ค.";
|
|
95
|
-
|
|
96
|
-
case "config":
|
|
97
|
-
return "์ค์ ํ์ผ(mandu.config.ts)์ ํ์ธํด์ฃผ์ธ์.";
|
|
98
|
-
|
|
99
|
-
case "system":
|
|
100
|
-
if (info.code === "ENOENT") {
|
|
101
|
-
const path = info.context?.path ?? "unknown";
|
|
102
|
-
return `ํ์ผ ๋๋ ๋๋ ํ ๋ฆฌ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค: ${path}`;
|
|
103
|
-
}
|
|
104
|
-
if (info.code === "EACCES" || info.code === "EPERM") {
|
|
105
|
-
return "ํ์ผ ์ ๊ทผ ๊ถํ์ ํ์ธํด์ฃผ์ธ์.";
|
|
106
|
-
}
|
|
107
|
-
return "์์คํ
๋ฆฌ์์ค๋ฅผ ํ์ธํด์ฃผ์ธ์.";
|
|
108
|
-
|
|
109
|
-
case "external":
|
|
110
|
-
return "์ธ๋ถ ์๋น์ค์ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.";
|
|
111
|
-
|
|
112
|
-
case "internal":
|
|
113
|
-
return "๋ด๋ถ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ฌธ์ ๊ฐ ์ง์๋๋ฉด ์ด์๋ฅผ ๋ณด๊ณ ํด์ฃผ์ธ์.";
|
|
114
|
-
|
|
115
|
-
default:
|
|
116
|
-
return undefined;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* ๋๊ตฌ๋ณ ํนํ๋ ์๋ฌ ์ ์
|
|
122
|
-
*/
|
|
123
|
-
function getToolSpecificSuggestion(toolName: string, info: ExtractedErrorInfo): string | undefined {
|
|
124
|
-
// spec ๊ด๋ จ ๋๊ตฌ
|
|
125
|
-
if (toolName.startsWith("mandu_") && toolName.includes("route")) {
|
|
126
|
-
if (info.code === "ENOENT") {
|
|
127
|
-
return "routes.manifest.json ํ์ผ์ด ์์ต๋๋ค. `mandu init`์ ๋จผ์ ์คํํด์ฃผ์ธ์.";
|
|
128
|
-
}
|
|
129
|
-
if (info.message.includes("not found")) {
|
|
130
|
-
return "ํด๋น ๋ผ์ฐํธ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. `mandu_list_routes`๋ก ์กด์ฌํ๋ ๋ผ์ฐํธ๋ฅผ ํ์ธํด์ฃผ์ธ์.";
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// guard ๊ด๋ จ ๋๊ตฌ
|
|
135
|
-
if (toolName === "mandu_guard_check") {
|
|
136
|
-
if (info.category === "config") {
|
|
137
|
-
return "Guard ์ค์ ์ ํ์ธํด์ฃผ์ธ์. mandu.config.ts์ guard ์น์
์ ๊ฒํ ํด์ฃผ์ธ์.";
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// contract ๊ด๋ จ ๋๊ตฌ
|
|
142
|
-
if (toolName.includes("contract")) {
|
|
143
|
-
if (info.category === "validation") {
|
|
144
|
-
return "Contract ์คํค๋ง๊ฐ ์ฌ๋ฐ๋ฅธ์ง ํ์ธํด์ฃผ์ธ์. Zod ์คํค๋ง ๋ฌธ๋ฒ์ ํ์ธํด์ฃผ์ธ์.";
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// generate ๊ด๋ จ ๋๊ตฌ
|
|
149
|
-
if (toolName === "mandu_generate") {
|
|
150
|
-
if (info.code === "EEXIST") {
|
|
151
|
-
return "ํ์ผ์ด ์ด๋ฏธ ์กด์ฌํฉ๋๋ค. ๋ฎ์ด์ฐ๋ ค๋ฉด force ์ต์
์ ์ฌ์ฉํด์ฃผ์ธ์.";
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// transaction ๊ด๋ จ ๋๊ตฌ
|
|
156
|
-
if (toolName.includes("tx") || toolName.includes("transaction")) {
|
|
157
|
-
if (info.message.includes("no active")) {
|
|
158
|
-
return "ํ์ฑํ๋ ํธ๋์ญ์
์ด ์์ต๋๋ค. `mandu_begin`์ผ๋ก ํธ๋์ญ์
์ ์์ํด์ฃผ์ธ์.";
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return undefined;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* ๋๊ตฌ ์คํ ๊ฒฐ๊ณผ๋ฅผ MCP ์๋ต์ผ๋ก ๋ณํ
|
|
167
|
-
*
|
|
168
|
-
* @example
|
|
169
|
-
* ```ts
|
|
170
|
-
* // ์ฑ๊ณต ์๋ต
|
|
171
|
-
* const response = createToolResponse("mandu_list_routes", { routes: [...] });
|
|
172
|
-
*
|
|
173
|
-
* // ์๋ฌ ์๋ต
|
|
174
|
-
* const response = createToolResponse("mandu_list_routes", null, new Error("..."));
|
|
175
|
-
* ```
|
|
176
|
-
*/
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
):
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
1
|
+
/**
|
|
2
|
+
* MCP Error Handler
|
|
3
|
+
*
|
|
4
|
+
* DNA-007 ์๋ฌ ์ถ์ถ ์์คํ
๊ธฐ๋ฐ MCP ์๋ฌ ์ฒ๋ฆฌ
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
extractErrorInfo,
|
|
9
|
+
classifyError,
|
|
10
|
+
serializeError,
|
|
11
|
+
isRetryableError,
|
|
12
|
+
type ErrorCategory,
|
|
13
|
+
type ExtractedErrorInfo,
|
|
14
|
+
} from "@mandujs/core";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* MCP ์๋ฌ ์๋ต ํ์
|
|
18
|
+
*/
|
|
19
|
+
export interface McpErrorResponse {
|
|
20
|
+
/** ์๋ฌ ๋ฉ์์ง */
|
|
21
|
+
error: string;
|
|
22
|
+
/** ์๋ฌ ์ฝ๋ (์๋ ๊ฒฝ์ฐ) */
|
|
23
|
+
code?: string;
|
|
24
|
+
/** ์๋ฌ ์นดํ
๊ณ ๋ฆฌ (DNA-007) */
|
|
25
|
+
category: ErrorCategory;
|
|
26
|
+
/** ์ฌ์๋ ๊ฐ๋ฅ ์ฌ๋ถ */
|
|
27
|
+
retryable: boolean;
|
|
28
|
+
/** ์ถ๊ฐ ์ปจํ
์คํธ */
|
|
29
|
+
context?: Record<string, unknown>;
|
|
30
|
+
/** ๋ณต๊ตฌ ์ ์ */
|
|
31
|
+
suggestion?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* MCP ๋๊ตฌ ์๋ต ํ์
|
|
36
|
+
*/
|
|
37
|
+
export interface McpToolResponse {
|
|
38
|
+
content: Array<{ type: string; text: string }>;
|
|
39
|
+
isError?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* ์๋ฌ๋ฅผ MCP ์๋ต ํ์์ผ๋ก ๋ณํ
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* try {
|
|
48
|
+
* await tool.execute(args);
|
|
49
|
+
* } catch (err) {
|
|
50
|
+
* const response = formatMcpError(err, "mandu_guard_check");
|
|
51
|
+
* // { error: "...", code: "...", category: "validation", ... }
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function formatMcpError(err: unknown, toolName?: string): McpErrorResponse {
|
|
56
|
+
const info = extractErrorInfo(err);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
error: info.message,
|
|
60
|
+
code: info.code,
|
|
61
|
+
category: info.category,
|
|
62
|
+
retryable: isRetryableError(err),
|
|
63
|
+
context: {
|
|
64
|
+
...info.context,
|
|
65
|
+
toolName,
|
|
66
|
+
errorName: info.name,
|
|
67
|
+
},
|
|
68
|
+
suggestion: generateSuggestion(info, toolName),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* ์๋ฌ ์นดํ
๊ณ ๋ฆฌ ๋ฐ ๋๊ตฌ๋ณ ๋ณต๊ตฌ ์ ์ ์์ฑ
|
|
74
|
+
*/
|
|
75
|
+
function generateSuggestion(info: ExtractedErrorInfo, toolName?: string): string | undefined {
|
|
76
|
+
// ๋๊ตฌ๋ณ ํนํ ์ ์
|
|
77
|
+
if (toolName) {
|
|
78
|
+
const toolSuggestion = getToolSpecificSuggestion(toolName, info);
|
|
79
|
+
if (toolSuggestion) return toolSuggestion;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ์นดํ
๊ณ ๋ฆฌ๋ณ ์ผ๋ฐ ์ ์
|
|
83
|
+
switch (info.category) {
|
|
84
|
+
case "network":
|
|
85
|
+
return "๋คํธ์ํฌ ์ฐ๊ฒฐ์ ํ์ธํ๊ณ ๋ค์ ์๋ํด์ฃผ์ธ์.";
|
|
86
|
+
|
|
87
|
+
case "timeout":
|
|
88
|
+
return "์์ฒญ ์๊ฐ์ด ์ด๊ณผ๋์์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.";
|
|
89
|
+
|
|
90
|
+
case "auth":
|
|
91
|
+
return "์ธ์ฆ ์ ๋ณด๋ฅผ ํ์ธํด์ฃผ์ธ์.";
|
|
92
|
+
|
|
93
|
+
case "validation":
|
|
94
|
+
return "์
๋ ฅ ๊ฐ์ ํ์ธํด์ฃผ์ธ์. ํ์ ํ๋ผ๋ฏธํฐ๊ฐ ๋๋ฝ๋์๊ฑฐ๋ ํ์์ด ์ฌ๋ฐ๋ฅด์ง ์์ ์ ์์ต๋๋ค.";
|
|
95
|
+
|
|
96
|
+
case "config":
|
|
97
|
+
return "์ค์ ํ์ผ(mandu.config.ts)์ ํ์ธํด์ฃผ์ธ์.";
|
|
98
|
+
|
|
99
|
+
case "system":
|
|
100
|
+
if (info.code === "ENOENT") {
|
|
101
|
+
const path = info.context?.path ?? "unknown";
|
|
102
|
+
return `ํ์ผ ๋๋ ๋๋ ํ ๋ฆฌ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค: ${path}`;
|
|
103
|
+
}
|
|
104
|
+
if (info.code === "EACCES" || info.code === "EPERM") {
|
|
105
|
+
return "ํ์ผ ์ ๊ทผ ๊ถํ์ ํ์ธํด์ฃผ์ธ์.";
|
|
106
|
+
}
|
|
107
|
+
return "์์คํ
๋ฆฌ์์ค๋ฅผ ํ์ธํด์ฃผ์ธ์.";
|
|
108
|
+
|
|
109
|
+
case "external":
|
|
110
|
+
return "์ธ๋ถ ์๋น์ค์ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.";
|
|
111
|
+
|
|
112
|
+
case "internal":
|
|
113
|
+
return "๋ด๋ถ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ฌธ์ ๊ฐ ์ง์๋๋ฉด ์ด์๋ฅผ ๋ณด๊ณ ํด์ฃผ์ธ์.";
|
|
114
|
+
|
|
115
|
+
default:
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* ๋๊ตฌ๋ณ ํนํ๋ ์๋ฌ ์ ์
|
|
122
|
+
*/
|
|
123
|
+
function getToolSpecificSuggestion(toolName: string, info: ExtractedErrorInfo): string | undefined {
|
|
124
|
+
// spec ๊ด๋ จ ๋๊ตฌ
|
|
125
|
+
if (toolName.startsWith("mandu_") && toolName.includes("route")) {
|
|
126
|
+
if (info.code === "ENOENT") {
|
|
127
|
+
return "routes.manifest.json ํ์ผ์ด ์์ต๋๋ค. `mandu init`์ ๋จผ์ ์คํํด์ฃผ์ธ์.";
|
|
128
|
+
}
|
|
129
|
+
if (info.message.includes("not found")) {
|
|
130
|
+
return "ํด๋น ๋ผ์ฐํธ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. `mandu_list_routes`๋ก ์กด์ฌํ๋ ๋ผ์ฐํธ๋ฅผ ํ์ธํด์ฃผ์ธ์.";
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// guard ๊ด๋ จ ๋๊ตฌ
|
|
135
|
+
if (toolName === "mandu_guard_check") {
|
|
136
|
+
if (info.category === "config") {
|
|
137
|
+
return "Guard ์ค์ ์ ํ์ธํด์ฃผ์ธ์. mandu.config.ts์ guard ์น์
์ ๊ฒํ ํด์ฃผ์ธ์.";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// contract ๊ด๋ จ ๋๊ตฌ
|
|
142
|
+
if (toolName.includes("contract")) {
|
|
143
|
+
if (info.category === "validation") {
|
|
144
|
+
return "Contract ์คํค๋ง๊ฐ ์ฌ๋ฐ๋ฅธ์ง ํ์ธํด์ฃผ์ธ์. Zod ์คํค๋ง ๋ฌธ๋ฒ์ ํ์ธํด์ฃผ์ธ์.";
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// generate ๊ด๋ จ ๋๊ตฌ
|
|
149
|
+
if (toolName === "mandu.generate" || toolName === "mandu_generate") {
|
|
150
|
+
if (info.code === "EEXIST") {
|
|
151
|
+
return "ํ์ผ์ด ์ด๋ฏธ ์กด์ฌํฉ๋๋ค. ๋ฎ์ด์ฐ๋ ค๋ฉด force ์ต์
์ ์ฌ์ฉํด์ฃผ์ธ์.";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// transaction ๊ด๋ จ ๋๊ตฌ
|
|
156
|
+
if (toolName.includes("tx") || toolName.includes("transaction")) {
|
|
157
|
+
if (info.message.includes("no active")) {
|
|
158
|
+
return "ํ์ฑํ๋ ํธ๋์ญ์
์ด ์์ต๋๋ค. `mandu_begin`์ผ๋ก ํธ๋์ญ์
์ ์์ํด์ฃผ์ธ์.";
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* ๋๊ตฌ ์คํ ๊ฒฐ๊ณผ๋ฅผ MCP ์๋ต์ผ๋ก ๋ณํ
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```ts
|
|
170
|
+
* // ์ฑ๊ณต ์๋ต
|
|
171
|
+
* const response = createToolResponse("mandu_list_routes", { routes: [...] });
|
|
172
|
+
*
|
|
173
|
+
* // ์๋ฌ ์๋ต
|
|
174
|
+
* const response = createToolResponse("mandu_list_routes", null, new Error("..."));
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
/**
|
|
178
|
+
* Detect "soft error" results: handler returned { error: "..." } or { success: false, error: "..." }
|
|
179
|
+
* without throwing. These should still be flagged as isError for the MCP client.
|
|
180
|
+
*/
|
|
181
|
+
function isSoftErrorResult(result: unknown): boolean {
|
|
182
|
+
if (result == null || typeof result !== "object") return false;
|
|
183
|
+
const obj = result as Record<string, unknown>;
|
|
184
|
+
// Has an explicit "error" field (string or array)
|
|
185
|
+
if ("error" in obj && obj.error != null) return true;
|
|
186
|
+
// Has success: false with no data payload
|
|
187
|
+
if (obj.success === false && "error" in obj) return true;
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function createToolResponse(
|
|
192
|
+
toolName: string,
|
|
193
|
+
result: unknown,
|
|
194
|
+
error?: unknown
|
|
195
|
+
): McpToolResponse {
|
|
196
|
+
if (error) {
|
|
197
|
+
const errorResponse = formatMcpError(error, toolName);
|
|
198
|
+
return {
|
|
199
|
+
content: [
|
|
200
|
+
{
|
|
201
|
+
type: "text",
|
|
202
|
+
text: JSON.stringify(errorResponse, null, 2),
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
isError: true,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Detect soft errors returned by handlers (e.g. { error: "Route not found" })
|
|
210
|
+
const softError = isSoftErrorResult(result);
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
content: [
|
|
214
|
+
{
|
|
215
|
+
type: "text",
|
|
216
|
+
text: JSON.stringify(result, null, 2),
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
...(softError && { isError: true }),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* ์๋ฌ ์๋ต์ธ์ง ํ์ธ
|
|
225
|
+
*/
|
|
226
|
+
export function isErrorResponse(response: McpToolResponse): boolean {
|
|
227
|
+
return response.isError === true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* ์๋ฌ ์๋ต์์ McpErrorResponse ์ถ์ถ
|
|
232
|
+
*/
|
|
233
|
+
export function extractErrorFromResponse(response: McpToolResponse): McpErrorResponse | null {
|
|
234
|
+
if (!response.isError) return null;
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const text = response.content[0]?.text;
|
|
238
|
+
if (!text) return null;
|
|
239
|
+
return JSON.parse(text) as McpErrorResponse;
|
|
240
|
+
} catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* ์๋ฌ ๋ก๊น
ํฌํผ
|
|
247
|
+
*/
|
|
248
|
+
export function logToolError(
|
|
249
|
+
toolName: string,
|
|
250
|
+
error: unknown,
|
|
251
|
+
args?: Record<string, unknown>
|
|
252
|
+
): void {
|
|
253
|
+
const info = extractErrorInfo(error);
|
|
254
|
+
|
|
255
|
+
console.error(`[MCP:${toolName}] ${info.category.toUpperCase()}: ${info.message}`);
|
|
256
|
+
|
|
257
|
+
if (info.code) {
|
|
258
|
+
console.error(` Code: ${info.code}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (args && Object.keys(args).length > 0) {
|
|
262
|
+
console.error(` Args:`, JSON.stringify(args, null, 2));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (info.context && Object.keys(info.context).length > 0) {
|
|
266
|
+
console.error(` Context:`, info.context);
|
|
267
|
+
}
|
|
268
|
+
}
|