praisonaiagents 0.0.81__tar.gz → 0.0.82__tar.gz

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.
Files changed (51) hide show
  1. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/PKG-INFO +1 -1
  2. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/agent/agent.py +301 -160
  3. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/agents/agents.py +347 -193
  4. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents.egg-info/PKG-INFO +1 -1
  5. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/pyproject.toml +1 -1
  6. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/README.md +0 -0
  7. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/__init__.py +0 -0
  8. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/agent/__init__.py +0 -0
  9. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/agent/image_agent.py +0 -0
  10. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/agents/__init__.py +0 -0
  11. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/agents/autoagents.py +0 -0
  12. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/knowledge/__init__.py +0 -0
  13. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/knowledge/chunking.py +0 -0
  14. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/knowledge/knowledge.py +0 -0
  15. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/llm/__init__.py +0 -0
  16. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/llm/llm.py +0 -0
  17. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/main.py +0 -0
  18. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/mcp/__init__.py +0 -0
  19. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/mcp/mcp.py +0 -0
  20. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/mcp/mcp_sse.py +0 -0
  21. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/memory/memory.py +0 -0
  22. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/process/__init__.py +0 -0
  23. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/process/process.py +0 -0
  24. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/task/__init__.py +0 -0
  25. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/task/task.py +0 -0
  26. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/__init__.py +0 -0
  27. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/arxiv_tools.py +0 -0
  28. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/calculator_tools.py +0 -0
  29. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/csv_tools.py +0 -0
  30. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/duckdb_tools.py +0 -0
  31. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/duckduckgo_tools.py +0 -0
  32. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/excel_tools.py +0 -0
  33. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/file_tools.py +0 -0
  34. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/json_tools.py +0 -0
  35. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/newspaper_tools.py +0 -0
  36. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/pandas_tools.py +0 -0
  37. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/python_tools.py +0 -0
  38. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/shell_tools.py +0 -0
  39. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/spider_tools.py +0 -0
  40. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/test.py +0 -0
  41. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/tools.py +0 -0
  42. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/train/data/generatecot.py +0 -0
  43. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/wikipedia_tools.py +0 -0
  44. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/xml_tools.py +0 -0
  45. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/yaml_tools.py +0 -0
  46. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents/tools/yfinance_tools.py +0 -0
  47. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents.egg-info/SOURCES.txt +0 -0
  48. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents.egg-info/dependency_links.txt +0 -0
  49. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents.egg-info/requires.txt +0 -0
  50. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/praisonaiagents.egg-info/top_level.txt +0 -0
  51. {praisonaiagents-0.0.81 → praisonaiagents-0.0.82}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praisonaiagents
3
- Version: 0.0.81
3
+ Version: 0.0.82
4
4
  Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
5
5
  Author: Mervin Praison
6
6
  Requires-Dist: pydantic
@@ -1095,6 +1095,10 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
1095
1095
  start_time = time.time()
1096
1096
  reasoning_steps = reasoning_steps or self.reasoning_steps
1097
1097
  try:
1098
+ # Default to self.tools if tools argument is None
1099
+ if tools is None:
1100
+ tools = self.tools
1101
+
1098
1102
  # Search for existing knowledge if any knowledge is provided
1099
1103
  if self.knowledge:
1100
1104
  search_results = self.knowledge.search(prompt, agent_id=self.agent_id)
@@ -1408,192 +1412,329 @@ Your Goal: {self.goal}
1408
1412
  logging.error(f"Error in execute_tool_async: {str(e)}", exc_info=True)
1409
1413
  return {"error": f"Error in execute_tool_async: {str(e)}"}
1410
1414
 
1411
- def launch(self, path: str = '/', port: int = 8000, host: str = '0.0.0.0', debug: bool = False):
1415
+ def launch(self, path: str = '/', port: int = 8000, host: str = '0.0.0.0', debug: bool = False, protocol: str = "http"):
1412
1416
  """
1413
- Launch the agent as an HTTP API endpoint.
1417
+ Launch the agent as an HTTP API endpoint or an MCP server.
1414
1418
 
1415
1419
  Args:
1416
- path: API endpoint path (default: '/')
1420
+ path: API endpoint path (default: '/') for HTTP, or base path for MCP.
1417
1421
  port: Server port (default: 8000)
1418
1422
  host: Server host (default: '0.0.0.0')
1419
1423
  debug: Enable debug mode for uvicorn (default: False)
1424
+ protocol: "http" to launch as FastAPI, "mcp" to launch as MCP server.
1420
1425
 
1421
1426
  Returns:
1422
1427
  None
1423
1428
  """
