Skip to content

Commit 14a70c5

Browse files
committed
added mcp server to cli
1 parent f02d248 commit 14a70c5

File tree

4 files changed

+520
-23
lines changed

4 files changed

+520
-23
lines changed

src/cli.ts

Lines changed: 184 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import {
66
ApiConfiguration,
77
createApiClient,
88
BaseToolExecutor,
9-
CliContextProvider,
109
MessageParser,
1110
AVAILABLE_TOOLS,
1211
ToolResponse,
1312
ApiClient
1413
} from './lib';
14+
import { McpClient } from './lib/mcp/McpClient';
1515
import { Anthropic } from '@anthropic-ai/sdk';
1616

1717
function formatToolResponse(response: ToolResponse): string {
@@ -54,10 +54,12 @@ class CliToolExecutor extends BaseToolExecutor {
5454

5555
async function main() {
5656
const args = process.argv.slice(2);
57-
const task = args[0];
57+
const toolsFlag = args.includes('--tools');
58+
const debugFlag = args.includes('--debug');
59+
const task = args.find(arg => !['--tools', '--debug'].includes(arg));
5860

5961
if (!task) {
60-
console.error('Usage: cline "My Task" [--tools]');
62+
console.error('Usage: cline "My Task" [--tools] [--debug]');
6163
process.exit(1);
6264
}
6365

@@ -75,12 +77,66 @@ async function main() {
7577

7678
const cwd = process.cwd();
7779
const toolExecutor = new CliToolExecutor(cwd);
78-
const contextProvider = new CliContextProvider(cwd);
7980
const messageParser = new MessageParser(AVAILABLE_TOOLS);
8081
const apiClient = createApiClient(apiConfiguration);
82+
const mcpClient = new McpClient();
83+
84+
// Initialize MCP client early if --tools flag is present
85+
if (toolsFlag) {
86+
console.log('Loading available tools...');
87+
try {
88+
await mcpClient.initializeServers();
89+
// Get built-in tools documentation
90+
const toolDocs = AVAILABLE_TOOLS.map(tool => {
91+
const params = Object.entries(tool.parameters)
92+
.map(([name, param]) => '- ' + name + ': (' + (param.required ? 'required' : 'optional') + ') ' + param.description)
93+
.join('\n');
94+
return '## ' + tool.name + '\nDescription: ' + tool.description + '\nParameters:\n' + params;
95+
}).join('\n\n');
96+
97+
// Get MCP server tools
98+
const mcpTools = [];
99+
mcpTools.push('\nMCP SERVERS\n');
100+
mcpTools.push('\nThe Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities.\n');
101+
mcpTools.push('\n# Connected MCP Servers\n');
102+
mcpTools.push('\nWhen a server is connected, you can use the server\'s tools via the `use_mcp_tool` tool, and access the server\'s resources via the `access_mcp_resource` tool.\n');
103+
104+
// Get list of available servers
105+
const availableServers = mcpClient.getAvailableServers();
106+
107+
mcpTools.push(`\nCurrently connected servers: ${availableServers.join(', ') || 'None'}\n`);
108+
mcpTools.push('\nExample usage:\n');
109+
mcpTools.push('```xml');
110+
mcpTools.push('<use_mcp_tool>');
111+
mcpTools.push('<server_name>mcp-rand</server_name>');
112+
mcpTools.push('<tool_name>generate_uuid</tool_name>');
113+
mcpTools.push('<arguments>');
114+
mcpTools.push('{}');
115+
mcpTools.push('</arguments>');
116+
mcpTools.push('</use_mcp_tool>');
117+
mcpTools.push('```\n');
118+
119+
const serverTools = await mcpClient.getServerTools();
120+
if (serverTools.length > 0) {
121+
mcpTools.push(...serverTools);
122+
} else {
123+
mcpTools.push('\n(No MCP servers currently connected)');
124+
}
125+
126+
console.log('\nAVAILABLE TOOLS\n');
127+
console.log(toolDocs);
128+
console.log(mcpTools.filter(Boolean).join(''));
129+
process.exit(0);
130+
} catch (error) {
131+
console.error('Error loading tools:', error);
132+
process.exit(1);
133+
}
134+
}
81135

82136
try {
83-
await initiateTaskLoop(task, toolExecutor, contextProvider, messageParser, apiClient);
137+
// Initialize MCP servers before starting the task loop
138+
await mcpClient.initializeServers();
139+
await initiateTaskLoop(task, toolExecutor, messageParser, apiClient, mcpClient, debugFlag);
84140
} catch (error) {
85141
console.error('Error:', error.message);
86142
process.exit(1);
@@ -90,22 +146,65 @@ async function main() {
90146
async function initiateTaskLoop(
91147
task: string,
92148
toolExecutor: CliToolExecutor,
93-
contextProvider: CliContextProvider,
94149
messageParser: MessageParser,
95-
apiClient: ApiClient
150+
apiClient: ApiClient,
151+
mcpClient: McpClient,
152+
debugFlag: boolean
96153
) {
97154
let history: Anthropic.MessageParam[] = [];
98155
let includeFileDetails = true;
99156

100157
while (true) {
101-
const envDetails = await contextProvider.getEnvironmentDetails(includeFileDetails);
158+
const envDetails = `Current Working Directory: ${process.cwd()}`;
159+
160+
// Get built-in tools documentation
102161
const toolDocs = AVAILABLE_TOOLS.map(tool => {
103162
const params = Object.entries(tool.parameters)
104163
.map(([name, param]) => '- ' + name + ': (' + (param.required ? 'required' : 'optional') + ') ' + param.description)
105164
.join('\n');
106165
return '## ' + tool.name + '\nDescription: ' + tool.description + '\nParameters:\n' + params;
107166
}).join('\n\n');
108167

168+
// Get MCP server tools
169+
const mcpTools = [];
170+
mcpTools.push('\nMCP SERVERS\n');
171+
mcpTools.push('\nThe Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities.\n');
172+
mcpTools.push('\n# Connected MCP Servers\n');
173+
mcpTools.push('\nWhen a server is connected, you can use the server\'s tools via the `use_mcp_tool` tool, and access the server\'s resources via the `access_mcp_resource` tool.\n');
174+
175+
// Get list of available servers
176+
const availableServers = mcpClient.getAvailableServers();
177+
178+
mcpTools.push(`\nCurrently connected servers: ${availableServers.join(', ') || 'None'}\n`);
179+
mcpTools.push('\nExample usage:\n');
180+
mcpTools.push('```xml');
181+
mcpTools.push('<use_mcp_tool>');
182+
mcpTools.push('<server_name>mcp-rand</server_name>');
183+
mcpTools.push('<tool_name>generate_uuid</tool_name>');
184+
mcpTools.push('<arguments>');
185+
mcpTools.push('{}');
186+
mcpTools.push('</arguments>');
187+
mcpTools.push('</use_mcp_tool>');
188+
mcpTools.push('```\n');
189+
190+
const serverTools = await mcpClient.getServerTools();
191+
if (serverTools.length > 0) {
192+
mcpTools.push(...serverTools);
193+
} else {
194+
mcpTools.push('\n(No MCP servers currently connected)');
195+
}
196+
197+
// Get the last tool result if any
198+
const lastMessage = history.length >= 2 ? history[history.length - 1] : null;
199+
let previousResult: string | null = null;
200+
201+
if (lastMessage && typeof lastMessage.content === 'string') {
202+
const content = lastMessage.content;
203+
if (content.startsWith('[') && content.includes('] Result: ')) {
204+
previousResult = content.substring(content.indexOf('] Result: ') + 9);
205+
}
206+
}
207+
109208
const systemPromptParts = [
110209
'You are Cline, a highly skilled software engineer.',
111210
'',
@@ -115,16 +214,24 @@ async function initiateTaskLoop(
115214
'',
116215
toolDocs,
117216
'',
217+
...mcpTools.filter(Boolean),
218+
'',
118219
'RULES',
119220
'',
120-
'1. Use one tool at a time',
221+
'1. YOU MUST use exactly one tool in each response. If this is the final response, you must use the `attempt_completion` tool.',
121222
'2. Wait for tool execution results before proceeding',
122223
'3. Handle errors appropriately',
123224
'4. Document your changes',
124225
'',
125226
'TASK',
126227
'',
127-
task
228+
task,
229+
'',
230+
...(previousResult ? [
231+
'PREVIOUS RESULT',
232+
'',
233+
previousResult
234+
] : [])
128235
];
129236

130237
const systemPrompt = systemPromptParts.join('\n');
@@ -141,7 +248,11 @@ async function initiateTaskLoop(
141248
let didAlreadyUseTool = false;
142249
let assistantMessage = '';
143250

144-
console.log(chalk.yellow(`[DEBUG] System prompt:\n${systemPrompt}`));
251+
if (debugFlag) {
252+
console.log(chalk.yellow(`[DEBUG] System prompt:\n${systemPrompt}`));
253+
}
254+
255+
console.log(chalk.gray('Thinking...'));
145256
for await (const chunk of apiClient.createMessage(systemPrompt, history)) {
146257
if (chunk.type === 'text' && chunk.text) {
147258
assistantMessage += chunk.text;
@@ -163,44 +274,89 @@ async function initiateTaskLoop(
163274
let result: ToolResponse = '';
164275

165276
switch (toolUse.name) {
166-
case 'write_to_file':
277+
case 'write_to_file': {
167278
[error, result] = await toolExecutor.writeFile(
168279
toolUse.params.path,
169280
toolUse.params.content,
170281
parseInt(toolUse.params.line_count)
171282
);
172283
break;
173-
case 'read_file':
284+
}
285+
case 'read_file': {
174286
[error, result] = await toolExecutor.readFile(toolUse.params.path);
175287
break;
176-
case 'list_files':
288+
}
289+
case 'list_files': {
290+
const recursive = toolUse.params.recursive === 'true';
177291
[error, result] = await toolExecutor.listFiles(
178292
toolUse.params.path,
179-
toolUse.params.recursive === 'true'
293+
recursive
180294
);
181295
break;
182-
case 'search_files':
296+
}
297+
case 'search_files': {
183298
[error, result] = await toolExecutor.searchFiles(
184299
toolUse.params.path,
185300
toolUse.params.regex,
186301
toolUse.params.file_pattern
187302
);
188303
break;
189-
case 'execute_command':
304+
}
305+
case 'execute_command': {
190306
[error, result] = await toolExecutor.executeCommand(toolUse.params.command);
191307
break;
192-
case 'attempt_completion':
308+
}
309+
case 'attempt_completion': {
193310
console.log(chalk.green(toolUse.params.result));
194311
if (toolUse.params.command) {
195312
[error, result] = await toolExecutor.executeCommand(toolUse.params.command);
196313
}
197-
return;
198-
case 'list_code_definition_names':
314+
// Add result to history before returning
315+
if (result) {
316+
history.push(
317+
{ role: 'assistant', content: assistantMessage },
318+
{ role: 'user', content: `[${toolUse.name}] Result: ${formatToolResponse(result)}` }
319+
);
320+
}
321+
process.exit(0);
322+
}
323+
case 'list_code_definition_names': {
199324
[error, result] = await toolExecutor.listCodeDefinitions(toolUse.params.path);
200325
break;
201-
default:
326+
}
327+
case 'use_mcp_tool': {
328+
try {
329+
const mcpResult = await mcpClient.callTool(
330+
toolUse.params.server_name,
331+
toolUse.params.tool_name,
332+
JSON.parse(toolUse.params.arguments || '{}')
333+
);
334+
error = mcpResult.isError === true;
335+
result = mcpResult.content.map(item => item.text).join('\n');
336+
} catch (err) {
337+
error = true;
338+
result = `Error executing MCP tool: ${err.message}`;
339+
}
340+
break;
341+
}
342+
case 'access_mcp_resource': {
343+
try {
344+
const mcpResult = await mcpClient.readResource(
345+
toolUse.params.server_name,
346+
toolUse.params.uri
347+
);
348+
error = false;
349+
result = mcpResult.contents.map(item => item.text).join('\n');
350+
} catch (err) {
351+
error = true;
352+
result = `Error accessing MCP resource: ${err.message}`;
353+
}
354+
break;
355+
}
356+
default: {
202357
error = true;
203358
result = `Unknown tool: ${toolUse.name}`;
359+
}
204360
}
205361

206362
// Mark that we've used a tool and add the result to history
@@ -210,7 +366,9 @@ async function initiateTaskLoop(
210366
{ role: 'user', content: `[${toolUse.name}] Result: ${formatToolResponse(result)}` }
211367
);
212368
// Log full response in yellow for debugging
213-
console.log(chalk.yellow(`[DEBUG] Full response:\n${assistantMessage}`));
369+
if (debugFlag) {
370+
console.log(chalk.yellow(`[DEBUG] Full response:\n${assistantMessage}`));
371+
}
214372
continue;
215373
}
216374
}
@@ -222,13 +380,16 @@ async function initiateTaskLoop(
222380
{ role: 'user', content: 'You responded with only text but have not called attempt_completion yet. Please either use a tool to proceed with the task or call attempt_completion if the task is complete.' }
223381
);
224382
// Log full response in yellow for debugging
225-
console.log(chalk.yellow(`[DEBUG] Full response:\n${assistantMessage}`));
383+
if (debugFlag) {
384+
console.log(chalk.yellow(`[DEBUG] Full response:\n${assistantMessage}`));
385+
}
226386
}
227387

228388
// Only include file details in first iteration
229389
includeFileDetails = false;
230390
}
231391
}
392+
232393
main().catch(error => {
233394
console.error('Fatal error:', error);
234395
process.exit(1);

0 commit comments

Comments
 (0)