praisonaiagents 0.0.76__py3-none-any.whl → 0.0.78__py3-none-any.whl

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.
@@ -22,9 +22,16 @@ import inspect
22
22
  import uuid
23
23
  from dataclasses import dataclass
24
24
 
25
+ # Don't import FastAPI dependencies here - use lazy loading instead
26
+
25
27
  if TYPE_CHECKING:
26
28
  from ..task.task import Task
27
29
 
30
+ # Shared variables for API server
31
+ _shared_app = None
32
+ _server_started = False
33
+ _registered_agents = {}
34
+
28
35
  @dataclass
29
36
  class ChatCompletionMessage:
30
37
  content: str
@@ -530,11 +537,21 @@ Your Goal: {self.goal}
530
537
  from ..mcp.mcp import MCP
531
538
  if isinstance(self.tools, MCP):
532
539
  logging.debug(f"Looking for MCP tool {function_name}")
533
- # Check if any of the MCP tools match the function name
534
- for mcp_tool in self.tools.runner.tools:
535
- if hasattr(mcp_tool, 'name') and mcp_tool.name == function_name:
536
- logging.debug(f"Found matching MCP tool: {function_name}")
537
- return self.tools.runner.call_tool(function_name, arguments)
540
+
541
+ # Handle SSE MCP client
542
+ if hasattr(self.tools, 'is_sse') and self.tools.is_sse:
543
+ if hasattr(self.tools, 'sse_client'):
544
+ for tool in self.tools.sse_client.tools:
545
+ if tool.name == function_name:
546
+ logging.debug(f"Found matching SSE MCP tool: {function_name}")
547
+ return tool(**arguments)
548
+ # Handle stdio MCP client
549
+ elif hasattr(self.tools, 'runner'):
550
+ # Check if any of the MCP tools match the function name
551
+ for mcp_tool in self.tools.runner.tools:
552
+ if hasattr(mcp_tool, 'name') and mcp_tool.name == function_name:
553
+ logging.debug(f"Found matching MCP tool: {function_name}")
554
+ return self.tools.runner.call_tool(function_name, arguments)
538
555
 
539
556
  # Try to find the function in the agent's tools list first
540
557
  func = None
@@ -815,7 +832,11 @@ Your Goal: {self.goal}
815
832
  logging.debug("Converting MCP tool to OpenAI format")
816
833
  openai_tool = tool_param.to_openai_tool()
817
834
  if openai_tool:
818
- tool_param = [openai_tool]
835
+ # Handle both single tool and list of tools
836
+ if isinstance(openai_tool, list):
837
+ tool_param = openai_tool
838
+ else:
839
+ tool_param = [openai_tool]
819
840
  logging.debug(f"Converted MCP tool: {tool_param}")
820
841
 
821
842
  # Pass everything to LLM class
@@ -1385,4 +1406,157 @@ Your Goal: {self.goal}
1385
1406
 
1386
1407
  except Exception as e:
1387
1408
  logging.error(f"Error in execute_tool_async: {str(e)}", exc_info=True)