1424
- global _server_started, _registered_agents, _shared_apps
1425
-
1426
- # Try to import FastAPI dependencies - lazy loading
1427
- try:
1428
- import uvicorn
1429
- from fastapi import FastAPI, HTTPException, Request
1430
- from fastapi.responses import JSONResponse
1431
- from pydantic import BaseModel
1432
- import threading
1433
- import time
1429
+ if protocol == "http":
1430
+ global _server_started, _registered_agents, _shared_apps
1434
1431
 
1435
- # Define the request model here since we need pydantic
1436
- class AgentQuery(BaseModel):
1437
- query: str
1432
+ # Try to import FastAPI dependencies - lazy loading
1433
+ try:
1434
+ import uvicorn
1435
+ from fastapi import FastAPI, HTTPException, Request
1436
+ from fastapi.responses import JSONResponse
1437
+ from pydantic import BaseModel
1438
+ import threading
1439
+ import time
1438
1440
 
1439
- except ImportError as e:
1440
- # Check which specific module is missing
1441
- missing_module = str(e).split("No module named '")[-1].rstrip("'")
1442
- display_error(f"Missing dependency: {missing_module}. Required for launch() method.")
1443
- logging.error(f"Missing dependency: {missing_module}. Required for launch() method.")
1444
- print(f"\nTo add API capabilities, install the required dependencies:")
1445
- print(f"pip install {missing_module}")
1446
- print("\nOr install all API dependencies with:")
1447
- print("pip install 'praisonaiagents[api]'")
1448
- return None
1449
-
1450
- # Initialize port-specific collections if needed
1451
- if port not in _registered_agents:
1452
- _registered_agents[port] = {}
1453
-
1454
- # Initialize shared FastAPI app if not already created for this port
1455
- if _shared_apps.get(port) is None:
1456
- _shared_apps[port] = FastAPI(
1457
- title=f"PraisonAI Agents API (Port {port})",
1458
- description="API for interacting with PraisonAI Agents"
1459
- )
1441
+ # Define the request model here since we need pydantic
1442
+ class AgentQuery(BaseModel):
1443
+ query: str
1444
+
1445
+ except ImportError as e:
1446
+ # Check which specific module is missing
1447
+ missing_module = str(e).split("No module named '")[-1].rstrip("'")
1448
+ display_error(f"Missing dependency: {missing_module}. Required for launch() method with HTTP mode.")
1449
+ logging.error(f"Missing dependency: {missing_module}. Required for launch() method with HTTP mode.")
1450
+ print(f"\nTo add API capabilities, install the required dependencies:")
1451
+ print(f"pip install {missing_module}")
1452
+ print("\nOr install all API dependencies with:")
1453
+ print("pip install 'praisonaiagents[api]'")
1454
+ return None
1455
+
1456
+ # Initialize port-specific collections if needed
1457
+ if port not in _registered_agents:
1458
+ _registered_agents[port] = {}
1459
+
1460
+ # Initialize shared FastAPI app if not already created for this port
1461
+ if _shared_apps.get(port) is None:
1462
+ _shared_apps[port] = FastAPI(
1463
+ title=f"PraisonAI Agents API (Port {port})",
1464
+ description="API for interacting with PraisonAI Agents"
1465
+ )
1466
+
1467
+ # Add a root endpoint with a welcome message
1468
+ @_shared_apps[port].get("/")
1469
+ async def root():
1470
+ return {
1471
+ "message": f"Welcome to PraisonAI Agents API on port {port}. See /docs for usage.",
1472
+ "endpoints": list(_registered_agents[port].keys())
1473
+ }
1474
+
1475
+ # Add healthcheck endpoint
1476
+ @_shared_apps[port].get("/health")
1477
+ async def healthcheck():
1478
+ return {
1479
+ "status": "ok",
1480
+ "endpoints": list(_registered_agents[port].keys())
1481
+ }
1460
1482
 
