You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
swarms/attached_assets/Pasted-Below-is-a-surgical-...

189 lines
6.0 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

Below is a **surgical “diffstyle” checklist** that shows exactly what you have to change (and what you can delete outright) to migrate your current `Agent`/`mcp_integration` pair from **JSONfunctioncalling → FastMCP**.
I kept it to the two files you pasted, so you can copypaste or cherrypick with your IDEs patch tool.
---
## 0. New dependency
```bash
pip install fastmcp # tiny async wrapper around mcp.ClientSession in “fast” mode
```
---
## 1. `swarms/tools/mcp_integration.py`
### 1.1 Imports
```diff
-from mcp import (
- ClientSession,
- StdioServerParameters,
- Tool as MCPTool,
- stdio_client,
-)
+# fastmcp gives us a dropin “FastClientSession” that sets the right SSE headers
+from fastmcp import FastClientSession as ClientSession
+from fastmcp.servers import fast_sse_client as sse_client # replaces std one
```
*(Keep the rest of the imports asis; they still compile.)*
### 1.2 Replace the SSE transport factory
FastMCP reuses the same SSE wire format but forces `FAST_MCP_MODE=1` headers and keeps the connection hot.
```diff
- def create_streams(self, ...) -> AbstractAsyncContextManager[...]:
- return sse_client(
- url=self.params["url"],
- headers=self.params.get("headers", None),
- timeout=self.params.get("timeout", 5),
- sse_read_timeout=self.params.get("sse_read_timeout", 60*5),
- )
+ def create_streams(self, ...) -> AbstractAsyncContextManager[...]:
+ return sse_client( # NOTE: imported from fastmcp.servers above
+ url=self.params["url"],
+ headers=self.params.get("headers", None),
+ timeout=self.params.get("timeout", 5),
+ sse_read_timeout=self.params.get("sse_read_timeout", 60 * 5),
+ )
```
### 1.3 Add **fast** helper for a single call (optional)
```python
async def call_tool_fast(server: MCPServerSse, payload: dict[str, Any]):
"""
Convenience wrapper that opens → calls → closes in one shot.
"""
await server.connect()
result = await server.call_tool(payload)
await server.cleanup()
return result.model_dump() if hasattr(result, "model_dump") else result
```
---
## 2. `swarms/structs/agent.py`
### 2.1 accept `mcp_servers` parameter (you commented it out)
```diff
- tools_list_dictionary: Optional[List[Dict[str, Any]]] = None,
- # mcp_servers: List[MCPServerSseParams] = [],
+ tools_list_dictionary: Optional[List[Dict[str, Any]]] = None,
+ mcp_servers: Optional[List[Dict[str, Any]]] = None, # NEW
```
and save it:
```diff
self.tools_list_dictionary = tools_list_dictionary
+# FastMCP
+self.mcp_servers = mcp_servers or []
```
### 2.2 Drop `parse_and_execute_json` branch and replace with FastMCP
Inside `_run()` where you currently have:
```python
if self.tools is not None or hasattr(self, 'mcp_servers'):
...
```
replace everything in that `if` block with:
```diff
-if self.tools is not None or hasattr(self, 'mcp_servers'):
- if self.tools:
- out = self.parse_and_execute_tools(response)
- if hasattr(self, 'mcp_servers') and self.mcp_servers:
- out = self.mcp_execution_flow(response)
-
- self.short_memory.add(role="Tool Executor", content=out)
- ...
+if self.mcp_servers: # ـ فقط FastMCP path
+ # Response from the model **will be** JSONRPC already. Convert str → dict
+ try:
+ fn_call = json.loads(response) if isinstance(response, str) else response
+ except Exception:
+ # Not a toolcall, skip.
+ fn_call = None
+
+ if fn_call and isinstance(fn_call, dict):
+ # roundrobin you can pick a smarter loadbalancer later
+ target = random.choice(self.mcp_servers)
+ out = mcp_flow(target, fn_call) # <- from mcp_integration.py
+
+ self.short_memory.add(role="Tool", content=out)
+ agent_print(f"{self.agent_name} tool result", out, loop_count, self.streaming_on)
+
+ # Let the model reflect on the tools answer
+ follow_up = self.llm.run(out)
+ self.short_memory.add(role=self.agent_name, content=follow_up)
```
### 2.3 Delete **parse_and_execute_tools** helper altogether
If nothing else in your codebase uses it, just remove the whole method to avoid dead weight.
### 2.4 Optional: preload tool schemas into the model (good prompt hygiene)
At the end of `init_handling()` add:
```python
# Advertise remote tools to the model (tool descriptions feed)
if self.mcp_servers:
try:
first = self.mcp_servers[0]
schema_txt = any_to_str(mcp_flow_get_tool_schema(first))
self.short_memory.add(role="system", content=f"REMOTE_TOOLS:\n{schema_txt}")
except Exception as e:
logger.warning(f"Could not fetch tool schema: {e}")
```
---
## 3. Quick smoke test
```python
from swarms.structs.agent import Agent
FLOWISE = {"url": "https://mcp.flowise.ai"} # no auth for public demo
bot = Agent(
agent_name="fastmcp-demo",
model_name="gpt-4o-mini",
streaming_on=True,
mcp_servers=[FLOWISE], # <- the only change you really need
)
print(
bot("Use `serp.search` to fetch todays ETH price and summarise in one sentence")
)
```
You should see:
1. LLM emits a `call_tool` JSON.
2. Agent relays it to Flowise server via FastMCP.
3. Response streams back; LLM reasons on it; final answer printed.
---
### What we just *removed*
* `parse_and_execute_json`
* `tool_choice`, `function_calling_format_type`, etc. (theyre harmless but unused)
* Manual “functioncalling” retries.
### What we just *added*
* `fastmcp` dependency + a **single** SSE connection that stays alive for the whole agent run.
---
Thats it!
Apply the diff, run the smoke test, and youre on FastMCP.
If you bump into a specific traceback, paste it and well debug the next inch. Happy hacking 🚀