beswarm 0.1.91__py3-none-any.whl → 0.1.93__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.
- beswarm/aient/src/aient/core/request.py +4 -2
- beswarm/bemcp/bemcp/__init__.py +4 -0
- beswarm/bemcp/bemcp/main.py +220 -0
- beswarm/bemcp/bemcp/utils.py +30 -0
- beswarm/bemcp/test/client.py +49 -0
- beswarm/bemcp/test/server.py +43 -0
- beswarm/tools/search_web.py +11 -6
- beswarm/tools/worker.py +37 -5
- beswarm/utils.py +96 -0
- {beswarm-0.1.91.dist-info → beswarm-0.1.93.dist-info}/METADATA +2 -1
- {beswarm-0.1.91.dist-info → beswarm-0.1.93.dist-info}/RECORD +13 -8
- {beswarm-0.1.91.dist-info → beswarm-0.1.93.dist-info}/WHEEL +0 -0
- {beswarm-0.1.91.dist-info → beswarm-0.1.93.dist-info}/top_level.txt +0 -0
@@ -341,7 +341,8 @@ async def get_vertex_gemini_payload(request, engine, provider, api_key=None):
|
|
341
341
|
else:
|
342
342
|
location = gemini1
|
343
343
|
|
344
|
-
if "gemini-2.5-
|
344
|
+
if "gemini-2.5-flash-lite-preview-06-17" == original_model or \
|
345
|
+
"gemini-2.5-pro-preview-06-05" == original_model:
|
345
346
|
location = gemini2_5_pro_exp
|
346
347
|
|
347
348
|
if "google-vertex-ai" in provider.get("base_url", ""):
|
@@ -362,7 +363,8 @@ async def get_vertex_gemini_payload(request, engine, provider, api_key=None):
|
|
362
363
|
else:
|
363
364
|
url = f"https://aiplatform.googleapis.com/v1/publishers/google/models/{original_model}:{gemini_stream}?key={api_key}"
|
364
365
|
headers.pop("Authorization", None)
|
365
|
-
elif "gemini-2.5-
|
366
|
+
elif "gemini-2.5-flash-lite-preview-06-17" == original_model or \
|
367
|
+
"gemini-2.5-pro-preview-06-05" == original_model:
|
366
368
|
url = "https://aiplatform.googleapis.com/v1/projects/{PROJECT_ID}/locations/{LOCATION}/publishers/google/models/{MODEL_ID}:{stream}".format(
|
367
369
|
LOCATION=await location.next(),
|
368
370
|
PROJECT_ID=project_id,
|
@@ -0,0 +1,220 @@
|
|
1
|
+
import asyncio
|
2
|
+
from typing import Any, Dict, List, Optional
|
3
|
+
from contextlib import AsyncExitStack
|
4
|
+
|
5
|
+
from mcp import ClientSession, StdioServerParameters
|
6
|
+
from mcp.client.stdio import stdio_client
|
7
|
+
from mcp.client.sse import sse_client
|
8
|
+
import mcp.types as types
|
9
|
+
|
10
|
+
class MCPClient:
|
11
|
+
"""
|
12
|
+
A client for interacting with a single Model Context Protocol (MCP) server.
|
13
|
+
It can connect to a server via stdio or a URL (SSE).
|
14
|
+
This class is an asynchronous context manager.
|
15
|
+
"""
|
16
|
+
def __init__(self, server_config: Dict[str, Any]):
|
17
|
+
"""
|
18
|
+
Initializes the MCPClient.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
server_config: A dictionary containing server configuration.
|
22
|
+
It should have either 'command' and 'args' for stdio,
|
23
|
+
or 'url' for SSE.
|
24
|
+
"""
|
25
|
+
if not ("command" in server_config or "url" in server_config):
|
26
|
+
raise ValueError("Server config must contain 'command' or 'url'")
|
27
|
+
self.server_config = server_config
|
28
|
+
self.session: Optional[ClientSession] = None
|
29
|
+
self._exit_stack: Optional[AsyncExitStack] = None
|
30
|
+
|
31
|
+
async def connect(self):
|
32
|
+
"""Connects to the MCP server and initializes resources."""
|
33
|
+
return await self.__aenter__()
|
34
|
+
|
35
|
+
async def disconnect(self):
|
36
|
+
"""Disconnects from the MCP server and cleans up resources."""
|
37
|
+
return await self.__aexit__(None, None, None)
|
38
|
+
|
39
|
+
async def __aenter__(self):
|
40
|
+
"""Connects to the MCP server and initializes resources."""
|
41
|
+
if self.session:
|
42
|
+
return self
|
43
|
+
|
44
|
+
self._exit_stack = AsyncExitStack()
|
45
|
+
try:
|
46
|
+
if "command" in self.server_config:
|
47
|
+
server_params = StdioServerParameters(**self.server_config)
|
48
|
+
transport = await self._exit_stack.enter_async_context(stdio_client(server_params))
|
49
|
+
else:
|
50
|
+
transport = await self._exit_stack.enter_async_context(sse_client(**self.server_config))
|
51
|
+
|
52
|
+
read_stream, write_stream = transport
|
53
|
+
self.session = await self._exit_stack.enter_async_context(ClientSession(read_stream, write_stream))
|
54
|
+
await self.session.initialize()
|
55
|
+
print(f"Connected to server.")
|
56
|
+
return self
|
57
|
+
except Exception:
|
58
|
+
# If setup fails, make sure to clean up anything that was started.
|
59
|
+
if self._exit_stack:
|
60
|
+
await self._exit_stack.aclose()
|
61
|
+
raise
|
62
|
+
|
63
|
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
64
|
+
"""Disconnects from the MCP server and cleans up resources."""
|
65
|
+
if self._exit_stack:
|
66
|
+
await self._exit_stack.aclose()
|
67
|
+
self.session = None
|
68
|
+
self._exit_stack = None
|
69
|
+
print("Disconnected from server.")
|
70
|
+
|
71
|
+
async def list_tools(self) -> List[types.Tool]:
|
72
|
+
"""Lists available tools from the server."""
|
73
|
+
if not self.session:
|
74
|
+
raise ConnectionError("Not connected to any server.")
|
75
|
+
response = await self.session.list_tools()
|
76
|
+
return response.tools
|
77
|
+
|
78
|
+
async def call_tool(self, name: str, args: Dict[str, Any]) -> types.CallToolResult:
|
79
|
+
"""Calls a tool on the server."""
|
80
|
+
if not self.session:
|
81
|
+
raise ConnectionError("Not connected to any server.")
|
82
|
+
return await self.session.call_tool(name, args)
|
83
|
+
|
84
|
+
async def list_resources(self) -> List[types.Resource]:
|
85
|
+
"""Lists available resources from the server."""
|
86
|
+
if not self.session:
|
87
|
+
raise ConnectionError("Not connected to any server.")
|
88
|
+
response = await self.session.list_resources()
|
89
|
+
return response.resources
|
90
|
+
|
91
|
+
async def read_resource(self, uri: str) -> types.ReadResourceResult:
|
92
|
+
"""Reads a resource from the server."""
|
93
|
+
if not self.session:
|
94
|
+
raise ConnectionError("Not connected to any server.")
|
95
|
+
return await self.session.read_resource(uri)
|
96
|
+
|
97
|
+
|
98
|
+
class MCPManager:
|
99
|
+
"""
|
100
|
+
Manages connections to multiple MCP servers.
|
101
|
+
"""
|
102
|
+
def __init__(self):
|
103
|
+
self.clients: Dict[str, MCPClient] = {}
|
104
|
+
self._exit_stack = AsyncExitStack()
|
105
|
+
|
106
|
+
async def add_server(self, name: str, config: Dict[str, Any]):
|
107
|
+
"""
|
108
|
+
Adds and connects to a new MCP server.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
name: A unique name for the server.
|
112
|
+
config: The server configuration dictionary.
|
113
|
+
"""
|
114
|
+
if name in self.clients:
|
115
|
+
print(f"Server '{name}' already exists.")
|
116
|
+
return
|
117
|
+
|
118
|
+
client = MCPClient(config)
|
119
|
+
await self._exit_stack.enter_async_context(client)
|
120
|
+
self.clients[name] = client
|
121
|
+
print(f"Server '{name}' added and connected.")
|
122
|
+
|
123
|
+
async def remove_server(self, name: str):
|
124
|
+
"""
|
125
|
+
Disconnects and removes an MCP server.
|
126
|
+
"""
|
127
|
+
# NOTE: With the new ExitStack-based management, removing a single
|
128
|
+
# server is complex. This method is left as a placeholder and will
|
129
|
+
# not correctly clean up resources for the removed server.
|
130
|
+
if name in self.clients:
|
131
|
+
del self.clients[name]
|
132
|
+
print(f"Server '{name}' removed (Warning: resources may not be cleaned up immediately).")
|
133
|
+
else:
|
134
|
+
print(f"Server '{name}' not found.")
|
135
|
+
|
136
|
+
async def get_all_tools(self) -> Dict[str, List[types.Tool]]:
|
137
|
+
"""Gets a dictionary of all tools from all connected servers."""
|
138
|
+
all_tools = {}
|
139
|
+
for name, client in self.clients.items():
|
140
|
+
try:
|
141
|
+
tools = await client.list_tools()
|
142
|
+
all_tools[name] = tools
|
143
|
+
except Exception as e:
|
144
|
+
print(f"Error getting tools from server '{name}': {e}")
|
145
|
+
return all_tools
|
146
|
+
|
147
|
+
async def call_tool(self, server_name: str, tool_name: str, args: Dict[str, Any]) -> types.CallToolResult:
|
148
|
+
"""
|
149
|
+
Calls a specific tool on a specific server.
|
150
|
+
"""
|
151
|
+
if server_name not in self.clients:
|
152
|
+
raise ValueError(f"Server '{server_name}' not found.")
|
153
|
+
return await self.clients[server_name].call_tool(tool_name, args)
|
154
|
+
|
155
|
+
async def cleanup(self):
|
156
|
+
"""Disconnects all clients by closing the manager's exit stack."""
|
157
|
+
if self.clients:
|
158
|
+
await self._exit_stack.aclose()
|
159
|
+
self.clients.clear()
|
160
|
+
|
161
|
+
async def test_bemcp():
|
162
|
+
"""
|
163
|
+
Test function for MCPManager and MCPClient.
|
164
|
+
"""
|
165
|
+
manager = MCPManager()
|
166
|
+
|
167
|
+
# Configuration for a test server (using the existing test/server.py)
|
168
|
+
test_server_config = {
|
169
|
+
"command": "uv",
|
170
|
+
"args": ["run", "test/server.py"],
|
171
|
+
"env": None
|
172
|
+
}
|
173
|
+
|
174
|
+
# Add the test server
|
175
|
+
await manager.add_server("test_server", test_server_config)
|
176
|
+
|
177
|
+
# List all tools
|
178
|
+
all_tools = await manager.get_all_tools()
|
179
|
+
print("\n--- All Tools ---")
|
180
|
+
for server_name, tools in all_tools.items():
|
181
|
+
print(f"Server: {server_name}")
|
182
|
+
for tool in tools:
|
183
|
+
print(f" - {tool.name}: {tool.description}")
|
184
|
+
print("-------------------\n")
|
185
|
+
|
186
|
+
# Call a tool
|
187
|
+
print("--- Calling 'add' tool on 'test_server' with a=2, b=3 ---")
|
188
|
+
try:
|
189
|
+
result = await manager.call_tool("test_server", "add", {"a": 2, "b": 3})
|
190
|
+
print("Result:", result.content[0].text)
|
191
|
+
except Exception as e:
|
192
|
+
print(f"Error calling tool: {e}")
|
193
|
+
print("--------------------------------------------------------\n")
|
194
|
+
|
195
|
+
# Call another tool
|
196
|
+
print("--- Calling 'calculate_bmi' tool on 'test_server' with weight_kg=70, height_m=1.75 ---")
|
197
|
+
try:
|
198
|
+
result = await manager.call_tool("test_server", "calculate_bmi", {"weight_kg": 70, "height_m": 1.75})
|
199
|
+
print("Result:", result.content[0].text)
|
200
|
+
except Exception as e:
|
201
|
+
print(f"Error calling tool: {e}")
|
202
|
+
print("-------------------------------------------------------------------------------------\n")
|
203
|
+
|
204
|
+
# Read a resource
|
205
|
+
print("--- Reading resource 'config://app' from 'test_server' ---")
|
206
|
+
client = manager.clients.get("test_server")
|
207
|
+
if client:
|
208
|
+
try:
|
209
|
+
resource_result = await client.read_resource('config://app')
|
210
|
+
print("Resource content:", resource_result.contents[0].text)
|
211
|
+
except Exception as e:
|
212
|
+
print(f"Error reading resource: {e}")
|
213
|
+
print("----------------------------------------------------------\n")
|
214
|
+
|
215
|
+
|
216
|
+
# Clean up
|
217
|
+
await manager.cleanup()
|
218
|
+
|
219
|
+
if __name__ == "__main__":
|
220
|
+
asyncio.run(test_bemcp())
|
@@ -0,0 +1,30 @@
|
|
1
|
+
from typing import Any, Dict
|
2
|
+
from mcp import types
|
3
|
+
|
4
|
+
def convert_tool_format(tool: types.Tool) -> Dict[str, Any]:
|
5
|
+
"""
|
6
|
+
Converts an MCP tool to a format compatible with LLM providers.
|
7
|
+
"""
|
8
|
+
# This is one possible format, similar to OpenAI's function calling.
|
9
|
+
# The commented-out section shows another common variation.
|
10
|
+
# converted_tool = {
|
11
|
+
# "type": "function",
|
12
|
+
# "function": {
|
13
|
+
# "name": tool.name,
|
14
|
+
# "description": tool.description,
|
15
|
+
# "parameters": {
|
16
|
+
# "type": "object",
|
17
|
+
# "properties": tool.inputSchema["properties"],
|
18
|
+
# "required": tool.inputSchema["required"]
|
19
|
+
# }
|
20
|
+
# }
|
21
|
+
# }
|
22
|
+
converted_tool = {
|
23
|
+
"type": "function",
|
24
|
+
"function": {
|
25
|
+
"name": tool.name,
|
26
|
+
"description": tool.description,
|
27
|
+
"input_schema": tool.inputSchema
|
28
|
+
}
|
29
|
+
}
|
30
|
+
return converted_tool
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import asyncio
|
2
|
+
|
3
|
+
from mcp.client.stdio import stdio_client
|
4
|
+
from mcp import ClientSession, StdioServerParameters
|
5
|
+
|
6
|
+
# 为 stdio 连接创建服务器参数
|
7
|
+
server_params = StdioServerParameters(
|
8
|
+
# 服务器执行的命令,这里我们使用 uv 来运行 web_search.py
|
9
|
+
command='uv',
|
10
|
+
# 运行的参数
|
11
|
+
args=['run', 'server.py'],
|
12
|
+
# 环境变量,默认为 None,表示使用当前环境变量
|
13
|
+
# env=None
|
14
|
+
)
|
15
|
+
|
16
|
+
|
17
|
+
async def main():
|
18
|
+
# 创建 stdio 客户端
|
19
|
+
async with stdio_client(server_params) as (stdio, write):
|
20
|
+
# 创建 ClientSession 对象
|
21
|
+
async with ClientSession(stdio, write) as session:
|
22
|
+
# 初始化 ClientSession
|
23
|
+
await session.initialize()
|
24
|
+
|
25
|
+
# 列出可用的工具
|
26
|
+
response = await session.list_tools()
|
27
|
+
print(response.model_dump_json(indent=2))
|
28
|
+
|
29
|
+
response = await session.list_resources()
|
30
|
+
print(response.model_dump_json(indent=2))
|
31
|
+
|
32
|
+
response = await session.list_prompts()
|
33
|
+
print(response.model_dump_json(indent=2))
|
34
|
+
|
35
|
+
# 调用工具
|
36
|
+
response = await session.call_tool('add', {"a": "2", "b": "3"})
|
37
|
+
print(response.model_dump_json(indent=2))
|
38
|
+
|
39
|
+
# 调用工具
|
40
|
+
response = await session.read_resource('config://app')
|
41
|
+
print(response.model_dump_json(indent=2))
|
42
|
+
|
43
|
+
# 调用工具
|
44
|
+
response = await session.get_prompt('debug_error', {"error": "test"})
|
45
|
+
print(response.model_dump_json(indent=2))
|
46
|
+
|
47
|
+
|
48
|
+
if __name__ == '__main__':
|
49
|
+
asyncio.run(main())
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# server.py
|
2
|
+
import httpx
|
3
|
+
from mcp.server.fastmcp import FastMCP
|
4
|
+
from mcp.server.fastmcp.prompts import base
|
5
|
+
|
6
|
+
# Create an MCP server
|
7
|
+
mcp = FastMCP("Demo")
|
8
|
+
|
9
|
+
|
10
|
+
# Add an addition tool
|
11
|
+
@mcp.tool()
|
12
|
+
def add(a: int, b: int) -> int:
|
13
|
+
"""Add two numbers"""
|
14
|
+
return a + b
|
15
|
+
|
16
|
+
@mcp.tool()
|
17
|
+
def calculate_bmi(weight_kg: float, height_m: float) -> float:
|
18
|
+
"""Calculate BMI given weight in kg and height in meters"""
|
19
|
+
return weight_kg / (height_m**2)
|
20
|
+
|
21
|
+
@mcp.tool()
|
22
|
+
async def fetch_weather(city: str) -> str:
|
23
|
+
"""Fetch current weather for a city"""
|
24
|
+
async with httpx.AsyncClient() as client:
|
25
|
+
response = await client.get(f"https://api.weather.com/{city}")
|
26
|
+
return response.text
|
27
|
+
|
28
|
+
@mcp.resource("config://app")
|
29
|
+
def get_config() -> str:
|
30
|
+
"""Static configuration data"""
|
31
|
+
return "App configuration here"
|
32
|
+
|
33
|
+
@mcp.prompt()
|
34
|
+
def debug_error(error: str) -> list[base.Message]:
|
35
|
+
return [
|
36
|
+
base.UserMessage("I'm seeing this error:"),
|
37
|
+
base.UserMessage(error),
|
38
|
+
base.AssistantMessage("I'll help debug that. What have you tried so far?"),
|
39
|
+
]
|
40
|
+
|
41
|
+
if __name__ == "__main__":
|
42
|
+
mcp.run(transport='stdio')
|
43
|
+
# mcp.run(transport='sse')
|
beswarm/tools/search_web.py
CHANGED
@@ -48,27 +48,32 @@ async def search_web(query: str):
|
|
48
48
|
except httpx.HTTPStatusError as e:
|
49
49
|
return {
|
50
50
|
"error": f"HTTP error occurred: {e.response.status_code} - {e.response.text}",
|
51
|
-
"status_code": e.response.status_code
|
51
|
+
"status_code": e.response.status_code,
|
52
|
+
"code": 400
|
52
53
|
}
|
53
54
|
except httpx.RequestError as e:
|
54
55
|
return {
|
55
56
|
"error": f"An error occurred while requesting {e.request.url!r}: {e}",
|
56
|
-
"request_url": str(e.request.url)
|
57
|
+
"request_url": str(e.request.url),
|
58
|
+
"code": 400
|
57
59
|
}
|
58
60
|
except json.JSONDecodeError:
|
59
61
|
return {
|
60
62
|
"error": "Failed to decode JSON response from the API.",
|
61
|
-
"response_text": response.text if 'response' in locals() else "No response text available"
|
63
|
+
"response_text": response.text if 'response' in locals() else "No response text available",
|
64
|
+
"code": 400
|
62
65
|
}
|
63
66
|
except Exception as e:
|
64
67
|
return {
|
65
|
-
"error": f"An unexpected error occurred: {str(e)}"
|
68
|
+
"error": f"An unexpected error occurred: {str(e)}",
|
69
|
+
"code": 400
|
66
70
|
}
|
67
71
|
|
68
72
|
unique_urls = []
|
69
|
-
if "error" in results:
|
70
|
-
print(f"Error fetching search results for '{query}':")
|
73
|
+
if "error" in results or results.get("code", 200) != 200:
|
74
|
+
# print(f"Error fetching search results for '{query}':")
|
71
75
|
print(json.dumps(results, indent=2, ensure_ascii=False))
|
76
|
+
raise Exception(f"Error fetching search results for '{query}':")
|
72
77
|
else:
|
73
78
|
# print(f"Search results for '{query}':")
|
74
79
|
html_content = results.get("data", {}).get("result", {}).get("html", "")
|
beswarm/tools/worker.py
CHANGED
@@ -7,9 +7,12 @@ from pathlib import Path
|
|
7
7
|
from datetime import datetime
|
8
8
|
|
9
9
|
from ..aient.src.aient.models import chatgpt
|
10
|
-
from ..aient.src.aient.plugins import register_tool, get_function_call_list
|
10
|
+
from ..aient.src.aient.plugins import register_tool, get_function_call_list, registry
|
11
11
|
from ..prompt import worker_system_prompt, instruction_system_prompt
|
12
|
-
from ..utils import extract_xml_content, get_current_screen_image_message, replace_xml_content
|
12
|
+
from ..utils import extract_xml_content, get_current_screen_image_message, replace_xml_content, register_mcp_tools
|
13
|
+
from ..bemcp.bemcp import MCPClient, convert_tool_format, MCPManager
|
14
|
+
|
15
|
+
manager = MCPManager()
|
13
16
|
|
14
17
|
@register_tool()
|
15
18
|
async def worker(goal, tools, work_dir, cache_messages=None):
|
@@ -31,6 +34,19 @@ async def worker(goal, tools, work_dir, cache_messages=None):
|
|
31
34
|
start_time = datetime.now()
|
32
35
|
finish_flag = 0
|
33
36
|
|
37
|
+
mcp_list = [item for item in tools if isinstance(item, dict)]
|
38
|
+
if mcp_list:
|
39
|
+
for mcp_item in mcp_list:
|
40
|
+
mcp_name, mcp_config = list(mcp_item.items())[0]
|
41
|
+
await manager.add_server(mcp_name, mcp_config)
|
42
|
+
client = manager.clients.get(mcp_name)
|
43
|
+
await register_mcp_tools(client, registry)
|
44
|
+
all_tools = await manager.get_all_tools()
|
45
|
+
mcp_tools_name = [tool.name for tool in sum(all_tools.values(), [])]
|
46
|
+
tools += mcp_tools_name
|
47
|
+
|
48
|
+
tools = [item for item in tools if not isinstance(item, dict)]
|
49
|
+
|
34
50
|
tools_json = [value for _, value in get_function_call_list(tools).items()]
|
35
51
|
work_agent_system_prompt = worker_system_prompt.format(
|
36
52
|
os_version=platform.platform(),
|
@@ -56,7 +72,7 @@ async def worker(goal, tools, work_dir, cache_messages=None):
|
|
56
72
|
if cache_file_path.exists():
|
57
73
|
with cache_file_path.open("r", encoding="utf-8") as f:
|
58
74
|
cache_messages = json.load(f)
|
59
|
-
if cache_messages and isinstance(cache_messages, list) and len(cache_messages) >
|
75
|
+
if cache_messages and isinstance(cache_messages, list) and len(cache_messages) > 1:
|
60
76
|
first_user_message = replace_xml_content(cache_messages[1]["content"], "goal", goal)
|
61
77
|
work_agent_config["cache_messages"] = cache_messages[0:1] + [{"role": "user", "content": first_user_message}] + cache_messages[2:]
|
62
78
|
|
@@ -179,11 +195,26 @@ async def worker(goal, tools, work_dir, cache_messages=None):
|
|
179
195
|
print(f"\n任务开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
180
196
|
print(f"任务结束时间: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
181
197
|
print(f"总用时: {total_time}")
|
198
|
+
await manager.cleanup()
|
182
199
|
return "任务已完成"
|
183
200
|
|
184
201
|
async def worker_gen(goal, tools, work_dir, cache_messages=None):
|
185
202
|
start_time = datetime.now()
|
186
203
|
finish_flag = 0
|
204
|
+
|
205
|
+
mcp_list = [item for item in tools if isinstance(item, dict)]
|
206
|
+
if mcp_list:
|
207
|
+
for mcp_item in mcp_list:
|
208
|
+
mcp_name, mcp_config = list(mcp_item.items())[0]
|
209
|
+
await manager.add_server(mcp_name, mcp_config)
|
210
|
+
client = manager.clients.get(mcp_name)
|
211
|
+
await register_mcp_tools(client, registry)
|
212
|
+
all_tools = await manager.get_all_tools()
|
213
|
+
mcp_tools_name = [tool.name for tool in sum(all_tools.values(), [])]
|
214
|
+
tools += mcp_tools_name
|
215
|
+
|
216
|
+
tools = [item for item in tools if not isinstance(item, dict)]
|
217
|
+
|
187
218
|
tools_json = [value for _, value in get_function_call_list(tools).items()]
|
188
219
|
work_agent_system_prompt = worker_system_prompt.format(
|
189
220
|
os_version=platform.platform(),
|
@@ -209,7 +240,7 @@ async def worker_gen(goal, tools, work_dir, cache_messages=None):
|
|
209
240
|
if cache_file_path.exists():
|
210
241
|
with cache_file_path.open("r", encoding="utf-8") as f:
|
211
242
|
cache_messages = json.load(f)
|
212
|
-
if cache_messages and isinstance(cache_messages, list) and len(cache_messages) >
|
243
|
+
if cache_messages and isinstance(cache_messages, list) and len(cache_messages) > 1:
|
213
244
|
first_user_message = replace_xml_content(cache_messages[1]["content"], "goal", goal)
|
214
245
|
work_agent_config["cache_messages"] = cache_messages[0:1] + [{"role": "user", "content": first_user_message}] + cache_messages[2:]
|
215
246
|
|
@@ -335,4 +366,5 @@ async def worker_gen(goal, tools, work_dir, cache_messages=None):
|
|
335
366
|
total_time = end_time - start_time
|
336
367
|
print(f"\n任务开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
337
368
|
print(f"任务结束时间: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
338
|
-
print(f"总用时: {total_time}")
|
369
|
+
print(f"总用时: {total_time}")
|
370
|
+
await manager.cleanup()
|
beswarm/utils.py
CHANGED
@@ -68,6 +68,102 @@ async def get_current_screen_image_message(prompt):
|
|
68
68
|
message_list.append(image_message)
|
69
69
|
return message_list
|
70
70
|
|
71
|
+
import asyncio
|
72
|
+
from .bemcp.bemcp import MCPClient, convert_tool_format
|
73
|
+
|
74
|
+
async def register_mcp_tools(mcp_client: MCPClient, registry: "Registry"):
|
75
|
+
"""
|
76
|
+
Fetches tools from an MCP client, dynamically creates corresponding
|
77
|
+
Python functions with correct signatures, and registers them using the
|
78
|
+
provided registry instance.
|
79
|
+
"""
|
80
|
+
try:
|
81
|
+
mcp_tools = await mcp_client.list_tools()
|
82
|
+
print(f"Found {len(mcp_tools)} tools on the MCP server.")
|
83
|
+
except Exception as e:
|
84
|
+
print(f"Error fetching tools from MCP server: {e}")
|
85
|
+
return
|
86
|
+
|
87
|
+
# Create a dummy file for inspect.getsource to work with exec
|
88
|
+
# This helps make the created functions inspectable
|
89
|
+
source_storage = {}
|
90
|
+
|
91
|
+
for tool in mcp_tools:
|
92
|
+
openai_spec = convert_tool_format(tool)
|
93
|
+
func_spec = openai_spec.get('function', {})
|
94
|
+
|
95
|
+
tool_name = func_spec.get('name')
|
96
|
+
if not tool_name or not tool_name.isidentifier():
|
97
|
+
print(f"Skipping tool with invalid name: {tool_name}")
|
98
|
+
continue
|
99
|
+
|
100
|
+
description = func_spec.get('description', 'No description available.')
|
101
|
+
params_spec = func_spec.get('parameters', {})
|
102
|
+
properties = params_spec.get('properties', {})
|
103
|
+
required = set(params_spec.get('required', []))
|
104
|
+
|
105
|
+
param_defs = []
|
106
|
+
for name, spec in properties.items():
|
107
|
+
if name in required:
|
108
|
+
param_defs.append(name)
|
109
|
+
else:
|
110
|
+
# Add default value for optional parameters
|
111
|
+
default = spec.get('default', 'None')
|
112
|
+
param_defs.append(f"{name}={default}")
|
113
|
+
|
114
|
+
params_str = ", ".join(param_defs)
|
115
|
+
arg_names = list(properties.keys())
|
116
|
+
# Create a dictionary of arguments to pass to call_tool
|
117
|
+
args_dict_str = "{" + ", ".join(f"'{k}': {k}" for k in arg_names) + "}"
|
118
|
+
|
119
|
+
# Dynamically generate the Python source code for the function
|
120
|
+
func_source = f"""
|
121
|
+
async def {tool_name}({params_str}):
|
122
|
+
\"\"\"{description}\"\"\"
|
123
|
+
tool_args = {args_dict_str}
|
124
|
+
|
125
|
+
# This function is a dynamically generated wrapper for an MCP tool.
|
126
|
+
print(f"-> Calling MCP tool '{tool_name}' with args: {{tool_args}}")
|
127
|
+
result = await mcp_client.call_tool('{tool_name}', tool_args)
|
128
|
+
|
129
|
+
# Extract text content if available, otherwise return the raw result
|
130
|
+
if hasattr(result, 'content') and isinstance(result.content, list) and result.content:
|
131
|
+
if hasattr(result.content[0], 'text'):
|
132
|
+
return result.content[0].text
|
133
|
+
return result
|
134
|
+
"""
|
135
|
+
# Store the source code so inspect.getsource can find it
|
136
|
+
source_storage[tool_name] = func_source
|
137
|
+
|
138
|
+
# Use a dictionary for the execution scope
|
139
|
+
exec_scope = {'mcp_client': mcp_client, 'asyncio': asyncio}
|
140
|
+
|
141
|
+
try:
|
142
|
+
# Execute the source code to create the function object
|
143
|
+
exec(func_source, exec_scope)
|
144
|
+
generated_func = exec_scope[tool_name]
|
145
|
+
|
146
|
+
# Monkey-patch getsourcelines to work with our storage
|
147
|
+
# This makes our generated function fully compatible with the registry
|
148
|
+
import inspect
|
149
|
+
import linecache
|
150
|
+
linecache.cache[f"<mcp_adapter/{tool_name}>"] = (
|
151
|
+
len(func_source),
|
152
|
+
None,
|
153
|
+
[line + '\n' for line in func_source.splitlines()],
|
154
|
+
f"<mcp_adapter/{tool_name}>",
|
155
|
+
)
|
156
|
+
generated_func.__code__ = generated_func.__code__.replace(co_filename=f"<mcp_adapter/{tool_name}>")
|
157
|
+
|
158
|
+
# Register the newly created function
|
159
|
+
registry.register(type="tool", name=tool_name)(generated_func)
|
160
|
+
print(f"Successfully registered MCP tool: '{tool_name}'")
|
161
|
+
|
162
|
+
except Exception as e:
|
163
|
+
import traceback
|
164
|
+
print(f"Failed to create or register function for tool '{tool_name}': {e}")
|
165
|
+
traceback.print_exc()
|
166
|
+
|
71
167
|
if __name__ == "__main__":
|
72
168
|
print(extract_xml_content("<instructions>\n123</instructions>", "instructions"))
|
73
169
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: beswarm
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.93
|
4
4
|
Summary: MAS
|
5
5
|
Requires-Python: >=3.11
|
6
6
|
Description-Content-Type: text/markdown
|
@@ -11,6 +11,7 @@ Requires-Dist: fastapi>=0.115.12
|
|
11
11
|
Requires-Dist: grep-ast>=0.8.1
|
12
12
|
Requires-Dist: httpx>=0.28.1
|
13
13
|
Requires-Dist: httpx-socks>=0.10.0
|
14
|
+
Requires-Dist: mcp[cli]>=1.9.4
|
14
15
|
Requires-Dist: msgspec>=0.19.0
|
15
16
|
Requires-Dist: networkx>=3.4.2
|
16
17
|
Requires-Dist: numpy>=2.2.5
|
@@ -1,13 +1,13 @@
|
|
1
1
|
beswarm/__init__.py,sha256=HZjUOJtZR5QhMuDbq-wukQQn1VrBusNWai_ysGo-VVI,20
|
2
2
|
beswarm/prompt.py,sha256=cBz8sSmFj0edIGOZjCMg0rg3pFXQYo8xGvXSA8Py1hg,31591
|
3
|
-
beswarm/utils.py,sha256=
|
3
|
+
beswarm/utils.py,sha256=lm0drN1ebXM9haoKaW2DLzJJRCOpLmiJ864mH4jAdB4,6697
|
4
4
|
beswarm/aient/main.py,sha256=SiYAIgQlLJqYusnTVEJOx1WNkSJKMImhgn5aWjfroxg,3814
|
5
5
|
beswarm/aient/setup.py,sha256=IUr9oCEhGFNVvz7v8J9tedwE_JK6Hlf8XpnENoq2K2s,487
|
6
6
|
beswarm/aient/src/aient/__init__.py,sha256=SRfF7oDVlOOAi6nGKiJIUK6B_arqYLO9iSMp-2IZZps,21
|
7
7
|
beswarm/aient/src/aient/core/__init__.py,sha256=NxjebTlku35S4Dzr16rdSqSTWUvvwEeACe8KvHJnjPg,34
|
8
8
|
beswarm/aient/src/aient/core/log_config.py,sha256=kz2_yJv1p-o3lUQOwA3qh-LSc3wMHv13iCQclw44W9c,274
|
9
9
|
beswarm/aient/src/aient/core/models.py,sha256=oUGsytAVBuhE_MLco7PqIQGwWD8lEYkZxgb8HEuynmA,7444
|
10
|
-
beswarm/aient/src/aient/core/request.py,sha256=
|
10
|
+
beswarm/aient/src/aient/core/request.py,sha256=7IaN5iFYQgDK9W8ej-OZCom4bSZiPN5t_S_91DBRb28,67313
|
11
11
|
beswarm/aient/src/aient/core/response.py,sha256=WIJhvf3Th3-XW3MsDq8QdzxXcNe5q1mCKcCZx8pzm2A,33213
|
12
12
|
beswarm/aient/src/aient/core/utils.py,sha256=zidsBUBd3KskzcxQcPB1y5x1RhtWcbZeWvmgb4LAadA,27318
|
13
13
|
beswarm/aient/src/aient/core/test/test_base_api.py,sha256=pWnycRJbuPSXKKU9AQjWrMAX1wiLC_014Qc9hh5C2Pw,524
|
@@ -71,6 +71,11 @@ beswarm/aient/test/test_url.py,sha256=ASE3kT2-ooaX6Flw4botjXnuqaBgutqRWPx3fow5nL
|
|
71
71
|
beswarm/aient/test/test_whisper.py,sha256=GxKYzhh3lA8t62V_DDj42VQTXkdjocSFcl5u96WF580,378
|
72
72
|
beswarm/aient/test/test_wildcard.py,sha256=EQwwNrTMHV4CEcZKdwUVGrwtNUsF0CCKbM-_W9IOYsk,693
|
73
73
|
beswarm/aient/test/test_yjh.py,sha256=MsHuBLNOqi3fyX-uboBKmTevkZW_KVv12p-pkF5ir3Y,787
|
74
|
+
beswarm/bemcp/bemcp/__init__.py,sha256=Ss6bDXiZJgVIZS6KWytcGwXmIFu7SsagIXa5NpeWJ7c,140
|
75
|
+
beswarm/bemcp/bemcp/main.py,sha256=dadyyAv0yzEX5EYnBM14SmBN7X3keJTrheDVie2rVJ4,8370
|
76
|
+
beswarm/bemcp/bemcp/utils.py,sha256=YDRa4JvBjdNTJBzbrkwRXmRXy2gw8s9lb61WsnYZN10,953
|
77
|
+
beswarm/bemcp/test/client.py,sha256=j7PDg5Esyri-e2vz2ubZ4foDSAq5Iv8Yfl_xyzTDsFY,1593
|
78
|
+
beswarm/bemcp/test/server.py,sha256=58F0_xQTHc6mt_MTXLSz_JE1COzDdEYueco1OVuFay0,1159
|
74
79
|
beswarm/queries/tree-sitter-language-pack/README.md,sha256=ivZSEuWqYfUVLZl2AZZGRlm0bQsaG-VTBKBwACyM07k,291
|
75
80
|
beswarm/queries/tree-sitter-language-pack/arduino-tags.scm,sha256=HbgdothT9Jjk56COXTtUkVAdZ14rZNnqzLbWVLeRs5U,177
|
76
81
|
beswarm/queries/tree-sitter-language-pack/c-tags.scm,sha256=EIz45o5hBh8yEuck5ZR_4IpcGyWSeNrzxFmtkKZGt2k,461
|
@@ -126,10 +131,10 @@ beswarm/tools/planner.py,sha256=lguBCS6kpwNPoXQvqH-WySabVubT82iyWOkJnjt6dXw,1265
|
|
126
131
|
beswarm/tools/repomap.py,sha256=N09K0UgwjCN7Zjg_5TYlVsulp3n2fztYlS8twalChU8,45003
|
127
132
|
beswarm/tools/screenshot.py,sha256=u6t8FCgW5YHJ_Oc4coo8e0F3wTusWE_-H8dFh1rBq9Q,1011
|
128
133
|
beswarm/tools/search_arxiv.py,sha256=GpuIOYX8T0iRC-X-hmuR9AUJVn15WWZq864DaoC7BUc,8004
|
129
|
-
beswarm/tools/search_web.py,sha256=
|
134
|
+
beswarm/tools/search_web.py,sha256=w0T0aCqOVlb6Of5hn_TtpnrGXo6bMtw2aKZdkrYjycI,12069
|
130
135
|
beswarm/tools/think.py,sha256=WLw-7jNIsnS6n8MMSYUin_f-BGLENFmnKM2LISEp0co,1760
|
131
|
-
beswarm/tools/worker.py,sha256=
|
132
|
-
beswarm-0.1.
|
133
|
-
beswarm-0.1.
|
134
|
-
beswarm-0.1.
|
135
|
-
beswarm-0.1.
|
136
|
+
beswarm/tools/worker.py,sha256=E0NmvoAqHAxRCIUkn75Tf51kPmp3si1RA56TT-akzP8,17597
|
137
|
+
beswarm-0.1.93.dist-info/METADATA,sha256=kbRl2mGdzYj3vVZuPfZHjIUI6QGiRfi_jHC7-p8r1Ww,3584
|
138
|
+
beswarm-0.1.93.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
139
|
+
beswarm-0.1.93.dist-info/top_level.txt,sha256=pJw4O87wvt5882smuSO6DfByJz7FJ8SxxT8h9fHCmpo,8
|
140
|
+
beswarm-0.1.93.dist-info/RECORD,,
|
File without changes
|
File without changes
|