open-swarm 0.1.1743070217__py3-none-any.whl → 0.1.1743362777__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.
Files changed (217) hide show
  1. open_swarm-0.1.1743362777.dist-info/METADATA +217 -0
  2. open_swarm-0.1.1743362777.dist-info/RECORD +260 -0
  3. {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743362777.dist-info}/WHEEL +1 -2
  4. open_swarm-0.1.1743362777.dist-info/entry_points.txt +2 -0
  5. swarm/__init__.py +0 -2
  6. swarm/auth.py +53 -49
  7. swarm/blueprints/README.md +67 -0
  8. swarm/blueprints/burnt_noodles/blueprint_burnt_noodles.py +412 -0
  9. swarm/blueprints/chatbot/blueprint_chatbot.py +98 -0
  10. swarm/blueprints/chatbot/templates/chatbot/chatbot.html +33 -0
  11. swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +183 -0
  12. swarm/blueprints/dilbot_universe/blueprint_dilbot_universe.py +285 -0
  13. swarm/blueprints/divine_code/__init__.py +0 -0
  14. swarm/blueprints/divine_code/apps.py +11 -0
  15. swarm/blueprints/divine_code/blueprint_divine_code.py +219 -0
  16. swarm/blueprints/django_chat/apps.py +6 -0
  17. swarm/blueprints/django_chat/blueprint_django_chat.py +84 -0
  18. swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +37 -0
  19. swarm/blueprints/django_chat/urls.py +8 -0
  20. swarm/blueprints/django_chat/views.py +32 -0
  21. swarm/blueprints/echocraft/blueprint_echocraft.py +44 -0
  22. swarm/blueprints/family_ties/apps.py +11 -0
  23. swarm/blueprints/family_ties/blueprint_family_ties.py +152 -0
  24. swarm/blueprints/family_ties/models.py +19 -0
  25. swarm/blueprints/family_ties/serializers.py +7 -0
  26. swarm/blueprints/family_ties/settings.py +16 -0
  27. swarm/blueprints/family_ties/urls.py +10 -0
  28. swarm/blueprints/family_ties/views.py +26 -0
  29. swarm/blueprints/flock/__init__.py +0 -0
  30. swarm/blueprints/gaggle/blueprint_gaggle.py +184 -0
  31. swarm/blueprints/gotchaman/blueprint_gotchaman.py +232 -0
  32. swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +133 -0
  33. swarm/blueprints/messenger/templates/messenger/messenger.html +46 -0
  34. swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +234 -0
  35. swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +248 -0
  36. swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +156 -0
  37. swarm/blueprints/omniplex/blueprint_omniplex.py +221 -0
  38. swarm/blueprints/rue_code/__init__.py +0 -0
  39. swarm/blueprints/rue_code/blueprint_rue_code.py +291 -0
  40. swarm/blueprints/suggestion/blueprint_suggestion.py +110 -0
  41. swarm/blueprints/unapologetic_press/blueprint_unapologetic_press.py +298 -0
  42. swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
  43. swarm/blueprints/whiskeytango_foxtrot/apps.py +11 -0
  44. swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +256 -0
  45. swarm/extensions/blueprint/__init__.py +30 -15
  46. swarm/extensions/blueprint/agent_utils.py +16 -40
  47. swarm/extensions/blueprint/blueprint_base.py +141 -543
  48. swarm/extensions/blueprint/blueprint_discovery.py +112 -98
  49. swarm/extensions/blueprint/cli_handler.py +185 -0
  50. swarm/extensions/blueprint/config_loader.py +122 -0
  51. swarm/extensions/blueprint/django_utils.py +181 -79
  52. swarm/extensions/blueprint/interactive_mode.py +1 -1
  53. swarm/extensions/config/config_loader.py +83 -200
  54. swarm/extensions/launchers/build_swarm_wrapper.py +0 -0
  55. swarm/extensions/launchers/swarm_cli.py +199 -287
  56. swarm/llm/chat_completion.py +26 -55
  57. swarm/management/__init__.py +0 -0
  58. swarm/management/commands/__init__.py +0 -0
  59. swarm/management/commands/runserver.py +58 -0
  60. swarm/permissions.py +38 -0
  61. swarm/serializers.py +96 -5
  62. swarm/settings.py +95 -110
  63. swarm/static/contrib/fonts/fontawesome-webfont.ttf +7 -0
  64. swarm/static/contrib/fonts/fontawesome-webfont.woff +7 -0
  65. swarm/static/contrib/fonts/fontawesome-webfont.woff2 +7 -0
  66. swarm/static/contrib/markedjs/marked.min.js +6 -0
  67. swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +27 -0
  68. swarm/static/contrib/tabler-icons/alert-triangle.svg +21 -0
  69. swarm/static/contrib/tabler-icons/archive.svg +21 -0
  70. swarm/static/contrib/tabler-icons/artboard.svg +27 -0
  71. swarm/static/contrib/tabler-icons/automatic-gearbox.svg +23 -0
  72. swarm/static/contrib/tabler-icons/box-multiple.svg +19 -0
  73. swarm/static/contrib/tabler-icons/carambola.svg +19 -0
  74. swarm/static/contrib/tabler-icons/copy.svg +20 -0
  75. swarm/static/contrib/tabler-icons/download.svg +21 -0
  76. swarm/static/contrib/tabler-icons/edit.svg +21 -0
  77. swarm/static/contrib/tabler-icons/filled/carambola.svg +13 -0
  78. swarm/static/contrib/tabler-icons/filled/paint.svg +13 -0
  79. swarm/static/contrib/tabler-icons/headset.svg +22 -0
  80. swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +21 -0
  81. swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +21 -0
  82. swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +21 -0
  83. swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +21 -0
  84. swarm/static/contrib/tabler-icons/message-chatbot.svg +22 -0
  85. swarm/static/contrib/tabler-icons/message-star.svg +22 -0
  86. swarm/static/contrib/tabler-icons/message-x.svg +23 -0
  87. swarm/static/contrib/tabler-icons/message.svg +21 -0
  88. swarm/static/contrib/tabler-icons/paperclip.svg +18 -0
  89. swarm/static/contrib/tabler-icons/playlist-add.svg +22 -0
  90. swarm/static/contrib/tabler-icons/robot.svg +26 -0
  91. swarm/static/contrib/tabler-icons/search.svg +19 -0
  92. swarm/static/contrib/tabler-icons/settings.svg +20 -0
  93. swarm/static/contrib/tabler-icons/thumb-down.svg +19 -0
  94. swarm/static/contrib/tabler-icons/thumb-up.svg +19 -0
  95. swarm/static/css/dropdown.css +22 -0
  96. swarm/static/htmx/htmx.min.js +0 -0
  97. swarm/static/js/dropdown.js +23 -0
  98. swarm/static/rest_mode/css/base.css +470 -0
  99. swarm/static/rest_mode/css/chat-history.css +286 -0
  100. swarm/static/rest_mode/css/chat.css +251 -0
  101. swarm/static/rest_mode/css/chatbot.css +74 -0
  102. swarm/static/rest_mode/css/chatgpt.css +62 -0
  103. swarm/static/rest_mode/css/colors/corporate.css +74 -0
  104. swarm/static/rest_mode/css/colors/pastel.css +81 -0
  105. swarm/static/rest_mode/css/colors/tropical.css +82 -0
  106. swarm/static/rest_mode/css/general.css +142 -0
  107. swarm/static/rest_mode/css/layout.css +167 -0
  108. swarm/static/rest_mode/css/layouts/messenger-layout.css +17 -0
  109. swarm/static/rest_mode/css/layouts/minimalist-layout.css +57 -0
  110. swarm/static/rest_mode/css/layouts/mobile-layout.css +8 -0
  111. swarm/static/rest_mode/css/messages.css +84 -0
  112. swarm/static/rest_mode/css/messenger.css +135 -0
  113. swarm/static/rest_mode/css/settings.css +91 -0
  114. swarm/static/rest_mode/css/simple.css +44 -0
  115. swarm/static/rest_mode/css/slack.css +58 -0
  116. swarm/static/rest_mode/css/style.css +156 -0
  117. swarm/static/rest_mode/css/theme.css +30 -0
  118. swarm/static/rest_mode/css/toast.css +40 -0
  119. swarm/static/rest_mode/js/auth.js +9 -0
  120. swarm/static/rest_mode/js/blueprint.js +41 -0
  121. swarm/static/rest_mode/js/blueprintUtils.js +12 -0
  122. swarm/static/rest_mode/js/chatLogic.js +79 -0
  123. swarm/static/rest_mode/js/debug.js +63 -0
  124. swarm/static/rest_mode/js/events.js +98 -0
  125. swarm/static/rest_mode/js/main.js +19 -0
  126. swarm/static/rest_mode/js/messages.js +264 -0
  127. swarm/static/rest_mode/js/messengerLogic.js +355 -0
  128. swarm/static/rest_mode/js/modules/apiService.js +84 -0
  129. swarm/static/rest_mode/js/modules/blueprintManager.js +162 -0
  130. swarm/static/rest_mode/js/modules/chatHistory.js +110 -0
  131. swarm/static/rest_mode/js/modules/debugLogger.js +14 -0
  132. swarm/static/rest_mode/js/modules/eventHandlers.js +107 -0
  133. swarm/static/rest_mode/js/modules/messageProcessor.js +120 -0
  134. swarm/static/rest_mode/js/modules/state.js +7 -0
  135. swarm/static/rest_mode/js/modules/userInteractions.js +29 -0
  136. swarm/static/rest_mode/js/modules/validation.js +23 -0
  137. swarm/static/rest_mode/js/rendering.js +119 -0
  138. swarm/static/rest_mode/js/settings.js +130 -0
  139. swarm/static/rest_mode/js/sidebar.js +94 -0
  140. swarm/static/rest_mode/js/simpleLogic.js +37 -0
  141. swarm/static/rest_mode/js/slackLogic.js +66 -0
  142. swarm/static/rest_mode/js/splash.js +76 -0
  143. swarm/static/rest_mode/js/theme.js +111 -0
  144. swarm/static/rest_mode/js/toast.js +36 -0
  145. swarm/static/rest_mode/js/ui.js +265 -0
  146. swarm/static/rest_mode/js/validation.js +57 -0
  147. swarm/static/rest_mode/svg/animated_spinner.svg +12 -0
  148. swarm/static/rest_mode/svg/arrow_down.svg +5 -0
  149. swarm/static/rest_mode/svg/arrow_left.svg +5 -0
  150. swarm/static/rest_mode/svg/arrow_right.svg +5 -0
  151. swarm/static/rest_mode/svg/arrow_up.svg +5 -0
  152. swarm/static/rest_mode/svg/attach.svg +8 -0
  153. swarm/static/rest_mode/svg/avatar.svg +7 -0
  154. swarm/static/rest_mode/svg/canvas.svg +6 -0
  155. swarm/static/rest_mode/svg/chat_history.svg +4 -0
  156. swarm/static/rest_mode/svg/close.svg +5 -0
  157. swarm/static/rest_mode/svg/copy.svg +4 -0
  158. swarm/static/rest_mode/svg/dark_mode.svg +3 -0
  159. swarm/static/rest_mode/svg/edit.svg +5 -0
  160. swarm/static/rest_mode/svg/layout.svg +9 -0
  161. swarm/static/rest_mode/svg/logo.svg +29 -0
  162. swarm/static/rest_mode/svg/logout.svg +5 -0
  163. swarm/static/rest_mode/svg/mobile.svg +5 -0
  164. swarm/static/rest_mode/svg/new_chat.svg +4 -0
  165. swarm/static/rest_mode/svg/not_visible.svg +5 -0
  166. swarm/static/rest_mode/svg/plus.svg +7 -0
  167. swarm/static/rest_mode/svg/run_code.svg +6 -0
  168. swarm/static/rest_mode/svg/save.svg +4 -0
  169. swarm/static/rest_mode/svg/search.svg +6 -0
  170. swarm/static/rest_mode/svg/settings.svg +4 -0
  171. swarm/static/rest_mode/svg/speaker.svg +5 -0
  172. swarm/static/rest_mode/svg/stop.svg +6 -0
  173. swarm/static/rest_mode/svg/thumbs_down.svg +3 -0
  174. swarm/static/rest_mode/svg/thumbs_up.svg +3 -0
  175. swarm/static/rest_mode/svg/toggle_off.svg +6 -0
  176. swarm/static/rest_mode/svg/toggle_on.svg +6 -0
  177. swarm/static/rest_mode/svg/trash.svg +10 -0
  178. swarm/static/rest_mode/svg/undo.svg +3 -0
  179. swarm/static/rest_mode/svg/visible.svg +8 -0
  180. swarm/static/rest_mode/svg/voice.svg +10 -0
  181. swarm/templates/account/login.html +22 -0
  182. swarm/templates/account/signup.html +32 -0
  183. swarm/templates/base.html +30 -0
  184. swarm/templates/chat.html +43 -0
  185. swarm/templates/index.html +35 -0
  186. swarm/templates/rest_mode/components/chat_sidebar.html +55 -0
  187. swarm/templates/rest_mode/components/header.html +45 -0
  188. swarm/templates/rest_mode/components/main_chat_pane.html +41 -0
  189. swarm/templates/rest_mode/components/settings_dialog.html +97 -0
  190. swarm/templates/rest_mode/components/splash_screen.html +7 -0
  191. swarm/templates/rest_mode/components/top_bar.html +28 -0
  192. swarm/templates/rest_mode/message_ui.html +50 -0
  193. swarm/templates/rest_mode/slackbot.html +30 -0
  194. swarm/templates/simple_blueprint_page.html +24 -0
  195. swarm/templates/websocket_partials/final_system_message.html +3 -0
  196. swarm/templates/websocket_partials/system_message.html +4 -0
  197. swarm/templates/websocket_partials/user_message.html +5 -0
  198. swarm/urls.py +57 -74
  199. swarm/utils/log_utils.py +63 -0
  200. swarm/views/api_views.py +48 -39
  201. swarm/views/chat_views.py +156 -70
  202. swarm/views/core_views.py +85 -90
  203. swarm/views/model_views.py +64 -121
  204. swarm/views/utils.py +65 -441
  205. open_swarm-0.1.1743070217.dist-info/METADATA +0 -258
  206. open_swarm-0.1.1743070217.dist-info/RECORD +0 -89
  207. open_swarm-0.1.1743070217.dist-info/entry_points.txt +0 -3
  208. open_swarm-0.1.1743070217.dist-info/top_level.txt +0 -1
  209. swarm/agent/agent.py +0 -49
  210. swarm/core.py +0 -326
  211. swarm/extensions/mcp/__init__.py +0 -1
  212. swarm/extensions/mcp/cache_utils.py +0 -36
  213. swarm/extensions/mcp/mcp_client.py +0 -341
  214. swarm/extensions/mcp/mcp_constants.py +0 -7
  215. swarm/extensions/mcp/mcp_tool_provider.py +0 -110
  216. swarm/types.py +0 -126
  217. {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743362777.dist-info}/licenses/LICENSE +0 -0
