aiexecode 1.0.84 → 1.0.89
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.
Potentially problematic release.
This version of aiexecode might be problematic. Click here for more details.
- package/index.js +128 -23
- package/mcp-agent-lib/src/mcp_client.js +302 -77
- package/package.json +1 -1
- package/payload_viewer/out/404/index.html +1 -1
- package/payload_viewer/out/404.html +1 -1
- package/payload_viewer/out/index.html +1 -1
- package/payload_viewer/out/index.txt +1 -1
- package/src/ai_based/orchestrator.js +4 -1
- package/src/cli/mcp_cli.js +14 -7
- package/src/cli/mcp_commands.js +31 -15
- package/src/commands/mcp.js +36 -18
- package/src/frontend/App.js +54 -4
- package/src/frontend/components/BlankLine.js +5 -3
- package/src/frontend/components/ConversationItem.js +43 -10
- package/src/system/code_executer.js +6 -0
- package/src/system/mcp_integration.js +94 -40
- package/src/tools/file_reader.js +6 -0
- package/src/tools/glob.js +3 -0
- package/src/tools/ripgrep.js +2 -0
- package/src/tools/web_downloader.js +3 -0
- package/src/util/mcp_config_manager.js +41 -20
- /package/payload_viewer/out/_next/static/{-54trqBUDxbHkeuHWXRLc → hx7XCxOhiEpobKtUk3R15}/_buildManifest.js +0 -0
- /package/payload_viewer/out/_next/static/{-54trqBUDxbHkeuHWXRLc → hx7XCxOhiEpobKtUk3R15}/_clientMiddlewareManifest.json +0 -0
- /package/payload_viewer/out/_next/static/{-54trqBUDxbHkeuHWXRLc → hx7XCxOhiEpobKtUk3R15}/_ssgManifest.js +0 -0
|
@@ -9,22 +9,36 @@
|
|
|
9
9
|
* @author AI Agent Library Team
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
// Node.js 이벤트 시스템 - 서버 연결/해제 등의 이벤트를 외부로 발행하기 위해 사용
|
|
12
13
|
import { EventEmitter } from 'events';
|
|
14
|
+
// MCP SDK의 핵심 클라이언트 - 실제 MCP 프로토콜 통신 담당
|
|
13
15
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
16
|
+
// Stdio 전송: 로컬 프로세스를 spawn하여 stdin/stdout으로 통신
|
|
14
17
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
18
|
+
// SSE 전송: Server-Sent Events 기반 HTTP 스트리밍 통신
|
|
15
19
|
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
20
|
+
// HTTP 전송: 일반 HTTP/HTTPS 요청-응답 기반 통신
|
|
16
21
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
17
22
|
|
|
23
|
+
// 내부 디버그용 로그 함수 - 실제로는 아무 작업도 하지 않음 (성능 최적화)
|
|
18
24
|
function consolelog() { }
|
|
25
|
+
|
|
19
26
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
27
|
+
* MCPAgentClient - MCP 서버들과 통신하는 메인 클라이언트 클래스
|
|
28
|
+
*
|
|
29
|
+
* 주요 기능:
|
|
30
|
+
* - 여러 MCP 서버에 동시 연결 (stdio/HTTP/SSE 방식 지원)
|
|
31
|
+
* - Tool 실행, Resource 읽기, Prompt 가져오기 등 MCP 기능 제공
|
|
32
|
+
* - 자동 재시도, 에러 핸들링, 보안 검증 내장
|
|
33
|
+
* - 이벤트 기반 상태 알림 (연결/해제/에러)
|
|
22
34
|
*/
|
|
23
35
|
export class MCPAgentClient extends EventEmitter {
|
|
24
36
|
constructor(options = {}) {
|
|
25
37
|
super();
|
|
26
38
|
|
|
27
|
-
//
|
|
39
|
+
// 클라이언트 설정 초기화
|
|
40
|
+
// 환경변수(process.env.MCP_*)와 options 매개변수를 병합하여 우선순위 적용
|
|
41
|
+
// 타임아웃, 재시도, 로깅, 보안 정책 등 모든 동작 방식을 제어
|
|
28
42
|
this.options = {
|
|
29
43
|
autoConnect: options.autoConnect ?? true,
|
|
30
44
|
logLevel: options.logLevel || process.env.MCP_LOG_LEVEL || 'info',
|
|
@@ -41,24 +55,33 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
41
55
|
maxJsonSize: MCPAgentClient._safeParseInt(process.env.MCP_MAX_JSON_SIZE || options.maxJsonSize, 10 * 1024 * 1024),
|
|
42
56
|
serverReadyTimeout: MCPAgentClient._safeParseInt(process.env.MCP_SERVER_READY_TIMEOUT || options.serverReadyTimeout, 10000),
|
|
43
57
|
serverReadyRetries: MCPAgentClient._safeParseInt(process.env.MCP_SERVER_READY_RETRIES || options.serverReadyRetries, 5),
|
|
44
|
-
|
|
58
|
+
|
|
59
|
+
// 보안 설정: stdio 서버에서 실행 가능한 명령어 화이트리스트
|
|
45
60
|
allowedCommands: ['node', 'python', 'python3', 'npx', 'deno'],
|
|
61
|
+
|
|
62
|
+
// 보안 설정: 명령 인자에서 금지되는 셸 특수문자 (command injection 방지)
|
|
46
63
|
dangerousChars: ['&', ';', '|', '`', '$', '>', '<', '*', '?'],
|
|
64
|
+
|
|
65
|
+
// 응답 파싱: JSON으로 파싱하지 않을 응답의 시작 패턴들
|
|
47
66
|
responseIndicators: ['✅', '❌', 'Error:', 'Warning:', 'Info:'],
|
|
48
|
-
|
|
67
|
+
|
|
68
|
+
// 보안 설정: 프로토타입 오염 공격 탐지 패턴 (prototype pollution 방지)
|
|
49
69
|
prototypePollutionPatterns: ['__proto__', 'constructor', 'prototype', '["__proto__"]', "['__proto__']", '["constructor"]', "['constructor']", 'Object.prototype', 'Function.prototype', 'Array.prototype'],
|
|
50
|
-
|
|
70
|
+
|
|
71
|
+
// MCP 프로토콜: 이 클라이언트가 지원하는 기능들을 서버에 알림
|
|
51
72
|
clientCapabilities: {
|
|
52
73
|
roots: { listChanged: true },
|
|
53
74
|
sampling: {},
|
|
54
75
|
experimental: {}
|
|
55
76
|
},
|
|
56
|
-
|
|
77
|
+
|
|
78
|
+
// MCP 프로토콜: 클라이언트 식별 정보
|
|
57
79
|
clientInfo: {
|
|
58
80
|
name: process.env.MCP_CLIENT_NAME || 'mcp-agent-lib',
|
|
59
81
|
version: process.env.MCP_CLIENT_VERSION || '1.0.0'
|
|
60
82
|
},
|
|
61
|
-
|
|
83
|
+
|
|
84
|
+
// 사용자 메시지 템플릿 (다국어 지원용)
|
|
62
85
|
messages: {
|
|
63
86
|
initStart: '🚀 MCP Agent Client 초기화 중...',
|
|
64
87
|
emptyConfig: '⚠️ 빈 설정으로 초기화합니다. 서버를 연결하려면 mcpServers 설정을 제공하세요.',
|
|
@@ -83,18 +106,24 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
83
106
|
...options
|
|
84
107
|
};
|
|
85
108
|
|
|
86
|
-
//
|
|
109
|
+
// 연결된 서버들의 정보 저장소
|
|
110
|
+
// servers: 서버 메타데이터(이름, 타입, tools, resources, prompts 등)
|
|
111
|
+
// clients: 실제 MCP SDK Client 인스턴스들
|
|
87
112
|
this.servers = new Map();
|
|
88
113
|
this.clients = new Map();
|
|
89
114
|
this.isInitialized = false;
|
|
90
115
|
|
|
91
|
-
// 메모리 관리
|
|
116
|
+
// 메모리 관리 설정
|
|
117
|
+
// 연결 해제된 서버 정보를 주기적으로 정리 (interval 대신 on-demand 방식)
|
|
92
118
|
this.memoryCleanupEnabled = options.memoryCleanupEnabled ?? true;
|
|
93
119
|
this.lastCleanupTime = Date.now();
|
|
94
120
|
}
|
|
95
121
|
|
|
96
122
|
/**
|
|
97
|
-
*
|
|
123
|
+
* 정수 파싱 유틸리티
|
|
124
|
+
*
|
|
125
|
+
* 환경변수나 설정값을 안전하게 숫자로 변환
|
|
126
|
+
* 파싱 실패 시 기본값(fallback) 반환
|
|
98
127
|
*/
|
|
99
128
|
static _safeParseInt(value, fallback) {
|
|
100
129
|
const parsed = parseInt(value);
|
|
@@ -102,7 +131,9 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
102
131
|
}
|
|
103
132
|
|
|
104
133
|
/**
|
|
105
|
-
*
|
|
134
|
+
* 실수 파싱 유틸리티
|
|
135
|
+
*
|
|
136
|
+
* _safeParseInt와 동일하지만 소수점 숫자 처리
|
|
106
137
|
*/
|
|
107
138
|
static _safeParseFloat(value, fallback) {
|
|
108
139
|
const parsed = parseFloat(value);
|
|
@@ -110,7 +141,15 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
110
141
|
}
|
|
111
142
|
|
|
112
143
|
/**
|
|
113
|
-
*
|
|
144
|
+
* 안전한 JSON 직렬화
|
|
145
|
+
*
|
|
146
|
+
* 문제점:
|
|
147
|
+
* - 순환 참조가 있는 객체는 JSON.stringify 시 에러 발생
|
|
148
|
+
* - 함수나 undefined는 JSON으로 변환 불가
|
|
149
|
+
*
|
|
150
|
+
* 해결책:
|
|
151
|
+
* - WeakSet으로 이미 방문한 객체 추적하여 순환 참조 탐지
|
|
152
|
+
* - 함수와 undefined를 문자열로 표현
|
|
114
153
|
*/
|
|
115
154
|
safeJsonStringify(obj, space = null) {
|
|
116
155
|
const seen = new WeakSet();
|
|
@@ -121,7 +160,6 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
121
160
|
}
|
|
122
161
|
seen.add(value);
|
|
123
162
|
}
|
|
124
|
-
// Handle functions and undefined
|
|
125
163
|
if (typeof value === 'function') {
|
|
126
164
|
return '[Function]';
|
|
127
165
|
}
|
|
@@ -133,13 +171,23 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
133
171
|
}
|
|
134
172
|
|
|
135
173
|
/**
|
|
136
|
-
*
|
|
174
|
+
* 안전한 JSON 파싱
|
|
175
|
+
*
|
|
176
|
+
* 보안 위협:
|
|
177
|
+
* 1. Prototype Pollution: __proto__, constructor 등을 이용한 공격
|
|
178
|
+
* 2. JSON Injection: 악의적인 JSON 데이터 삽입
|
|
179
|
+
*
|
|
180
|
+
* 보안 대책:
|
|
181
|
+
* 1. 위험한 패턴 사전 검사 (prototypePollutionPatterns)
|
|
182
|
+
* 2. 응답 크기 제한 (maxResponseSize)
|
|
183
|
+
* 3. 파싱 중 위험한 키 제거
|
|
184
|
+
* 4. 파싱 후 객체를 freeze하여 변경 불가능하게 만듦
|
|
137
185
|
*/
|
|
138
186
|
safeJsonParse(text) {
|
|
139
187
|
try {
|
|
140
188
|
if (typeof text !== 'string') return text;
|
|
141
189
|
|
|
142
|
-
//
|
|
190
|
+
// 1단계: 프로토타입 오염 패턴 사전 검사
|
|
143
191
|
const dangerousPatterns = this.options.prototypePollutionPatterns;
|
|
144
192
|
|
|
145
193
|
const lowerText = text.toLowerCase();
|
|
@@ -153,22 +201,24 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
153
201
|
}
|
|
154
202
|
}
|
|
155
203
|
|
|
204
|
+
// 2단계: 응답 크기 검사 (DoS 공격 방지)
|
|
156
205
|
if (text.length > this.options.maxResponseSize) {
|
|
157
206
|
this.secureLog('warn', this.options.messages.responseSizeExceeds, { size: text.length, limit: this.options.maxResponseSize });
|
|
158
207
|
return text;
|
|
159
208
|
}
|
|
160
209
|
|
|
210
|
+
// 3단계: JSON 파싱 with reviver 함수
|
|
211
|
+
// reviver: 각 key-value 파싱 시 호출되어 위험한 키 필터링
|
|
161
212
|
const parsed = JSON.parse(text, (key, value) => {
|
|
162
|
-
// Additional key validation during parsing
|
|
163
213
|
if (typeof key === 'string' && this.options.prototypePollutionPatterns.includes(key)) {
|
|
164
214
|
this.secureLog('warn', this.options.messages.dangerousKey, { key });
|
|
165
|
-
return undefined;
|
|
215
|
+
return undefined;
|
|
166
216
|
}
|
|
167
217
|
return value;
|
|
168
218
|
});
|
|
169
219
|
|
|
220
|
+
// 4단계: 파싱된 객체를 재귀적으로 freeze (불변성 보장)
|
|
170
221
|
if (parsed && typeof parsed === 'object') {
|
|
171
|
-
// Recursively freeze objects to prevent mutation
|
|
172
222
|
this.deepFreeze(parsed);
|
|
173
223
|
}
|
|
174
224
|
return parsed;
|
|
@@ -179,7 +229,11 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
179
229
|
}
|
|
180
230
|
|
|
181
231
|
/**
|
|
182
|
-
*
|
|
232
|
+
* 객체 재귀적 동결
|
|
233
|
+
*
|
|
234
|
+
* Object.freeze()는 얕은 동결만 수행
|
|
235
|
+
* 이 메서드는 중첩된 모든 객체를 재귀적으로 동결하여
|
|
236
|
+
* 파싱된 JSON 데이터의 완전한 불변성 보장
|
|
183
237
|
*/
|
|
184
238
|
deepFreeze(obj) {
|
|
185
239
|
if (!obj || typeof obj !== 'object') return obj;
|
|
@@ -195,7 +249,10 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
195
249
|
}
|
|
196
250
|
|
|
197
251
|
/**
|
|
198
|
-
*
|
|
252
|
+
* MCP 표준 에러 생성
|
|
253
|
+
*
|
|
254
|
+
* MCP 프로토콜은 JSON-RPC 기반이므로 에러에 code와 data 필드 포함
|
|
255
|
+
* code: JSON-RPC 에러 코드 (예: -32602 = Invalid params)
|
|
199
256
|
*/
|
|
200
257
|
createMCPError(code, message, data = null) {
|
|
201
258
|
const error = new Error(message);
|
|
@@ -205,7 +262,12 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
205
262
|
}
|
|
206
263
|
|
|
207
264
|
/**
|
|
208
|
-
*
|
|
265
|
+
* 보안 로깅 시스템
|
|
266
|
+
*
|
|
267
|
+
* 기능:
|
|
268
|
+
* 1. 로그 레벨 필터링 (debug < info < warn < error < silent)
|
|
269
|
+
* 2. 민감 정보 자동 제거 (sanitizeLogData 호출)
|
|
270
|
+
* 3. 타임스탬프 자동 추가
|
|
209
271
|
*/
|
|
210
272
|
secureLog(level, message, data = {}) {
|
|
211
273
|
if (!this.options.enableLogging) return;
|
|
@@ -228,7 +290,11 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
228
290
|
}
|
|
229
291
|
|
|
230
292
|
/**
|
|
231
|
-
*
|
|
293
|
+
* 로그 데이터 민감정보 제거
|
|
294
|
+
*
|
|
295
|
+
* 보안을 위해 로그에 출력되는 데이터에서 민감 정보 제거:
|
|
296
|
+
* - auth, token, key, secret, password 등이 포함된 필드는 [REDACTED]로 대체
|
|
297
|
+
* - 너무 긴 문자열은 잘라내고 [TRUNCATED] 표시
|
|
232
298
|
*/
|
|
233
299
|
sanitizeLogData(data) {
|
|
234
300
|
if (!data || typeof data !== 'object') return data;
|
|
@@ -251,7 +317,10 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
251
317
|
}
|
|
252
318
|
|
|
253
319
|
/**
|
|
254
|
-
*
|
|
320
|
+
* 간단한 로깅 헬퍼
|
|
321
|
+
*
|
|
322
|
+
* secureLog와 유사하지만 더 간단한 버전
|
|
323
|
+
* 이모지 아이콘과 함께 로그 출력
|
|
255
324
|
*/
|
|
256
325
|
_log(level, message) {
|
|
257
326
|
const levels = { debug: 0, info: 1, warn: 2, error: 3, silent: 4 };
|
|
@@ -274,7 +343,22 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
274
343
|
}
|
|
275
344
|
|
|
276
345
|
/**
|
|
277
|
-
*
|
|
346
|
+
* 클라이언트 초기화
|
|
347
|
+
*
|
|
348
|
+
* 설정 객체(config)를 받아서 여러 MCP 서버에 연결
|
|
349
|
+
*
|
|
350
|
+
* config 형식:
|
|
351
|
+
* {
|
|
352
|
+
* mcpServers: {
|
|
353
|
+
* "서버이름": {
|
|
354
|
+
* type: "stdio" | "http" | "sse",
|
|
355
|
+
* command: "node", // stdio만
|
|
356
|
+
* args: [...], // stdio만
|
|
357
|
+
* url: "https://...", // http/sse만
|
|
358
|
+
* headers: {...} // http/sse만
|
|
359
|
+
* }
|
|
360
|
+
* }
|
|
361
|
+
* }
|
|
278
362
|
*/
|
|
279
363
|
async initialize(config = {}) {
|
|
280
364
|
try {
|
|
@@ -290,10 +374,11 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
290
374
|
configuration = config;
|
|
291
375
|
}
|
|
292
376
|
|
|
377
|
+
// 설정에 정의된 모든 서버에 연결 시도
|
|
293
378
|
await this.connectFromConfig(configuration);
|
|
294
379
|
this.isInitialized = true;
|
|
295
380
|
|
|
296
|
-
//
|
|
381
|
+
// 연결 성공한 서버들의 정보 정리
|
|
297
382
|
for (const [serverName, serverInfo] of this.servers) {
|
|
298
383
|
if (serverInfo.connected) {
|
|
299
384
|
this.servers.set(serverName, {
|
|
@@ -315,7 +400,13 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
315
400
|
}
|
|
316
401
|
|
|
317
402
|
/**
|
|
318
|
-
*
|
|
403
|
+
* 서버 설정 보안 검증
|
|
404
|
+
*
|
|
405
|
+
* 연결 전에 서버 설정이 안전한지 검사:
|
|
406
|
+
* 1. stdio 서버: 허용된 명령어만 실행 가능한지 확인
|
|
407
|
+
* 2. stdio 서버: 인자에 위험한 셸 문자가 없는지 확인
|
|
408
|
+
* 3. http 서버: URL이 http/https 프로토콜인지 확인
|
|
409
|
+
* 4. 환경변수가 문자열 key-value인지 확인
|
|
319
410
|
*/
|
|
320
411
|
validateServerConfig(serverName, serverConfig) {
|
|
321
412
|
if (!serverName || typeof serverName !== 'string') {
|
|
@@ -326,15 +417,16 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
326
417
|
throw new Error('Server config must be an object');
|
|
327
418
|
}
|
|
328
419
|
|
|
329
|
-
//
|
|
420
|
+
// stdio 서버 보안 검증
|
|
330
421
|
if (serverConfig.command) {
|
|
331
422
|
const allowedCommands = this.options.allowedCommands || ['node', 'python', 'python3', 'npx', 'deno'];
|
|
332
423
|
|
|
424
|
+
// 화이트리스트에 없는 명령어는 실행 불가
|
|
333
425
|
if (!allowedCommands.includes(serverConfig.command)) {
|
|
334
426
|
throw new Error(`Command '${serverConfig.command}' is not allowed. Allowed commands: ${allowedCommands.join(', ')}`);
|
|
335
427
|
}
|
|
336
428
|
|
|
337
|
-
//
|
|
429
|
+
// 명령 인자에 셸 특수문자 포함 여부 검사 (command injection 방지)
|
|
338
430
|
if (serverConfig.args && Array.isArray(serverConfig.args)) {
|
|
339
431
|
const dangerousChars = this.options.dangerousChars || ['&', ';', '|', '`', '$', '>', '<', '*', '?'];
|
|
340
432
|
for (const arg of serverConfig.args) {
|
|
@@ -344,7 +436,7 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
344
436
|
}
|
|
345
437
|
}
|
|
346
438
|
|
|
347
|
-
//
|
|
439
|
+
// 환경변수 형식 검증 (문자열 key-value만 허용)
|
|
348
440
|
if (serverConfig.env && typeof serverConfig.env === 'object') {
|
|
349
441
|
for (const [key, value] of Object.entries(serverConfig.env)) {
|
|
350
442
|
if (typeof key !== 'string' || typeof value !== 'string') {
|
|
@@ -354,7 +446,7 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
354
446
|
}
|
|
355
447
|
}
|
|
356
448
|
|
|
357
|
-
//
|
|
449
|
+
// HTTP/SSE 서버 URL 검증
|
|
358
450
|
if (serverConfig.type === 'http' && serverConfig.url) {
|
|
359
451
|
try {
|
|
360
452
|
const url = new URL(serverConfig.url);
|
|
@@ -371,7 +463,12 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
371
463
|
}
|
|
372
464
|
|
|
373
465
|
/**
|
|
374
|
-
*
|
|
466
|
+
* 설정 파일의 모든 서버에 연결
|
|
467
|
+
*
|
|
468
|
+
* 각 서버 설정을 순회하며:
|
|
469
|
+
* 1. 보안 검증 수행
|
|
470
|
+
* 2. 서버 타입에 맞는 연결 메서드 호출
|
|
471
|
+
* 3. 연결 실패 시 다른 서버 연결 계속 진행
|
|
375
472
|
*/
|
|
376
473
|
async connectFromConfig(config) {
|
|
377
474
|
if (!config || !config.mcpServers) {
|
|
@@ -383,13 +480,14 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
383
480
|
|
|
384
481
|
for (const [serverName, serverConfig] of Object.entries(config.mcpServers)) {
|
|
385
482
|
try {
|
|
386
|
-
//
|
|
483
|
+
// 보안 검증 먼저 수행 (fail-fast)
|
|
387
484
|
this.validateServerConfig(serverName, serverConfig);
|
|
388
485
|
|
|
389
486
|
this._log('info', `🔗 Connecting to server: ${serverName}`);
|
|
390
487
|
|
|
391
488
|
let server;
|
|
392
489
|
|
|
490
|
+
// 서버 타입에 따라 적절한 연결 메서드 선택
|
|
393
491
|
if (serverConfig.type === 'http') {
|
|
394
492
|
server = await this.connectToHttpServer(serverName, serverConfig);
|
|
395
493
|
} else if (serverConfig.type === 'sse') {
|
|
@@ -404,11 +502,11 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
404
502
|
results.push({ name: serverName, success: true, server });
|
|
405
503
|
|
|
406
504
|
} catch (error) {
|
|
407
|
-
//
|
|
505
|
+
// 보안 에러는 즉시 전파 (다른 서버 연결 중단)
|
|
408
506
|
if (error.message.includes('not allowed') || error.message.includes('dangerous characters')) {
|
|
409
507
|
throw error;
|
|
410
508
|
}
|
|
411
|
-
//
|
|
509
|
+
// 일반 연결 에러는 로그만 남기고 다음 서버 계속 진행
|
|
412
510
|
this._log('error', `❌ Failed to connect to ${serverName}: ${error.message}`);
|
|
413
511
|
results.push({ name: serverName, success: false, error: error.message });
|
|
414
512
|
}
|
|
@@ -418,21 +516,33 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
418
516
|
}
|
|
419
517
|
|
|
420
518
|
/**
|
|
421
|
-
*
|
|
519
|
+
* Stdio 전송 방식으로 MCP 서버 연결
|
|
520
|
+
*
|
|
521
|
+
* 동작 방식:
|
|
522
|
+
* 1. 로컬 프로세스 spawn (예: node server.js)
|
|
523
|
+
* 2. stdin/stdout을 통해 JSON-RPC 통신
|
|
524
|
+
* 3. stderr은 에러 로깅용으로 사용
|
|
525
|
+
*
|
|
526
|
+
* 주요 단계:
|
|
527
|
+
* 1. SDK Transport 객체 생성
|
|
528
|
+
* 2. SDK Client 생성 및 연결
|
|
529
|
+
* 3. 서버 준비 대기 (_waitForServerReady)
|
|
530
|
+
* 4. 사용 가능한 tools/resources/prompts 목록 조회
|
|
422
531
|
*/
|
|
423
532
|
async connectToStdioServer(serverName, serverConfig) {
|
|
424
533
|
try {
|
|
425
534
|
this._log('info', `🔌 Connecting to STDIO server: ${serverName}`);
|
|
426
535
|
|
|
427
|
-
//
|
|
536
|
+
// SDK Transport 생성 - SDK가 자동으로 프로세스 spawn
|
|
428
537
|
const transport = new StdioClientTransport({
|
|
429
538
|
command: serverConfig.command,
|
|
430
539
|
args: serverConfig.args || [],
|
|
431
540
|
env: serverConfig.env,
|
|
432
541
|
cwd: serverConfig.cwd,
|
|
433
|
-
stderr: 'pipe'
|
|
542
|
+
stderr: 'pipe'
|
|
434
543
|
});
|
|
435
544
|
|
|
545
|
+
// MCP SDK Client 생성
|
|
436
546
|
const client = new Client(
|
|
437
547
|
this.options.clientInfo,
|
|
438
548
|
{
|
|
@@ -440,6 +550,7 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
440
550
|
}
|
|
441
551
|
);
|
|
442
552
|
|
|
553
|
+
// 서버 메타데이터 객체 생성
|
|
443
554
|
const server = {
|
|
444
555
|
name: serverName,
|
|
445
556
|
type: 'stdio',
|
|
@@ -452,7 +563,7 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
452
563
|
config: serverConfig
|
|
453
564
|
};
|
|
454
565
|
|
|
455
|
-
//
|
|
566
|
+
// stderr 스트림 모니터링 설정 (서버 에러 로깅)
|
|
456
567
|
const stderrStream = transport.stderr;
|
|
457
568
|
if (stderrStream) {
|
|
458
569
|
stderrStream.on('data', (data) => {
|
|
@@ -465,7 +576,7 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
465
576
|
});
|
|
466
577
|
}
|
|
467
578
|
|
|
468
|
-
//
|
|
579
|
+
// Transport 이벤트 핸들러 설정 (연결 상태 추적)
|
|
469
580
|
transport.onerror = (error) => {
|
|
470
581
|
this._log('error', `💥 Transport error for ${serverName}: ${error}`);
|
|
471
582
|
this.emit('serverError', serverName, error);
|
|
@@ -477,21 +588,21 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
477
588
|
this.emit('serverDisconnected', serverName);
|
|
478
589
|
};
|
|
479
590
|
|
|
480
|
-
//
|
|
591
|
+
// MCP 프로토콜 핸드셰이크 수행
|
|
481
592
|
await client.connect(transport);
|
|
482
593
|
|
|
483
|
-
//
|
|
594
|
+
// 서버가 제공하는 기능 정보 가져오기
|
|
484
595
|
server.capabilities = client.getServerCapabilities();
|
|
485
596
|
this._setServerStatus(serverName, 'connected');
|
|
486
597
|
|
|
487
|
-
//
|
|
598
|
+
// Map에 저장 (이후 조회 가능하도록)
|
|
488
599
|
this.servers.set(serverName, server);
|
|
489
600
|
this.clients.set(serverName, client);
|
|
490
601
|
|
|
491
|
-
//
|
|
602
|
+
// 서버가 완전히 준비될 때까지 대기 (재시도 로직 포함)
|
|
492
603
|
await this._waitForServerReady(client, serverName);
|
|
493
604
|
|
|
494
|
-
//
|
|
605
|
+
// 사용 가능한 tools/resources/prompts 목록 조회 및 저장
|
|
495
606
|
await this.listServerCapabilities(serverName);
|
|
496
607
|
|
|
497
608
|
// Log final server statistics without duplicate status setting
|
|
@@ -799,14 +910,25 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
799
910
|
}
|
|
800
911
|
|
|
801
912
|
/**
|
|
802
|
-
*
|
|
913
|
+
* Tool 실행 (고수준 API)
|
|
914
|
+
*
|
|
915
|
+
* Tool 이름만으로 자동 실행:
|
|
916
|
+
* 1. Tool을 제공하는 서버 자동 검색
|
|
917
|
+
* 2. 해당 서버에서 Tool 실행
|
|
918
|
+
* 3. 실패 시 자동 재시도 (exponential backoff)
|
|
919
|
+
* 4. 결과 정제 및 반환
|
|
920
|
+
*
|
|
921
|
+
* @param toolName - 실행할 Tool 이름
|
|
922
|
+
* @param args - Tool에 전달할 인자 (JSON 객체)
|
|
923
|
+
* @param options - timeout, retries 등 실행 옵션
|
|
924
|
+
* @returns 정제된 실행 결과
|
|
803
925
|
*/
|
|
804
926
|
async executeTool(toolName, args = {}, options = {}) {
|
|
805
927
|
this._ensureInitialized();
|
|
806
928
|
|
|
807
929
|
const { timeout = this.options.timeout, retries = this.options.retries } = options;
|
|
808
930
|
|
|
809
|
-
//
|
|
931
|
+
// Tool을 제공하는 서버 찾기
|
|
810
932
|
const serverName = this._findServerForTool(toolName);
|
|
811
933
|
if (!serverName) {
|
|
812
934
|
throw new Error(this.options.messages.toolNotFound(toolName));
|
|
@@ -815,16 +937,19 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
815
937
|
this._log('info', this.options.messages.toolExecuting(toolName, serverName));
|
|
816
938
|
this._log('debug', `📝 인수: ${JSON.stringify(args)}`);
|
|
817
939
|
|
|
940
|
+
// 재시도 로직
|
|
818
941
|
let lastError;
|
|
819
942
|
const maxAttempts = Math.max(1, retries);
|
|
820
943
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
821
944
|
try {
|
|
822
945
|
const result = await this.callTool(serverName, toolName, args);
|
|
823
946
|
|
|
947
|
+
// MCP 프로토콜에서 에러 응답 처리
|
|
824
948
|
if (result.isError) {
|
|
825
949
|
throw new Error(result.content[0]?.text || this.options.messages.unknownError);
|
|
826
950
|
}
|
|
827
951
|
|
|
952
|
+
// 성공 시 정제된 결과 반환
|
|
828
953
|
const cleanResult = {
|
|
829
954
|
success: true,
|
|
830
955
|
data: this._cleanToolResult(result),
|
|
@@ -840,28 +965,32 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
840
965
|
lastError = error;
|
|
841
966
|
this._log('warn', this.options.messages.toolRetry(attempt, maxAttempts, error?.message || error));
|
|
842
967
|
|
|
843
|
-
//
|
|
968
|
+
// 서버 연결 끊김 감지 시 즉시 중단
|
|
844
969
|
const serverInfo = this.servers.get(serverName);
|
|
845
970
|
if (serverInfo && !this._isServerConnected(serverInfo)) {
|
|
846
971
|
this._log('error', `Server ${serverName} disconnected during tool execution`);
|
|
847
972
|
throw new Error(`Server ${serverName} is no longer connected`);
|
|
848
973
|
}
|
|
849
974
|
|
|
975
|
+
// Exponential backoff로 재시도 지연
|
|
850
976
|
if (attempt < maxAttempts) {
|
|
851
|
-
// Robust exponential backoff with proper bounds
|
|
852
977
|
const delay = this._calculateRetryDelay(attempt);
|
|
853
978
|
await this._delay(delay);
|
|
854
979
|
}
|
|
855
980
|
}
|
|
856
981
|
}
|
|
857
982
|
|
|
983
|
+
// 모든 재시도 실패 시 최종 에러
|
|
858
984
|
this._log('error', this.options.messages.toolFinalFail(toolName));
|
|
859
985
|
const errorMessage = lastError?.message || lastError?.toString() || this.options.messages.unknownError;
|
|
860
986
|
throw new Error(this.options.messages.toolFailedWithRetries(toolName, maxAttempts, errorMessage));
|
|
861
987
|
}
|
|
862
988
|
|
|
863
989
|
/**
|
|
864
|
-
*
|
|
990
|
+
* Tool 실행 (저수준 API)
|
|
991
|
+
*
|
|
992
|
+
* 특정 서버의 Tool을 직접 호출
|
|
993
|
+
* executeTool과 달리 서버 이름을 직접 지정해야 함
|
|
865
994
|
*/
|
|
866
995
|
async callTool(serverName, toolName, arguments_ = {}) {
|
|
867
996
|
this._log('info', `🔧 Calling tool '${toolName}' on ${serverName}`);
|
|
@@ -1220,12 +1349,26 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
1220
1349
|
}
|
|
1221
1350
|
}
|
|
1222
1351
|
|
|
1223
|
-
//
|
|
1352
|
+
// ========================================
|
|
1353
|
+
// Private 헬퍼 메서드들
|
|
1354
|
+
// ========================================
|
|
1224
1355
|
|
|
1356
|
+
/**
|
|
1357
|
+
* 초기화 여부 확인
|
|
1358
|
+
*
|
|
1359
|
+
* 대부분의 메서드 시작 시 호출하여
|
|
1360
|
+
* initialize()가 먼저 호출되었는지 확인
|
|
1361
|
+
*/
|
|
1225
1362
|
_ensureInitialized() {
|
|
1226
1363
|
if (!this.isInitialized) throw new Error('초기화가 필요합니다. initialize()를 먼저 호출하세요.');
|
|
1227
1364
|
}
|
|
1228
1365
|
|
|
1366
|
+
/**
|
|
1367
|
+
* Tool 이름으로 서버 찾기
|
|
1368
|
+
*
|
|
1369
|
+
* 연결된 모든 서버를 순회하며 해당 Tool을 제공하는 서버 검색
|
|
1370
|
+
* 여러 서버가 같은 이름의 Tool을 제공할 경우 먼저 발견된 서버 반환
|
|
1371
|
+
*/
|
|
1229
1372
|
_findServerForTool(toolName) {
|
|
1230
1373
|
for (const [name, info] of this.servers) {
|
|
1231
1374
|
if (this._isServerConnected(info) &&
|
|
@@ -1234,12 +1377,29 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
1234
1377
|
return null;
|
|
1235
1378
|
}
|
|
1236
1379
|
|
|
1380
|
+
/**
|
|
1381
|
+
* Tool 실행 결과 정제
|
|
1382
|
+
*
|
|
1383
|
+
* MCP 프로토콜 응답 형식:
|
|
1384
|
+
* {
|
|
1385
|
+
* content: [
|
|
1386
|
+
* { type: 'text', text: '...' },
|
|
1387
|
+
* { type: 'image', data: '...' },
|
|
1388
|
+
* ...
|
|
1389
|
+
* ]
|
|
1390
|
+
* }
|
|
1391
|
+
*
|
|
1392
|
+
* 정제 작업:
|
|
1393
|
+
* 1. text 타입만 있으면 문자열로 추출
|
|
1394
|
+
* 2. JSON 형태면 파싱 시도
|
|
1395
|
+
* 3. 배열이면 각 항목 개별 처리
|
|
1396
|
+
*/
|
|
1237
1397
|
_cleanToolResult(result) {
|
|
1238
1398
|
if (!result.content || !Array.isArray(result.content)) {
|
|
1239
1399
|
return result;
|
|
1240
1400
|
}
|
|
1241
1401
|
|
|
1242
|
-
//
|
|
1402
|
+
// Raw text 모드: JSON 파싱 없이 그대로 반환
|
|
1243
1403
|
if (result._isRawText) {
|
|
1244
1404
|
if (result.content.length === 1 && result.content[0].type === 'text') {
|
|
1245
1405
|
return result.content[0].text;
|
|
@@ -1247,9 +1407,9 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
1247
1407
|
return result.content.map(item => item.type === 'text' ? item.text : item);
|
|
1248
1408
|
}
|
|
1249
1409
|
|
|
1410
|
+
// 단일 text 항목: JSON 파싱 시도
|
|
1250
1411
|
if (result.content.length === 1 && result.content[0].type === 'text') {
|
|
1251
1412
|
const text = result.content[0].text;
|
|
1252
|
-
// Only try to parse as JSON if it looks like structured data
|
|
1253
1413
|
if (this._looksLikeJson(text)) {
|
|
1254
1414
|
try {
|
|
1255
1415
|
return this.safeJsonParse(text);
|
|
@@ -1264,6 +1424,7 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
1264
1424
|
return text;
|
|
1265
1425
|
}
|
|
1266
1426
|
|
|
1427
|
+
// 다중 항목: 각각 처리
|
|
1267
1428
|
return result.content.map(item => {
|
|
1268
1429
|
if (item.type === 'text') {
|
|
1269
1430
|
if (this._looksLikeJson(item.text)) {
|
|
@@ -1284,7 +1445,14 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
1284
1445
|
}
|
|
1285
1446
|
|
|
1286
1447
|
/**
|
|
1287
|
-
*
|
|
1448
|
+
* 서버 상태 중앙 관리
|
|
1449
|
+
*
|
|
1450
|
+
* 서버 상태를 일관되게 업데이트하고 이벤트 발행:
|
|
1451
|
+
* - status와 connected 필드 동시 업데이트
|
|
1452
|
+
* - 상태 변경 시 'serverStatusChange' 이벤트 발행
|
|
1453
|
+
* - disconnected 시 메모리 정리 트리거
|
|
1454
|
+
*
|
|
1455
|
+
* 가능한 상태: connecting, connected, disconnected, partially_connected, error
|
|
1288
1456
|
*/
|
|
1289
1457
|
_setServerStatus(serverName, status, additionalInfo = {}) {
|
|
1290
1458
|
const server = this.servers.get(serverName);
|
|
@@ -1325,7 +1493,9 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
1325
1493
|
}
|
|
1326
1494
|
|
|
1327
1495
|
/**
|
|
1328
|
-
*
|
|
1496
|
+
* 서버 연결 상태 확인
|
|
1497
|
+
*
|
|
1498
|
+
* connected 또는 partially_connected 상태만 "연결됨"으로 간주
|
|
1329
1499
|
*/
|
|
1330
1500
|
_isServerConnected(server) {
|
|
1331
1501
|
if (!server) return false;
|
|
@@ -1333,7 +1503,9 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
1333
1503
|
}
|
|
1334
1504
|
|
|
1335
1505
|
/**
|
|
1336
|
-
*
|
|
1506
|
+
* 서버 상태 조회 (폴백 포함)
|
|
1507
|
+
*
|
|
1508
|
+
* status 필드 우선, 없으면 connected 필드로 판단
|
|
1337
1509
|
*/
|
|
1338
1510
|
_getServerStatus(serverName) {
|
|
1339
1511
|
const server = this.servers.get(serverName);
|
|
@@ -1342,10 +1514,16 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
1342
1514
|
}
|
|
1343
1515
|
|
|
1344
1516
|
/**
|
|
1345
|
-
*
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
*
|
|
1517
|
+
* 서버 준비 상태 대기
|
|
1518
|
+
*
|
|
1519
|
+
* 문제: 서버가 connect 직후 바로 listTools() 등을 호출하면 실패할 수 있음
|
|
1520
|
+
* 해결: 서버가 완전히 준비될 때까지 재시도하며 대기
|
|
1521
|
+
*
|
|
1522
|
+
* 동작:
|
|
1523
|
+
* 1. listTools()와 getServerCapabilities() 동시 호출
|
|
1524
|
+
* 2. 둘 중 하나라도 성공하면 준비된 것으로 판단
|
|
1525
|
+
* 3. 실패 시 exponential backoff로 재시도
|
|
1526
|
+
* 4. 타임아웃 또는 최대 재시도 횟수 도달 시 포기
|
|
1349
1527
|
*/
|
|
1350
1528
|
async _waitForServerReady(client, serverName) {
|
|
1351
1529
|
const maxRetries = this.options.serverReadyRetries;
|
|
@@ -1440,11 +1618,13 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
1440
1618
|
}
|
|
1441
1619
|
|
|
1442
1620
|
/**
|
|
1443
|
-
*
|
|
1621
|
+
* 서버 정보 조회 (에러 무시)
|
|
1622
|
+
*
|
|
1623
|
+
* 준비 상태 확인용으로 서버 capabilities 조회
|
|
1624
|
+
* 실패해도 에러를 던지지 않고 null 반환
|
|
1444
1625
|
*/
|
|
1445
1626
|
async _getServerInfo(client) {
|
|
1446
1627
|
try {
|
|
1447
|
-
// Try to get server capabilities/info without causing errors
|
|
1448
1628
|
return await client.getServerCapabilities?.() || null;
|
|
1449
1629
|
} catch (error) {
|
|
1450
1630
|
return null;
|
|
@@ -1452,47 +1632,55 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
1452
1632
|
}
|
|
1453
1633
|
|
|
1454
1634
|
/**
|
|
1455
|
-
*
|
|
1635
|
+
* 재시도 지연시간 계산
|
|
1636
|
+
*
|
|
1637
|
+
* Exponential backoff + jitter:
|
|
1638
|
+
* - 기본 지연 * 2^(attempt-1) 로 지수적 증가
|
|
1639
|
+
* - 랜덤 jitter 추가로 thundering herd 방지
|
|
1640
|
+
* - 최대 지연시간 제한
|
|
1641
|
+
*
|
|
1642
|
+
* 예: baseDelay=1000, attempt=3
|
|
1643
|
+
* → 1000 * 2^2 = 4000ms + jitter
|
|
1456
1644
|
*/
|
|
1457
1645
|
_calculateRetryDelay(attempt) {
|
|
1458
1646
|
const baseDelay = this.options.retryDelay;
|
|
1459
1647
|
const maxDelay = this.options.maxRetryDelay;
|
|
1460
1648
|
|
|
1461
|
-
// Exponential backoff: baseDelay * 2^(attempt-1)
|
|
1462
1649
|
const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);
|
|
1463
1650
|
|
|
1464
|
-
|
|
1465
|
-
const jitterPercent = MCPAgentClient._safeParseFloat(process.env.MCP_JITTER_PERCENT, 0.15); // Default 15%
|
|
1651
|
+
const jitterPercent = MCPAgentClient._safeParseFloat(process.env.MCP_JITTER_PERCENT, 0.15);
|
|
1466
1652
|
const jitter = exponentialDelay * jitterPercent * Math.random();
|
|
1467
1653
|
|
|
1468
|
-
// Cap at maximum delay
|
|
1469
1654
|
return Math.min(exponentialDelay + jitter, maxDelay);
|
|
1470
1655
|
}
|
|
1471
1656
|
|
|
1472
1657
|
/**
|
|
1473
|
-
*
|
|
1658
|
+
* 텍스트가 JSON인지 판단
|
|
1659
|
+
*
|
|
1660
|
+
* 파싱 시도 여부를 결정하기 위한 휴리스틱:
|
|
1661
|
+
* - { } 또는 [ ]로 감싸져 있어야 함
|
|
1662
|
+
* - base64 문자열 제외
|
|
1663
|
+
* - 이모지나 'Error:' 등으로 시작하는 일반 메시지 제외
|
|
1474
1664
|
*/
|
|
1475
1665
|
_looksLikeJson(text) {
|
|
1476
1666
|
if (typeof text !== 'string' || text.length === 0) return false;
|
|
1477
1667
|
|
|
1478
|
-
// Skip if it's likely a base64 encoded string
|
|
1479
1668
|
if (/^[A-Za-z0-9+/]*={0,2}$/.test(text.trim())) return false;
|
|
1480
1669
|
|
|
1481
|
-
// Skip if it starts with response indicators (configurable)
|
|
1482
1670
|
const responseIndicators = this.options.responseIndicators || ['✅', '❌', 'Error:', 'Warning:', 'Info:'];
|
|
1483
1671
|
if (responseIndicators.some(indicator => text.startsWith(indicator))) return false;
|
|
1484
1672
|
|
|
1485
|
-
// Only parse if it starts with { or [ (likely JSON)
|
|
1486
1673
|
const trimmed = text.trim();
|
|
1487
1674
|
return (trimmed.startsWith('{') && trimmed.endsWith('}')) ||
|
|
1488
1675
|
(trimmed.startsWith('[') && trimmed.endsWith(']'));
|
|
1489
1676
|
}
|
|
1490
1677
|
|
|
1491
1678
|
/**
|
|
1492
|
-
* 메모리 정리
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
*
|
|
1679
|
+
* 메모리 정리
|
|
1680
|
+
*
|
|
1681
|
+
* 연결 해제된 서버 정보를 정리하여 메모리 누수 방지
|
|
1682
|
+
* interval 대신 on-demand 방식으로 필요시에만 실행
|
|
1683
|
+
* (disconnected 이벤트 발생 시 또는 명시적 호출 시)
|
|
1496
1684
|
*/
|
|
1497
1685
|
performMemoryCleanup() {
|
|
1498
1686
|
if (!this.memoryCleanupEnabled) return;
|
|
@@ -1544,24 +1732,61 @@ export class MCPAgentClient extends EventEmitter {
|
|
|
1544
1732
|
}
|
|
1545
1733
|
}
|
|
1546
1734
|
|
|
1735
|
+
/**
|
|
1736
|
+
* 지연 유틸리티
|
|
1737
|
+
*
|
|
1738
|
+
* Promise 기반 setTimeout으로 async/await에서 사용 가능
|
|
1739
|
+
*/
|
|
1547
1740
|
_delay(ms) {
|
|
1548
1741
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1549
1742
|
}
|
|
1550
1743
|
|
|
1744
|
+
/**
|
|
1745
|
+
* Tool 카테고리 분류
|
|
1746
|
+
*
|
|
1747
|
+
* 향후 Tool을 카테고리별로 분류할 수 있도록 확장 가능
|
|
1748
|
+
* 현재는 모두 'tool'로 반환
|
|
1749
|
+
*/
|
|
1551
1750
|
_categorizeTool(toolName) {
|
|
1552
1751
|
return 'tool';
|
|
1553
1752
|
}
|
|
1554
1753
|
}
|
|
1555
1754
|
|
|
1755
|
+
// ========================================
|
|
1756
|
+
// 팩토리 함수들
|
|
1757
|
+
// ========================================
|
|
1758
|
+
|
|
1556
1759
|
/**
|
|
1557
|
-
*
|
|
1760
|
+
* MCPAgentClient 인스턴스 생성 헬퍼
|
|
1761
|
+
*
|
|
1762
|
+
* new MCPAgentClient(options)의 간편 버전
|
|
1763
|
+
*
|
|
1764
|
+
* @param options - 클라이언트 설정 옵션
|
|
1765
|
+
* @returns MCPAgentClient 인스턴스 (초기화 전 상태)
|
|
1558
1766
|
*/
|
|
1559
1767
|
export function createMCPAgent(options = {}) {
|
|
1560
1768
|
return new MCPAgentClient(options);
|
|
1561
1769
|
}
|
|
1562
1770
|
|
|
1563
1771
|
/**
|
|
1564
|
-
* 빠른 시작 함수
|
|
1772
|
+
* 빠른 시작 함수
|
|
1773
|
+
*
|
|
1774
|
+
* 클라이언트 생성 + 초기화를 한 번에 수행
|
|
1775
|
+
* 가장 간단하게 사용할 수 있는 방법
|
|
1776
|
+
*
|
|
1777
|
+
* 사용 예:
|
|
1778
|
+
* const client = await quickStart({
|
|
1779
|
+
* mcpServers: {
|
|
1780
|
+
* "myserver": {
|
|
1781
|
+
* command: "node",
|
|
1782
|
+
* args: ["server.js"]
|
|
1783
|
+
* }
|
|
1784
|
+
* }
|
|
1785
|
+
* });
|
|
1786
|
+
*
|
|
1787
|
+
* @param config - 서버 설정 (mcpServers 객체 포함)
|
|
1788
|
+
* @param options - 클라이언트 옵션
|
|
1789
|
+
* @returns 초기화 완료된 MCPAgentClient 인스턴스
|
|
1565
1790
|
*/
|
|
1566
1791
|
export async function quickStart(config = {}, options = {}) {
|
|
1567
1792
|
const agent = new MCPAgentClient(options);
|