@mandujs/mcp 0.10.0 โ 0.12.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 +26 -1
- package/package.json +1 -1
- package/src/adapters/index.ts +20 -0
- package/src/adapters/monitor-adapter.ts +100 -0
- package/src/adapters/tool-adapter.ts +88 -0
- package/src/executor/error-handler.ts +250 -0
- package/src/executor/index.ts +22 -0
- package/src/executor/tool-executor.ts +148 -0
- package/src/hooks/config-watcher.ts +174 -0
- package/src/hooks/index.ts +23 -0
- package/src/hooks/mcp-hooks.ts +227 -0
- package/src/index.ts +103 -17
- package/src/logging/index.ts +15 -0
- package/src/logging/mcp-transport.ts +134 -0
- package/src/registry/index.ts +13 -0
- package/src/registry/mcp-tool-registry.ts +298 -0
- package/src/server.ts +172 -101
- package/src/tools/index.ts +133 -0
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ bunx @mandujs/mcp --root /path/to/project
|
|
|
64
64
|
|
|
65
65
|
---
|
|
66
66
|
|
|
67
|
-
## Tools (
|
|
67
|
+
## Tools (35+)
|
|
68
68
|
|
|
69
69
|
### Spec Management
|
|
70
70
|
|
|
@@ -105,10 +105,35 @@ bunx @mandujs/mcp --root /path/to/project
|
|
|
105
105
|
| Tool | Description |
|
|
106
106
|
|------|-------------|
|
|
107
107
|
| `mandu_guard_check` | Run all guard checks |
|
|
108
|
+
| `mandu_guard_heal` | Self-Healing Guard - detect + auto-fix suggestions |
|
|
109
|
+
| `mandu_explain_rule` | Explain architecture rule with examples |
|
|
108
110
|
| `mandu_check_location` | Validate file location before creating |
|
|
109
111
|
| `mandu_check_import` | Validate imports against architecture rules |
|
|
110
112
|
| `mandu_get_architecture` | Get project architecture rules |
|
|
111
113
|
|
|
114
|
+
### Decision Memory (RFC-001) ๐
|
|
115
|
+
|
|
116
|
+
| Tool | Description |
|
|
117
|
+
|------|-------------|
|
|
118
|
+
| `mandu_search_decisions` | Search ADRs by tags or status |
|
|
119
|
+
| `mandu_save_decision` | Save new architecture decision |
|
|
120
|
+
| `mandu_check_consistency` | Check decision-implementation consistency |
|
|
121
|
+
|
|
122
|
+
### Semantic Slots (RFC-001) ๐
|
|
123
|
+
|
|
124
|
+
| Tool | Description |
|
|
125
|
+
|------|-------------|
|
|
126
|
+
| `mandu_validate_slot` | Validate slot against constraints |
|
|
127
|
+
| `mandu_validate_slots` | Batch validate multiple slots |
|
|
128
|
+
|
|
129
|
+
### Architecture Negotiation (RFC-001) ๐
|
|
130
|
+
|
|
131
|
+
| Tool | Description |
|
|
132
|
+
|------|-------------|
|
|
133
|
+
| `mandu_negotiate` | AI-Framework negotiation dialog |
|
|
134
|
+
| `mandu_generate_scaffold` | Generate structure scaffold |
|
|
135
|
+
| `mandu_analyze_structure` | Analyze existing project structure |
|
|
136
|
+
|
|
112
137
|
### Brain & Monitoring
|
|
113
138
|
|
|
114
139
|
| Tool | Description |
|
package/package.json
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Adapters
|
|
3
|
+
*
|
|
4
|
+
* DNA ๊ธฐ๋ฅ๊ณผ MCP ๊ฐ์ ๋ณํ ์ด๋ํฐ
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
toolToPlugin,
|
|
9
|
+
pluginToTool,
|
|
10
|
+
moduleToPlugins,
|
|
11
|
+
pluginsToTools,
|
|
12
|
+
pluginsToHandlers,
|
|
13
|
+
} from "./tool-adapter.js";
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
monitorEventToRecord,
|
|
17
|
+
recordToMonitorEvent,
|
|
18
|
+
severityToLevel,
|
|
19
|
+
levelToSeverity,
|
|
20
|
+
} from "./monitor-adapter.js";
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monitor Event Adapter
|
|
3
|
+
*
|
|
4
|
+
* ActivityMonitor ์ด๋ฒคํธ์ DNA-008 LogTransportRecord ๊ฐ ๋ณํ
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { LogTransportRecord } from "@mandujs/core";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Monitor ์ด๋ฒคํธ ํ์
(ActivityMonitor์์ ์ฌ์ฉ)
|
|
11
|
+
*/
|
|
12
|
+
export interface MonitorEvent {
|
|
13
|
+
ts: string;
|
|
14
|
+
type: string;
|
|
15
|
+
severity: MonitorSeverity;
|
|
16
|
+
source: string;
|
|
17
|
+
message?: string;
|
|
18
|
+
data?: Record<string, unknown>;
|
|
19
|
+
actionRequired?: boolean;
|
|
20
|
+
fingerprint?: string;
|
|
21
|
+
count?: number;
|
|
22
|
+
schemaVersion?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type MonitorSeverity = "info" | "warn" | "error";
|
|
26
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* MonitorSeverity โ LogLevel ๋ณํ
|
|
30
|
+
*/
|
|
31
|
+
export function severityToLevel(severity: MonitorSeverity): LogLevel {
|
|
32
|
+
switch (severity) {
|
|
33
|
+
case "error":
|
|
34
|
+
return "error";
|
|
35
|
+
case "warn":
|
|
36
|
+
return "warn";
|
|
37
|
+
case "info":
|
|
38
|
+
default:
|
|
39
|
+
return "info";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* LogLevel โ MonitorSeverity ๋ณํ
|
|
45
|
+
*/
|
|
46
|
+
export function levelToSeverity(level: LogLevel): MonitorSeverity {
|
|
47
|
+
switch (level) {
|
|
48
|
+
case "error":
|
|
49
|
+
return "error";
|
|
50
|
+
case "warn":
|
|
51
|
+
return "warn";
|
|
52
|
+
case "debug":
|
|
53
|
+
case "info":
|
|
54
|
+
default:
|
|
55
|
+
return "info";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* MonitorEvent โ LogTransportRecord ๋ณํ
|
|
61
|
+
*
|
|
62
|
+
* ActivityMonitor ์ด๋ฒคํธ๋ฅผ DNA-008 ๋ก๊น
์์คํ
์ผ๋ก ์ ์กํ ๋ ์ฌ์ฉ
|
|
63
|
+
*/
|
|
64
|
+
export function monitorEventToRecord(event: MonitorEvent): LogTransportRecord {
|
|
65
|
+
return {
|
|
66
|
+
timestamp: event.ts,
|
|
67
|
+
level: severityToLevel(event.severity),
|
|
68
|
+
meta: {
|
|
69
|
+
type: event.type,
|
|
70
|
+
source: event.source,
|
|
71
|
+
fingerprint: event.fingerprint,
|
|
72
|
+
count: event.count,
|
|
73
|
+
actionRequired: event.actionRequired,
|
|
74
|
+
schemaVersion: event.schemaVersion,
|
|
75
|
+
...event.data,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* LogTransportRecord โ MonitorEvent ๋ณํ (์ญ๋ฐฉํฅ)
|
|
82
|
+
*
|
|
83
|
+
* DNA-008 ๋ก๊ทธ๋ฅผ ActivityMonitor์์ ํ์ํ ๋ ์ฌ์ฉ
|
|
84
|
+
*/
|
|
85
|
+
export function recordToMonitorEvent(record: LogTransportRecord): MonitorEvent {
|
|
86
|
+
const meta = record.meta ?? {};
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
ts: record.timestamp,
|
|
90
|
+
type: (meta.type as string) ?? "log",
|
|
91
|
+
severity: levelToSeverity(record.level),
|
|
92
|
+
source: (meta.source as string) ?? "unknown",
|
|
93
|
+
message: record.error?.message,
|
|
94
|
+
data: meta,
|
|
95
|
+
actionRequired: (meta.actionRequired as boolean) ?? false,
|
|
96
|
+
fingerprint: meta.fingerprint as string | undefined,
|
|
97
|
+
count: meta.count as number | undefined,
|
|
98
|
+
schemaVersion: meta.schemaVersion as string | undefined,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool Adapter
|
|
3
|
+
*
|
|
4
|
+
* MCP SDK Tool์ DNA-001 McpToolPlugin์ผ๋ก ๋ณํ
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
import type { McpToolPlugin } from "@mandujs/core";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* MCP SDK Tool์ McpToolPlugin์ผ๋ก ๋ณํ
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const plugin = toolToPlugin(
|
|
16
|
+
* { name: "my_tool", description: "...", inputSchema: {} },
|
|
17
|
+
* async (args) => ({ success: true })
|
|
18
|
+
* );
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function toolToPlugin(
|
|
22
|
+
definition: Tool,
|
|
23
|
+
handler: (args: Record<string, unknown>) => Promise<unknown>
|
|
24
|
+
): McpToolPlugin {
|
|
25
|
+
return {
|
|
26
|
+
name: definition.name,
|
|
27
|
+
description: definition.description ?? "",
|
|
28
|
+
inputSchema: definition.inputSchema as Record<string, unknown>,
|
|
29
|
+
execute: handler,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* McpToolPlugin์ MCP SDK Tool๋ก ๋ณํ (์ญ๋ฐฉํฅ)
|
|
35
|
+
*/
|
|
36
|
+
export function pluginToTool(plugin: McpToolPlugin): Tool {
|
|
37
|
+
return {
|
|
38
|
+
name: plugin.name,
|
|
39
|
+
description: plugin.description,
|
|
40
|
+
inputSchema: plugin.inputSchema,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* ๊ธฐ์กด ๋๊ตฌ ๋ชจ๋(definitions + handlers)์ ํ๋ฌ๊ทธ์ธ ๋ฐฐ์ด๋ก ๋ณํ
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const plugins = moduleToPlugins(
|
|
50
|
+
* specToolDefinitions,
|
|
51
|
+
* specTools(projectRoot)
|
|
52
|
+
* );
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function moduleToPlugins(
|
|
56
|
+
definitions: Tool[],
|
|
57
|
+
handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>>
|
|
58
|
+
): McpToolPlugin[] {
|
|
59
|
+
return definitions.map((def) => {
|
|
60
|
+
const handler = handlers[def.name];
|
|
61
|
+
if (!handler) {
|
|
62
|
+
throw new Error(`Handler not found for tool: ${def.name}`);
|
|
63
|
+
}
|
|
64
|
+
return toolToPlugin(def, handler);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* ํ๋ฌ๊ทธ์ธ ๋ฐฐ์ด์ MCP SDK Tool ๋ฐฐ์ด๋ก ๋ณํ
|
|
70
|
+
*/
|
|
71
|
+
export function pluginsToTools(plugins: McpToolPlugin[]): Tool[] {
|
|
72
|
+
return plugins.map(pluginToTool);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* ํ๋ฌ๊ทธ์ธ ๋ฐฐ์ด์ ํธ๋ค๋ฌ ๋งต์ผ๋ก ๋ณํ
|
|
77
|
+
*/
|
|
78
|
+
export function pluginsToHandlers(
|
|
79
|
+
plugins: McpToolPlugin[]
|
|
80
|
+
): Record<string, (args: Record<string, unknown>) => Promise<unknown>> {
|
|
81
|
+
const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {};
|
|
82
|
+
|
|
83
|
+
for (const plugin of plugins) {
|
|
84
|
+
handlers[plugin.name] = async (args) => plugin.execute(args);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return handlers;
|
|
88
|
+
}
|
|
@@ -0,0 +1,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") {
|
|
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
|
+
export function createToolResponse(
|
|
178
|
+
toolName: string,
|
|
179
|
+
result: unknown,
|
|
180
|
+
error?: unknown
|
|
181
|
+
): McpToolResponse {
|
|
182
|
+
if (error) {
|
|
183
|
+
const errorResponse = formatMcpError(error, toolName);
|
|
184
|
+
return {
|
|
185
|
+
content: [
|
|
186
|
+
{
|
|
187
|
+
type: "text",
|
|
188
|
+
text: JSON.stringify(errorResponse, null, 2),
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
isError: true,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
content: [
|
|
197
|
+
{
|
|
198
|
+
type: "text",
|
|
199
|
+
text: JSON.stringify(result, null, 2),
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* ์๋ฌ ์๋ต์ธ์ง ํ์ธ
|
|
207
|
+
*/
|
|
208
|
+
export function isErrorResponse(response: McpToolResponse): boolean {
|
|
209
|
+
return response.isError === true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* ์๋ฌ ์๋ต์์ McpErrorResponse ์ถ์ถ
|
|
214
|
+
*/
|
|
215
|
+
export function extractErrorFromResponse(response: McpToolResponse): McpErrorResponse | null {
|
|
216
|
+
if (!response.isError) return null;
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
const text = response.content[0]?.text;
|
|
220
|
+
if (!text) return null;
|
|
221
|
+
return JSON.parse(text) as McpErrorResponse;
|
|
222
|
+
} catch {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* ์๋ฌ ๋ก๊น
ํฌํผ
|
|
229
|
+
*/
|
|
230
|
+
export function logToolError(
|
|
231
|
+
toolName: string,
|
|
232
|
+
error: unknown,
|
|
233
|
+
args?: Record<string, unknown>
|
|
234
|
+
): void {
|
|
235
|
+
const info = extractErrorInfo(error);
|
|
236
|
+
|
|
237
|
+
console.error(`[MCP:${toolName}] ${info.category.toUpperCase()}: ${info.message}`);
|
|
238
|
+
|
|
239
|
+
if (info.code) {
|
|
240
|
+
console.error(` Code: ${info.code}`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (args && Object.keys(args).length > 0) {
|
|
244
|
+
console.error(` Args:`, JSON.stringify(args, null, 2));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (info.context && Object.keys(info.context).length > 0) {
|
|
248
|
+
console.error(` Context:`, info.context);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Executor
|
|
3
|
+
*
|
|
4
|
+
* ๋๊ตฌ ์คํ ๋ฐ ์๋ฌ ์ฒ๋ฆฌ
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
formatMcpError,
|
|
9
|
+
createToolResponse,
|
|
10
|
+
isErrorResponse,
|
|
11
|
+
extractErrorFromResponse,
|
|
12
|
+
logToolError,
|
|
13
|
+
type McpErrorResponse,
|
|
14
|
+
type McpToolResponse,
|
|
15
|
+
} from "./error-handler.js";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
ToolExecutor,
|
|
19
|
+
createToolExecutor,
|
|
20
|
+
type ToolExecutorOptions,
|
|
21
|
+
type ExecutionResult,
|
|
22
|
+
} from "./tool-executor.js";
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool Executor
|
|
3
|
+
*
|
|
4
|
+
* ๋๊ตฌ ์คํ + ํ
+ ์๋ฌ ์ฒ๋ฆฌ ํตํฉ
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { McpToolPlugin, ManduConfig } from "@mandujs/core";
|
|
8
|
+
import { mcpToolRegistry } from "../registry/mcp-tool-registry.js";
|
|
9
|
+
import { mcpHookRegistry, type McpToolContext } from "../hooks/mcp-hooks.js";
|
|
10
|
+
import { createToolResponse, logToolError, type McpToolResponse } from "./error-handler.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Tool Executor ์ต์
|
|
14
|
+
*/
|
|
15
|
+
export interface ToolExecutorOptions {
|
|
16
|
+
/** ํ๋ก์ ํธ ๋ฃจํธ */
|
|
17
|
+
projectRoot: string;
|
|
18
|
+
/** Mandu ์ค์ */
|
|
19
|
+
config?: ManduConfig;
|
|
20
|
+
/** ํ๋ ๋ชจ๋ํฐ ๋ก๊น
ํจ์ */
|
|
21
|
+
logTool?: (name: string, args?: Record<string, unknown>, result?: unknown, error?: string) => void;
|
|
22
|
+
/** ๊ฒฐ๊ณผ ๋ก๊น
ํจ์ */
|
|
23
|
+
logResult?: (name: string, result: unknown) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* ๋๊ตฌ ์คํ ๊ฒฐ๊ณผ
|
|
28
|
+
*/
|
|
29
|
+
export interface ExecutionResult {
|
|
30
|
+
success: boolean;
|
|
31
|
+
response: McpToolResponse;
|
|
32
|
+
duration: number;
|
|
33
|
+
toolName: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* ๋๊ตฌ ์คํ๊ธฐ
|
|
38
|
+
*
|
|
39
|
+
* DNA ๊ธฐ๋ฅ๋ค(ํ๋ฌ๊ทธ์ธ, ํ
, ์๋ฌ ์ฒ๋ฆฌ)์ ํตํฉํ ๋๊ตฌ ์คํ
|
|
40
|
+
*/
|
|
41
|
+
export class ToolExecutor {
|
|
42
|
+
private options: ToolExecutorOptions;
|
|
43
|
+
|
|
44
|
+
constructor(options: ToolExecutorOptions) {
|
|
45
|
+
this.options = options;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* ๋๊ตฌ ์คํ
|
|
50
|
+
*
|
|
51
|
+
* @param name - ๋๊ตฌ ์ด๋ฆ
|
|
52
|
+
* @param args - ๋๊ตฌ ์ธ์
|
|
53
|
+
* @returns ์คํ ๊ฒฐ๊ณผ
|
|
54
|
+
*/
|
|
55
|
+
async execute(name: string, args: Record<string, unknown> = {}): Promise<ExecutionResult> {
|
|
56
|
+
const startTime = Date.now();
|
|
57
|
+
|
|
58
|
+
// ๋๊ตฌ ์กฐํ
|
|
59
|
+
const tool = mcpToolRegistry.get(name);
|
|
60
|
+
if (!tool) {
|
|
61
|
+
const response = createToolResponse(name, null, new Error(`Unknown tool: ${name}`));
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
response,
|
|
65
|
+
duration: Date.now() - startTime,
|
|
66
|
+
toolName: name,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ์คํ ์ปจํ
์คํธ ์์ฑ
|
|
71
|
+
const ctx: McpToolContext = {
|
|
72
|
+
toolName: name,
|
|
73
|
+
args,
|
|
74
|
+
projectRoot: this.options.projectRoot,
|
|
75
|
+
config: this.options.config,
|
|
76
|
+
startTime,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
// Pre-Tool ํ
์คํ
|
|
81
|
+
await mcpHookRegistry.runPreHooks(ctx);
|
|
82
|
+
|
|
83
|
+
// ํ๋ ๋ก๊น
(ํธ์ถ)
|
|
84
|
+
this.options.logTool?.(name, args);
|
|
85
|
+
|
|
86
|
+
// ๋๊ตฌ ์คํ
|
|
87
|
+
const result = await tool.execute(args);
|
|
88
|
+
|
|
89
|
+
// ํ๋ ๋ก๊น
(๊ฒฐ๊ณผ)
|
|
90
|
+
this.options.logResult?.(name, result);
|
|
91
|
+
|
|
92
|
+
// Post-Tool ํ
์คํ
|
|
93
|
+
await mcpHookRegistry.runPostHooks(ctx, result);
|
|
94
|
+
|
|
95
|
+
const response = createToolResponse(name, result);
|
|
96
|
+
return {
|
|
97
|
+
success: true,
|
|
98
|
+
response,
|
|
99
|
+
duration: Date.now() - startTime,
|
|
100
|
+
toolName: name,
|
|
101
|
+
};
|
|
102
|
+
} catch (error) {
|
|
103
|
+
// ์๋ฌ ๋ก๊น
|
|
104
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
105
|
+
this.options.logTool?.(name, args, null, errorMsg);
|
|
106
|
+
logToolError(name, error, args);
|
|
107
|
+
|
|
108
|
+
// Post-Tool ํ
์คํ (์๋ฌ์ ํจ๊ป)
|
|
109
|
+
await mcpHookRegistry.runPostHooks(ctx, null, error);
|
|
110
|
+
|
|
111
|
+
const response = createToolResponse(name, null, error);
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
response,
|
|
115
|
+
duration: Date.now() - startTime,
|
|
116
|
+
toolName: name,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* ์ค์ ์
๋ฐ์ดํธ
|
|
123
|
+
*/
|
|
124
|
+
updateConfig(config: ManduConfig): void {
|
|
125
|
+
this.options.config = config;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* ๋๊ตฌ ์กด์ฌ ์ฌ๋ถ ํ์ธ
|
|
130
|
+
*/
|
|
131
|
+
hasTool(name: string): boolean {
|
|
132
|
+
return mcpToolRegistry.has(name);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* ๋ฑ๋ก๋ ๋๊ตฌ ๋ชฉ๋ก
|
|
137
|
+
*/
|
|
138
|
+
getToolNames(): string[] {
|
|
139
|
+
return mcpToolRegistry.names;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Tool Executor ํฉํ ๋ฆฌ ํจ์
|
|
145
|
+
*/
|
|
146
|
+
export function createToolExecutor(options: ToolExecutorOptions): ToolExecutor {
|
|
147
|
+
return new ToolExecutor(options);
|
|
148
|
+
}
|