@mandujs/mcp 0.10.0 → 0.12.1
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 +3 -3
- 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
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool Registry
|
|
3
|
+
*
|
|
4
|
+
* DNA-001 플러그인 시스템 기반 MCP 도구 레지스트리
|
|
5
|
+
* - 동적 도구 등록/해제
|
|
6
|
+
* - 카테고리별 관리
|
|
7
|
+
* - MCP SDK 형식 변환
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
11
|
+
import type { McpToolPlugin } from "@mandujs/core";
|
|
12
|
+
import { pluginToTool } from "../adapters/tool-adapter.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 도구 등록 정보
|
|
16
|
+
*/
|
|
17
|
+
export interface ToolRegistration {
|
|
18
|
+
plugin: McpToolPlugin;
|
|
19
|
+
category?: string;
|
|
20
|
+
registeredAt: Date;
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* MCP 도구 레지스트리
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* // 도구 등록
|
|
30
|
+
* mcpToolRegistry.register({
|
|
31
|
+
* name: "custom_tool",
|
|
32
|
+
* description: "My custom tool",
|
|
33
|
+
* inputSchema: { type: "object", properties: {} },
|
|
34
|
+
* execute: async (args) => ({ success: true }),
|
|
35
|
+
* }, "custom");
|
|
36
|
+
*
|
|
37
|
+
* // MCP SDK 형식으로 변환
|
|
38
|
+
* const tools = mcpToolRegistry.toToolDefinitions();
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export class McpToolRegistry {
|
|
42
|
+
private tools = new Map<string, ToolRegistration>();
|
|
43
|
+
private categories = new Map<string, Set<string>>();
|
|
44
|
+
private listeners = new Set<(event: RegistryEvent) => void>();
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 도구 등록
|
|
48
|
+
*
|
|
49
|
+
* @param plugin - McpToolPlugin 인스턴스
|
|
50
|
+
* @param category - 도구 카테고리 (선택)
|
|
51
|
+
* @returns 등록 해제 함수
|
|
52
|
+
*/
|
|
53
|
+
register(plugin: McpToolPlugin, category?: string): () => void {
|
|
54
|
+
const registration: ToolRegistration = {
|
|
55
|
+
plugin,
|
|
56
|
+
category,
|
|
57
|
+
registeredAt: new Date(),
|
|
58
|
+
enabled: true,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
this.tools.set(plugin.name, registration);
|
|
62
|
+
|
|
63
|
+
if (category) {
|
|
64
|
+
if (!this.categories.has(category)) {
|
|
65
|
+
this.categories.set(category, new Set());
|
|
66
|
+
}
|
|
67
|
+
this.categories.get(category)!.add(plugin.name);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.emit({ type: "register", toolName: plugin.name, category });
|
|
71
|
+
|
|
72
|
+
return () => this.unregister(plugin.name);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 여러 도구 일괄 등록
|
|
77
|
+
*/
|
|
78
|
+
registerAll(plugins: McpToolPlugin[], category?: string): void {
|
|
79
|
+
for (const plugin of plugins) {
|
|
80
|
+
this.register(plugin, category);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 도구 해제
|
|
86
|
+
*/
|
|
87
|
+
unregister(name: string): boolean {
|
|
88
|
+
const registration = this.tools.get(name);
|
|
89
|
+
if (!registration) return false;
|
|
90
|
+
|
|
91
|
+
this.tools.delete(name);
|
|
92
|
+
|
|
93
|
+
// 카테고리에서도 제거
|
|
94
|
+
if (registration.category) {
|
|
95
|
+
const categorySet = this.categories.get(registration.category);
|
|
96
|
+
categorySet?.delete(name);
|
|
97
|
+
if (categorySet?.size === 0) {
|
|
98
|
+
this.categories.delete(registration.category);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this.emit({ type: "unregister", toolName: name });
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 카테고리 전체 해제
|
|
108
|
+
*/
|
|
109
|
+
unregisterCategory(category: string): number {
|
|
110
|
+
const names = this.categories.get(category);
|
|
111
|
+
if (!names) return 0;
|
|
112
|
+
|
|
113
|
+
let count = 0;
|
|
114
|
+
for (const name of Array.from(names)) {
|
|
115
|
+
if (this.unregister(name)) count++;
|
|
116
|
+
}
|
|
117
|
+
return count;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 도구 조회
|
|
122
|
+
*/
|
|
123
|
+
get(name: string): McpToolPlugin | undefined {
|
|
124
|
+
return this.tools.get(name)?.plugin;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 도구 존재 여부
|
|
129
|
+
*/
|
|
130
|
+
has(name: string): boolean {
|
|
131
|
+
return this.tools.has(name);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 도구 활성화/비활성화
|
|
136
|
+
*/
|
|
137
|
+
setEnabled(name: string, enabled: boolean): void {
|
|
138
|
+
const registration = this.tools.get(name);
|
|
139
|
+
if (registration) {
|
|
140
|
+
registration.enabled = enabled;
|
|
141
|
+
this.emit({ type: enabled ? "enable" : "disable", toolName: name });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* MCP SDK Tool 형식으로 변환
|
|
147
|
+
*
|
|
148
|
+
* 활성화된 도구만 반환
|
|
149
|
+
*/
|
|
150
|
+
toToolDefinitions(): Tool[] {
|
|
151
|
+
const tools: Tool[] = [];
|
|
152
|
+
|
|
153
|
+
for (const registration of this.tools.values()) {
|
|
154
|
+
if (registration.enabled) {
|
|
155
|
+
tools.push(pluginToTool(registration.plugin));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return tools;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 핸들러 맵 반환
|
|
164
|
+
*
|
|
165
|
+
* 활성화된 도구의 핸들러만 반환
|
|
166
|
+
*/
|
|
167
|
+
toHandlers(): Record<string, (args: Record<string, unknown>) => Promise<unknown>> {
|
|
168
|
+
const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {};
|
|
169
|
+
|
|
170
|
+
for (const [name, registration] of this.tools) {
|
|
171
|
+
if (registration.enabled) {
|
|
172
|
+
handlers[name] = async (args) => registration.plugin.execute(args);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return handlers;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 카테고리별 도구 목록
|
|
181
|
+
*/
|
|
182
|
+
getByCategory(category: string): McpToolPlugin[] {
|
|
183
|
+
const names = this.categories.get(category);
|
|
184
|
+
if (!names) return [];
|
|
185
|
+
|
|
186
|
+
return Array.from(names)
|
|
187
|
+
.map((name) => this.tools.get(name)?.plugin)
|
|
188
|
+
.filter((p): p is McpToolPlugin => p !== undefined);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 모든 카테고리 목록
|
|
193
|
+
*/
|
|
194
|
+
getCategories(): string[] {
|
|
195
|
+
return Array.from(this.categories.keys());
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 등록된 도구 수
|
|
200
|
+
*/
|
|
201
|
+
get size(): number {
|
|
202
|
+
return this.tools.size;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 활성화된 도구 수
|
|
207
|
+
*/
|
|
208
|
+
get enabledCount(): number {
|
|
209
|
+
let count = 0;
|
|
210
|
+
for (const registration of this.tools.values()) {
|
|
211
|
+
if (registration.enabled) count++;
|
|
212
|
+
}
|
|
213
|
+
return count;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 모든 도구 이름
|
|
218
|
+
*/
|
|
219
|
+
get names(): string[] {
|
|
220
|
+
return Array.from(this.tools.keys());
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 모든 도구 초기화
|
|
225
|
+
*/
|
|
226
|
+
clear(): void {
|
|
227
|
+
this.tools.clear();
|
|
228
|
+
this.categories.clear();
|
|
229
|
+
this.emit({ type: "clear" });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 이벤트 리스너 등록
|
|
234
|
+
*/
|
|
235
|
+
on(listener: (event: RegistryEvent) => void): () => void {
|
|
236
|
+
this.listeners.add(listener);
|
|
237
|
+
return () => this.listeners.delete(listener);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* 이벤트 발생
|
|
242
|
+
*/
|
|
243
|
+
private emit(event: RegistryEvent): void {
|
|
244
|
+
for (const listener of this.listeners) {
|
|
245
|
+
try {
|
|
246
|
+
listener(event);
|
|
247
|
+
} catch (err) {
|
|
248
|
+
console.error("[McpToolRegistry] Listener error:", err);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 디버그용 상태 덤프
|
|
255
|
+
*/
|
|
256
|
+
dump(): RegistryDump {
|
|
257
|
+
const tools: Record<string, { category?: string; enabled: boolean; registeredAt: string }> = {};
|
|
258
|
+
|
|
259
|
+
for (const [name, reg] of this.tools) {
|
|
260
|
+
tools[name] = {
|
|
261
|
+
category: reg.category,
|
|
262
|
+
enabled: reg.enabled,
|
|
263
|
+
registeredAt: reg.registeredAt.toISOString(),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
totalTools: this.size,
|
|
269
|
+
enabledTools: this.enabledCount,
|
|
270
|
+
categories: this.getCategories(),
|
|
271
|
+
tools,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* 레지스트리 이벤트
|
|
278
|
+
*/
|
|
279
|
+
export interface RegistryEvent {
|
|
280
|
+
type: "register" | "unregister" | "enable" | "disable" | "clear";
|
|
281
|
+
toolName?: string;
|
|
282
|
+
category?: string;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 레지스트리 상태 덤프
|
|
287
|
+
*/
|
|
288
|
+
export interface RegistryDump {
|
|
289
|
+
totalTools: number;
|
|
290
|
+
enabledTools: number;
|
|
291
|
+
categories: string[];
|
|
292
|
+
tools: Record<string, { category?: string; enabled: boolean; registeredAt: string }>;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* 전역 MCP 도구 레지스트리 인스턴스
|
|
297
|
+
*/
|
|
298
|
+
export const mcpToolRegistry = new McpToolRegistry();
|
package/src/server.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mandu MCP Server v2
|
|
3
|
+
*
|
|
4
|
+
* DNA 기능 통합:
|
|
5
|
+
* - DNA-001: 플러그인 기반 도구 등록
|
|
6
|
+
* - DNA-006: 설정 핫 리로드
|
|
7
|
+
* - DNA-007: 에러 추출 및 분류
|
|
8
|
+
* - DNA-008: 구조화된 로깅
|
|
9
|
+
* - DNA-016: Pre/Post 도구 훅
|
|
10
|
+
*/
|
|
11
|
+
|
|
1
12
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
13
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
14
|
import {
|
|
@@ -5,40 +16,60 @@ import {
|
|
|
5
16
|
ListToolsRequestSchema,
|
|
6
17
|
ListResourcesRequestSchema,
|
|
7
18
|
ReadResourceRequestSchema,
|
|
8
|
-
type Tool,
|
|
9
|
-
type Resource,
|
|
10
19
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
11
20
|
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import {
|
|
21
|
+
import { loadManduConfig, startWatcher, type ManduConfig } from "@mandujs/core";
|
|
22
|
+
|
|
23
|
+
// DNA-001: 플러그인 기반 도구 레지스트리
|
|
24
|
+
import { mcpToolRegistry } from "./registry/mcp-tool-registry.js";
|
|
25
|
+
import { registerBuiltinTools, getToolsSummary } from "./tools/index.js";
|
|
26
|
+
|
|
27
|
+
// DNA-007: 에러 처리
|
|
28
|
+
import { createToolResponse, logToolError } from "./executor/error-handler.js";
|
|
29
|
+
import { ToolExecutor, createToolExecutor } from "./executor/tool-executor.js";
|
|
30
|
+
|
|
31
|
+
// DNA-008: 로깅 통합
|
|
32
|
+
import { setupMcpLogging, teardownMcpLogging } from "./logging/mcp-transport.js";
|
|
33
|
+
|
|
34
|
+
// DNA-016: 훅 시스템
|
|
35
|
+
import { mcpHookRegistry, registerDefaultMcpHooks, type McpToolContext } from "./hooks/mcp-hooks.js";
|
|
36
|
+
|
|
37
|
+
// DNA-006: 설정 핫 리로드
|
|
38
|
+
import { startMcpConfigWatcher, type McpConfigWatcher } from "./hooks/config-watcher.js";
|
|
39
|
+
|
|
40
|
+
// 기존 컴포넌트
|
|
24
41
|
import { resourceHandlers, resourceDefinitions } from "./resources/handlers.js";
|
|
25
42
|
import { findProjectRoot } from "./utils/project.js";
|
|
26
43
|
import { applyWarningInjection } from "./utils/withWarnings.js";
|
|
27
44
|
import { ActivityMonitor } from "./activity-monitor.js";
|
|
28
|
-
import { startWatcher } from "../../core/src/index.js";
|
|
29
45
|
|
|
46
|
+
/**
|
|
47
|
+
* MCP 서버 버전
|
|
48
|
+
*/
|
|
49
|
+
const MCP_VERSION = "0.12.0";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* ManduMcpServer v2
|
|
53
|
+
*
|
|
54
|
+
* DNA 기능들을 통합한 MCP 서버
|
|
55
|
+
*/
|
|
30
56
|
export class ManduMcpServer {
|
|
31
57
|
private server: Server;
|
|
32
58
|
private projectRoot: string;
|
|
33
59
|
private monitor: ActivityMonitor;
|
|
60
|
+
private config?: ManduConfig;
|
|
61
|
+
private configWatcher?: McpConfigWatcher;
|
|
62
|
+
private toolExecutor: ToolExecutor;
|
|
34
63
|
|
|
35
64
|
constructor(projectRoot: string) {
|
|
36
65
|
this.projectRoot = projectRoot;
|
|
37
66
|
this.monitor = new ActivityMonitor(projectRoot);
|
|
67
|
+
|
|
68
|
+
// MCP Server 초기화
|
|
38
69
|
this.server = new Server(
|
|
39
70
|
{
|
|
40
71
|
name: "mandu-mcp",
|
|
41
|
-
version:
|
|
72
|
+
version: MCP_VERSION,
|
|
42
73
|
},
|
|
43
74
|
{
|
|
44
75
|
capabilities: {
|
|
@@ -49,100 +80,52 @@ export class ManduMcpServer {
|
|
|
49
80
|
}
|
|
50
81
|
);
|
|
51
82
|
|
|
83
|
+
// DNA-001: 플러그인 기반 도구 등록
|
|
84
|
+
registerBuiltinTools(projectRoot, this.server, this.monitor);
|
|
85
|
+
|
|
86
|
+
// DNA-008: 로깅 통합
|
|
87
|
+
setupMcpLogging({ consoleOutput: false });
|
|
88
|
+
|
|
89
|
+
// DNA-016: 기본 훅 등록
|
|
90
|
+
registerDefaultMcpHooks();
|
|
91
|
+
|
|
92
|
+
// Tool Executor 생성
|
|
93
|
+
this.toolExecutor = createToolExecutor({
|
|
94
|
+
projectRoot,
|
|
95
|
+
logTool: (name, args, result, error) => this.monitor.logTool(name, args, result, error),
|
|
96
|
+
logResult: (name, result) => this.monitor.logResult(name, result),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// 핸들러 등록
|
|
52
100
|
this.registerToolHandlers();
|
|
53
101
|
this.registerResourceHandlers();
|
|
54
102
|
}
|
|
55
103
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
...generateToolDefinitions,
|
|
60
|
-
...transactionToolDefinitions,
|
|
61
|
-
...historyToolDefinitions,
|
|
62
|
-
...guardToolDefinitions,
|
|
63
|
-
...slotToolDefinitions,
|
|
64
|
-
...hydrationToolDefinitions,
|
|
65
|
-
...contractToolDefinitions,
|
|
66
|
-
...brainToolDefinitions,
|
|
67
|
-
...runtimeToolDefinitions,
|
|
68
|
-
...seoToolDefinitions,
|
|
69
|
-
...projectToolDefinitions,
|
|
70
|
-
];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private getAllToolHandlers(): Record<string, (args: Record<string, unknown>) => Promise<unknown>> {
|
|
74
|
-
const handlers = {
|
|
75
|
-
...specTools(this.projectRoot),
|
|
76
|
-
...generateTools(this.projectRoot),
|
|
77
|
-
...transactionTools(this.projectRoot),
|
|
78
|
-
...historyTools(this.projectRoot),
|
|
79
|
-
...guardTools(this.projectRoot),
|
|
80
|
-
...slotTools(this.projectRoot),
|
|
81
|
-
...hydrationTools(this.projectRoot),
|
|
82
|
-
...contractTools(this.projectRoot),
|
|
83
|
-
...brainTools(this.projectRoot, this.server, this.monitor),
|
|
84
|
-
...runtimeTools(this.projectRoot),
|
|
85
|
-
...seoTools(this.projectRoot),
|
|
86
|
-
...projectTools(this.projectRoot, this.server, this.monitor),
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
return applyWarningInjection(handlers);
|
|
90
|
-
}
|
|
91
|
-
|
|
104
|
+
/**
|
|
105
|
+
* 도구 핸들러 등록 (DNA-001 레지스트리 사용)
|
|
106
|
+
*/
|
|
92
107
|
private registerToolHandlers(): void {
|
|
93
|
-
|
|
94
|
-
|
|
108
|
+
// 도구 목록 요청
|
|
95
109
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
96
110
|
return {
|
|
97
|
-
tools:
|
|
111
|
+
tools: mcpToolRegistry.toToolDefinitions(),
|
|
98
112
|
};
|
|
99
113
|
});
|
|
100
114
|
|
|
115
|
+
// 도구 실행 요청
|
|
101
116
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
102
117
|
const { name, arguments: args } = request.params;
|
|
103
118
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
this.monitor.logTool(name, args, null, "Unknown tool");
|
|
107
|
-
return {
|
|
108
|
-
content: [
|
|
109
|
-
{
|
|
110
|
-
type: "text",
|
|
111
|
-
text: JSON.stringify({ error: `Unknown tool: ${name}` }),
|
|
112
|
-
},
|
|
113
|
-
],
|
|
114
|
-
isError: true,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
119
|
+
// DNA-007 + DNA-016: Tool Executor로 실행
|
|
120
|
+
const result = await this.toolExecutor.execute(name, args || {});
|
|
117
121
|
|
|
118
|
-
|
|
119
|
-
this.monitor.logTool(name, args);
|
|
120
|
-
const result = await handler(args || {});
|
|
121
|
-
this.monitor.logResult(name, result);
|
|
122
|
-
return {
|
|
123
|
-
content: [
|
|
124
|
-
{
|
|
125
|
-
type: "text",
|
|
126
|
-
text: JSON.stringify(result, null, 2),
|
|
127
|
-
},
|
|
128
|
-
],
|
|
129
|
-
};
|
|
130
|
-
} catch (error) {
|
|
131
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
132
|
-
this.monitor.logTool(name, args, null, msg);
|
|
133
|
-
return {
|
|
134
|
-
content: [
|
|
135
|
-
{
|
|
136
|
-
type: "text",
|
|
137
|
-
text: JSON.stringify({ error: msg }),
|
|
138
|
-
},
|
|
139
|
-
],
|
|
140
|
-
isError: true,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
122
|
+
return result.response;
|
|
143
123
|
});
|
|
144
124
|
}
|
|
145
125
|
|
|
126
|
+
/**
|
|
127
|
+
* 리소스 핸들러 등록 (기존 유지)
|
|
128
|
+
*/
|
|
146
129
|
private registerResourceHandlers(): void {
|
|
147
130
|
const handlers = resourceHandlers(this.projectRoot);
|
|
148
131
|
|
|
@@ -157,7 +140,7 @@ export class ManduMcpServer {
|
|
|
157
140
|
|
|
158
141
|
const handler = handlers[uri];
|
|
159
142
|
if (!handler) {
|
|
160
|
-
//
|
|
143
|
+
// 동적 리소스 패턴 매칭
|
|
161
144
|
for (const [pattern, h] of Object.entries(handlers)) {
|
|
162
145
|
if (pattern.includes("{") && matchResourcePattern(pattern, uri)) {
|
|
163
146
|
const params = extractResourceParams(pattern, uri);
|
|
@@ -212,12 +195,44 @@ export class ManduMcpServer {
|
|
|
212
195
|
});
|
|
213
196
|
}
|
|
214
197
|
|
|
198
|
+
/**
|
|
199
|
+
* 서버 실행
|
|
200
|
+
*/
|
|
215
201
|
async run(): Promise<void> {
|
|
202
|
+
// 설정 로드
|
|
203
|
+
try {
|
|
204
|
+
this.config = await loadManduConfig(this.projectRoot);
|
|
205
|
+
this.toolExecutor.updateConfig(this.config);
|
|
206
|
+
} catch {
|
|
207
|
+
// 설정 로드 실패 시 기본값 사용
|
|
208
|
+
console.error("[MCP] Config load failed, using defaults");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// DNA-006: 설정 핫 리로드 시작
|
|
212
|
+
try {
|
|
213
|
+
this.configWatcher = await startMcpConfigWatcher(this.projectRoot, {
|
|
214
|
+
server: this.server,
|
|
215
|
+
onReload: (newConfig) => {
|
|
216
|
+
this.config = newConfig;
|
|
217
|
+
this.toolExecutor.updateConfig(newConfig);
|
|
218
|
+
},
|
|
219
|
+
onMcpConfigChange: async () => {
|
|
220
|
+
// MCP 설정 변경 시 도구 재등록 가능
|
|
221
|
+
// 현재는 알림만 전송
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
} catch {
|
|
225
|
+
console.error("[MCP] Config watcher start failed (non-critical)");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 서버 연결
|
|
216
229
|
const transport = new StdioServerTransport();
|
|
217
230
|
await this.server.connect(transport);
|
|
231
|
+
|
|
232
|
+
// 모니터 시작
|
|
218
233
|
this.monitor.start();
|
|
219
234
|
|
|
220
|
-
//
|
|
235
|
+
// 와처 자동 시작
|
|
221
236
|
try {
|
|
222
237
|
const watcher = await startWatcher({ rootDir: this.projectRoot });
|
|
223
238
|
watcher.onWarning((warning) => {
|
|
@@ -225,9 +240,10 @@ export class ManduMcpServer {
|
|
|
225
240
|
warning.level || "warn",
|
|
226
241
|
warning.ruleId,
|
|
227
242
|
warning.file,
|
|
228
|
-
warning.message
|
|
243
|
+
warning.message
|
|
229
244
|
);
|
|
230
|
-
|
|
245
|
+
|
|
246
|
+
// Claude Code에 알림
|
|
231
247
|
this.server.sendLoggingMessage({
|
|
232
248
|
level: "warning",
|
|
233
249
|
logger: "mandu-watch",
|
|
@@ -243,17 +259,67 @@ export class ManduMcpServer {
|
|
|
243
259
|
},
|
|
244
260
|
}).catch(() => {});
|
|
245
261
|
});
|
|
262
|
+
|
|
246
263
|
this.monitor.logEvent("SYSTEM", "Watcher auto-started");
|
|
247
264
|
} catch {
|
|
248
265
|
this.monitor.logEvent("SYSTEM", "Watcher auto-start failed (non-critical)");
|
|
249
266
|
}
|
|
250
267
|
|
|
251
|
-
|
|
268
|
+
// 시작 로그
|
|
269
|
+
const summary = getToolsSummary();
|
|
270
|
+
console.error(`Mandu MCP Server v${MCP_VERSION} running`);
|
|
271
|
+
console.error(` Project: ${this.projectRoot}`);
|
|
272
|
+
console.error(` Tools: ${summary.total} (${summary.categories.join(", ")})`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* 서버 종료
|
|
277
|
+
*/
|
|
278
|
+
async stop(): Promise<void> {
|
|
279
|
+
// 설정 감시 중지
|
|
280
|
+
this.configWatcher?.stop();
|
|
281
|
+
|
|
282
|
+
// 로깅 해제
|
|
283
|
+
teardownMcpLogging();
|
|
284
|
+
|
|
285
|
+
// 모니터 종료
|
|
286
|
+
this.monitor.stop();
|
|
287
|
+
|
|
288
|
+
// 훅 정리
|
|
289
|
+
mcpHookRegistry.clear();
|
|
290
|
+
|
|
291
|
+
// 도구 레지스트리 정리
|
|
292
|
+
mcpToolRegistry.clear();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* 현재 설정 반환
|
|
297
|
+
*/
|
|
298
|
+
getConfig(): ManduConfig | undefined {
|
|
299
|
+
return this.config;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* 도구 레지스트리 접근
|
|
304
|
+
*/
|
|
305
|
+
getToolRegistry(): typeof mcpToolRegistry {
|
|
306
|
+
return mcpToolRegistry;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* 훅 레지스트리 접근
|
|
311
|
+
*/
|
|
312
|
+
getHookRegistry(): typeof mcpHookRegistry {
|
|
313
|
+
return mcpHookRegistry;
|
|
252
314
|
}
|
|
253
315
|
}
|
|
254
316
|
|
|
317
|
+
// ============================================
|
|
318
|
+
// 유틸리티 함수
|
|
319
|
+
// ============================================
|
|
320
|
+
|
|
255
321
|
/**
|
|
256
|
-
*
|
|
322
|
+
* 리소스 패턴 매칭
|
|
257
323
|
*/
|
|
258
324
|
function matchResourcePattern(pattern: string, uri: string): boolean {
|
|
259
325
|
const regexPattern = pattern.replace(/\{[^}]+\}/g, "([^/]+)");
|
|
@@ -262,7 +328,7 @@ function matchResourcePattern(pattern: string, uri: string): boolean {
|
|
|
262
328
|
}
|
|
263
329
|
|
|
264
330
|
/**
|
|
265
|
-
*
|
|
331
|
+
* 리소스 파라미터 추출
|
|
266
332
|
*/
|
|
267
333
|
function extractResourceParams(pattern: string, uri: string): Record<string, string> {
|
|
268
334
|
const paramNames: string[] = [];
|
|
@@ -285,10 +351,15 @@ function extractResourceParams(pattern: string, uri: string): Record<string, str
|
|
|
285
351
|
}
|
|
286
352
|
|
|
287
353
|
/**
|
|
288
|
-
*
|
|
354
|
+
* MCP 서버 시작
|
|
289
355
|
*/
|
|
290
356
|
export async function startServer(projectRoot?: string): Promise<void> {
|
|
291
357
|
const root = projectRoot || (await findProjectRoot()) || process.cwd();
|
|
292
358
|
const server = new ManduMcpServer(root);
|
|
293
359
|
await server.run();
|
|
294
360
|
}
|
|
361
|
+
|
|
362
|
+
// Re-exports
|
|
363
|
+
export { mcpToolRegistry } from "./registry/mcp-tool-registry.js";
|
|
364
|
+
export { mcpHookRegistry } from "./hooks/mcp-hooks.js";
|
|
365
|
+
export { registerBuiltinTools } from "./tools/index.js";
|