1461
- # Add a root endpoint with a welcome message
1462
- @_shared_apps[port].get("/")
1463
- async def root():
1464
- return {
1465
- "message": f"Welcome to PraisonAI Agents API on port {port}. See /docs for usage.",
1466
- "endpoints": list(_registered_agents[port].keys())
1467
- }
1483
+ # Normalize path to ensure it starts with /
1484
+ if not path.startswith('/'):
1485
+ path = f'/{path}'
1486
+
1487
+ # Check if path is already registered for this port
1488
+ if path in _registered_agents[port]:
1489
+ logging.warning(f"Path '{path}' is already registered on port {port}. Please use a different path.")
1490
+ print(f"⚠️ Warning: Path '{path}' is already registered on port {port}.")
1491
+ # Use a modified path to avoid conflicts
1492
+ original_path = path
1493
+ path = f"{path}_{self.agent_id[:6]}"
1494
+ logging.warning(f"Using '{path}' instead of '{original_path}'")
1495
+ print(f"🔄 Using '{path}' instead")
1468
1496
 
1469
- # Add healthcheck endpoint
1470
- @_shared_apps[port].get("/health")
1471
- async def healthcheck():
1472
- return {
1473
- "status": "ok",
1474
- "endpoints": list(_registered_agents[port].keys())
1475
- }
1476
-
1477
- # Normalize path to ensure it starts with /
1478
- if not path.startswith('/'):
1479
- path = f'/{path}'
1497
+ # Register the agent to this path
1498
+ _registered_agents[port][path] = self.agent_id
1480
1499
 
1481
- # Check if path is already registered for this port
1482
- if path in _registered_agents[port]:
1483
- logging.warning(f"Path '{path}' is already registered on port {port}. Please use a different path.")
1484
- print(f"⚠️ Warning: Path '{path}' is already registered on port {port}.")
1485
- # Use a modified path to avoid conflicts
1486
- original_path = path
1487
- path = f"{path}_{self.agent_id[:6]}"
1488
- logging.warning(f"Using '{path}' instead of '{original_path}'")
1489
- print(f"🔄 Using '{path}' instead")
1490
-
1491
- # Register the agent to this path
1492
- _registered_agents[port][path] = self.agent_id
1493
-
1494
- # Define the endpoint handler
1495
- @_shared_apps[port].post(path)
1496
- async def handle_agent_query(request: Request, query_data: Optional[AgentQuery] = None):
1497
- # Handle both direct JSON with query field and form data
1498
- if query_data is None:
1500
+ # Define the endpoint handler
1501
+ @_shared_apps[port].post(path)
1502
+ async def handle_agent_query(request: Request, query_data: Optional[AgentQuery] = None):
1503
+ # Handle both direct JSON with query field and form data
1504
+ if query_data is None:
1505
+ try:
1506
+ request_data = await request.json()
1507
+ if "query" not in request_data:
1508
+ raise HTTPException(status_code=400, detail="Missing 'query' field in request")
1509
+ query = request_data["query"]
1510
+ except:
1511
+ # Fallback to form data or query params
1512
+ form_data = await request.form()
1513
+ if "query" in form_data:
1514
+ query = form_data["query"]
1515
+ else:
1516
+ raise HTTPException(status_code=400, detail="Missing 'query' field in request")
1517
+ else:
1518
+ query = query_data.query
1519
+
1499
1520
  try:
1500
- request_data = await request.json()
1501
- if "query" not in request_data:
1502
- raise HTTPException(status_code=400, detail="Missing 'query' field in request")
1503
- query = request_data["query"]
1504
- except:
1505
- # Fallback to form data or query params
1506
- form_data = await request.form()
1507
- if "query" in form_data:
1508
- query = form_data["query"]
1521
+ # Use async version if available, otherwise use sync version
1522
+ if asyncio.iscoroutinefunction(self.chat):
1523
+ response = await self.achat(query)
1509
1524
  else:
1510
- raise HTTPException(status_code=400, detail="Missing 'query' field in request")
1511
- else:
1512
- query = query_data.query
1525
+ # Run sync function in a thread to avoid blocking
1526
+ loop = asyncio.get_event_loop()
1527
+ response = await loop.run_in_executor(None, lambda p=query: self.chat(p))
1528
+
1529
+ return {"response": response}
1530
+ except Exception as e:
1531
+ logging.error(f"Error processing query: {str(e)}", exc_info=True)
1532
+ return JSONResponse(
1533
+ status_code=500,
1534
+ content={"error": f"Error processing query: {str(e)}"}
1535
+ )
1536
+
1537
+ print(f"🚀 Agent '{self.name}' available at http://{host}:{port}")
1538
+
1539
+ # Start the server if it's not already running for this port
1540
+ if not _server_started.get(port, False):
1541
+ # Mark the server as started first to prevent duplicate starts
1542
+ _server_started[port] = True
1513
1543
 
