auto-coder 0.1.209__py3-none-any.whl → 0.1.211__py3-none-any.whl

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 auto-coder might be problematic. Click here for more details.

@@ -0,0 +1,682 @@
1
+ from typing import Dict, Any, List, Optional, Union
2
+ from byzerllm import prompt
3
+ from ..common.mcp_hub import McpHub
4
+ import json
5
+ import byzerllm
6
+ import re
7
+ from pydantic import BaseModel, Field
8
+ from loguru import logger
9
+ import mcp.types as mcp_types
10
+
11
+
12
+ class McpToolCall(BaseModel):
13
+ server_name: str = Field(..., description="The name of the MCP server")
14
+ tool_name: str = Field(..., description="The name of the tool to call")
15
+ arguments: Dict[str, Any] = Field(
16
+ default_factory=dict, description="The arguments to pass to the tool")
17
+
18
+
19
+ class McpResourceAccess(BaseModel):
20
+ server_name: str = Field(..., description="The name of the MCP server")
21
+ uri: str = Field(..., description="The URI of the resource to access")
22
+
23
+
24
+ class McpExecutor:
25
+ def __init__(self, mcp_hub: McpHub, llm: byzerllm.ByzerLLM):
26
+ self.mcp_hub = mcp_hub
27
+ self.llm = llm
28
+
29
+ def get_server_names(self) -> List[str]:
30
+ """
31
+ Get the names of all connected MCP servers.
32
+
33
+ Returns:
34
+ List of server names
35
+
36
+ """
37
+ server_names = [server.name for server in self.mcp_hub.get_servers()]
38
+ return ",".join(server_names) or "(None running currently)"
39
+
40
+ def get_connected_servers_info(self) -> str:
41
+ """Generate formatted information about connected MCP servers
42
+
43
+ Args:
44
+ mcp_hub: McpHub instance to get server information from
45
+
46
+ Returns:
47
+ Formatted string with server details
48
+ """
49
+ servers = self.mcp_hub.get_servers()
50
+ if not servers:
51
+ return "(No MCP servers currently connected)"
52
+
53
+ info = []
54
+ for server in servers:
55
+ if server.status != "connected":
56
+ continue
57
+
58
+ # Format tools information
59
+ tools_info = []
60
+ if server.tools:
61
+ for tool in server.tools:
62
+ tool_str = f"- {tool.name}: {tool.description}"
63
+ if tool.input_schema:
64
+ schema_str = " Input Schema:\n" + \
65
+ "\n".join(f" {line}" for line in
66
+ json.dumps(tool.input_schema, indent=2).split("\n"))
67
+ tool_str += f"\n{schema_str}"
68
+ tools_info.append(tool_str)
69
+
70
+ # Format resource templates
71
+ templates_info = []
72
+ if server.resource_templates:
73
+ for template in server.resource_templates:
74
+ template_str = f"- {template.uri_template} ({template.name}): {template.description}"
75
+ templates_info.append(template_str)
76
+
77
+ # Format direct resources
78
+ resources_info = []
79
+ if server.resources:
80
+ for resource in server.resources:
81
+ resource_str = f"- {resource.uri} ({resource.name}): {resource.description}"
82
+ resources_info.append(resource_str)
83
+
84
+ # Parse server config
85
+ config = json.loads(server.config)
86
+ command = config['command']
87
+ args = config.get('args', [])
88
+ command_str = f"{command} {' '.join(args)}"
89
+
90
+ # Build server section
91
+ server_info = f"## {server.name} (`{command_str}`)"
92
+ if tools_info:
93
+ server_info += "\n\n### Available Tools\n" + \
94
+ "\n\n".join(tools_info)
95
+ if templates_info:
96
+ server_info += "\n\n### Resource Templates\n" + \
97
+ "\n".join(templates_info)
98
+ if resources_info:
99
+ server_info += "\n\n### Direct Resources\n" + \
100
+ "\n".join(resources_info)
101
+
102
+ info.append(server_info)
103
+
104
+ return "\n\n".join(info)
105
+
106
+ @byzerllm.prompt()
107
+ def mcp_prompt(self) -> str:
108
+ """
109
+ TOOL USE
110
+
111
+ You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
112
+
113
+ # Tool Use Formatting
114
+
115
+ Tool use is formatted using XML-style tags. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure:
116
+
117
+ <tool_name>
118
+ <parameter1_name>value1</parameter1_name>
119
+ <parameter2_name>value2</parameter2_name>
120
+ ...
121
+ </tool_name>
122
+
123
+ For example:
124
+
125
+ <read_file>
126
+ <path>src/main.js</path>
127
+ </read_file>
128
+
129
+ Always adhere to this format for the tool use to ensure proper parsing and execution.
130
+
131
+ # Tools
132
+
133
+ ## use_mcp_tool
134
+ Description: Request to use a tool provided by a connected MCP server. Each MCP server can provide multiple tools with different capabilities. Tools have defined input schemas that specify required and optional parameters.
135
+ Parameters:
136
+ - server_name: (required) The name of the MCP server providing the tool
137
+ - tool_name: (required) The name of the tool to execute
138
+ - arguments: (required) A JSON object containing the tool's input parameters, following the tool's input schema
139
+ Usage:
140
+ <use_mcp_tool>
141
+ <server_name>server name here</server_name>
142
+ <tool_name>tool name here</tool_name>
143
+ <arguments>
144
+ {
145
+ "param1": "value1",
146
+ "param2": "value2"
147
+ }
148
+ </arguments>
149
+ </use_mcp_tool>
150
+
151
+ ## access_mcp_resource
152
+ Description: Request to access a resource provided by a connected MCP server. Resources represent data sources that can be used as context, such as files, API responses, or system information.
153
+ Parameters:
154
+ - server_name: (required) The name of the MCP server providing the resource
155
+ - uri: (required) The URI identifying the specific resource to access
156
+ Usage:
157
+ <access_mcp_resource>
158
+ <server_name>server name here</server_name>
159
+ <uri>resource URI here</uri>
160
+ </access_mcp_resource>
161
+
162
+
163
+ # Tool Use Examples
164
+
165
+ ## Example 1: Requesting to use an MCP tool
166
+
167
+ <use_mcp_tool>
168
+ <server_name>weather-server</server_name>
169
+ <tool_name>get_forecast</tool_name>
170
+ <arguments>
171
+ {
172
+ "city": "San Francisco",
173
+ "days": 5
174
+ }
175
+ </arguments>
176
+ </use_mcp_tool>
177
+
178
+ ## Example 2: Requesting to access an MCP resource
179
+
180
+ <access_mcp_resource>
181
+ <server_name>weather-server</server_name>
182
+ <uri>weather://san-francisco/current</uri>
183
+ </access_mcp_resource>
184
+
185
+
186
+ ====
187
+
188
+ MCP SERVERS
189
+
190
+ The Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities.
191
+
192
+ # Connected MCP Servers
193
+
194
+ When 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.
195
+
196
+ {{ connected_servers_info }}
197
+
198
+ ## Creating an MCP Server
199
+
200
+ The user may ask you something along the lines of "add a tool" that does some function, in other words to create an MCP server that provides tools and resources that may connect to external APIs for example. You have the ability to create an MCP server and add it to a configuration file that will then expose the tools and resources for you to use with `use_mcp_tool` and `access_mcp_resource`.
201
+
202
+ When creating MCP servers, it's important to understand that they operate in a non-interactive environment. The server cannot initiate OAuth flows, open browser windows, or prompt for user input during runtime. All credentials and authentication tokens must be provided upfront through environment variables in the MCP settings configuration. For example, Spotify's API uses OAuth to get a refresh token for the user, but the MCP server cannot initiate this flow. While you can walk the user through obtaining an application client ID and secret, you may have to create a separate one-time setup script (like get-refresh-token.js) that captures and logs the final piece of the puzzle: the user's refresh token (i.e. you might run the script using execute_command which would open a browser for authentication, and then log the refresh token so that you can see it in the command output for you to use in the MCP settings configuration).
203
+
204
+ Unless the user specifies otherwise, new MCP servers should be created in: ${await mcpHub.getMcpServersPath()}
205
+
206
+ ### Example MCP Server
207
+
208
+ For example, if the user wanted to give you the ability to retrieve weather information, you could create an MCP server that uses the OpenWeather API to get weather information, add it to the MCP settings configuration file, and then notice that you now have access to new tools and resources in the system prompt that you might use to show the user your new capabilities.
209
+
210
+ The following example demonstrates how to build an MCP server that provides weather data functionality. While this example shows how to implement resources, resource templates, and tools, in practice you should prefer using tools since they are more flexible and can handle dynamic parameters. The resource and resource template implementations are included here mainly for demonstration purposes of the different MCP capabilities, but a real weather server would likely just expose tools for fetching weather data. (The following steps are for macOS)
211
+
212
+ 1. Use the `create-typescript-server` tool to bootstrap a new project in the default MCP servers directory:
213
+
214
+ ```bash
215
+ cd ${await mcpHub.getMcpServersPath()}
216
+ npx @modelcontextprotocol/create-server weather-server
217
+ cd weather-server
218
+ # Install dependencies
219
+ npm install axios
220
+ ```
221
+
222
+ This will create a new project with the following structure:
223
+
224
+ ```
225
+ weather-server/
226
+ ├── package.json
227
+ {
228
+ ...
229
+ "type": "module", // added by default, uses ES module syntax (import/export) rather than CommonJS (require/module.exports) (Important to know if you create additional scripts in this server repository like a get-refresh-token.js script)
230
+ "scripts": {
231
+ "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
232
+ ...
233
+ }
234
+ ...
235
+ }
236
+ ├── tsconfig.json
237
+ └── src/
238
+ └── weather-server/
239
+ └── index.ts # Main server implementation
240
+ ```
241
+
242
+ 2. Replace `src/index.ts` with the following:
243
+
244
+ ```typescript
245
+ #!/usr/bin/env node
246
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
247
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
248
+ import {
249
+ CallToolRequestSchema,
250
+ ErrorCode,
251
+ ListResourcesRequestSchema,
252
+ ListResourceTemplatesRequestSchema,
253
+ ListToolsRequestSchema,
254
+ McpError,
255
+ ReadResourceRequestSchema,
256
+ } from '@modelcontextprotocol/sdk/types.js';
257
+ import axios from 'axios';
258
+
259
+ const API_KEY = process.env.OPENWEATHER_API_KEY; // provided by MCP config
260
+ if (!API_KEY) {
261
+ throw new Error('OPENWEATHER_API_KEY environment variable is required');
262
+ }
263
+
264
+ interface OpenWeatherResponse {
265
+ main: {
266
+ temp: number;
267
+ humidity: number;
268
+ };
269
+ weather: [{ description: string }];
270
+ wind: { speed: number };
271
+ dt_txt?: string;
272
+ }
273
+
274
+ const isValidForecastArgs = (
275
+ args: any
276
+ ): args is { city: string; days?: number } =>
277
+ typeof args === 'object' &&
278
+ args !== null &&
279
+ typeof args.city === 'string' &&
280
+ (args.days === undefined || typeof args.days === 'number');
281
+
282
+ class WeatherServer {
283
+ private server: Server;
284
+ private axiosInstance;
285
+
286
+ constructor() {
287
+ this.server = new Server(
288
+ {
289
+ name: 'example-weather-server',
290
+ version: '0.1.0',
291
+ },
292
+ {
293
+ capabilities: {
294
+ resources: {},
295
+ tools: {},
296
+ },
297
+ }
298
+ );
299
+
300
+ this.axiosInstance = axios.create({
301
+ baseURL: 'http://api.openweathermap.org/data/2.5',
302
+ params: {
303
+ appid: API_KEY,
304
+ units: 'metric',
305
+ },
306
+ });
307
+
308
+ this.setupResourceHandlers();
309
+ this.setupToolHandlers();
310
+
311
+ // Error handling
312
+ this.server.onerror = (error) => console.error('[MCP Error]', error);
313
+ process.on('SIGINT', async () => {
314
+ await this.server.close();
315
+ process.exit(0);
316
+ });
317
+ }
318
+
319
+ // MCP Resources represent any kind of UTF-8 encoded data that an MCP server wants to make available to clients, such as database records, API responses, log files, and more. Servers define direct resources with a static URI or dynamic resources with a URI template that follows the format `[protocol]://[host]/[path]`.
320
+ private setupResourceHandlers() {
321
+ // For static resources, servers can expose a list of resources:
322
+ this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
323
+ resources: [
324
+ // This is a poor example since you could use the resource template to get the same information but this demonstrates how to define a static resource
325
+ {
326
+ uri: `weather://San Francisco/current`, // Unique identifier for San Francisco weather resource
327
+ name: `Current weather in San Francisco`, // Human-readable name
328
+ mimeType: 'application/json', // Optional MIME type
329
+ // Optional description
330
+ description:
331
+ 'Real-time weather data for San Francisco including temperature, conditions, humidity, and wind speed',
332
+ },
333
+ ],
334
+ }));
335
+
336
+ // For dynamic resources, servers can expose resource templates:
337
+ this.server.setRequestHandler(
338
+ ListResourceTemplatesRequestSchema,
339
+ async () => ({
340
+ resourceTemplates: [
341
+ {
342
+ uriTemplate: 'weather://{city}/current', // URI template (RFC 6570)
343
+ name: 'Current weather for a given city', // Human-readable name
344
+ mimeType: 'application/json', // Optional MIME type
345
+ description: 'Real-time weather data for a specified city', // Optional description
346
+ },
347
+ ],
348
+ })
349
+ );
350
+
351
+ // ReadResourceRequestSchema is used for both static resources and dynamic resource templates
352
+ this.server.setRequestHandler(
353
+ ReadResourceRequestSchema,
354
+ async (request) => {
355
+ const match = request.params.uri.match(
356
+ /^weather:\/\/([^/]+)\/current$/
357
+ );
358
+ if (!match) {
359
+ throw new McpError(
360
+ ErrorCode.InvalidRequest,
361
+ `Invalid URI format: ${request.params.uri}`
362
+ );
363
+ }
364
+ const city = decodeURIComponent(match[1]);
365
+
366
+ try {
367
+ const response = await this.axiosInstance.get(
368
+ 'weather', // current weather
369
+ {
370
+ params: { q: city },
371
+ }
372
+ );
373
+
374
+ return {
375
+ contents: [
376
+ {
377
+ uri: request.params.uri,
378
+ mimeType: 'application/json',
379
+ text: JSON.stringify(
380
+ {
381
+ temperature: response.data.main.temp,
382
+ conditions: response.data.weather[0].description,
383
+ humidity: response.data.main.humidity,
384
+ wind_speed: response.data.wind.speed,
385
+ timestamp: new Date().toISOString(),
386
+ },
387
+ null,
388
+ 2
389
+ ),
390
+ },
391
+ ],
392
+ };
393
+ } catch (error) {
394
+ if (axios.isAxiosError(error)) {
395
+ throw new McpError(
396
+ ErrorCode.InternalError,
397
+ `Weather API error: ${
398
+ error.response?.data.message ?? error.message
399
+ }`
400
+ );
401
+ }
402
+ throw error;
403
+ }
404
+ }
405
+ );
406
+ }
407
+
408
+ /* MCP Tools enable servers to expose executable functionality to the system. Through these tools, you can interact with external systems, perform computations, and take actions in the real world.
409
+ * - Like resources, tools are identified by unique names and can include descriptions to guide their usage. However, unlike resources, tools represent dynamic operations that can modify state or interact with external systems.
410
+ * - While resources and tools are similar, you should prefer to create tools over resources when possible as they provide more flexibility.
411
+ */
412
+ private setupToolHandlers() {
413
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
414
+ tools: [
415
+ {
416
+ name: 'get_forecast', // Unique identifier
417
+ description: 'Get weather forecast for a city', // Human-readable description
418
+ inputSchema: {
419
+ // JSON Schema for parameters
420
+ type: 'object',
421
+ properties: {
422
+ city: {
423
+ type: 'string',
424
+ description: 'City name',
425
+ },
426
+ days: {
427
+ type: 'number',
428
+ description: 'Number of days (1-5)',
429
+ minimum: 1,
430
+ maximum: 5,
431
+ },
432
+ },
433
+ required: ['city'], // Array of required property names
434
+ },
435
+ },
436
+ ],
437
+ }));
438
+
439
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
440
+ if (request.params.name !== 'get_forecast') {
441
+ throw new McpError(
442
+ ErrorCode.MethodNotFound,
443
+ `Unknown tool: ${request.params.name}`
444
+ );
445
+ }
446
+
447
+ if (!isValidForecastArgs(request.params.arguments)) {
448
+ throw new McpError(
449
+ ErrorCode.InvalidParams,
450
+ 'Invalid forecast arguments'
451
+ );
452
+ }
453
+
454
+ const city = request.params.arguments.city;
455
+ const days = Math.min(request.params.arguments.days || 3, 5);
456
+
457
+ try {
458
+ const response = await this.axiosInstance.get<{
459
+ list: OpenWeatherResponse[];
460
+ }>('forecast', {
461
+ params: {
462
+ q: city,
463
+ cnt: days * 8,
464
+ },
465
+ });
466
+
467
+ return {
468
+ content: [
469
+ {
470
+ type: 'text',
471
+ text: JSON.stringify(response.data.list, null, 2),
472
+ },
473
+ ],
474
+ };
475
+ } catch (error) {
476
+ if (axios.isAxiosError(error)) {
477
+ return {
478
+ content: [
479
+ {
480
+ type: 'text',
481
+ text: `Weather API error: ${
482
+ error.response?.data.message ?? error.message
483
+ }`,
484
+ },
485
+ ],
486
+ isError: true,
487
+ };
488
+ }
489
+ throw error;
490
+ }
491
+ });
492
+ }
493
+
494
+ async run() {
495
+ const transport = new StdioServerTransport();
496
+ await this.server.connect(transport);
497
+ console.error('Weather MCP server running on stdio');
498
+ }
499
+ }
500
+
501
+ const server = new WeatherServer();
502
+ server.run().catch(console.error);
503
+ ```
504
+
505
+ (Remember: This is just an example–you may use different dependencies, break the implementation up into multiple files, etc.)
506
+
507
+ 3. Build and compile the executable JavaScript file
508
+
509
+ ```bash
510
+ npm run build
511
+ ```
512
+
513
+ 4. Whenever you need an environment variable such as an API key to configure the MCP server, walk the user through the process of getting the key. For example, they may need to create an account and go to a developer dashboard to generate the key. Provide step-by-step instructions and URLs to make it easy for the user to retrieve the necessary information. Then use the ask_followup_question tool to ask the user for the key, in this case the OpenWeather API key.
514
+
515
+ 5. Install the MCP Server by adding the MCP server configuration to the settings file located at '${await mcpHub.getMcpSettingsFilePath()}'. The settings file may have other MCP servers already configured, so you would read it first and then add your new server to the existing `mcpServers` object.
516
+
517
+ ```json
518
+ {
519
+ "mcpServers": {
520
+ ...,
521
+ "weather": {
522
+ "command": "node",
523
+ "args": ["/path/to/weather-server/build/index.js"],
524
+ "env": {
525
+ "OPENWEATHER_API_KEY": "user-provided-api-key"
526
+ }
527
+ },
528
+ }
529
+ }
530
+ ```
531
+
532
+ (Note: the user may also ask you to install the MCP server to the Claude desktop app, in which case you would read then modify `~/Library/Application\ Support/Claude/claude_desktop_config.json` on macOS for example. It follows the same format of a top level `mcpServers` object.)
533
+
534
+ 6. After you have edited the MCP settings configuration file, the system will automatically run all the servers and expose the available tools and resources in the 'Connected MCP Servers' section.
535
+
536
+ 7. Now that you have access to these new tools and resources, you may suggest ways the user can command you to invoke them - for example, with this new weather tool now available, you can invite the user to ask "what's the weather in San Francisco?"
537
+
538
+ ## Editing MCP Servers
539
+
540
+ The user may ask to add tools or resources that may make sense to add to an existing MCP server (listed under 'Connected MCP Servers' above: {{ server_names }}, e.g. if it would use the same API. This would be possible if you can locate the MCP server repository on the user's system by looking at the server arguments for a filepath. You might then use list_files and read_file to explore the files in the repository, and use replace_in_file to make changes to the files.
541
+
542
+ However some MCP servers may be running from installed packages rather than a local repository, in which case it may make more sense to create a new MCP server.
543
+
544
+ # MCP Servers Are Not Always Necessary
545
+
546
+ The user may not always request the use or creation of MCP servers. Instead, they might provide tasks that can be completed with existing tools. While using the MCP SDK to extend your capabilities can be useful, it's important to understand that this is just one specialized type of task you can accomplish. You should only implement MCP servers when the user explicitly requests it (e.g., "add a tool that...").
547
+
548
+ Remember: The MCP documentation and example provided above are to help you understand and work with existing MCP servers or create new ones when requested by the user. You already have access to tools and capabilities that can be used to accomplish a wide range of tasks.
549
+ """
550
+ return {
551
+ "connected_servers_info": self.get_connected_servers_info(),
552
+ "server_names": self.get_server_names()
553
+ }
554
+
555
+ async def extract_mcp_calls(self,content: str) -> List[Union[McpToolCall, McpResourceAccess]]:
556
+ """
557
+ Extract MCP tool calls and resource accesses from content.
558
+
559
+ Args:
560
+ content: The content to parse for MCP tool calls
561
+
562
+ Returns:
563
+ List of McpToolCall and McpResourceAccess objects
564
+ """
565
+ results = []
566
+
567
+ # Regex pattern to match tool calls
568
+ tool_pattern = re.compile(
569
+ r"<use_mcp_tool>.*?<server_name>(.*?)</server_name>.*?"
570
+ r"<tool_name>(.*?)</tool_name>.*?"
571
+ r"<arguments>(.*?)</arguments>.*?</use_mcp_tool>",
572
+ re.DOTALL
573
+ )
574
+
575
+ # Regex pattern to match resource accesses
576
+ resource_pattern = re.compile(
577
+ r"<access_mcp_resource>.*?<server_name>(.*?)</server_name>.*?"
578
+ r"<uri>(.*?)</uri>.*?</access_mcp_resource>",
579
+ re.DOTALL
580
+ )
581
+
582
+ # Extract tool calls
583
+ for match in tool_pattern.finditer(content):
584
+ try:
585
+ arguments = json.loads(match.group(3).strip())
586
+ results.append(McpToolCall(
587
+ server_name=match.group(1).strip(),
588
+ tool_name=match.group(2).strip(),
589
+ arguments=arguments
590
+ ))
591
+ except json.JSONDecodeError:
592
+ continue
593
+
594
+ # Extract resource accesses
595
+ for match in resource_pattern.finditer(content):
596
+ results.append(McpResourceAccess(
597
+ server_name=match.group(1).strip(),
598
+ uri=match.group(2).strip()
599
+ ))
600
+
601
+ return results
602
+
603
+ async def run(self, conversations: List[Dict[str, Any]]):
604
+ new_conversations = [{
605
+ "role": "user",
606
+ "content": self.mcp_prompt.prompt()
607
+ }, {
608
+ "role": "assistant",
609
+ "content": "I have read the tools usage instructions."
610
+ }] + conversations
611
+
612
+ all_tool_results = []
613
+
614
+ v = self.llm.chat_oai(conversations=new_conversations)
615
+ content = v[0].output
616
+ tools = await self.extract_mcp_calls(content)
617
+ # while tools:
618
+ # results = await self.execute_mcp_tools(tools)
619
+ # all_tool_results += results
620
+ # str_results = "\n\n".join(self.format_mcp_result(result) for result in results)
621
+ # new_conversations += [{
622
+ # "role": "assistant",
623
+ # "content": f"Tool results: {str_results}"
624
+ # },{
625
+ # "role": "user",
626
+ # "content": "continue"
627
+ # }]
628
+ # v = self.llm.chat_oai(conversations=new_conversations)
629
+ # content = v[0].output
630
+ # tools = await self.extract_mcp_calls(content)
631
+ if tools:
632
+ results = await self.execute_mcp_tools(tools)
633
+ all_tool_results += results
634
+
635
+ return new_conversations,all_tool_results
636
+
637
+ def format_mcp_result(self, result: Any) -> str:
638
+ """
639
+ Format MCP tool or resource result into a human-readable string.
640
+
641
+ Args:
642
+ result: The result from MCP tool call or resource access
643
+
644
+ Returns:
645
+ Formatted string representation of the result
646
+ """
647
+ if result is None:
648
+ return "(No result)"
649
+ # if isinstance(result, mcp_types.CallToolResult):
650
+ # for content in result.contents:
651
+ # if isinstance(content, mcp_types.TextContent):
652
+ # return content.text
653
+ # if isinstance(result, mcp_types.ReadResourceResult):
654
+ # return result.contents
655
+ return json.dumps(result.model_dump(), indent=2, ensure_ascii=False)
656
+
657
+ async def execute_mcp_tools(self, tools: List[Union[McpToolCall, McpResourceAccess]]) -> List[Any]:
658
+ """
659
+ Execute MCP tools and return results in order.
660
+
661
+ Args:
662
+ mcp_hub: McpHub instance to execute tools
663
+ tools: List of McpToolCall and McpResourceAccess objects
664
+
665
+ Returns:
666
+ List of results in the same order as the input tools
667
+ """
668
+ results = []
669
+ for tool in tools:
670
+ try:
671
+ if isinstance(tool, McpToolCall):
672
+ result = await self.mcp_hub.call_tool(tool.server_name, tool.tool_name, tool.arguments)
673
+ results.append(result)
674
+ elif isinstance(tool, McpResourceAccess):
675
+ result = await self.mcp_hub.read_resource(tool.server_name, tool.uri)
676
+ results.append(result)
677
+ else:
678
+ results.append(None)
679
+ except Exception as e:
680
+ logger.error(f"Failed to execute MCP tool {tool}: {e}")
681
+ results.append(None)
682
+ return results