swarms 7.7.9__py3-none-any.whl → 7.8.0__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.
@@ -1,340 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any
4
-
5
-
6
- from loguru import logger
7
-
8
- import abc
9
- import asyncio
10
- from contextlib import AbstractAsyncContextManager, AsyncExitStack
11
- from pathlib import Path
12
- from typing import Literal
13
-
14
- from anyio.streams.memory import (
15
- MemoryObjectReceiveStream,
16
- MemoryObjectSendStream,
17
- )
18
- from mcp import (
19
- ClientSession,
20
- StdioServerParameters,
21
- Tool as MCPTool,
22
- stdio_client,
23
- )
24
- from mcp.client.sse import sse_client
25
- from mcp.types import CallToolResult, JSONRPCMessage
26
- from typing_extensions import NotRequired, TypedDict
27
-
28
-
29
- class MCPServer(abc.ABC):
30
- """Base class for Model Context Protocol servers."""
31
-
32
- @abc.abstractmethod
33
- async def connect(self):
34
- """Connect to the server. For example, this might mean spawning a subprocess or
35
- opening a network connection. The server is expected to remain connected until
36
- `cleanup()` is called.
37
- """
38
- pass
39
-
40
- @property
41
- @abc.abstractmethod
42
- def name(self) -> str:
43
- """A readable name for the server."""
44
- pass
45
-
46
- @abc.abstractmethod
47
- async def cleanup(self):
48
- """Cleanup the server. For example, this might mean closing a subprocess or
49
- closing a network connection.
50
- """
51
- pass
52
-
53
- @abc.abstractmethod
54
- async def list_tools(self) -> list[MCPTool]:
55
- """List the tools available on the server."""
56
- pass
57
-
58
- @abc.abstractmethod
59
- async def call_tool(
60
- self, tool_name: str, arguments: dict[str, Any] | None
61
- ) -> CallToolResult:
62
- """Invoke a tool on the server."""
63
- pass
64
-
65
-
66
- class _MCPServerWithClientSession(MCPServer, abc.ABC):
67
- """Base class for MCP servers that use a `ClientSession` to communicate with the server."""
68
-
69
- def __init__(self, cache_tools_list: bool):
70
- """
71
- Args:
72
- cache_tools_list: Whether to cache the tools list. If `True`, the tools list will be
73
- cached and only fetched from the server once. If `False`, the tools list will be
74
- fetched from the server on each call to `list_tools()`. The cache can be invalidated
75
- by calling `invalidate_tools_cache()`. You should set this to `True` if you know the
76
- server will not change its tools list, because it can drastically improve latency
77
- (by avoiding a round-trip to the server every time).
78
- """
79
- self.session: ClientSession | None = None
80
- self.exit_stack: AsyncExitStack = AsyncExitStack()
81
- self._cleanup_lock: asyncio.Lock = asyncio.Lock()
82
- self.cache_tools_list = cache_tools_list
83
-
84
- # The cache is always dirty at startup, so that we fetch tools at least once
85
- self._cache_dirty = True
86
- self._tools_list: list[MCPTool] | None = None
87
-
88
- @abc.abstractmethod
89
- def create_streams(
90
- self,
91
- ) -> AbstractAsyncContextManager[
92
- tuple[
93
- MemoryObjectReceiveStream[JSONRPCMessage | Exception],
94
- MemoryObjectSendStream[JSONRPCMessage],
95
- ]
96
- ]:
97
- """Create the streams for the server."""
98
- pass
99
-
100
- async def __aenter__(self):
101
- await self.connect()
102
- return self
103
-
104
- async def __aexit__(self, exc_type, exc_value, traceback):
105
- await self.cleanup()
106
-
107
- def invalidate_tools_cache(self):
108
- """Invalidate the tools cache."""
109
- self._cache_dirty = True
110
-
111
- async def connect(self):
112
- """Connect to the server."""
113
- try:
114
- transport = await self.exit_stack.enter_async_context(
115
- self.create_streams()
116
- )
117
- read, write = transport
118
- session = await self.exit_stack.enter_async_context(
119
- ClientSession(read, write)
120
- )
121
- await session.initialize()
122
- self.session = session
123
- except Exception as e:
124
- logger.error(f"Error initializing MCP server: {e}")
125
- await self.cleanup()
126
- raise
127
-
128
- async def list_tools(self) -> list[MCPTool]:
129
- """List the tools available on the server."""
130
- if not self.session:
131
- raise Exception(
132
- "Server not initialized. Make sure you call `connect()` first."
133
- )
134
-
135
- # Return from cache if caching is enabled, we have tools, and the cache is not dirty
136
- if (
137
- self.cache_tools_list
138
- and not self._cache_dirty
139
- and self._tools_list
140
- ):
141
- return self._tools_list
142
-
143
- # Reset the cache dirty to False
144
- self._cache_dirty = False
145
-
146
- # Fetch the tools from the server
147
- self._tools_list = (await self.session.list_tools()).tools
148
- return self._tools_list
149
-
150
- async def call_tool(
151
- self, arguments: dict[str, Any] | None
152
- ) -> CallToolResult:
153
- """Invoke a tool on the server."""
154
- tool_name = arguments.get("tool_name") or arguments.get(
155
- "name"
156
- )
157
-
158
- if not tool_name:
159
- raise Exception("No tool name found in arguments")
160
-
161
- if not self.session:
162
- raise Exception(
163
- "Server not initialized. Make sure you call `connect()` first."
164
- )
165
-
166
- return await self.session.call_tool(tool_name, arguments)
167
-
168
- async def cleanup(self):
169
- """Cleanup the server."""
170
- async with self._cleanup_lock:
171
- try:
172
- await self.exit_stack.aclose()
173
- self.session = None
174
- except Exception as e:
175
- logger.error(f"Error cleaning up server: {e}")
176
-
177
-
178
- class MCPServerStdioParams(TypedDict):
179
- """Mirrors `mcp.client.stdio.StdioServerParameters`, but lets you pass params without another
180
- import.
181
- """
182
-
183
- command: str
184
- """The executable to run to start the server. For example, `python` or `node`."""
185
-
186
- args: NotRequired[list[str]]
187
- """Command line args to pass to the `command` executable. For example, `['foo.py']` or
188
- `['server.js', '--port', '8080']`."""
189
-
190
- env: NotRequired[dict[str, str]]
191
- """The environment variables to set for the server. ."""
192
-
193
- cwd: NotRequired[str | Path]
194
- """The working directory to use when spawning the process."""
195
-
196
- encoding: NotRequired[str]
197
- """The text encoding used when sending/receiving messages to the server. Defaults to `utf-8`."""
198
-
199
- encoding_error_handler: NotRequired[
200
- Literal["strict", "ignore", "replace"]
201
- ]
202
- """The text encoding error handler. Defaults to `strict`.
203
-
204
- See https://docs.python.org/3/library/codecs.html#codec-base-classes for
205
- explanations of possible values.
206
- """
207
-
208
-
209
- class MCPServerStdio(_MCPServerWithClientSession):
210
- """MCP server implementation that uses the stdio transport. See the [spec]
211
- (https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#stdio) for
212
- details.
213
- """
214
-
215
- def __init__(
216
- self,
217
- params: MCPServerStdioParams,
218
- cache_tools_list: bool = False,
219
- name: str | None = None,
220
- ):
221
- """Create a new MCP server based on the stdio transport.
222
-
223
- Args:
224
- params: The params that configure the server. This includes the command to run to
225
- start the server, the args to pass to the command, the environment variables to
226
- set for the server, the working directory to use when spawning the process, and
227
- the text encoding used when sending/receiving messages to the server.
228
- cache_tools_list: Whether to cache the tools list. If `True`, the tools list will be
229
- cached and only fetched from the server once. If `False`, the tools list will be
230
- fetched from the server on each call to `list_tools()`. The cache can be
231
- invalidated by calling `invalidate_tools_cache()`. You should set this to `True`
232
- if you know the server will not change its tools list, because it can drastically
233
- improve latency (by avoiding a round-trip to the server every time).
234
- name: A readable name for the server. If not provided, we'll create one from the
235
- command.
236
- """
237
- super().__init__(cache_tools_list)
238
-
239
- self.params = StdioServerParameters(
240
- command=params["command"],
241
- args=params.get("args", []),
242
- env=params.get("env"),
243
- cwd=params.get("cwd"),
244
- encoding=params.get("encoding", "utf-8"),
245
- encoding_error_handler=params.get(
246
- "encoding_error_handler", "strict"
247
- ),
248
- )
249
-
250
- self._name = name or f"stdio: {self.params.command}"
251
-
252
- def create_streams(
253
- self,
254
- ) -> AbstractAsyncContextManager[
255
- tuple[
256
- MemoryObjectReceiveStream[JSONRPCMessage | Exception],
257
- MemoryObjectSendStream[JSONRPCMessage],
258
- ]
259
- ]:
260
- """Create the streams for the server."""
261
- return stdio_client(self.params)
262
-
263
- @property
264
- def name(self) -> str:
265
- """A readable name for the server."""
266
- return self._name
267
-
268
-
269
- class MCPServerSseParams(TypedDict):
270
- """Mirrors the params in`mcp.client.sse.sse_client`."""
271
-
272
- url: str
273
- """The URL of the server."""
274
-
275
- headers: NotRequired[dict[str, str]]
276
- """The headers to send to the server."""
277
-
278
- timeout: NotRequired[float]
279
- """The timeout for the HTTP request. Defaults to 5 seconds."""
280
-
281
- sse_read_timeout: NotRequired[float]
282
- """The timeout for the SSE connection, in seconds. Defaults to 5 minutes."""
283
-
284
-
285
- class MCPServerSse(_MCPServerWithClientSession):
286
- """MCP server implementation that uses the HTTP with SSE transport. See the [spec]
287
- (https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse)
288
- for details.
289
- """
290
-
291
- def __init__(
292
- self,
293
- params: MCPServerSseParams,
294
- cache_tools_list: bool = False,
295
- name: str | None = None,
296
- ):
297
- """Create a new MCP server based on the HTTP with SSE transport.
298
-
299
- Args:
300
- params: The params that configure the server. This includes the URL of the server,
301
- the headers to send to the server, the timeout for the HTTP request, and the
302
- timeout for the SSE connection.
303
-
304
- cache_tools_list: Whether to cache the tools list. If `True`, the tools list will be
305
- cached and only fetched from the server once. If `False`, the tools list will be
306
- fetched from the server on each call to `list_tools()`. The cache can be
307
- invalidated by calling `invalidate_tools_cache()`. You should set this to `True`
308
- if you know the server will not change its tools list, because it can drastically
309
- improve latency (by avoiding a round-trip to the server every time).
310
-
311
- name: A readable name for the server. If not provided, we'll create one from the
312
- URL.
313
- """
314
- super().__init__(cache_tools_list)
315
-
316
- self.params = params
317
- self._name = name or f"sse: {self.params['url']}"
318
-
319
- def create_streams(
320
- self,
321
- ) -> AbstractAsyncContextManager[
322
- tuple[
323
- MemoryObjectReceiveStream[JSONRPCMessage | Exception],
324
- MemoryObjectSendStream[JSONRPCMessage],
325
- ]
326
- ]:
327
- """Create the streams for the server."""
328
- return sse_client(
329
- url=self.params["url"],
330
- headers=self.params.get("headers", None),
331
- timeout=self.params.get("timeout", 5),
332
- sse_read_timeout=self.params.get(
333
- "sse_read_timeout", 60 * 5
334
- ),
335
- )
336
-
337
- @property
338
- def name(self) -> str:
339
- """A readable name for the server."""
340
- return self._name
File without changes