1514
- try:
1515
- # Use async version if available, otherwise use sync version
1516
- if asyncio.iscoroutinefunction(self.chat):
1517
- response = await self.achat(query)
1518
- else:
1519
- # Run sync function in a thread to avoid blocking
1520
- loop = asyncio.get_event_loop()
1521
- response = await loop.run_in_executor(None, lambda: self.chat(query))
1544
+ # Start the server in a separate thread
1545
+ def run_server():
1546
+ try:
1547
+ print(f"✅ FastAPI server started at http://{host}:{port}")
1548
+ print(f"📚 API documentation available at http://{host}:{port}/docs")
1549
+ print(f"🔌 Available endpoints: {', '.join(list(_registered_agents[port].keys()))}")
1550
+ uvicorn.run(_shared_apps[port], host=host, port=port, log_level="debug" if debug else "info")
1551
+ except Exception as e:
1552
+ logging.error(f"Error starting server: {str(e)}", exc_info=True)
1553
+ print(f"❌ Error starting server: {str(e)}")
1522
1554
 
1523
- return {"response": response}
1524
- except Exception as e:
1525
- logging.error(f"Error processing query: {str(e)}", exc_info=True)
1526
- return JSONResponse(
1527
- status_code=500,
1528
- content={"error": f"Error processing query: {str(e)}"}
1529
- )
1530
-
1531
- print(f"🚀 Agent '{self.name}' available at http://{host}:{port}{path}")
1532
-
1533
- # Start the server if it's not already running for this port
1534
- if not _server_started.get(port, False):
1535
- # Mark the server as started first to prevent duplicate starts
1536
- _server_started[port] = True
1555
+ # Run server in a background thread
1556
+ server_thread = threading.Thread(target=run_server, daemon=True)
1557
+ server_thread.start()
1558
+
1559
+ # Wait for a moment to allow the server to start and register endpoints
1560
+ time.sleep(0.5)
1561
+ else:
1562
+ # If server is already running, wait a moment to make sure the endpoint is registered
1563
+ time.sleep(0.1)
1564
+ print(f"🔌 Available endpoints on port {port}: {', '.join(list(_registered_agents[port].keys()))}")
1565
+
1566
+ # Get the stack frame to check if this is the last launch() call in the script
1567
+ import inspect
1568
+ stack = inspect.stack()
1537
1569
 
1538
- # Start the server in a separate thread
1539
- def run_server():
1570
+ # If this is called from a Python script (not interactive), try to detect if it's the last launch call
1571
+ if len(stack) > 1 and stack[1].filename.endswith('.py'):
1572
+ caller_frame = stack[1]
1573
+ caller_line = caller_frame.lineno
1574
+
1540
1575
  try:
1541
- print(f"✅ FastAPI server started at http://{host}:{port}")
1542
- print(f"📚 API documentation available at http://{host}:{port}/docs")
1543
- print(f"🔌 Available endpoints: {', '.join(list(_registered_agents[port].keys()))}")
1544
- uvicorn.run(_shared_apps[port], host=host, port=port, log_level="debug" if debug else "info")
1576
+ # Read the file to check if there are more launch calls after this one
1577
+ with open(caller_frame.filename, 'r') as f:
1578
+ lines = f.readlines()
1579
+
1580
+ # Check if there are more launch() calls after the current line
1581
+ has_more_launches = False
1582
+ for line_content in lines[caller_line:]: # renamed line to line_content
1583
+ if '.launch(' in line_content and not line_content.strip().startswith('#'):
1584
+ has_more_launches = True
1585
+ break
1586
+
1587
+ # If this is the last launch call, block the main thread
1588
+ if not has_more_launches:
1589
+ try:
1590
+ print("\nAll agents registered for HTTP mode. Press Ctrl+C to stop the servers.")
1591
+ while True:
1592
+ time.sleep(1)
1593
+ except KeyboardInterrupt:
1594
+ print("\nServers stopped")
1545
1595
  except Exception as e:
