Feat : MCP agent

This commit is contained in:
martin legrand 2025-05-04 18:34:05 +02:00
parent 887060acdf
commit 442bb4a340
18 changed files with 261 additions and 22 deletions

7
cli.py
View File

@ -7,7 +7,7 @@ import asyncio
from sources.llm_provider import Provider
from sources.interaction import Interaction
from sources.agents import Agent, CoderAgent, CasualAgent, FileAgent, PlannerAgent, BrowserAgent
from sources.agents import Agent, CoderAgent, CasualAgent, FileAgent, PlannerAgent, BrowserAgent, McpAgent
from sources.browser import Browser, create_driver
from sources.utility import pretty_print
@ -48,7 +48,10 @@ async def main():
provider=provider, verbose=False, browser=browser),
PlannerAgent(name="Planner",
prompt_path=f"prompts/{personality_folder}/planner_agent.txt",
provider=provider, verbose=False, browser=browser)
provider=provider, verbose=False, browser=browser),
McpAgent(name="MCP Agent",
prompt_path=f"prompts/{personality_folder}/mcp_agent.txt",
provider=provider, verbose=False),
]
interaction = Interaction(agents,

View File

@ -0,0 +1,66 @@
You are an agent designed to utilize the MCP protocol to accomplish tasks.
The MCP provide you with a standard way to use tools and data sources like databases, APIs, or apps (e.g., GitHub, Slack).
The are thousands of MCPs protocol that can accomplish a variety of tasks, for example:
- get weather information
- get stock data information
- Use software like blender
- Get messages from teams, stack, messenger
- Read and send email
Anything is possible with MCP.
To search for MCP a special format:
- Example 1:
User: what's the stock market of IBM like today?:
You: I will search for mcp to find information about IBM stock market.
```mcp_finder
stock
```
You search query must be one or two words at most.
This will provide you with informations about a specific MCP such as the json of parameters needed to use it.
For example, you might see:
-------
Name: Search Stock News
Usage name: @Cognitive-Stack/search-stock-news-mcp
Tools: [{'name': 'search-stock-news', 'description': 'Search for stock-related news using Tavily API', 'inputSchema': {'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['symbol', 'companyName'], 'properties': {'symbol': {'type': 'string', 'description': "Stock symbol to search for (e.g., 'AAPL')"}, 'companyName': {'type': 'string', 'description': 'Full company name to include in the search'}}, 'additionalProperties': False}}]
-------
You can then a MCP like so:
```<usage name>
{
"tool": "<tool name (without @)>",
"inputSchema": {<inputSchema json for the tool>}
}
```
For example:
Now that I know how to use the MCP, I will choose the search-stock-news tool and execute it to find out IBM stock market.
```Cognitive-Stack/search-stock-news-mcp
{
"tool": "search-stock-news",
"inputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["symbol"],
"properties": {
"symbol": "IBM"
}
}
}
```
If the schema require an information that you don't have ask the users for the information.

View File

@ -0,0 +1,62 @@
You are an agent designed to utilize the MCP protocol to accomplish tasks.
The MCP provide you with a standard way to use tools and data sources like databases, APIs, or apps (e.g., GitHub, Slack).
The are thousands of MCPs protocol that can accomplish a variety of tasks, for example:
- get weather information
- get stock data information
- Use software like blender
- Get messages from teams, stack, messenger
- Read and send email
Anything is possible with MCP.
To search for MCP a special format:
- Example 1:
User: what's the stock market of IBM like today?:
You: I will search for mcp to find information about IBM stock market.
```mcp_finder
stock
```
This will provide you with informations about a specific MCP such as the json of parameters needed to use it.
For example, you might see:
-------
Name: Search Stock News
Usage name: @Cognitive-Stack/search-stock-news-mcp
Tools: [{'name': 'search-stock-news', 'description': 'Search for stock-related news using Tavily API', 'inputSchema': {'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['symbol', 'companyName'], 'properties': {'symbol': {'type': 'string', 'description': "Stock symbol to search for (e.g., 'AAPL')"}, 'companyName': {'type': 'string', 'description': 'Full company name to include in the search'}}, 'additionalProperties': False}}]
-------
You can then a MCP like so:
```<usage name>
{
"tool": "<tool name (without @)>",
"inputSchema": {<inputSchema json for the tool>}
}
```
For example:
Now that I know how to use the MCP, I will choose the search-stock-news tool and execute it to find out IBM stock market.
```Cognitive-Stack/search-stock-news-mcp
{
"tool": "search-stock-news",
"inputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["symbol"],
"properties": {
"symbol": "IBM"
}
}
}
```

View File

@ -5,5 +5,6 @@ from .casual_agent import CasualAgent
from .file_agent import FileAgent
from .planner_agent import PlannerAgent
from .browser_agent import BrowserAgent
from .mcp_agent import McpAgent
__all__ = ["Agent", "CoderAgent", "CasualAgent", "FileAgent", "PlannerAgent", "BrowserAgent"]
__all__ = ["Agent", "CoderAgent", "CasualAgent", "FileAgent", "PlannerAgent", "BrowserAgent", "McpAgent"]

View File

@ -90,6 +90,12 @@ class Agent():
raise TypeError("Tool must be a callable object (a method)")
self.tools[name] = tool
def get_tools_name(self) -> list:
"""
Get the list of tools names.
"""
return list(self.tools.keys())
def load_prompt(self, file_path: str) -> str:
try:
with open(file_path, 'r', encoding="utf-8") as f:
@ -235,11 +241,13 @@ class Agent():
answer = "I will execute:\n" + answer # there should always be a text before blocks for the function that display answer
self.success = True
self.blocks_result = []
for name, tool in self.tools.items():
feedback = ""
blocks, save_path = tool.load_exec_block(answer)
if blocks != None:
pretty_print(f"Executing {len(blocks)} {name} blocks...", color="status")
for block in blocks:
self.show_block(block)
output = tool.execute([block])

View File

@ -62,14 +62,14 @@ class CoderAgent(Agent):
animate_thinking("Executing code...", color="status")
self.status_message = "Executing code..."
self.logger.info(f"Attempt {attempt + 1}:\n{answer}")
exec_success, _ = self.execute_modules(answer)
exec_success, feedback = self.execute_modules(answer)
self.logger.info(f"Execution result: {exec_success}")
answer = self.remove_blocks(answer)
self.last_answer = answer
await asyncio.sleep(0)
if exec_success and self.get_last_tool_type() != "bash":
break
pretty_print("Execution failure", color="failure")
pretty_print(f"Execution failure:\n{feedback}", color="failure")
pretty_print("Correcting code...", color="status")
self.status_message = "Correcting code..."
attempt += 1

View File

@ -0,0 +1,70 @@
import os
import asyncio
from sources.utility import pretty_print, animate_thinking
from sources.agents.agent import Agent
from sources.tools.mcpFinder import MCP_finder
# NOTE MCP agent is an active work in progress, not functional yet.
class McpAgent(Agent):
def __init__(self, name, prompt_path, provider, verbose=False):
"""
The mcp agent is a special agent for using MCPs.
MCP agent will be disabled if the user does not explicitly set the MCP_FINDER_API_KEY in environment variable.
"""
super().__init__(name, prompt_path, provider, verbose, None)
keys = self.get_api_keys()
self.tools = {
"mcp_finder": MCP_finder(keys["mcp_finder"]),
# add mcp tools here
}
self.role = "mcp"
self.type = "mcp_agent"
self.enabled = True
def get_api_keys(self) -> dict:
"""
Returns the API keys for the tools.
"""
api_key_mcp_finder = os.getenv("MCP_FINDER_API_KEY")
if not api_key_mcp_finder or api_key_mcp_finder == "":
pretty_print("MCP Finder API key not found. Please set the MCP_FINDER_API_KEY environment variable.", color="failure")
pretty_print("MCP Finder disabled.", color="failure")
self.enabled = False
return {
"mcp_finder": api_key_mcp_finder
}
def expand_prompt(self, prompt):
"""
Expands the prompt with the tools available.
"""
tools_name = self.get_tools_name()
tools_str = ", ".join(tools_name)
prompt += f"""
You can use the following tools and MCPs:
{tools_str}
"""
return prompt
async def process(self, prompt, speech_module) -> str:
if self.enabled == False:
return "MCP Agent is disabled."
prompt = self.expand_prompt(prompt)
self.memory.push('user', prompt)
working = True
while working == True:
animate_thinking("Thinking...", color="status")
answer, reasoning = await self.llm_request()
exec_success, _ = self.execute_modules(answer)
answer = self.remove_blocks(answer)
self.last_answer = answer
self.status_message = "Ready"
if len(self.blocks_result) == 0:
working = False
return answer, reasoning
if __name__ == "__main__":
pass

View File

@ -141,6 +141,9 @@ class AgentRouter:
("Search the web for tips on improving coding skills", "LOW"),
("Write a Python script to count words in a text file", "LOW"),
("Search the web for restaurant", "LOW"),
("Use a MCP to find the latest stock market data", "LOW"),
("Use a MCP to send an email to my boss", "LOW"),
("Could you use a MCP to find the latest news on climate change?", "LOW"),
("Create a simple HTML page with CSS styling", "LOW"),
("Use file.txt and then use it to ...", "HIGH"),
("Yo, whats good? Find my mixtape.mp3 real quick", "LOW"),
@ -162,6 +165,7 @@ class AgentRouter:
("Find a public API for book data and create a Flask app to list bestsellers", "HIGH"),
("Organize my desktop files by extension and then write a script to list them", "HIGH"),
("Find the latest research on renewable energy and build a web app to display it", "HIGH"),
("search online for popular sci-fi movies from 2024 and pick three to watch tonight. Save the list in movie_night.txt", "HIGH"),
("can you find vitess repo, clone it and install by following the readme", "HIGH"),
("Create a JavaScript game using Phaser.js with multiple levels", "HIGH"),
("Search the web for the latest trends in web development and build a sample site", "HIGH"),
@ -330,6 +334,11 @@ class AgentRouter:
("can you make a web app in python that use the flask framework", "code"),
("can you build a web server in go that serve a simple html page", "code"),
("can you find out who Jacky yougouri is ?", "web"),
("Can you use MCP to find stock market for IBM ?", "mcp"),
("Can you use MCP to to export my contacts to a csv file?", "mcp"),
("Can you use a MCP to find write notes to flomo", "mcp"),
("Can you use a MCP to query my calendar and find the next meeting?", "mcp"),
("Can you use a mcp to get the distance between Shanghai and Paris?", "mcp"),
("Setup a new flutter project called 'new_flutter_project'", "files"),
("can you create a new project called 'new_project'", "files"),
("can you make a simple web app that display a list of files in my dir", "code"),

View File

@ -17,6 +17,8 @@ class BashInterpreter(Tools):
def __init__(self):
super().__init__()
self.tag = "bash"
self.name = "Bash Interpreter"
self.description = "This tool allows the agent to execute bash commands."
def language_bash_attempt(self, command: str):
"""

View File

@ -15,6 +15,8 @@ class CInterpreter(Tools):
def __init__(self):
super().__init__()
self.tag = "c"
self.name = "C Interpreter"
self.description = "This tool allows the agent to execute C code."
def execute(self, codes: str, safety=False) -> str:
"""

View File

@ -15,6 +15,8 @@ class GoInterpreter(Tools):
def __init__(self):
super().__init__()
self.tag = "go"
self.name = "Go Interpreter"
self.description = "This tool allows you to execute Go code."
def execute(self, codes: str, safety=False) -> str:
"""

View File

@ -15,6 +15,8 @@ class JavaInterpreter(Tools):
def __init__(self):
super().__init__()
self.tag = "java"
self.name = "Java Interpreter"
self.description = "This tool allows you to execute Java code."
def execute(self, codes: str, safety=False) -> str:
"""

View File

@ -16,6 +16,8 @@ class PyInterpreter(Tools):
def __init__(self):
super().__init__()
self.tag = "python"
self.name = "Python Interpreter"
self.description = "This tool allows the agent to execute python code."
def execute(self, codes:str, safety = False) -> str:
"""

View File

@ -15,6 +15,8 @@ class FileFinder(Tools):
def __init__(self):
super().__init__()
self.tag = "file_finder"
self.name = "File Finder"
self.description = "Finds files in the current directory and returns their information."
def read_file(self, file_path: str) -> str:
"""

View File

@ -16,6 +16,8 @@ class FlightSearch(Tools):
"""
super().__init__()
self.tag = "flight_search"
self.name = "Flight Search"
self.description = "Search for flight information using a flight number via AviationStack API."
self.api_key = None
self.api_key = api_key or os.getenv("AVIATIONSTACK_API_KEY")
@ -24,7 +26,7 @@ class FlightSearch(Tools):
return "Error: No AviationStack API key provided."
for block in blocks:
flight_number = block.strip()
flight_number = block.strip().lower().replace('\n', '')
if not flight_number:
return "Error: No flight number provided."

View File

@ -3,11 +3,10 @@ import requests
from urllib.parse import urljoin
from typing import Dict, Any, Optional
from sources.tools.tools import Tools
if __name__ == "__main__": # if running as a script for individual testing
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sources.tools.tools import Tools
class MCP_finder(Tools):
"""
@ -15,7 +14,9 @@ class MCP_finder(Tools):
"""
def __init__(self, api_key: str = None):
super().__init__()
self.tag = "mcp"
self.tag = "mcp_finder"
self.name = "MCP Finder"
self.description = "Find MCP servers and their tools"
self.base_url = "https://registry.smithery.ai"
self.headers = {
"Authorization": f"Bearer {api_key}",
@ -61,11 +62,7 @@ class MCP_finder(Tools):
for mcp in mcps.get("servers", []):
name = mcp.get("qualifiedName", "")
if query.lower() in name.lower():
details = {
"name": name,
"description": mcp.get("description", "No description available"),
"params": mcp.get("connections", [])
}
details = self.get_mcp_server_details(name)
matching_mcp.append(details)
return matching_mcp
@ -79,7 +76,7 @@ class MCP_finder(Tools):
try:
matching_mcp_infos = self.find_mcp_servers(block_clean)
except requests.exceptions.RequestException as e:
output += "Connection failed. Is the API in environement?\n"
output += "Connection failed. Is the API key in environement?\n"
continue
except Exception as e:
output += f"Error: {str(e)}\n"
@ -88,10 +85,12 @@ class MCP_finder(Tools):
output += f"Error: No MCP server found for query '{block}'\n"
continue
for mcp_infos in matching_mcp_infos:
output += f"Name: {mcp_infos['name']}\n"
output += f"Description: {mcp_infos['description']}\n"
output += f"Params: {', '.join(mcp_infos['params'])}\n"
output += "-------\n"
if mcp_infos['tools'] is None:
continue
output += f"Name: {mcp_infos['displayName']}\n"
output += f"Usage name: {mcp_infos['qualifiedName']}\n"
output += f"Tools: {mcp_infos['tools']}"
output += "\n-------\n"
return output.strip()
def execution_failure_check(self, output: str) -> bool:
@ -107,13 +106,16 @@ class MCP_finder(Tools):
Not really needed for this tool (use return of execute() directly)
"""
if not output:
return "No output generated."
return output.strip()
raise ValueError("No output to interpret.")
return f"""
The following MCPs were found:
{output}
"""
if __name__ == "__main__":
api_key = os.getenv("MCP_FINDER")
tool = MCP_finder(api_key)
result = tool.execute(["""
news
stock
"""], False)
print(result)

View File

@ -14,6 +14,8 @@ class searxSearch(Tools):
"""
super().__init__()
self.tag = "web_search"
self.name = "searxSearch"
self.description = "A tool for searching a SearxNG for web search"
self.base_url = base_url or os.getenv("SEARXNG_BASE_URL") # Requires a SearxNG base URL
self.user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36"
self.paywall_keywords = [

View File

@ -33,6 +33,8 @@ class Tools():
"""
def __init__(self):
self.tag = "undefined"
self.name = "undefined"
self.description = "undefined"
self.client = None
self.messages = []
self.logger = Logger("tools.log")