@@ -1,341 +0,0 @@
1
- """
2
- MCP Client Module
3
-
4
- Manages connections and interactions with MCP servers using the MCP Python SDK.
5
- Redirects MCP server stderr to log files unless debug mode is enabled.
6
- """
7
-
8
- import asyncio
9
- import logging
10
- import os
11
- from typing import Any, Dict, List, Callable
12
- from contextlib import contextmanager
13
- import sys
14
- import json # Added for result parsing
15
-
16
- # Attempt to import mcp types carefully
17
- try:
18
- from mcp import ClientSession, StdioServerParameters # type: ignore
19
- from mcp.client.stdio import stdio_client # type: ignore
20
- MCP_AVAILABLE = True
21
- except ImportError:
22
- MCP_AVAILABLE = False
23
- # Define dummy classes if mcp is not installed
24
- class ClientSession: pass
25
- class StdioServerParameters: pass
26
- def stdio_client(*args, **kwargs): raise ImportError("mcp library not installed")
27
-
28
- from ...types import Tool # Use Tool from swarm.types
29
- from .cache_utils import get_cache
30
- from ...settings import Settings # Import Swarm settings
31
-
32
- # Use Swarm's settings for logging configuration
33
- swarm_settings = Settings()
34
- logger = logging.getLogger(__name__)
35
- logger.setLevel(swarm_settings.log_level.upper()) # Use log level from settings
36
- # Ensure handler is added only if needed, respecting potential global config
37
- if not logger.handlers and not logging.getLogger('swarm').handlers:
38
- handler = logging.StreamHandler()
39
- # Use log format from settings
40
- formatter = logging.Formatter(swarm_settings.log_format.value)
41
- handler.setFormatter(formatter)
42
- logger.addHandler(handler)
43
-
44
- class MCPClient:
45
- """
46
- Manages connections and interactions with MCP servers using the MCP Python SDK.
47
- """
48
-
49
- def __init__(self, server_config: Dict[str, Any], timeout: int = 15, debug: bool = False):
50
- """
51
- Initialize the MCPClient with server configuration.
52
-
53
- Args:
54
- server_config (dict): Configuration dictionary for the MCP server.
55
- timeout (int): Timeout for operations in seconds.
56
- debug (bool): If True, MCP server stderr goes to console; otherwise, suppressed.
57
- """
58
- if not MCP_AVAILABLE:
59
- raise ImportError("The 'mcp-client' library is required for MCP functionality but is not installed.")
60
-
61
- self.command = server_config.get("command", "npx")
62
- self.args = server_config.get("args", [])
63
- self.env = {**os.environ.copy(), **server_config.get("env", {})}
64
- self.timeout = timeout
65
- self.debug = debug or swarm_settings.debug # Use instance debug or global debug
66
- self._tool_cache: Dict[str, Tool] = {}
67
- self.cache = get_cache()
68
-
69
- # Validate command and args types
70
- if not isinstance(self.command, str):
71
- raise TypeError(f"MCP server command must be a string, got {type(self.command)}")
72
- if not isinstance(self.args, list) or not all(isinstance(a, str) for a in self.args):
73
- raise TypeError(f"MCP server args must be a list of strings, got {self.args}")
74
-
75
-
76
- logger.info(f"Initialized MCPClient with command={self.command}, args={self.args}, debug={self.debug}")
77
-
78
- @contextmanager
79
- def _redirect_stderr(self):
80
- """Redirects stderr to /dev/null if not in debug mode."""
81
- if not self.debug:
82
- original_stderr = sys.stderr
83
- devnull = None
84
- try:
85
- devnull = open(os.devnull, "w")
86
- sys.stderr = devnull
87
- yield
88
- except Exception:
89
- # Restore stderr even if there was an error opening /dev/null or during yield
90
- if devnull: devnull.close()
91
- sys.stderr = original_stderr
92
- raise # Re-raise the exception
93
- finally:
94
- if devnull: devnull.close()
95
- sys.stderr = original_stderr
96
- else:
97
- # If debug is True, don't redirect
98
- yield
99
-
100
- async def list_tools(self) -> List[Tool]:
101
- """
102
- Discover tools from the MCP server and cache their schemas.
103
-
104
- Returns:
105
- List[Tool]: A list of discovered tools with schemas.
106
- """
107
- logger.debug(f"Entering list_tools for command={self.command}, args={self.args}")
108
-
109
- # Attempt to retrieve tools from cache
110
- # Create a more robust cache key
111
- args_string = json.dumps(self.args, sort_keys=True) # Serialize args consistently
112
- cache_key = f"mcp_tools_{self.command}_{args_string}"
113
- cached_tools_data = self.cache.get(cache_key)
114
-
115
- if cached_tools_data:
116
- logger.debug("Retrieved tools data from cache")
117
- tools = []
118
- for tool_data in cached_tools_data:
119
- tool_name = tool_data["name"]
120
- # Create Tool instance, ensuring func is a callable wrapper
121
- tool = Tool(
122
- name=tool_name,
123
- description=tool_data["description"],
124
- input_schema=tool_data.get("input_schema", {"type": "object", "properties": {}}),
125
- func=self._create_tool_callable(tool_name), # Use the factory method
126
- )
127
- self._tool_cache[tool_name] = tool # Store in instance cache too
128
- tools.append(tool)
129
- logger.debug(f"Returning {len(tools)} cached tools")
130
- return tools
131
-
132
- # If not in cache, discover from server
133
- server_params = StdioServerParameters(command=self.command, args=self.args, env=self.env)
134
- logger.debug("Opening stdio_client connection")
135
- try:
136
- async with stdio_client(server_params) as (read, write):
137
- logger.debug("Opening ClientSession")
138
- async with ClientSession(read, write) as session:
139
- logger.info("Initializing session for tool discovery")
140
- await asyncio.wait_for(session.initialize(), timeout=self.timeout)
141
- logger.info("Requesting tool list from MCP server...")
142
- tools_response = await asyncio.wait_for(session.list_tools(), timeout=self.timeout)
143
- logger.debug(f"Tool list received: {tools_response}")
144
-
145
- if not hasattr(tools_response, 'tools') or not isinstance(tools_response.tools, list):
146
- logger.error(f"Invalid tool list response from MCP server: {tools_response}")
147
- return []
148
-
149
- serialized_tools = []
150
- tools = []
151
- for tool_proto in tools_response.tools:
152
- if not hasattr(tool_proto, 'name') or not tool_proto.name:
153
- logger.warning(f"Skipping tool with missing name in response: {tool_proto}")
154
- continue
155
-
156
- # Ensure inputSchema exists and is a dict, default if not
157
- input_schema = getattr(tool_proto, 'inputSchema', None)
158
- if not isinstance(input_schema, dict):
159
- input_schema = {"type": "object", "properties": {}}
160
-
161
- description = getattr(tool_proto, 'description', "") or "" # Ensure description is string
162
-
163
- serialized_tool_data = {
164
- 'name': tool_proto.name,
165
- 'description': description,
166
- 'input_schema': input_schema,
167
- }
168
- serialized_tools.append(serialized_tool_data)
169
-
170
- # Create Tool instance for returning
171
- discovered_tool = Tool(
172
- name=tool_proto.name,
173
- description=description,
174
- input_schema=input_schema,
175
- func=self._create_tool_callable(tool_proto.name),
176
- )
177
- self._tool_cache[tool_proto.name] = discovered_tool # Cache instance
178
- tools.append(discovered_tool)
179
- logger.debug(f"Discovered tool: {tool_proto.name} with schema: {input_schema}")
180
-
181
- # Cache the serialized data
182
- self.cache.set(cache_key, serialized_tools, 3600)
183
- logger.debug(f"Cached {len(serialized_tools)} tools.")
184
-
185
- logger.debug(f"Returning {len(tools)} tools from MCP server")
186
- return tools
187
-
188
- except asyncio.TimeoutError:
189
- logger.error(f"Timeout after {self.timeout}s waiting for tool list")
190
- raise RuntimeError("Tool list request timed out")
191
- except Exception as e:
192
- logger.error(f"Error listing tools: {e}", exc_info=True)
193
- raise RuntimeError(f"Failed to list tools: {e}") from e
194
-
195
- async def _do_list_resources(self) -> Any:
196
- """Internal method to list resources with timeout."""
197
- server_params = StdioServerParameters(command=self.command, args=self.args, env=self.env)
198
- logger.debug("Opening stdio_client connection for resources")
199
- try:
200
- async with stdio_client(server_params) as (read, write):
201
- logger.debug("Opening ClientSession for resources")
202
- async with ClientSession(read, write) as session:
203
- with self._redirect_stderr(): # Suppress stderr if not debugging
204
- logger.debug("Initializing session before listing resources")
205
- await asyncio.wait_for(session.initialize(), timeout=self.timeout)
206
- logger.info("Requesting resource list from MCP server...")
207
- resources_response = await asyncio.wait_for(session.list_resources(), timeout=self.timeout)
208
- logger.debug("Resource list received from MCP server")
209
- return resources_response
210
- except asyncio.TimeoutError:
211
- logger.error(f"Timeout listing resources after {self.timeout}s")
212
- raise RuntimeError("Resource list request timed out")
213
- except Exception as e:
214
- logger.error(f"Error listing resources: {e}", exc_info=True)
215
- raise RuntimeError(f"Failed to list resources: {e}") from e
216
-
217
- def _create_tool_callable(self, tool_name: str) -> Callable[..., Any]:
218
- """
219
- Dynamically create an async callable function for the specified tool.
220
- This callable will establish a connection and execute the tool on demand.
221
- """
222
- async def dynamic_tool_func(**kwargs) -> Any:
223
- logger.debug(f"Creating tool callable for '{tool_name}'")
224
- server_params = StdioServerParameters(command=self.command, args=self.args, env=self.env)
225
- try:
226
- async with stdio_client(server_params) as (read, write):
227
- async with ClientSession(read, write) as session:
228
- # Initialize session first
229
- logger.debug(f"Initializing session for tool '{tool_name}'")
230
- await asyncio.wait_for(session.initialize(), timeout=self.timeout)
231
-
232
- # Validate input if schema is available in instance cache
233
- if tool_name in self._tool_cache:
234
- tool = self._tool_cache[tool_name]
235
- self._validate_input_schema(tool.input_schema, kwargs)
236
- else:
237
- logger.warning(f"Schema for tool '{tool_name}' not found in cache for validation.")
238
-
239
- logger.info(f"Calling tool '{tool_name}' with arguments: {kwargs}")
240
- # Execute the tool call
241
- result_proto = await asyncio.wait_for(session.call_tool(tool_name, kwargs), timeout=self.timeout)
242
-
243
- # Process result (assuming result_proto has a 'result' attribute)
244
- result_data = getattr(result_proto, 'result', None)
245
- if result_data is None:
246
- logger.warning(f"Tool '{tool_name}' executed but returned no result data.")
247
- return None # Or raise error?
248
-
249
- # Attempt to parse if it looks like JSON, otherwise return as is
250
- if isinstance(result_data, str):
251
- try:
252
- parsed_result = json.loads(result_data)
253
- logger.info(f"Tool '{tool_name}' executed successfully (result parsed as JSON).")
254
- return parsed_result
255
- except json.JSONDecodeError:
256
- logger.info(f"Tool '{tool_name}' executed successfully (result returned as string).")
257
- return result_data # Return raw string if not JSON
258
- else:
259
- logger.info(f"Tool '{tool_name}' executed successfully (result type: {type(result_data)}).")
260
- return result_data # Return non-string result directly
261
-
262
- except asyncio.TimeoutError:
263
- logger.error(f"Timeout after {self.timeout}s executing tool '{tool_name}'")
264
- raise RuntimeError(f"Tool '{tool_name}' execution timed out")
265
- except Exception as e:
266
- logger.error(f"Failed to execute tool '{tool_name}': {e}", exc_info=True)
267
- raise RuntimeError(f"Tool execution failed: {e}") from e
268
-
269
- return dynamic_tool_func
270
-
271
- def _validate_input_schema(self, schema: Dict[str, Any], kwargs: Dict[str, Any]):
272
- """
273
- Validate the provided arguments against the input schema.
274
- """
275
- # Ensure schema is a dictionary, default to no-op if not
276
- if not isinstance(schema, dict):
277
- logger.warning(f"Invalid schema format for validation: {type(schema)}. Skipping.")
278
- return
279
-
280
- required_params = schema.get("required", [])
281
- # Ensure required_params is a list
282
- if not isinstance(required_params, list):
283
- logger.warning(f"Invalid 'required' list in schema: {type(required_params)}. Skipping requirement check.")
284
- required_params = []
285
-
286
- for param in required_params:
287
- if param not in kwargs:
288
- raise ValueError(f"Missing required parameter: '{param}'")
289
-
290
- # Optional: Add type validation based on schema['properties'][param]['type']
291
- properties = schema.get("properties", {})
292
- if isinstance(properties, dict):
293
- for key, value in kwargs.items():
294
- if key in properties:
295
- expected_type = properties[key].get("type")
296
- # Basic type mapping (add more as needed)
297
- type_map = {"string": str, "integer": int, "number": (int, float), "boolean": bool, "array": list, "object": dict}
298
- if expected_type in type_map:
299
- if not isinstance(value, type_map[expected_type]):
300
- logger.warning(f"Type mismatch for parameter '{key}'. Expected '{expected_type}', got '{type(value).__name__}'. Attempting to proceed.")
301
- # Allow proceeding but log warning, or raise ValueError for strict validation
302
-
303
- logger.debug(f"Validated input against schema: {schema} with arguments: {kwargs}")
304
-
305
- async def list_resources(self) -> Any:
306
- """
307
- Discover resources from the MCP server using the internal method with enforced timeout.
308
- """
309
- return await self._do_list_resources() # Timeout handled in _do_list_resources
310
-
311
- async def get_resource(self, resource_uri: str) -> Any:
312
- """
313
- Retrieve a specific resource from the MCP server.
314
-
315
- Args:
316
- resource_uri (str): The URI of the resource to retrieve.
317
-
318
- Returns:
319
- Any: The resource retrieval response.
320
- """
321
- server_params = StdioServerParameters(command=self.command, args=self.args, env=self.env)
322
- logger.debug("Opening stdio_client connection for resource retrieval")
323
- try:
324
- async with stdio_client(server_params) as (read, write):
325
- logger.debug("Opening ClientSession for resource retrieval")
326
- async with ClientSession(read, write) as session:
327
- with self._redirect_stderr(): # Suppress stderr if not debugging
328
- logger.debug(f"Initializing session for resource retrieval of {resource_uri}")
329
- await asyncio.wait_for(session.initialize(), timeout=self.timeout)
330
- logger.info(f"Retrieving resource '{resource_uri}' from MCP server")
331
- response = await asyncio.wait_for(session.read_resource(resource_uri), timeout=self.timeout)
332
- logger.info(f"Resource '{resource_uri}' retrieved successfully")
333
- # Process response if needed (e.g., getattr(response, 'content', None))
334
- return response
335
- except asyncio.TimeoutError:
336
- logger.error(f"Timeout retrieving resource '{resource_uri}' after {self.timeout}s")
337
- raise RuntimeError(f"Resource '{resource_uri}' retrieval timed out")
338
- except Exception as e:
339
- logger.error(f"Failed to retrieve resource '{resource_uri}': {e}", exc_info=True)
340
- raise RuntimeError(f"Resource retrieval failed: {e}") from e
341
-
@@ -1,7 +0,0 @@
1
- """
2
- Constants specific to MCP interactions.
3
- """
4
-
5
- # Separator used in tool results to signal agent handoff
6
- MCP_SEPARATOR = ":::"
7
-
@@ -1,110 +0,0 @@
1
- """
2
- MCPToolProvider Module for Open-Swarm
3
-
4
- This module is responsible for discovering tools from MCP (Model Context Protocol) servers
5
- and integrating them into the Open-Swarm framework as `Tool` instances.
6
- """
7
-
8
- import logging
9
- import json
10
- import re # Standard library for regular expressions
11
- from typing import List, Dict, Any
12
-
13
- from ...settings import Settings # Use Swarm settings
14
- from ...types import Tool, Agent
15
- from .mcp_client import MCPClient
16
- from .cache_utils import get_cache
17
-
18
- # Use Swarm's settings for logging configuration
19
- swarm_settings = Settings()
20
- logger = logging.getLogger(__name__)
21
- logger.setLevel(swarm_settings.log_level.upper())
22
- # Ensure handler is added only if needed
23
- if not logger.handlers and not logging.getLogger('swarm').handlers:
24
- handler = logging.StreamHandler()
25
- formatter = logging.Formatter(swarm_settings.log_format.value)
26
- handler.setFormatter(formatter)
27
- logger.addHandler(handler)
28
-
29
-
30
- class MCPToolProvider:
31
- """
32
- MCPToolProvider discovers tools from an MCP server and converts them into `Tool` instances.
33
- Uses caching to avoid repeated discovery.
34
- """
35
- _instances: Dict[str, "MCPToolProvider"] = {}
36
-
37
- @classmethod
38
- def get_instance(cls, server_name: str, server_config: Dict[str, Any], timeout: int = 15, debug: bool = False) -> "MCPToolProvider":
39
- """Get or create an instance for the given server name."""
40
- config_key = json.dumps(server_config, sort_keys=True)
41
- instance_key = f"{server_name}_{config_key}_{timeout}_{debug}"
42
-
43
- if instance_key not in cls._instances:
44
- logger.debug(f"Creating new MCPToolProvider instance for key: {instance_key}")
45
- cls._instances[instance_key] = cls(server_name, server_config, timeout, debug)
46
- else:
47
- logger.debug(f"Reusing existing MCPToolProvider instance for key: {instance_key}")
48
- return cls._instances[instance_key]
49
-
50
- def __init__(self, server_name: str, server_config: Dict[str, Any], timeout: int = 15, debug: bool = False):
51
- """
52
- Initialize an MCPToolProvider instance. Use get_instance() for shared instances.
53
- """
54
- self.server_name = server_name
55
- effective_debug = debug or swarm_settings.debug
56
- try:
57
- self.client = MCPClient(server_config=server_config, timeout=timeout, debug=effective_debug)
58
- except ImportError as e:
59
- logger.error(f"Failed to initialize MCPClient for '{server_name}': {e}. MCP features will be unavailable.")
60
- self.client = None
61
- except Exception as e:
62
- logger.error(f"Error initializing MCPClient for '{server_name}': {e}", exc_info=True)
63
- self.client = None
64
-
65
- self.cache = get_cache()
66
- logger.debug(f"Initialized MCPToolProvider for server '{self.server_name}' with timeout {timeout}s.")
67
-
68
- async def discover_tools(self, agent: Agent) -> List[Tool]:
69
- """
70
- Discover tools from the MCP server using the MCPClient.
71
-
72
- Args:
73
- agent (Agent): The agent for which tools are being discovered.
74
-
75
- Returns:
76
- List[Tool]: A list of discovered `Tool` instances with prefixed names.
77
- """
78
- if not self.client:
79
- logger.warning(f"MCPClient for '{self.server_name}' not initialized. Cannot discover tools.")
80
- return []
81
-
82
- logger.debug(f"Starting tool discovery via MCPClient for server '{self.server_name}'.")
83
- try:
84
- tools = await self.client.list_tools()
85
- logger.debug(f"Discovered {len(tools)} tools from MCP server '{self.server_name}'.")
86
-
87
- separator = "__"
88
- prefixed_tools = []
89
- for tool in tools:
90
- prefixed_name = f"{self.server_name}{separator}{tool.name}"
91
- # Validate prefixed name against OpenAI pattern
92
- if not re.match(r"^[a-zA-Z0-9_-]{1,64}$", prefixed_name):
93
- logger.warning(f"Generated MCP tool name '{prefixed_name}' might violate OpenAI pattern. Skipping.")
94
- continue
95
-
96
- prefixed_tool = Tool(
97
- name=prefixed_name,
98
- description=tool.description,
99
- input_schema=tool.input_schema,
100
- func=tool.func # Callable already targets this client/tool
101
- )
102
- prefixed_tools.append(prefixed_tool)
103
- logger.debug(f"Added prefixed tool: {prefixed_tool.name}")
104
-
105
- return prefixed_tools
106
-
107
- except Exception as e:
108
- logger.error(f"Failed to discover tools from MCP server '{self.server_name}': {e}", exc_info=True)
109
- return []
110
-
swarm/types.py DELETED
@@ -1,126 +0,0 @@
1
- from openai.types.chat import ChatCompletionMessage
2
- from openai.types.chat.chat_completion_message_tool_call import (
3
- ChatCompletionMessageToolCall,
4
- Function as OpenAIFunction, # Renamed to avoid clash
5
- )
6
- from typing import List, Callable, Union, Optional, Dict, Any
7
-
8
- from pydantic import BaseModel, ConfigDict, Field
9
- from pydantic_settings import BaseSettings, SettingsConfigDict
10
- import uuid
11
- from enum import Enum
12
-
13
- # --- Pydantic Settings for Swarm Core ---
14
- class LogFormat(str, Enum):
15
- standard = "[%(levelname)s] %(asctime)s - %(name)s:%(lineno)d - %(message)s"
16
- simple = "[%(levelname)s] %(name)s - %(message)s"
17
-
18
- class Settings(BaseSettings):
19
- model_config = SettingsConfigDict(env_prefix='SWARM_', case_sensitive=False)
20
-
21
- log_level: str = Field(default='INFO', description="Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)")
22
- log_format: LogFormat = Field(default=LogFormat.standard, description="Logging format")
23
- debug: bool = Field(default=False, description="Global debug flag")
24
-
25
- # --- LLMConfig ---
26
- class LLMConfig(BaseModel):
27
- """Configuration for a specific LLM profile."""
28
- provider: Optional[str] = "openai"
29
- model: Optional[str] = None
30
- api_key: Optional[str] = None
31
- base_url: Optional[str] = None
32
- max_tokens: Optional[int] = None # Max tokens supported by model
33
- temperature: Optional[float] = 0.7
34
- cost: Optional[float] = None
35
- speed: Optional[float] = None
36
- intelligence: Optional[float] = None
37
- passthrough: Optional[bool] = False
38
-
39
- model_config = ConfigDict(extra='allow')
40
-
41
- # --- Moved Tool Types Definition Higher ---
42
- class ToolFunction(BaseModel):
43
- name: str
44
- arguments: str # Should be a JSON string
45
-
46
- class ToolCall(BaseModel):
47
- id: str
48
- type: str = "function"
49
- function: ToolFunction
50
-
51
- class ToolResult(BaseModel):
52
- tool_call_id: str
53
- role: str = "tool" # Added role for consistency
54
- name: Optional[str] = None # Name of the function that was called
55
- content: str
56
- # --- End Tool Types ---
57
-
58
- # AgentFunction needs Agent defined, so keep it below Agent
59
- # AgentFunction = Callable[[], Union[str, "Agent", dict]]
60
- AgentFunction = Callable[..., Union[str, "Agent", dict]]
61
-
62
- class Agent(BaseModel):
63
- name: str = "Agent"
64
- model: str = "default" # LLM profile name to use
65
- instructions: Union[str, Callable[[], str]] = "You are a helpful agent."
66
- functions: List[AgentFunction] = []
67
- resources: List[Dict[str, Any]] = []
68
- tool_choice: Optional[str] = None
69
- parallel_tool_calls: bool = False
70
- mcp_servers: Optional[List[str]] = None
71
- env_vars: Optional[Dict[str, str]] = None
72
- response_format: Optional[Dict[str, Any]] = None
73
-
74
- # --- ChatMessage Definition (Now ToolCall is defined) ---
75
- class ChatMessage(BaseModel):
76
- """Represents a message in the chat history, potentially with tool calls."""
77
- role: str
78
- content: Optional[str] = None
79
- tool_calls: Optional[List[ToolCall]] = None
80
- tool_call_id: Optional[str] = None # For tool results
81
- name: Optional[str] = None # For tool results or function name
82
- sender: Optional[str] = None # Track the agent sending the message
83
-
84
- model_config = ConfigDict(extra="allow")
85
- # --- End ChatMessage ---
86
-
87
- class Response(BaseModel):
88
- id: Optional[str] = Field(default_factory=lambda: f"response-{uuid.uuid4()}")
89
- messages: List[ChatMessage] = [] # Use ChatMessage type hint
90
- agent: Optional[Agent] = None
91
- context_variables: dict = {}
92
-
93
- class Result(BaseModel):
94
- """
95
- Encapsulates the possible return values for an agent function.
96
- """
97
- value: str = ""
98
- agent: Optional[Agent] = None
99
- context_variables: dict = {}
100
-
101
- # Re-defined Tool class
102
- class Tool:
103
- def __init__(
104
- self,
105
- name: str,
106
- func: Callable,
107
- description: str = "",
108
- input_schema: Optional[Dict[str, Any]] = None,
109
- dynamic: bool = False,
110
- ):
111
- self.name = name
112
- self.func = func
113
- self.description = description
114
- self.input_schema = input_schema or {"type": "object", "properties": {}} # Default schema
115
- self.dynamic = dynamic
116
-
117
- @property
118
- def __name__(self): return self.name
119
- @property
120
- def __code__(self): return getattr(self.func, "__code__", None)
121
- def __call__(self, *args, **kwargs): return self.func(*args, **kwargs)
122
-
123
- # Type alias for tool definitions used in discovery
124
- ToolDefinition = Dict[str, Any] # A dictionary representing a tool's schema
125
- Resource = Dict[str, Any] # A dictionary representing a resource
126
-