1546
- logging.error(f"Error starting server: {str(e)}", exc_info=True)
1547
- print(f"Error starting server: {str(e)}")
1548
-
1549
- # Run server in a background thread
1550
- server_thread = threading.Thread(target=run_server, daemon=True)
1551
- server_thread.start()
1552
-
1553
- # Wait for a moment to allow the server to start and register endpoints
1554
- time.sleep(0.5)
1555
- else:
1556
- # If server is already running, wait a moment to make sure the endpoint is registered
1557
- time.sleep(0.1)
1558
- print(f"🔌 Available endpoints on port {port}: {', '.join(list(_registered_agents[port].keys()))}")
1559
-
1560
- # Get the stack frame to check if this is the last launch() call in the script
1561
- import inspect
1562
- stack = inspect.stack()
1563
-
1564
- # If this is called from a Python script (not interactive), try to detect if it's the last launch call
1565
- if len(stack) > 1 and stack[1].filename.endswith('.py'):
1566
- caller_frame = stack[1]
1567
- caller_line = caller_frame.lineno
1596
+ # If something goes wrong with detection, block anyway to be safe
1597
+ logging.error(f"Error in launch detection: {e}")
1598
+ try:
1599
+ print("\nKeeping HTTP servers alive. Press Ctrl+C to stop.")
1600
+ while True:
1601
+ time.sleep(1)
1602
+ except KeyboardInterrupt:
1603
+ print("\nServers stopped")
1604
+ return None
1568
1605
 
1606
+ elif protocol == "mcp":
1569
1607
  try:
1570
- # Read the file to check if there are more launch calls after this one
1571
- with open(caller_frame.filename, 'r') as f:
1572
- lines = f.readlines()
1608
+ import uvicorn
1609
+ from mcp.server.fastmcp import FastMCP
1610
+ from mcp.server.sse import SseServerTransport
1611
+ from starlette.applications import Starlette
1612
+ from starlette.requests import Request
1613
+ from starlette.routing import Mount, Route
1614
+ from mcp.server import Server as MCPServer # Alias to avoid conflict
1615
+ import threading
1616
+ import time
1617
+ import inspect
1618
+ import asyncio # Ensure asyncio is imported
1619
+ # logging is already imported at the module level
1573
1620
 
