feat MCP adapter: functional adapter and tool calls. Tools need to be fine grained

This commit is contained in:
Irina Rueegg 2026-05-10 13:46:48 +02:00
parent 88c647a428
commit e5831f69a8
4 changed files with 82 additions and 46 deletions

View File

@ -21,11 +21,13 @@ import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
import asyncio import asyncio
import pprint
import requests import requests
import httpx import httpx
from dotenv import load_dotenv from dotenv import load_dotenv
#from backend.agent.mcp_server_adapter import MCPToolRAGAdapter #from backend.agent.mcp_server_adapter import MCPToolRAGAdapter
#from mcp_server_adapter import MCPToolAdapter # Import from current directory for easier testing without package structure
from backend.agent.mcp_server_adapter import MCPToolAdapter from backend.agent.mcp_server_adapter import MCPToolAdapter
# ── mcp server initialization ──────────────────────────────────────────────────────────────── # ── mcp server initialization ────────────────────────────────────────────────────────────────
@ -206,20 +208,23 @@ def build_all_tool_description() -> str:
descriptions = [] descriptions = []
for tool in all_tools: for tool in all_tools:
params = tool.inputSchema.get("properties", {}) pprint.pprint(f"{tool}")
if params: descriptions.append(f"- {tool['tool_name']}: {tool['tool_description']}")
param_lines = [] #
for pname, pinfo in params.items(): #params = tool.tool_description.inputSchema.get("properties", {})
ptype = pinfo.get("type", "any") #if params:
pdesc = pinfo.get("description", "") # param_lines = []
param_lines.append(f" - {pname} ({ptype}): {pdesc}") # for pname, pinfo in params.items():
param_str = "\n".join(param_lines) # ptype = pinfo.get("type", "any")
else: # pdesc = pinfo.get("description", "")
param_str = " (none)" # param_lines.append(f" - {pname} ({ptype}): {pdesc}")
descriptions.append( # param_str = "\n".join(param_lines)
f"- {tool.name}: {tool.description}\n" #else:
f"Parameters:\n{param_str}" # param_str = " (none)"
) #descriptions.append(
# f"- {tool.name}: {tool.description}\n"
# f"Parameters:\n{param_str}"
#)
return "\n".join(descriptions) return "\n".join(descriptions)
@ -232,6 +237,7 @@ async def dispatch_tool(tool_name: str, arguments: dict) -> str:
return f"DONE: {summary}" return f"DONE: {summary}"
try: try:
print(f"Trying to call tool '{tool_name}' in dispatch_tool through MCPToolAdapter...")
result = await adapter.call_tool(tool_name, arguments) result = await adapter.call_tool(tool_name, arguments)
if result.isError: if result.isError:
@ -455,28 +461,11 @@ class CodingAgent:
return {"thought": "Max iterations reached.", "tool": "done", return {"thought": "Max iterations reached.", "tool": "done",
"arguments": {"summary": "Stopped: max iterations reached."}} "arguments": {"summary": "Stopped: max iterations reached."}}
#current_context = self.messages[-1]["content"] if self.messages else ""
self.iteration += 1 self.iteration += 1
self.messages = trim_messages(self.messages) self.messages = trim_messages(self.messages)
#tool_prompt = await get_tools_for_prompt(current_context)
#
#enhanced_messages = self.messages.copy()
#
#enhanced_messages.append({
# "role": "system",
# "content": f"""
#Available tools for this step:
#
#{tool_prompt}
#
#You MUST choose one of these tools.
#"""
#})
try: try:
raw = await self._call_api(self.messages) raw = self._call_api(self.messages)
raw = _strip_code_fences(raw) raw = _strip_code_fences(raw)
action = json.loads(raw) action = json.loads(raw)
except json.JSONDecodeError: except json.JSONDecodeError:
@ -591,4 +580,38 @@ class CodingAgent:
}) })
self.pending_action = None self.pending_action = None
def main():
"""Example of how to use the CodingAgent in a simple loop."""
agent = CodingAgent()
task = "Write a Python function that returns the nth Fibonacci number."
agent.start_task(task)
if agent.pending_action:
print(f"Initial proposed action: {agent.pending_action['action']}")
while not agent.is_done:
action = asyncio.run(agent.propose_next_action())
print(f"Proposed action: {action}")
if action["tool"] == "done":
print("Task completed.")
break
else:
user_feedback = input("Approve this action? (y/n) ")
if user_feedback.lower() == "y":
result = asyncio.run(agent.approve())
print(f"Tool result: {result}")
elif user_feedback.lower() == "n":
feedback = input("Enter feedback for the agent: ")
agent.reject(feedback)
if result["is_done"]:
print("Task completed.")
break
if __name__ == "__main__":
main()

View File

