|
|
@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
Below is a **surgical “diff‑style” checklist** that shows exactly what you have to change (and what you can delete outright) to migrate your current `Agent`/`mcp_integration` pair from **JSON‑function‑calling → FastMCP**.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
I kept it to the two files you pasted, so you can copy‑paste or cherry‑pick with your IDE’s 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 drop‑in “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 as‑is; they still compile.)*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 1.2 Replace the SSE transport factory
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FastMCP re‑uses 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 tool‑call, skip.
|
|
|
|
|
|
|
|
+ fn_call = None
|
|
|
|
|
|
|
|
+
|
|
|
|
|
|
|
|
+ if fn_call and isinstance(fn_call, dict):
|
|
|
|
|
|
|
|
+ # round‑robin – you can pick a smarter load‑balancer 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 tool’s 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 today’s 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. (they’re harmless but unused)
|
|
|
|
|
|
|
|
* Manual “function‑calling” retries.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### What we just *added*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* `fastmcp` dependency + a **single** SSE connection that stays alive for the whole agent run.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
That’s it!
|
|
|
|
|
|
|
|
Apply the diff, run the smoke test, and you’re on FastMCP.
|
|
|
|
|
|
|
|
If you bump into a specific traceback, paste it and we’ll debug the next inch. Happy hacking 🚀
|