1574
- # Check if there are more launch() calls after the current line
1575
- has_more_launches = False
1576
- for line in lines[caller_line:]:
1577
- if '.launch(' in line and not line.strip().startswith('#'):
1578
- has_more_launches = True
1579
- break
1580
-
1581
- # If this is the last launch call, block the main thread
1582
- if not has_more_launches:
1621
+ except ImportError as e:
1622
+ missing_module = str(e).split("No module named '")[-1].rstrip("'")
1623
+ display_error(f"Missing dependency: {missing_module}. Required for launch() method with MCP mode.")
1624
+ logging.error(f"Missing dependency: {missing_module}. Required for launch() method with MCP mode.")
1625
+ print(f"\nTo add MCP capabilities, install the required dependencies:")
1626
+ print(f"pip install {missing_module} mcp praison-mcp starlette uvicorn") # Added mcp, praison-mcp, starlette, uvicorn
1627
+ print("\nOr install all MCP dependencies with relevant packages.")
1628
+ return None
1629
+
1630
+ mcp_server_instance_name = f"{self.name}_mcp_server" if self.name else "agent_mcp_server"
1631
+ mcp = FastMCP(mcp_server_instance_name)
1632
+
1633
+ # Determine the MCP tool name based on self.name
1634
+ actual_mcp_tool_name = f"execute_{self.name.lower().replace(' ', '_').replace('-', '_')}_task" if self.name \
1635
+ else "execute_task"
1636
+
1637
+ @mcp.tool(name=actual_mcp_tool_name)
1638
+ async def execute_agent_task(prompt: str) -> str:
1639
+ """Executes the agent's primary task with the given prompt."""
1640
+ logging.info(f"MCP tool '{actual_mcp_tool_name}' called with prompt: {prompt}")
1641
+ try:
1642
+ # Ensure self.achat is used as it's the async version and pass its tools
1643
+ if hasattr(self, 'achat') and asyncio.iscoroutinefunction(self.achat):
1644
+ response = await self.achat(prompt, tools=self.tools)
1645
+ elif hasattr(self, 'chat'): # Fallback for synchronous chat
1646
+ loop = asyncio.get_event_loop()
1647
+ response = await loop.run_in_executor(None, lambda p=prompt: self.chat(p, tools=self.tools))
1648
+ else:
1649
+ logging.error(f"Agent {self.name} has no suitable chat or achat method for MCP tool.")
1650
+ return f"Error: Agent {self.name} misconfigured for MCP."
1651
+ return response if response is not None else "Agent returned no response."
1652
+ except Exception as e:
1653
+ logging.error(f"Error in MCP tool '{actual_mcp_tool_name}': {e}", exc_info=True)
1654
+ return f"Error executing task: {str(e)}"
1655
+
1656
+ # Normalize base_path for MCP routes
1657
+ base_path = path.rstrip('/')
1658
+ sse_path = f"{base_path}/sse"
1659
+ messages_path_prefix = f"{base_path}/messages" # Prefix for message posting
1660
+
1661
+ # Ensure messages_path ends with a slash for Mount
1662
+ if not messages_path_prefix.endswith('/'):
1663
+ messages_path_prefix += '/'
1664
+
1665
+
1666
+ sse_transport = SseServerTransport(messages_path_prefix) # Pass the full prefix
1667
+
1668
+ async def handle_sse_connection(request: Request) -> None:
1669
+ logging.debug(f"SSE connection request received from {request.client} for path {request.url.path}")
1670
+ async with sse_transport.connect_sse(
1671
+ request.scope,
1672
+ request.receive,
1673
+ request._send, # noqa: SLF001
1674
+ ) as (read_stream, write_stream):
1675
+ await mcp._mcp_server.run( # Use the underlying server from FastMCP
1676
+ read_stream,
1677
+ write_stream,
1678
+ mcp._mcp_server.create_initialization_options(),
1679
+ )
1680
+
1681
+ starlette_app = Starlette(
1682
+ debug=debug,
1683
+ routes=[
1684
+ Route(sse_path, endpoint=handle_sse_connection),
1685
+ Mount(messages_path_prefix, app=sse_transport.handle_post_message),
1686
+ ],
1687
+ )
1688
+
1689
+ print(f"🚀 Agent '{self.name}' MCP server starting on http://{host}:{port}")
1690
+ print(f"📡 MCP SSE endpoint available at {sse_path}")
1691
+ print(f"📢 MCP messages post to {messages_path_prefix}")
1692
+ # Instead of trying to extract tool names, hardcode the known tool name
1693
+ tool_names = [actual_mcp_tool_name] # Use the determined dynamic tool name
1694
+ print(f"🛠️ Available MCP tools: {', '.join(tool_names)}")
1695
+
1696
+ # Uvicorn server running logic (similar to HTTP mode but standalone for MCP)
1697
+ def run_mcp_server():
1698
+ try:
1699
+ uvicorn.run(starlette_app, host=host, port=port, log_level="debug" if debug else "info")
1700
+ except Exception as e:
1701
+ logging.error(f"Error starting MCP server: {str(e)}", exc_info=True)
1702
+ print(f"❌ Error starting MCP server: {str(e)}")
1703
+
1704
+ server_thread = threading.Thread(target=run_mcp_server, daemon=True)
1705
+ server_thread.start()
1706
+ time.sleep(0.5) # Allow server to start
1707
+
1708
+ # Blocking logic for MCP mode
1709
+ import inspect # Already imported but good for clarity
1710
+ stack = inspect.stack()
1711
+ if len(stack) > 1 and stack[1].filename.endswith('.py'):
1712
+ caller_frame = stack[1]
1713
+ caller_line = caller_frame.lineno
1714
+ try:
1715
+ with open(caller_frame.filename, 'r') as f:
1716
+ lines = f.readlines()
1717
+ has_more_launches = False
1718
+ for line_content in lines[caller_line:]: # renamed line to line_content
1719
+ if '.launch(' in line_content and not line_content.strip().startswith('#'):
1720
+ has_more_launches = True
1721
+ break
1722
+ if not has_more_launches:
1723
+ try:
1724
+ print("\nAgent MCP server running. Press Ctrl+C to stop.")
1725
+ while True:
1726
+ time.sleep(1)
1727
+ except KeyboardInterrupt:
1728
+ print("\nMCP Server stopped")
1729
+ except Exception as e:
1730
+ logging.error(f"Error in MCP launch detection: {e}")
1583
1731
  try:
1584
- print("\nAll agents registered. Press Ctrl+C to stop the servers.")
1732
+ print("\nKeeping MCP server alive. Press Ctrl+C to stop.")
1585
1733
  while True:
1586
1734
  time.sleep(1)
1587
1735
  except KeyboardInterrupt:
1588
- print("\nServers stopped")
1589
- except Exception as e:
1590
- # If something goes wrong with detection, block anyway to be safe
1591
- logging.error(f"Error in launch detection: {e}")
1592
- try:
1593
- print("\nKeeping servers alive. Press Ctrl+C to stop.")
1594
- while True:
1595
- time.sleep(1)
1596
- except KeyboardInterrupt:
1597
- print("\nServers stopped")
1598
-
1599
- return None
1736
+ print("\nMCP Server stopped")
1737
+ return None
1738
+ else:
1739
+ display_error(f"Invalid protocol: {protocol}. Choose 'http' or 'mcp'.")
1740
+ return None