1388
- return {"error": f"Error in execute_tool_async: {str(e)}"}
1409
+ return {"error": f"Error in execute_tool_async: {str(e)}"}
1410
+
1411
+ def launch(self, path: str = '/', port: int = 8000, host: str = '0.0.0.0', autostart: bool = True, debug: bool = False, blocking: bool = True):
1412
+ """
1413
+ Launch the agent as an HTTP API endpoint.
1414
+
1415
+ Args:
1416
+ path: API endpoint path (default: '/')
1417
+ port: Server port (default: 8000)
1418
+ host: Server host (default: '0.0.0.0')
1419
+ autostart: Whether to start the server automatically (default: True)
1420
+ debug: Enable debug mode for uvicorn (default: False)
1421
+ blocking: If True, blocks the main thread to keep the server running (default: True)
1422
+
1423
+ Returns:
1424
+ None
1425
+ """
1426
+ global _server_started, _registered_agents, _shared_app
1427
+
1428
+ # Try to import FastAPI dependencies - lazy loading
1429
+ try:
1430
+ import uvicorn
1431
+ from fastapi import FastAPI, HTTPException, Request
1432
+ from fastapi.responses import JSONResponse
1433
+ from pydantic import BaseModel
1434
+
1435
+ # Define the request model here since we need pydantic
1436
+ class AgentQuery(BaseModel):
1437
+ query: str
1438
+
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 shared FastAPI app if not already created
1451
+ if _shared_app is None:
1452
+ _shared_app = FastAPI(
1453
+ title="PraisonAI Agents API",
1454
+ description="API for interacting with PraisonAI Agents"
1455
+ )
1456
+
1457
+ # Add a root endpoint with a welcome message
1458
+ @_shared_app.get("/")
1459
+ async def root():
1460
+ return {"message": "Welcome to PraisonAI Agents API. See /docs for usage."}
1461
+
1462
+ # Normalize path to ensure it starts with /
1463
+ if not path.startswith('/'):
1464
+ path = f'/{path}'
1465
+
1466
+ # Check if path is already registered by another agent
1467
+ if path in _registered_agents and _registered_agents[path] != self.agent_id:
1468
+ existing_agent = _registered_agents[path]
1469
+ logging.warning(f"Path '{path}' is already registered by another agent. Please use a different path.")
1470
+ print(f"⚠️ Warning: Path '{path}' is already registered by another agent.")
1471
+ # Use a modified path to avoid conflicts
1472
+ original_path = path
1473
+ path = f"{path}_{self.agent_id[:6]}"
1474
+ logging.warning(f"Using '{path}' instead of '{original_path}'")
1475
+ print(f"🔄 Using '{path}' instead")
1476
+
1477
+ # Register the agent to this path
1478
+ _registered_agents[path] = self.agent_id
1479
+
1480
+ # Define the endpoint handler
1481
+ @_shared_app.post(path)
1482
+ async def handle_agent_query(request: Request, query_data: Optional[AgentQuery] = None):
1483
+ # Handle both direct JSON with query field and form data
1484
+ if query_data is None:
1485
+ try:
1486
+ request_data = await request.json()
1487
+ if "query" not in request_data:
1488
+ raise HTTPException(status_code=400, detail="Missing 'query' field in request")
1489
+ query = request_data["query"]
1490
+ except:
1491
+ # Fallback to form data or query params
1492
+ form_data = await request.form()
1493
+ if "query" in form_data:
1494
+ query = form_data["query"]
1495
+ else:
1496
+ raise HTTPException(status_code=400, detail="Missing 'query' field in request")
1497
+ else:
1498
+ query = query_data.query
1499
+
1500
+ try:
1501
+ # Use async version if available, otherwise use sync version
1502
+ if asyncio.iscoroutinefunction(self.chat):
1503
+ response = await self.achat(query)
1504
+ else:
1505
+ # Run sync function in a thread to avoid blocking
1506
+ loop = asyncio.get_event_loop()
1507
+ response = await loop.run_in_executor(None, lambda: self.chat(query))
1508
+
1509
+ return {"response": response}
1510
+ except Exception as e:
1511
+ logging.error(f"Error processing query: {str(e)}", exc_info=True)
1512
+ return JSONResponse(
1513
+ status_code=500,
1514
+ content={"error": f"Error processing query: {str(e)}"}
1515
+ )
1516
+
1517
+ print(f"🚀 Agent '{self.name}' available at http://{host}:{port}{path}")
1518
+
1519
+ # Start the server if this is the first launch call and autostart is True
1520
+ if autostart and not _server_started:
1521
+ _server_started = True
1522
+
1523
+ # Add healthcheck endpoint
1524
+ @_shared_app.get("/health")
1525
+ async def healthcheck():
1526
+ return {"status": "ok", "agents": list(_registered_agents.keys())}
1527
+
1528
+ # Start the server in a separate thread to not block execution
1529
+ import threading
1530
+ def run_server():
1531
+ try:
1532
+ uvicorn.run(_shared_app, host=host, port=port, log_level="debug" if debug else "info")
1533
+ except Exception as e:
1534
+ logging.error(f"Error starting server: {str(e)}", exc_info=True)
1535
+ print(f"❌ Error starting server: {str(e)}")
1536
+
1537
+ server_thread = threading.Thread(target=run_server, daemon=True)
1538
+ server_thread.start()
1539
+
1540
+ # Give the server a moment to start up
1541
+ import time
1542
+ time.sleep(0.5)
1543
+
1544
+ print(f"✅ FastAPI server started at http://{host}:{port}")
1545
+ print(f"📚 API documentation available at http://{host}:{port}/docs")
1546
+
1547
+ # If blocking is True, keep the main thread alive
1548
+ if blocking:
1549
+ print("\nServer is running in blocking mode. Press Ctrl+C to stop...")
1550
+ try:
1551
+ while True:
1552
+ time.sleep(1)
1553
+ except KeyboardInterrupt:
1554
+ print("\nServer stopped")
1555
+ else:
1556
+ # Note for non-blocking mode
1557
+ print("\nNote: Server is running in a background thread. To keep it alive, either:")
1558
+ print("1. Set blocking=True when calling launch()")
1559
+ print("2. Keep your main application running")
1560
+ print("3. Use a loop in your code to prevent the program from exiting")
1561
+
1562
+ return None
@@ -293,6 +293,12 @@ class LLM:
293
293
  if isinstance(tool, dict) and 'type' in tool and tool['type'] == 'function':