@ -1,5 +1,6 @@
import asyncio import asyncio
import json import json
import sys
from typing import List, Dict, Any from typing import List, Dict, Any
from pathlib import Path from pathlib import Path
@ -37,17 +38,21 @@ class MCPToolAdapter:
print(f"Testing connection to {server_name}...") print(f"Testing connection to {server_name}...")
self.servers[server_name] = params self.servers[server_name] = params
server_script = str(Path(__file__).parent / params["args"][0])
if params.get("command") in ["py", "python", "python3"]:
server_command = sys.executable
else:
server_command = params["command"]
server_params = StdioServerParameters( server_params = StdioServerParameters(
command=params["command"], command=server_command,
args=params.get("args", []) args=[server_script],
) )
try: try:
# Verbindung aufbauen # Verbindung aufbauen
async with stdio_client(server_params) as (read_stream, write_stream): async with stdio_client(server_params) as (read_stream, write_stream):
print(f"Connected to {server_name}. Initializing session...") print(f"Connected to {server_name}. Initializing session...")
#print(f"read_stream: {read_stream}\nwrite_stream: {write_stream}")
async with ClientSession(read_stream, write_stream) as session: async with ClientSession(read_stream, write_stream) as session:
await session.initialize() await session.initialize()
print(f"Session initialized for {server_name}. Requesting tools...") print(f"Session initialized for {server_name}. Requesting tools...")
@ -61,7 +66,7 @@ class MCPToolAdapter:
t_params = tool.inputSchema.get("properties", {}) t_params = tool.inputSchema.get("properties", {})
if t_params: if t_params:
param_lines = [] param_lines = []
for pname, pinfo in params.items(): for pname, pinfo in t_params.items():
ptype = pinfo.get("type", "any") ptype = pinfo.get("type", "any")
pdesc = pinfo.get("description", "") pdesc = pinfo.get("description", "")
param_lines.append(f" - {pname} ({ptype}): {pdesc}") param_lines.append(f" - {pname} ({ptype}): {pdesc}")
@ -93,18 +98,25 @@ class MCPToolAdapter:
async def call_tool(self, tool_name: str, arguments: Dict[str, Any]): async def call_tool(self, tool_name: str, arguments: Dict[str, Any]):
"""Findet den richtigen Server für ein Tool und führt es aus.""" """Findet den richtigen Server für ein Tool und führt es aus."""
# Suche in der Registry nach dem passenden Server # Suche in der Registry nach dem passenden Server
tool_entry = next((t for t in self.tool_registry if t["name"] == tool_name), None) tool_entry = next((t for t in self.tool_registry if t["tool_name"] == tool_name), None)
if not tool_entry: if not tool_entry:
print(f"Tool '{tool_name}' not found in MCP adapter registry.")
return f"Error: Tool '{tool_name}' not found in registry." return f"Error: Tool '{tool_name}' not found in registry."
server_name = tool_entry["server"] server_name = tool_entry["server"]
server = self.servers.get(server_name) s_params = self.servers.get(server_name)
if server: if s_params:
server_script = str(Path(__file__).parent / s_params["args"][0])
if s_params.get("command") in ["py", "python", "python3"]:
server_command = sys.executable
else:
server_command = s_params["command"]
server_params = StdioServerParameters( server_params = StdioServerParameters(
command=server["command"], command=server_command,
args=server.get("args", []) args=[server_script],
) )
try: try:
@ -133,7 +145,7 @@ def main():
asyncio.run(adapter.initialize_all_servers()) asyncio.run(adapter.initialize_all_servers())
print("All servers initialized. Registered tools:") print("All servers initialized. Registered tools:")
for tool in adapter.get_all_tools(): for tool in adapter.get_all_tools():
print(f"- {tool['name']} (from {tool['server']})") print(f"- {tool['tool_name']} (from {tool['server']})")
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,10 +1,10 @@
{"FileSearchServer": { {"FileSearchServer": {
"command": "python3", "command": "py",
"args": ["servers/mcp_server_file_search.py"] "args": ["servers/mcp_server_file_search.py"]
}, },
"WebSearchServer": { "WebSearchServer": {
"command": "python3", "command": "py",
"args": ["servers/mcp_server_web_search.py"], "args": ["servers/mcp_server_web_search.py"],
"env": { "env": {
"DDGS_API_KEY": "your_ddgs_api_key_here" "DDGS_API_KEY": "your_ddgs_api_key_here"
@ -12,7 +12,7 @@
}, },
"CodeExecutionServer": { "CodeExecutionServer": {
"command": "python3", "command": "py",
"args": ["servers/mcp_server_code_execution.py"] "args": ["servers/mcp_server_code_execution.py"]
} }

View File

@ -136,4 +136,5 @@ def search_files(query: str) -> str:
# ── Run the server ─────────────────────────────────────────────────────────── # ── Run the server ───────────────────────────────────────────────────────────
if __name__ == "__main__": if __name__ == "__main__":
mcp.run(transport="stdio") mcp.run(transport="stdio")