aiexecode 1.0.88 → 1.0.90
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 -20
- package/mcp-agent-lib/.claude/settings.local.json +9 -0
- package/mcp-agent-lib/example/01-basic-usage.js +82 -0
- package/mcp-agent-lib/example/02-quick-start.js +52 -0
- package/mcp-agent-lib/example/03-http-server.js +76 -0
- package/mcp-agent-lib/example/04-multiple-servers.js +117 -0
- package/mcp-agent-lib/example/05-error-handling.js +116 -0
- package/mcp-agent-lib/example/06-resources-and-prompts.js +174 -0
- package/mcp-agent-lib/example/07-advanced-configuration.js +191 -0
- package/mcp-agent-lib/example/08-real-world-chatbot.js +331 -0
- package/mcp-agent-lib/example/README.md +346 -0
- package/mcp-agent-lib/sampleMCPHost/index.js +267 -0
- package/mcp-agent-lib/sampleMCPHost/mcp_config.json +18 -0
- 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/{dp582oDmc4bDYYIktREJ4 → w4dMVYalgk7djrLxRxWiE}/_buildManifest.js +0 -0
- /package/payload_viewer/out/_next/static/{dp582oDmc4bDYYIktREJ4 → w4dMVYalgk7djrLxRxWiE}/_clientMiddlewareManifest.json +0 -0
- /package/payload_viewer/out/_next/static/{dp582oDmc4bDYYIktREJ4 → w4dMVYalgk7djrLxRxWiE}/_ssgManifest.js +0 -0
package/src/frontend/App.js
CHANGED
|
@@ -803,7 +803,7 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
|
|
|
803
803
|
nextItem
|
|
804
804
|
});
|
|
805
805
|
})
|
|
806
|
-
|
|
806
|
+
, []);
|
|
807
807
|
|
|
808
808
|
const renderHistoryItem = useCallback((item, index, prefix = 'history', allItems = []) => {
|
|
809
809
|
const nextItem = allItems[index + 1];
|
|
@@ -1027,16 +1027,66 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
|
|
|
1027
1027
|
);
|
|
1028
1028
|
}
|
|
1029
1029
|
|
|
1030
|
-
return React.createElement(Box, { flexDirection: "column", padding:
|
|
1030
|
+
return React.createElement(Box, { flexDirection: "column", padding: 0 },
|
|
1031
1031
|
// History area (grows to fill available space, shrinks when needed)
|
|
1032
1032
|
React.createElement(Box, {
|
|
1033
1033
|
flexDirection: "column",
|
|
1034
1034
|
flexGrow: 1,
|
|
1035
1035
|
flexShrink: 1,
|
|
1036
|
-
overflow: 'hidden' // Prevent history from pushing out fixed elements
|
|
1036
|
+
overflow: 'hidden', // Prevent history from pushing out fixed elements
|
|
1037
|
+
gap: 0 // No gap between children
|
|
1037
1038
|
},
|
|
1039
|
+
// React.createElement(BlankLine, {
|
|
1040
|
+
// reason: 'separator between static and dynamic',
|
|
1041
|
+
// afterType: 'static_area',
|
|
1042
|
+
// beforeType: 'dynamic_area',
|
|
1043
|
+
// content: '---DYNAMIC1---'
|
|
1044
|
+
// }),
|
|
1045
|
+
|
|
1046
|
+
// ===== Static 영역 시작 =====
|
|
1038
1047
|
// Static items: Header와 히스토리 포함
|
|
1039
|
-
React.createElement(
|
|
1048
|
+
React.createElement(Box, { marginBottom: 0, paddingBottom: 0 },
|
|
1049
|
+
React.createElement(Static, { items: staticItems }, (item) => item)
|
|
1050
|
+
),
|
|
1051
|
+
// ===== Static 영역 끝 =====
|
|
1052
|
+
|
|
1053
|
+
// Static과 Dynamic 사이 구분선
|
|
1054
|
+
// React.createElement(BlankLine, {
|
|
1055
|
+
// reason: 'separator between static and dynamic',
|
|
1056
|
+
// afterType: 'static_area',
|
|
1057
|
+
// beforeType: 'dynamic_area',
|
|
1058
|
+
// content: '---DYNAMIC2---'
|
|
1059
|
+
// }),
|
|
1060
|
+
|
|
1061
|
+
// ===== Dynamic 영역 시작 =====
|
|
1062
|
+
// Dynamic pending items: 실행 중인 도구들을 즉시 표시
|
|
1063
|
+
pendingHistory.length > 0 && pendingHistory.flatMap((item, index) => {
|
|
1064
|
+
const nextItem = pendingHistory[index + 1];
|
|
1065
|
+
const elements = [
|
|
1066
|
+
React.createElement(ConversationItem, {
|
|
1067
|
+
key: `pending-${index}`,
|
|
1068
|
+
item,
|
|
1069
|
+
terminalWidth,
|
|
1070
|
+
nextItem,
|
|
1071
|
+
isPending: true // Pending 상태 전달
|
|
1072
|
+
})
|
|
1073
|
+
];
|
|
1074
|
+
|
|
1075
|
+
// 각 pending 아이템 뒤에 BlankLine 추가
|
|
1076
|
+
elements.push(
|
|
1077
|
+
React.createElement(BlankLine, {
|
|
1078
|
+
key: `pending-blank-${index}`,
|
|
1079
|
+
reason: 'after pending item',
|
|
1080
|
+
afterType: item.type,
|
|
1081
|
+
afterToolName: item.toolName,
|
|
1082
|
+
beforeType: nextItem ? nextItem.type : null,
|
|
1083
|
+
content: ' '
|
|
1084
|
+
})
|
|
1085
|
+
);
|
|
1086
|
+
|
|
1087
|
+
return elements;
|
|
1088
|
+
})
|
|
1089
|
+
// ===== Dynamic 영역 끝 =====
|
|
1040
1090
|
),
|
|
1041
1091
|
|
|
1042
1092
|
// Fixed input/control area (does not shrink, stays at bottom)
|
|
@@ -47,14 +47,16 @@ import { createDebugLogger } from '../../util/debug_log.js';
|
|
|
47
47
|
|
|
48
48
|
const debugLog = createDebugLogger('ui_components.log', 'BlankLine');
|
|
49
49
|
|
|
50
|
-
export function BlankLine({ reason, afterType, afterToolName, beforeType }) {
|
|
50
|
+
export function BlankLine({ reason, afterType, afterToolName, beforeType, content = ' ' }) {
|
|
51
|
+
// if (content === ' ') content = 'aaaaa';
|
|
51
52
|
// 디버그 모드에서만 빈 줄 정보 로깅
|
|
52
53
|
React.useEffect(() => {
|
|
53
|
-
debugLog(`[BlankLine] Rendered - reason: ${reason}, after: ${afterType}${afterToolName ? `(${afterToolName})` : ''}, before: ${beforeType || 'N/A'}`);
|
|
54
|
+
debugLog(`[BlankLine] Rendered - reason: ${reason}, after: ${afterType}${afterToolName ? `(${afterToolName})` : ''}, before: ${beforeType || 'N/A'}, content: ${content}`);
|
|
54
55
|
}, []);
|
|
55
56
|
|
|
56
57
|
// 빈 줄은 공백 문자를 포함한 Text로 렌더링
|
|
57
58
|
// Ink의 flexDirection: 'column'에서 각 컴포넌트는 한 줄씩 차지하므로
|
|
58
59
|
// Text(' ')는 한 줄의 빈 줄로 렌더링됨
|
|
59
|
-
|
|
60
|
+
// content prop으로 표시할 내용을 커스터마이징 가능
|
|
61
|
+
return React.createElement(Text, null, content);
|
|
60
62
|
}
|
|
@@ -79,11 +79,11 @@ const TYPE_CONFIG = {
|
|
|
79
79
|
assistant_progress: { icon: ' ', color: theme.text.secondary, bold: false }, // 중간 안내 메시지
|
|
80
80
|
system: { icon: '* ', color: theme.text.secondary, bold: false },
|
|
81
81
|
error: { icon: '✗ ', color: theme.status.error, bold: true },
|
|
82
|
-
tool: { icon: '
|
|
83
|
-
tool_start: { icon: '
|
|
82
|
+
tool: { icon: '• ', color: theme.status.success, bold: false },
|
|
83
|
+
tool_start: { icon: '• ', color: theme.status.success, bold: true },
|
|
84
84
|
tool_result: { icon: ' ㄴ', color: theme.text.secondary, bold: false },
|
|
85
85
|
thinking: { icon: '◇ ', color: theme.text.link, bold: false },
|
|
86
|
-
code_execution: { icon: '
|
|
86
|
+
code_execution: { icon: '• ', color: theme.status.warning, bold: false },
|
|
87
87
|
code_result: { icon: '◀ ', color: theme.status.success, bold: false },
|
|
88
88
|
default: { icon: '• ', color: theme.text.primary, bold: false }
|
|
89
89
|
};
|
|
@@ -92,7 +92,7 @@ function getTypeConfig(type) {
|
|
|
92
92
|
return TYPE_CONFIG[type] || TYPE_CONFIG.default;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
function CodeExecutionDisplay({ item, hasFollowingResult, nextItem }) {
|
|
95
|
+
function CodeExecutionDisplay({ item, hasFollowingResult, nextItem, isPending }) {
|
|
96
96
|
const config = getTypeConfig(item.type);
|
|
97
97
|
|
|
98
98
|
const languageName = item.language.charAt(0).toUpperCase() + item.language.slice(1);
|
|
@@ -101,6 +101,19 @@ function CodeExecutionDisplay({ item, hasFollowingResult, nextItem }) {
|
|
|
101
101
|
const resultStderr = item.result?.stderr || nextItem?.stderr;
|
|
102
102
|
const isDenied = resultStderr?.includes('User denied code execution');
|
|
103
103
|
|
|
104
|
+
// Blinking icon for pending state
|
|
105
|
+
const [showIcon, setShowIcon] = useState(true);
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (isPending) {
|
|
108
|
+
const interval = setInterval(() => {
|
|
109
|
+
setShowIcon(prev => !prev);
|
|
110
|
+
}, 500); // 0.5초 간격으로 깜빡임
|
|
111
|
+
return () => clearInterval(interval);
|
|
112
|
+
} else {
|
|
113
|
+
setShowIcon(true);
|
|
114
|
+
}
|
|
115
|
+
}, [isPending]);
|
|
116
|
+
|
|
104
117
|
/**
|
|
105
118
|
* 간격: marginBottom 항상 0
|
|
106
119
|
* 빈 줄은 BlankLine 컴포넌트로 관리됨
|
|
@@ -109,10 +122,13 @@ function CodeExecutionDisplay({ item, hasFollowingResult, nextItem }) {
|
|
|
109
122
|
*/
|
|
110
123
|
const marginBottom = 0;
|
|
111
124
|
|
|
125
|
+
// Pending 상태일 때는 회색, 아니면 기본 색상
|
|
126
|
+
const iconColor = isPending ? theme.text.secondary : config.color;
|
|
127
|
+
|
|
112
128
|
if (isDenied) {
|
|
113
129
|
return React.createElement(Box, { flexDirection: "column", marginBottom, marginLeft: 2 },
|
|
114
130
|
React.createElement(Box, { flexDirection: "row", marginBottom: 0 },
|
|
115
|
-
React.createElement(Text, { color:
|
|
131
|
+
React.createElement(Text, { color: iconColor, bold: true }, showIcon ? config.icon : ' '),
|
|
116
132
|
React.createElement(Text, { color: theme.text.primary }, 'Executing '),
|
|
117
133
|
React.createElement(Text, { color: theme.status.warning, bold: true }, languageName)
|
|
118
134
|
),
|
|
@@ -124,7 +140,7 @@ function CodeExecutionDisplay({ item, hasFollowingResult, nextItem }) {
|
|
|
124
140
|
|
|
125
141
|
return React.createElement(Box, { flexDirection: "column", marginBottom, marginLeft: 2 },
|
|
126
142
|
React.createElement(Box, { flexDirection: "row", marginBottom: 0 },
|
|
127
|
-
React.createElement(Text, { color:
|
|
143
|
+
React.createElement(Text, { color: iconColor, bold: true }, showIcon ? config.icon : ' '),
|
|
128
144
|
React.createElement(Text, { color: theme.text.primary }, 'Executing '),
|
|
129
145
|
React.createElement(Text, { color: theme.status.warning, bold: true }, languageName),
|
|
130
146
|
// React.createElement(Text, { color: theme.text.primary }, ' code:')
|
|
@@ -177,6 +193,19 @@ function StandardDisplay({ item, isPending, hasFollowingResult, nextItem, isLast
|
|
|
177
193
|
const { type, text, operations = [], toolName, toolInput, args } = item;
|
|
178
194
|
const config = getTypeConfig(type);
|
|
179
195
|
|
|
196
|
+
// Blinking icon for pending tool_start
|
|
197
|
+
const [showIcon, setShowIcon] = useState(true);
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
if (isPending && type === 'tool_start') {
|
|
200
|
+
const interval = setInterval(() => {
|
|
201
|
+
setShowIcon(prev => !prev);
|
|
202
|
+
}, 500); // 0.5초 간격으로 깜빡임
|
|
203
|
+
return () => clearInterval(interval);
|
|
204
|
+
} else {
|
|
205
|
+
setShowIcon(true);
|
|
206
|
+
}
|
|
207
|
+
}, [isPending, type]);
|
|
208
|
+
|
|
180
209
|
debugLog('---------- StandardDisplay START ----------');
|
|
181
210
|
debugLog(`type: ${type}, toolName: ${toolName || 'N/A'}`);
|
|
182
211
|
debugLog(`text: ${typeof text === 'string' ? text?.substring(0, 100) : JSON.stringify(text)?.substring(0, 100) || 'N/A'}...`);
|
|
@@ -727,13 +756,17 @@ function StandardDisplay({ item, isPending, hasFollowingResult, nextItem, isLast
|
|
|
727
756
|
|
|
728
757
|
// Check if this tool was denied by user
|
|
729
758
|
const originalResult = item.result?.originalResult || nextItem?.result?.originalResult;
|
|
730
|
-
const isDenied = originalResult?.operation_successful === false &&
|
|
759
|
+
const isDenied = originalResult?.operation_successful === false &&
|
|
731
760
|
originalResult?.error_message === 'User denied tool execution';
|
|
732
761
|
|
|
762
|
+
// Pending 상태일 때는 회색, 아니면 기본 색상
|
|
763
|
+
const iconColor = isPending ? theme.text.secondary : config.color;
|
|
764
|
+
const displayIcon = showIcon ? config.icon : ' ';
|
|
765
|
+
|
|
733
766
|
if (isDenied) {
|
|
734
767
|
return React.createElement(Box, { flexDirection: "column", marginBottom, marginTop, marginLeft: 2 },
|
|
735
768
|
React.createElement(Box, { flexDirection: "row" },
|
|
736
|
-
React.createElement(Text, { color:
|
|
769
|
+
React.createElement(Text, { color: iconColor, bold: config.bold }, displayIcon),
|
|
737
770
|
React.createElement(Text, { color: 'white', bold: true }, displayName)
|
|
738
771
|
),
|
|
739
772
|
React.createElement(Box, { marginLeft: 2, marginTop: 0 },
|
|
@@ -744,7 +777,7 @@ function StandardDisplay({ item, isPending, hasFollowingResult, nextItem, isLast
|
|
|
744
777
|
|
|
745
778
|
return React.createElement(Box, { flexDirection: "column", marginBottom, marginTop, marginLeft: 2 },
|
|
746
779
|
React.createElement(Box, { flexDirection: "row" },
|
|
747
|
-
React.createElement(Text, { color:
|
|
780
|
+
React.createElement(Text, { color: iconColor, bold: config.bold }, displayIcon),
|
|
748
781
|
React.createElement(Text, {
|
|
749
782
|
color: 'white',
|
|
750
783
|
bold: true
|
|
@@ -824,7 +857,7 @@ export function ConversationItem({ item, isPending = false, terminalWidth, nextI
|
|
|
824
857
|
if (item.type === 'code_execution' && item.code && item.language) {
|
|
825
858
|
const hasFollowingResult = nextItem && nextItem.type === 'code_result';
|
|
826
859
|
debugLog('Rendering CodeExecutionDisplay');
|
|
827
|
-
return React.createElement(CodeExecutionDisplay, { item, hasFollowingResult, nextItem });
|
|
860
|
+
return React.createElement(CodeExecutionDisplay, { item, hasFollowingResult, nextItem, isPending });
|
|
828
861
|
}
|
|
829
862
|
|
|
830
863
|
if (item.type === 'code_result') {
|
|
@@ -225,6 +225,9 @@ export async function execPythonCode(python_code, args = [], timeoutMs = 1200000
|
|
|
225
225
|
debugLog(`[execPythonCode] Args: ${JSON.stringify(args)}`);
|
|
226
226
|
debugLog(`[execPythonCode] Timeout: ${timeoutMs}ms`);
|
|
227
227
|
|
|
228
|
+
// Intentional delay for testing pending state
|
|
229
|
+
await new Promise(resolve => setTimeout(resolve, 13));
|
|
230
|
+
|
|
228
231
|
debugLog(`[execPythonCode] Creating/checking virtual environment...`);
|
|
229
232
|
const venvPath = await makePythonVirtualEnv(process.env.PYTHON_VENV_PATH || "venv");
|
|
230
233
|
|
|
@@ -329,6 +332,9 @@ export async function execShellScript(script, timeoutMs = 1200000) {
|
|
|
329
332
|
debugLog(`[execShellScript] Script preview (first 200 chars): ${script.substring(0, 200)}${script.length > 200 ? '...' : ''}`);
|
|
330
333
|
debugLog(`[execShellScript] Timeout: ${timeoutMs}ms`);
|
|
331
334
|
|
|
335
|
+
// Intentional delay for testing pending state
|
|
336
|
+
await new Promise(resolve => setTimeout(resolve, 13));
|
|
337
|
+
|
|
332
338
|
debugLog(`[execShellScript] Finding shell executable...`);
|
|
333
339
|
let shellPath = await whichCommand("bash") || await whichCommand("sh");
|
|
334
340
|
if (!shellPath) {
|
|
@@ -15,8 +15,7 @@ const debugLog = createDebugLogger('mcp_integration.log', 'mcp_integration');
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
export class MCPIntegration {
|
|
18
|
-
constructor(
|
|
19
|
-
this.projectRoot = projectRoot;
|
|
18
|
+
constructor() {
|
|
20
19
|
this.mcpAgent = null;
|
|
21
20
|
this.isInitialized = false;
|
|
22
21
|
this.mcpTools = new Map(); // toolName -> { server, schema, execute }
|
|
@@ -27,61 +26,84 @@ export class MCPIntegration {
|
|
|
27
26
|
*/
|
|
28
27
|
async initialize() {
|
|
29
28
|
try {
|
|
30
|
-
debugLog('MCP Integration initializing...');
|
|
29
|
+
debugLog('[MCP] Integration initializing...');
|
|
31
30
|
|
|
32
|
-
// 전역 설정 로드
|
|
31
|
+
// MCP 서버 설정 파일(~/.aiexe/mcp_config.json)에서 전역 설정 로드
|
|
32
|
+
debugLog('[MCP] Loading MCP config from ~/.aiexe/mcp_config.json');
|
|
33
33
|
const mergedServers = await loadMergedMcpConfig();
|
|
34
34
|
|
|
35
|
-
//
|
|
35
|
+
// MCP 서버가 설정되지 않은 경우 초기화 중단
|
|
36
36
|
if (Object.keys(mergedServers).length === 0) {
|
|
37
|
-
debugLog('No MCP servers configured.');
|
|
38
|
-
debugLog(' Use "aiexecode mcp add" to configure servers.');
|
|
37
|
+
debugLog('[MCP] No MCP servers configured.');
|
|
38
|
+
debugLog('[MCP] Use "aiexecode mcp add" to configure servers.');
|
|
39
39
|
return { success: false, reason: 'no_servers' };
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
debugLog(`[MCP] Found ${Object.keys(mergedServers).length} server(s) in config: ${Object.keys(mergedServers).join(', ')}`);
|
|
43
|
+
|
|
44
|
+
// 각 서버 설정의 환경 변수 확장 (${VAR} 형식을 실제 값으로 치환)
|
|
45
|
+
debugLog('[MCP] Expanding environment variables in server configs...');
|
|
43
46
|
const servers = {};
|
|
44
47
|
for (const [name, config] of Object.entries(mergedServers)) {
|
|
45
48
|
try {
|
|
49
|
+
debugLog(`[MCP] Processing server '${name}' (type: ${config.type})`);
|
|
46
50
|
servers[name] = expandEnvVars(config);
|
|
51
|
+
debugLog(`[MCP] ✓ Server '${name}' config ready`);
|
|
47
52
|
} catch (error) {
|
|
48
|
-
debugLog(`Error expanding env vars for server '${name}': ${error.message}`);
|
|
49
|
-
debugLog(` Skipping server '${name}'`);
|
|
53
|
+
debugLog(`[MCP] ✗ Error expanding env vars for server '${name}': ${error.message}`);
|
|
54
|
+
debugLog(`[MCP] Skipping server '${name}'`);
|
|
50
55
|
continue;
|
|
51
56
|
}
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
if (Object.keys(servers).length === 0) {
|
|
55
|
-
debugLog('No valid MCP servers after configuration processing.');
|
|
60
|
+
debugLog('[MCP] No valid MCP servers after configuration processing.');
|
|
56
61
|
return { success: false, reason: 'no_valid_servers' };
|
|
57
62
|
}
|
|
58
63
|
|
|
59
|
-
|
|
64
|
+
debugLog(`[MCP] ${Object.keys(servers).length} server(s) ready for connection`);
|
|
65
|
+
|
|
66
|
+
// mcp-agent-lib에 전달할 설정 객체 생성
|
|
60
67
|
const config = {
|
|
61
68
|
mcpServers: servers
|
|
62
69
|
};
|
|
63
70
|
|
|
64
|
-
// mcp-agent-lib를
|
|
71
|
+
// mcp-agent-lib의 createMCPAgent()를 호출하여 MCP Agent 인스턴스 생성
|
|
72
|
+
// MCP Agent는 여러 MCP 서버와의 연결 및 통신을 관리하는 핵심 객체
|
|
73
|
+
debugLog('[MCP] Creating MCP Agent instance via mcp-agent-lib...');
|
|
74
|
+
const logLevel = process.env.MCP_LOG_LEVEL || 'info';
|
|
75
|
+
const consoleDebug = process.env.MCP_DEBUG === 'true';
|
|
76
|
+
debugLog(`[MCP] Log level: ${logLevel}, Console debug: ${consoleDebug}`);
|
|
77
|
+
|
|
65
78
|
this.mcpAgent = await createMCPAgent({
|
|
66
|
-
logLevel:
|
|
67
|
-
enableConsoleDebug:
|
|
79
|
+
logLevel: logLevel,
|
|
80
|
+
enableConsoleDebug: consoleDebug
|
|
68
81
|
});
|
|
82
|
+
debugLog('[MCP] MCP Agent instance created');
|
|
69
83
|
|
|
70
|
-
// MCP Agent
|
|
84
|
+
// MCP Agent 초기화: 설정된 모든 MCP 서버에 연결 시도
|
|
85
|
+
// stdio, http, sse 등 다양한 전송 방식을 지원
|
|
86
|
+
debugLog('[MCP] Initializing MCP Agent and connecting to servers...');
|
|
71
87
|
await this.mcpAgent.initialize(config);
|
|
88
|
+
debugLog('[MCP] MCP Agent initialization complete');
|
|
72
89
|
|
|
73
|
-
// 사용 가능한 도구 목록
|
|
90
|
+
// 연결된 모든 MCP 서버로부터 사용 가능한 도구(tool) 목록 조회
|
|
91
|
+
debugLog('[MCP] Fetching available tools from connected servers...');
|
|
74
92
|
const availableTools = this.mcpAgent.getAvailableTools();
|
|
75
93
|
|
|
76
|
-
debugLog(`MCP
|
|
94
|
+
debugLog(`[MCP] Found ${availableTools.length} tool(s) across all servers`);
|
|
77
95
|
|
|
78
|
-
//
|
|
96
|
+
// 각 도구를 내부 맵에 저장하고 AI Agent 시스템에 등록
|
|
97
|
+
debugLog('[MCP] Registering tools to AI Agent system...');
|
|
79
98
|
for (const tool of availableTools) {
|
|
80
|
-
|
|
99
|
+
debugLog(`[MCP] Registering tool '${tool.name}' from server '${tool.server}'`);
|
|
100
|
+
|
|
101
|
+
// AI가 도구의 출처 서버를 명확히 인식할 수 있도록 설명에 서버 이름 추가
|
|
81
102
|
const enhancedDescription = tool.description
|
|
82
103
|
? `[${tool.server} MCP] - ${tool.description}`
|
|
83
104
|
: `MCP tool from ${tool.server}`;
|
|
84
105
|
|
|
106
|
+
// 도구 정보를 mcpTools Map에 저장 (toolName -> { server, schema, execute })
|
|
85
107
|
this.mcpTools.set(tool.name, {
|
|
86
108
|
server: tool.server,
|
|
87
109
|
schema: {
|
|
@@ -93,46 +115,66 @@ export class MCPIntegration {
|
|
|
93
115
|
required: []
|
|
94
116
|
}
|
|
95
117
|
},
|
|
118
|
+
// 도구 실행 함수: MCP Agent를 통해 실제 MCP 서버에 도구 실행 요청 전송
|
|
96
119
|
execute: async (args) => {
|
|
97
|
-
|
|
120
|
+
debugLog(`[MCP] Executing tool '${tool.name}' on server '${tool.server}'`);
|
|
121
|
+
debugLog(`[MCP] Args: ${JSON.stringify(args).substring(0, 200)}`);
|
|
122
|
+
const result = await this.mcpAgent.executeTool(tool.name, args);
|
|
123
|
+
debugLog(`[MCP] Result: success=${result.success}`);
|
|
124
|
+
return result;
|
|
98
125
|
}
|
|
99
126
|
});
|
|
100
127
|
|
|
101
|
-
// UI 표시
|
|
128
|
+
// UI 시스템에 MCP 도구 표시 정보 등록
|
|
102
129
|
registerMCPToolDisplay(tool.name, tool.server);
|
|
103
130
|
|
|
104
|
-
// 승인 시스템에 MCP 도구 등록
|
|
131
|
+
// 도구 승인 시스템에 MCP 도구 등록 (사용자 승인 필요 시 처리)
|
|
105
132
|
registerMCPTool(tool.name, tool.server, tool.description || '');
|
|
106
133
|
|
|
107
|
-
debugLog(`
|
|
134
|
+
debugLog(`[MCP] ✓ Tool '${tool.name}' registered`);
|
|
108
135
|
}
|
|
109
136
|
|
|
110
137
|
this.isInitialized = true;
|
|
138
|
+
|
|
139
|
+
const serverList = Array.from(this.mcpAgent.servers.keys());
|
|
140
|
+
debugLog(`[MCP] ========================================`);
|
|
141
|
+
debugLog(`[MCP] Integration initialized successfully!`);
|
|
142
|
+
debugLog(`[MCP] Servers: ${serverList.join(', ')}`);
|
|
143
|
+
debugLog(`[MCP] Tools: ${availableTools.length}`);
|
|
144
|
+
debugLog(`[MCP] ========================================`);
|
|
145
|
+
|
|
111
146
|
return {
|
|
112
147
|
success: true,
|
|
113
148
|
toolCount: availableTools.length,
|
|
114
|
-
servers:
|
|
149
|
+
servers: serverList
|
|
115
150
|
};
|
|
116
151
|
|
|
117
152
|
} catch (error) {
|
|
118
|
-
debugLog(`MCP
|
|
119
|
-
debugLog(
|
|
153
|
+
debugLog(`[MCP] ========================================`);
|
|
154
|
+
debugLog(`[MCP] Integration initialization FAILED`);
|
|
155
|
+
debugLog(`[MCP] Error: ${error.message}`);
|
|
156
|
+
debugLog(`[MCP] Stack: ${error.stack}`);
|
|
157
|
+
debugLog(`[MCP] Continuing without MCP functionality.`);
|
|
158
|
+
debugLog(`[MCP] ========================================`);
|
|
120
159
|
return { success: false, reason: 'initialization_error', error: error.message };
|
|
121
160
|
}
|
|
122
161
|
}
|
|
123
162
|
|
|
124
163
|
/**
|
|
125
|
-
* MCP 도구
|
|
164
|
+
* MCP 도구 목록을 실행 가능한 함수 형태로 반환
|
|
165
|
+
* AI Agent의 기존 toolMap에 통합하기 위한 형식
|
|
126
166
|
*/
|
|
127
167
|
getToolFunctions() {
|
|
128
168
|
const toolFunctions = {};
|
|
129
169
|
|
|
130
170
|
for (const [toolName, toolInfo] of this.mcpTools) {
|
|
171
|
+
// 각 도구에 대한 실행 함수 생성
|
|
131
172
|
toolFunctions[toolName] = async (args) => {
|
|
132
173
|
try {
|
|
174
|
+
// MCP Agent를 통해 도구 실행 (MCP 프로토콜로 서버와 통신)
|
|
133
175
|
const result = await toolInfo.execute(args);
|
|
134
176
|
|
|
135
|
-
// MCP
|
|
177
|
+
// MCP 서버의 응답을 AI Agent 시스템의 표준 형식으로 변환
|
|
136
178
|
if (result.success) {
|
|
137
179
|
return {
|
|
138
180
|
operation_successful: true,
|
|
@@ -164,20 +206,21 @@ export class MCPIntegration {
|
|
|
164
206
|
}
|
|
165
207
|
|
|
166
208
|
/**
|
|
167
|
-
* MCP 도구 스키마
|
|
209
|
+
* MCP 도구 스키마 목록을 OpenAI API 형식으로 반환
|
|
210
|
+
* Orchestrator가 AI 모델에게 사용 가능한 도구 목록을 전달할 때 사용
|
|
168
211
|
*/
|
|
169
212
|
getToolSchemas() {
|
|
170
213
|
const schemas = [];
|
|
171
214
|
|
|
172
215
|
for (const [toolName, toolInfo] of this.mcpTools) {
|
|
173
|
-
//
|
|
216
|
+
// MCP 서버가 제공한 inputSchema를 OpenAI function calling 형식으로 변환
|
|
174
217
|
const inputSchema = toolInfo.schema.inputSchema || {};
|
|
175
218
|
const properties = inputSchema.properties || {};
|
|
176
219
|
const originalRequired = inputSchema.required || [];
|
|
177
220
|
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
221
|
+
// OpenAI API의 function calling 스키마 형식으로 변환
|
|
222
|
+
// strict: true는 모든 properties가 required에 포함되어야 하는 제약이 있어
|
|
223
|
+
// MCP 도구의 유연성을 위해 strict: false로 설정
|
|
181
224
|
const cleanedSchema = {
|
|
182
225
|
name: toolInfo.schema.name,
|
|
183
226
|
description: toolInfo.schema.description,
|
|
@@ -219,21 +262,29 @@ export class MCPIntegration {
|
|
|
219
262
|
}
|
|
220
263
|
|
|
221
264
|
/**
|
|
222
|
-
* MCP Agent 정리
|
|
265
|
+
* MCP Agent 정리 및 모든 서버 연결 종료
|
|
266
|
+
* 프로그램 종료 시 호출되어 리소스를 정리
|
|
223
267
|
*/
|
|
224
268
|
async cleanup() {
|
|
225
269
|
if (this.mcpAgent) {
|
|
226
270
|
try {
|
|
227
|
-
|
|
271
|
+
debugLog('[MCP] Starting cleanup process...');
|
|
272
|
+
// mcp-agent-lib 버전에 따라 cleanup() 또는 disconnect() 메서드 호출
|
|
273
|
+
// 모든 MCP 서버와의 연결을 정상적으로 종료
|
|
228
274
|
if (typeof this.mcpAgent.cleanup === 'function') {
|
|
275
|
+
debugLog('[MCP] Calling mcpAgent.cleanup()...');
|
|
229
276
|
await this.mcpAgent.cleanup();
|
|
230
277
|
} else if (typeof this.mcpAgent.disconnect === 'function') {
|
|
278
|
+
debugLog('[MCP] Calling mcpAgent.disconnect()...');
|
|
231
279
|
await this.mcpAgent.disconnect();
|
|
232
280
|
}
|
|
233
|
-
debugLog('MCP
|
|
281
|
+
debugLog('[MCP] All MCP server connections terminated successfully');
|
|
234
282
|
} catch (error) {
|
|
235
|
-
debugLog(`Error during
|
|
283
|
+
debugLog(`[MCP] Error during cleanup: ${error.message}`);
|
|
284
|
+
debugLog(`[MCP] Stack: ${error.stack}`);
|
|
236
285
|
}
|
|
286
|
+
} else {
|
|
287
|
+
debugLog('[MCP] No MCP Agent to cleanup (never initialized)');
|
|
237
288
|
}
|
|
238
289
|
}
|
|
239
290
|
}
|
|
@@ -241,10 +292,13 @@ export class MCPIntegration {
|
|
|
241
292
|
/**
|
|
242
293
|
* 전역 MCP Integration 인스턴스 생성 헬퍼
|
|
243
294
|
*/
|
|
244
|
-
export async function initializeMCPIntegration(
|
|
245
|
-
|
|
295
|
+
export async function initializeMCPIntegration() {
|
|
296
|
+
debugLog('[MCP] initializeMCPIntegration() called');
|
|
297
|
+
const integration = new MCPIntegration();
|
|
246
298
|
const result = await integration.initialize();
|
|
247
299
|
|
|
300
|
+
debugLog(`[MCP] initializeMCPIntegration() complete - success: ${result?.success}, reason: ${result?.reason || 'N/A'}`);
|
|
301
|
+
|
|
248
302
|
// 서버가 없어도 객체는 반환 (빈 서버 목록으로 동작)
|
|
249
303
|
return integration;
|
|
250
304
|
}
|
package/src/tools/file_reader.js
CHANGED
|
@@ -32,6 +32,9 @@ export async function read_file({ filePath }) {
|
|
|
32
32
|
debugLog(` - filePath starts with '../': ${filePath?.startsWith('../') || false}`);
|
|
33
33
|
debugLog(` - Current Working Directory: ${process.cwd()}`);
|
|
34
34
|
|
|
35
|
+
// Intentional delay for testing pending state
|
|
36
|
+
await new Promise(resolve => setTimeout(resolve, 13));
|
|
37
|
+
|
|
35
38
|
try {
|
|
36
39
|
// 경로를 절대경로로 정규화
|
|
37
40
|
const absolutePath = resolve(filePath);
|
|
@@ -169,6 +172,9 @@ export async function read_file_range({ filePath, startLine, endLine }) {
|
|
|
169
172
|
debugLog(` endLine: ${endLine}`);
|
|
170
173
|
debugLog(` - Current Working Directory: ${process.cwd()}`);
|
|
171
174
|
|
|
175
|
+
// Intentional delay for testing pending state
|
|
176
|
+
await new Promise(resolve => setTimeout(resolve, 13));
|
|
177
|
+
|
|
172
178
|
try {
|
|
173
179
|
// 경로를 절대경로로 정규화
|
|
174
180
|
const absolutePath = resolve(filePath);
|
package/src/tools/glob.js
CHANGED
|
@@ -40,6 +40,9 @@ export async function globSearch({
|
|
|
40
40
|
debugLog(` maxResults: ${maxResults}`);
|
|
41
41
|
debugLog(` - Current Working Directory: ${process.cwd()}`);
|
|
42
42
|
|
|
43
|
+
// Intentional delay for testing pending state
|
|
44
|
+
await new Promise(resolve => setTimeout(resolve, 13));
|
|
45
|
+
|
|
43
46
|
try {
|
|
44
47
|
if (typeof pattern !== 'string' || !pattern.trim()) {
|
|
45
48
|
debugLog(`ERROR: Invalid pattern`);
|
package/src/tools/ripgrep.js
CHANGED
|
@@ -332,6 +332,8 @@ export async function ripgrep({
|
|
|
332
332
|
head_limit: headLimit = null,
|
|
333
333
|
includeHidden = false
|
|
334
334
|
}) {
|
|
335
|
+
// Intentional delay for testing pending state
|
|
336
|
+
await new Promise(resolve => setTimeout(resolve, 13));
|
|
335
337
|
|
|
336
338
|
if (typeof pattern !== 'string' || !pattern.trim()) {
|
|
337
339
|
return {
|
|
@@ -19,6 +19,9 @@ import { theme } from '../frontend/design/themeColors.js';
|
|
|
19
19
|
* @returns {Promise<{operation_successful: boolean, url: string, content: string, error_message: string|null}>} 결과 객체
|
|
20
20
|
*/
|
|
21
21
|
export async function fetch_web_page({ url }) {
|
|
22
|
+
// Intentional delay for testing pending state
|
|
23
|
+
await new Promise(resolve => setTimeout(resolve, 13));
|
|
24
|
+
|
|
22
25
|
const timeout = 30;
|
|
23
26
|
const user_agent = 'Mozilla/5.0 (compatible; WebFetcher/1.0)';
|
|
24
27
|
const encoding = 'utf-8';
|