294
294
  logging.debug(f"Using pre-formatted OpenAI tool: {tool['function']['name']}")
295
295
  formatted_tools.append(tool)
296
+ # Handle lists of tools (e.g. from MCP.to_openai_tool())
297
+ elif isinstance(tool, list):
298
+ for subtool in tool:
299
+ if isinstance(subtool, dict) and 'type' in subtool and subtool['type'] == 'function':
300
+ logging.debug(f"Using pre-formatted OpenAI tool from list: {subtool['function']['name']}")
301
+ formatted_tools.append(subtool)
296
302
  elif callable(tool):
297
303
  tool_def = self._generate_tool_definition(tool.__name__)
298
304
  if tool_def:
@@ -1,5 +1,8 @@
1
1
  """
2
2
  Model Context Protocol (MCP) integration for PraisonAI Agents.
3
+
4
+ This package provides classes and utilities for connecting to MCP servers
5
+ using different transport methods (stdio, SSE, etc.).
3
6
  """
4
7
  from .mcp import MCP
5
8
 
@@ -6,6 +6,7 @@ import inspect
6
6
  import shlex
7
7
  import logging
8
8
  import os
9
+ import re
9
10
  from typing import Any, List, Optional, Callable, Iterable, Union
10
11
  from functools import wraps, partial
11
12
 
@@ -126,6 +127,13 @@ class MCP:
126
127
  tools=MCP("/path/to/python /path/to/app.py")
127
128
  )
128
129
 
130
+ # Method 3: Using an SSE endpoint
131
+ agent = Agent(
132
+ instructions="You are a helpful assistant...",
133
+ llm="gpt-4o-mini",
134
+ tools=MCP("http://localhost:8080/sse")
135
+ )
136
+
129
137
  agent.start("What is the stock price of Tesla?")
