@mandujs/mcp 0.18.9 → 0.19.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/README.md +0 -1
- package/package.json +2 -2
- package/src/activity-adapter.ts +23 -0
- 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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mandujs/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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.19.
|
|
35
|
+
"@mandujs/core": "^0.19.1",
|
|
36
36
|
"@mandujs/ate": "^0.17.2",
|
|
37
37
|
"@modelcontextprotocol/sdk": "^1.25.3"
|
|
38
38
|
},
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ActivityMonitor -> EventBus adapter
|
|
3
|
+
* Emits MCP tool execution events into the unified observability bus.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { eventBus } from "@mandujs/core";
|
|
7
|
+
|
|
8
|
+
export function emitMcpEvent(
|
|
9
|
+
toolName: string,
|
|
10
|
+
args: unknown,
|
|
11
|
+
result: unknown,
|
|
12
|
+
duration: number,
|
|
13
|
+
success: boolean,
|
|
14
|
+
): void {
|
|
15
|
+
eventBus.emit({
|
|
16
|
+
type: "mcp",
|
|
17
|
+
severity: success ? "info" : "error",
|
|
18
|
+
source: "mcp",
|
|
19
|
+
message: `${toolName} ${success ? "ok" : "fail"} ${duration}ms`,
|
|
20
|
+
duration,
|
|
21
|
+
data: { tool: toolName, args, success },
|
|
22
|
+
});
|
|
23
|
+
}
|
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
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -83,6 +83,14 @@ export {
|
|
|
83
83
|
getToolsSummary,
|
|
84
84
|
} from "./tools/index.js";
|
|
85
85
|
|
|
86
|
+
// Profile exports
|
|
87
|
+
export {
|
|
88
|
+
type McpProfile,
|
|
89
|
+
PROFILE_CATEGORIES,
|
|
90
|
+
getProfileCategories,
|
|
91
|
+
isValidProfile,
|
|
92
|
+
} from "./profiles.js";
|
|
93
|
+
|
|
86
94
|
// CLI entry point
|
|
87
95
|
import { startServer } from "./server.js";
|
|
88
96
|
import path from "path";
|