130
138
  ```
131
139
  """
@@ -139,6 +147,7 @@ class MCP:
139
147
  - The command to run the MCP server (e.g., Python path)
140
148
  - A complete command string (e.g., "/path/to/python /path/to/app.py")
141
149
  - For NPX: 'npx' command with args for smithery tools
150
+ - An SSE URL (e.g., "http://localhost:8080/sse")
142
151
  args: Arguments to pass to the command (when command_or_string is the command)
143
152
  command: Alternative parameter name for backward compatibility
144
153
  timeout: Timeout in seconds for MCP server initialization and tool calls (default: 60)
@@ -149,7 +158,44 @@ class MCP:
149
158
  if command_or_string is None and command is not None:
150
159
  command_or_string = command
151
160
 
152
- # Handle the single string format
161
+ # Set up logging - default to WARNING level to hide INFO messages
162
+ if debug:
163
+ logging.getLogger("mcp-wrapper").setLevel(logging.DEBUG)
164
+ logging.getLogger("mcp-sse").setLevel(logging.DEBUG)
165
+ logging.getLogger("mcp.client").setLevel(logging.DEBUG)
166
+ logging.getLogger("sse").setLevel(logging.DEBUG)
167
+ logging.getLogger("mcp-server").setLevel(logging.DEBUG)
168
+ logging.getLogger("mcp-client").setLevel(logging.DEBUG)
169
+ logging.getLogger("_client").setLevel(logging.DEBUG)
170
+ logging.getLogger("httpx").setLevel(logging.DEBUG)
171
+ logging.getLogger("llm").setLevel(logging.DEBUG)
172
+ else:
173
+ # Set all MCP-related loggers to WARNING level by default
174
+ logging.getLogger("mcp-wrapper").setLevel(logging.WARNING)
175
+ logging.getLogger("mcp-sse").setLevel(logging.WARNING)
176
+ logging.getLogger("mcp.client").setLevel(logging.WARNING)
177
+ logging.getLogger("sse").setLevel(logging.WARNING)
178
+ logging.getLogger("mcp-server").setLevel(logging.WARNING)
179
+ logging.getLogger("mcp-client").setLevel(logging.WARNING)
180
+ logging.getLogger("_client").setLevel(logging.WARNING)
181
+ logging.getLogger("httpx").setLevel(logging.WARNING)
182
+ logging.getLogger("llm").setLevel(logging.WARNING)
183
+
184
+ # Store additional parameters
185
+ self.timeout = timeout
186
+ self.debug = debug
187
+
188
+ # Check if this is an SSE URL
189
+ if isinstance(command_or_string, str) and re.match(r'^https?://', command_or_string):
190
+ # Import the SSE client implementation
191
+ from .mcp_sse import SSEMCPClient
192
+ self.sse_client = SSEMCPClient(command_or_string, debug=debug)
193
+ self._tools = list(self.sse_client.tools)
194
+ self.is_sse = True
195
+ self.is_npx = False
196
+ return
197
+
198
+ # Handle the single string format for stdio client
153
199
  if isinstance(command_or_string, str) and args is None:
154
200
  # Split the string into command and args using shell-like parsing
155
201
  parts = shlex.split(command_or_string)
@@ -162,7 +208,9 @@ class MCP:
162
208
  # Use the original format with separate command and args
163
209
  cmd = command_or_string
164
210
  arguments = args or []
165
-
211
+
212
+ # Set up stdio client
213
+ self.is_sse = False
166
214
  self.server_params = StdioServerParameters(
167
215
  command=cmd,
168
216
  args=arguments,
@@ -173,13 +221,6 @@ class MCP:
173
221
  # Wait for initialization
174
222
  if not self.runner.initialized.wait(timeout=30):
175
223
  print("Warning: MCP initialization timed out")
176
-
177
- # Store additional parameters
178
- self.timeout = timeout
179
- self.debug = debug
180
-
181
- if debug:
182
- logging.getLogger("mcp-wrapper").setLevel(logging.DEBUG)
183
224
 
184
225
  # Automatically detect if this is an NPX command
185
226
  self.is_npx = cmd == 'npx' or (isinstance(cmd, str) and os.path.basename(cmd) == 'npx')
@@ -199,6 +240,9 @@ class MCP:
199
240
  Returns:
200
241
  List[Callable]: Functions that can be used as tools
201
242
  """
243
+ if self.is_sse:
244
+ return list(self.sse_client.tools)
245
+
202
246
  tool_functions = []
203
247
 
204
248
  for tool in self.runner.tools:
@@ -303,8 +347,6 @@ class MCP:
303
347
  logging.error(f"Failed to initialize NPX MCP tools: {e}")
304
348
  raise RuntimeError(f"Failed to initialize NPX MCP tools: {e}")
305
349
 
306
-
307
-
308
350
  def __iter__(self) -> Iterable[Callable]:
309
351
  """
310
352
  Allow the MCP instance to be used directly as an iterable of tools.
@@ -320,37 +362,43 @@ class MCP:
320
362
  provider/model format (e.g., "openai/gpt-4o-mini").
321
363
 
322
364
  Returns:
323
- dict: OpenAI-compatible tool definition
365
+ dict or list: OpenAI-compatible tool definition(s)
324
366
  """
367
+ if self.is_sse and hasattr(self, 'sse_client') and self.sse_client.tools:
368
+ # Return all tools from SSE client
369
+ return self.sse_client.to_openai_tools()
370
+
325
371
  # For simplicity, we'll convert the first tool only if multiple exist
326
372
  # More complex implementations could handle multiple tools
327
- if not self.runner.tools:
373
+ if not hasattr(self, 'runner') or not self.runner.tools:
328
374
  logging.warning("No MCP tools available to convert to OpenAI format")
329
375
  return None
330
376
 
331
- # Get the first tool's schema
332
- tool = self.runner.tools[0]
377
+ # Convert all tools to OpenAI format
378
+ openai_tools = []
379
+ for tool in self.runner.tools:
380
+ # Create OpenAI tool definition
381
+ parameters = {}
382
+ if hasattr(tool, 'inputSchema') and tool.inputSchema:
383
+ parameters = tool.inputSchema
384
+ else:
385
+ # Create a minimal schema if none exists
386
+ parameters = {
387
+ "type": "object",
388
+ "properties": {},
389
+ "required": []
390
+ }
391
+
392
+ openai_tools.append({
393
+ "type": "function",
394
+ "function": {
395
+ "name": tool.name,
396
+ "description": tool.description if hasattr(tool, 'description') else f"Call the {tool.name} tool",
397
+ "parameters": parameters
398
+ }
399
+ })
333
400
 
334
- # Create OpenAI tool definition
335
- parameters = {}
336
- if hasattr(tool, 'inputSchema') and tool.inputSchema:
337
- parameters = tool.inputSchema
338
- else:
339
- # Create a minimal schema if none exists
340
- parameters = {
341
- "type": "object",
342
- "properties": {},
343
- "required": []
344
- }
345
-
346
- return {
347
- "type": "function",
348
- "function": {
349
- "name": tool.name,
350
- "description": tool.description if hasattr(tool, 'description') else f"Call the {tool.name} tool",
351
- "parameters": parameters
352
- }
353
- }
401
+ return openai_tools
354
402
 
355
403
  def __del__(self):
356
404
  """Clean up resources when the object is garbage collected."""
@@ -0,0 +1,184 @@
1
+ """
2
+ SSE (Server-Sent Events) client implementation for MCP (Model Context Protocol).
3
+ This module provides the necessary classes and functions to connect to an MCP server
4
+ over SSE transport.
5
+ """
6
+
7
+ import asyncio
8
+ import logging
9
+ import threading
10
+ import inspect
11
+ import json
12
+ from typing import List, Dict, Any, Optional, Callable, Iterable
13
+
14
+ from mcp import ClientSession
15
+ from mcp.client.sse import sse_client
16
+
17
+ logger = logging.getLogger("mcp-sse")
18
+
19
+ # Global event loop for async operations
20
+ _event_loop = None
21
+
22
+ def get_event_loop():
23
+ """Get or create a global event loop."""
24
+ global _event_loop
25
+ if _event_loop is None or _event_loop.is_closed():
26
+ _event_loop = asyncio.new_event_loop()
27
+ asyncio.set_event_loop(_event_loop)
28
+ return _event_loop
29
+
30
+
31
+ class SSEMCPTool:
32
+ """A wrapper for an MCP tool that can be used with praisonaiagents."""
33
+
34
+ def __init__(self, name: str, description: str, session: ClientSession, input_schema: Optional[Dict[str, Any]] = None):
35
+ self.name = name
36
+ self.__name__ = name # Required for Agent to recognize it as a tool
37
+ self.__qualname__ = name # Required for Agent to recognize it as a tool
38
+ self.__doc__ = description # Required for Agent to recognize it as a tool
39
+ self.description = description
40
+ self.session = session
41
+ self.input_schema = input_schema or {}
42
+
43
+ # Create a signature based on input schema
44
+ params = []
45
+ if input_schema and 'properties' in input_schema:
46
+ for param_name in input_schema['properties']:
47
+ params.append(
48
+ inspect.Parameter(
49
+ name=param_name,
50
+ kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
51
+ default=inspect.Parameter.empty if param_name in input_schema.get('required', []) else None,
52
+ annotation=str # Default to string
53
+ )
54
+ )
55
+
56
+ self.__signature__ = inspect.Signature(params)
57
+
58
+ def __call__(self, **kwargs):
59
+ """Synchronous wrapper for the async call."""
60
+ logger.debug(f"Tool {self.name} called with args: {kwargs}")
61
+
62
+ # Use the global event loop
63
+ loop = get_event_loop()
64
+
65
+ # Run the async call in the event loop
66
+ future = asyncio.run_coroutine_threadsafe(self._async_call(**kwargs), loop)
67
+ try:
68
+ # Wait for the result with a timeout
69
+ return future.result(timeout=30)
70
+ except Exception as e:
71
+ logger.error(f"Error calling tool {self.name}: {e}")
72
+ return f"Error: {str(e)}"
73
+
74
+ async def _async_call(self, **kwargs):
75
+ """Call the tool with the provided arguments."""
76
+ logger.debug(f"Async calling tool {self.name} with args: {kwargs}")
77
+ try:
78
+ result = await self.session.call_tool(self.name, kwargs)
79
+
80
+ # Extract text from result
81
+ if hasattr(result, 'content') and result.content:
82
+ if hasattr(result.content[0], 'text'):
83
+ return result.content[0].text
84
+ return str(result.content[0])
85
+ return str(result)
86
+ except Exception as e:
87
+ logger.error(f"Error in _async_call for {self.name}: {e}")
88
+ raise
89
+
90
+ def to_openai_tool(self):
91
+ """Convert the tool to OpenAI format."""
92
+ return {
93
+ "type": "function",
94
+ "function": {
95
+ "name": self.name,
96
+ "description": self.description,
97
+ "parameters": self.input_schema
98
+ }
99
+ }
100
+
101
+
102
+ class SSEMCPClient:
103
+ """A client for connecting to an MCP server over SSE."""
104
+
105
+ def __init__(self, server_url: str, debug: bool = False):
106
+ """
107
+ Initialize an SSE MCP client.
108
+
109
+ Args:
110
+ server_url: The URL of the SSE MCP server
111
+ debug: Whether to enable debug logging
112
+ """
113
+ self.server_url = server_url
114
+ self.debug = debug
115
+ self.session = None
116
+ self.tools = []
117
+
118
+ # Set up logging
119
+ if debug:
120
+ logger.setLevel(logging.DEBUG)
121
+ else:
122
+ # Set to WARNING by default to hide INFO messages
123
+ logger.setLevel(logging.WARNING)
124
+
125
+ self._initialize()
126
+
127
+ def _initialize(self):
128
+ """Initialize the connection and tools."""
129
+ # Use the global event loop
130
+ loop = get_event_loop()
131
+
132
+ # Start a background thread to run the event loop
133
+ def run_event_loop():
134
+ asyncio.set_event_loop(loop)
135
+ loop.run_forever()
136
+
137
+ self.loop_thread = threading.Thread(target=run_event_loop, daemon=True)
138
+ self.loop_thread.start()
139
+
140
+ # Run the initialization in the event loop
141
+ future = asyncio.run_coroutine_threadsafe(self._async_initialize(), loop)
142
+ self.tools = future.result(timeout=30)
143
+
144
+ async def _async_initialize(self):
145
+ """Asynchronously initialize the connection and tools."""
146
+ logger.debug(f"Connecting to MCP server at {self.server_url}")
147
+
148
+ # Create SSE client
149
+ self._streams_context = sse_client(url=self.server_url)
150
+ streams = await self._streams_context.__aenter__()
151
+
152
+ self._session_context = ClientSession(*streams)
153
+ self.session = await self._session_context.__aenter__()
154
+
155
+ # Initialize
156
+ await self.session.initialize()
157
+
158
+ # List available tools
159
+ logger.debug("Listing tools...")
160
+ response = await self.session.list_tools()
161
+ tools_data = response.tools
162
+ logger.debug(f"Found {len(tools_data)} tools: {[tool.name for tool in tools_data]}")
163
+
164
+ # Create tool wrappers
165
+ tools = []
166
+ for tool in tools_data:
167
+ input_schema = tool.inputSchema if hasattr(tool, 'inputSchema') else None
168
+ wrapper = SSEMCPTool(
169
+ name=tool.name,
170
+ description=tool.description if hasattr(tool, 'description') else f"Call the {tool.name} tool",
171
+ session=self.session,
172
+ input_schema=input_schema
173
+ )
174
+ tools.append(wrapper)
175
+
176
+ return tools
177
+
178
+ def __iter__(self):
179
+ """Return an iterator over the tools."""
180
+ return iter(self.tools)
181
+
182
+ def to_openai_tools(self):
183
+ """Convert all tools to OpenAI format."""
184
+ return [tool.to_openai_tool() for tool in self.tools]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praisonaiagents
3
- Version: 0.0.76
3
+ Version: 0.0.78
4
4
  Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
5
5
  Author: Mervin Praison
6
6
  Requires-Dist: pydantic
@@ -19,8 +19,12 @@ Requires-Dist: chonkie>=1.0.2; extra == "knowledge"
19
19
  Provides-Extra: llm
20
20
  Requires-Dist: litellm>=1.50.0; extra == "llm"
21
21
  Requires-Dist: pydantic>=2.4.2; extra == "llm"
22
+ Provides-Extra: api
23
+ Requires-Dist: fastapi>=0.115.0; extra == "api"
24
+ Requires-Dist: uvicorn>=0.34.0; extra == "api"
22
25
  Provides-Extra: all
23
26
  Requires-Dist: praisonaiagents[memory]; extra == "all"
24
27
  Requires-Dist: praisonaiagents[knowledge]; extra == "all"
25
28
  Requires-Dist: praisonaiagents[llm]; extra == "all"
26
29
  Requires-Dist: praisonaiagents[mcp]; extra == "all"
30
+ Requires-Dist: praisonaiagents[api]; extra == "all"
@@ -1,7 +1,7 @@
1
1
  praisonaiagents/__init__.py,sha256=Z2_rSA6mYozz0r3ioUgKzl3QV8uWRDS_QaqPg2oGjqg,1324
2
2
  praisonaiagents/main.py,sha256=l29nGEbV2ReBi4szURbnH0Fk0w2F_QZTmECysyZjYcA,15066
3
3
  praisonaiagents/agent/__init__.py,sha256=j0T19TVNbfZcClvpbZDDinQxZ0oORgsMrMqx16jZ-bA,128
4
- praisonaiagents/agent/agent.py,sha256=nxi_39Zr75IEdb7Iv6cofZICYwEbD9Jritw_R6cQHGk,65851
4
+ praisonaiagents/agent/agent.py,sha256=mkZOHL_qgZaiX_DXTgiDlMIfjTmrqeMpi088VCC_YOo,73946
5
5
  praisonaiagents/agent/image_agent.py,sha256=-5MXG594HVwSpFMcidt16YBp7udtik-Cp7eXlzLE1fY,8696
6
6
  praisonaiagents/agents/__init__.py,sha256=_1d6Pqyk9EoBSo7E68sKyd1jDRlN1vxvVIRpoMc0Jcw,168
7
7
  praisonaiagents/agents/agents.py,sha256=uAOHyn77noFvg3sYVFRhQUuc1LDpCMpfLND8CKOXAd4,37971
@@ -10,9 +10,10 @@ praisonaiagents/knowledge/__init__.py,sha256=xL1Eh-a3xsHyIcU4foOWF-JdWYIYBALJH9b
10
10
  praisonaiagents/knowledge/chunking.py,sha256=G6wyHa7_8V0_7VpnrrUXbEmUmptlT16ISJYaxmkSgmU,7678
11
11
  praisonaiagents/knowledge/knowledge.py,sha256=Po0JZsgjYJrXdNSggmUGOWidZEF0f8xo4nhsZZfh8tY,13217
12
12
  praisonaiagents/llm/__init__.py,sha256=ttPQQJQq6Tah-0updoEXDZFKWtJAM93rBWRoIgxRWO8,689
13
- praisonaiagents/llm/llm.py,sha256=1WjHumxzuc8sj81NQ4uVEIetUOrb-i58HYLQW7vjV3M,87921
14
- praisonaiagents/mcp/__init__.py,sha256=IkYdrAK1bDQDm_0t3Wjt63Zwv3_IJgqz84Wqz9GH2iQ,111
15
- praisonaiagents/mcp/mcp.py,sha256=BPPf5AIPXx28PaJJqOg6T3NRyymQH9YAD-Km7Ma9-KA,13681
13
+ praisonaiagents/llm/llm.py,sha256=5SII0qUgaVbDTHdNfq4foV_vAjSwilz9Mw6p_S5LZfk,88393
14
+ praisonaiagents/mcp/__init__.py,sha256=ibbqe3_7XB7VrIcUcetkZiUZS1fTVvyMy_AqCSFG8qc,240
15
+ praisonaiagents/mcp/mcp.py,sha256=Fub1x-LroOs8pQhmOLSislBLnoHHGzR0ARGyUTUMzsM,16270
16
+ praisonaiagents/mcp/mcp_sse.py,sha256=xi5auCf83GMnsXzNEu5ooxNKjSdzKM5plB_DBn4RLzQ,6565
16
17
  praisonaiagents/memory/memory.py,sha256=I8dOTkrl1i-GgQbDcrFOsSruzJ7MiI6Ys37DK27wrUs,35537
17
18
  praisonaiagents/process/__init__.py,sha256=lkYbL7Hn5a0ldvJtkdH23vfIIZLIcanK-65C0MwaorY,52
18
19
  praisonaiagents/process/process.py,sha256=HPw84OhnKQW3EyrDkpoQu0DcpxThbrzR2hWUgwQh9Pw,59955
@@ -39,7 +40,7 @@ praisonaiagents/tools/xml_tools.py,sha256=iYTMBEk5l3L3ryQ1fkUnNVYK-Nnua2Kx2S0dxN
39
40
  praisonaiagents/tools/yaml_tools.py,sha256=uogAZrhXV9O7xvspAtcTfpKSQYL2nlOTvCQXN94-G9A,14215
40
41
  praisonaiagents/tools/yfinance_tools.py,sha256=s2PBj_1v7oQnOobo2fDbQBACEHl61ftG4beG6Z979ZE,8529
41
42
  praisonaiagents/tools/train/data/generatecot.py,sha256=H6bNh-E2hqL5MW6kX3hqZ05g9ETKN2-kudSjiuU_SD8,19403
42
- praisonaiagents-0.0.76.dist-info/METADATA,sha256=-xgefbRpP_4S8NlG_tI-DgXv_40a7rzHIxnYYM1vwgA,982
43
- praisonaiagents-0.0.76.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
44
- praisonaiagents-0.0.76.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
45
- praisonaiagents-0.0.76.dist-info/RECORD,,
43
+ praisonaiagents-0.0.78.dist-info/METADATA,sha256=VwX1r4Ib2OxhTYVqSHpEluxljiGzNy7eifYi6wBRbTk,1149
44
+ praisonaiagents-0.0.78.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
45
+ praisonaiagents-0.0.78.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
46
+ praisonaiagents-0.0.78.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.4.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5