praisonaiagents 0.0.76__py3-none-any.whl → 0.0.77__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.
@@ -530,11 +530,21 @@ Your Goal: {self.goal}
530
530
  from ..mcp.mcp import MCP
531
531
  if isinstance(self.tools, MCP):
532
532
  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)
533
+
534
+ # Handle SSE MCP client
535
+ if hasattr(self.tools, 'is_sse') and self.tools.is_sse:
536
+ if hasattr(self.tools, 'sse_client'):
537
+ for tool in self.tools.sse_client.tools:
538
+ if tool.name == function_name:
539
+ logging.debug(f"Found matching SSE MCP tool: {function_name}")
540
+ return tool(**arguments)
541
+ # Handle stdio MCP client
542
+ elif hasattr(self.tools, 'runner'):
543
+ # Check if any of the MCP tools match the function name
544
+ for mcp_tool in self.tools.runner.tools:
545
+ if hasattr(mcp_tool, 'name') and mcp_tool.name == function_name:
546
+ logging.debug(f"Found matching MCP tool: {function_name}")
547
+ return self.tools.runner.call_tool(function_name, arguments)
538
548
 
539
549
  # Try to find the function in the agent's tools list first
540
550
  func = None
@@ -815,7 +825,11 @@ Your Goal: {self.goal}
815
825
  logging.debug("Converting MCP tool to OpenAI format")
816
826
  openai_tool = tool_param.to_openai_tool()
817
827
  if openai_tool:
818
- tool_param = [openai_tool]
828
+ # Handle both single tool and list of tools
829
+ if isinstance(openai_tool, list):
830
+ tool_param = openai_tool
831
+ else:
832
+ tool_param = [openai_tool]
819
833
  logging.debug(f"Converted MCP tool: {tool_param}")
820
834
 
821
835
  # Pass everything to LLM class
@@ -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.77
4
4
  Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
5
5
  Author: Mervin Praison
6
6
  Requires-Dist: pydantic
@@ -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=ZQOLF8tRr_TddhNRBFnna1AMrxeXwp1igw9I3KrngDk,66617
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.77.dist-info/METADATA,sha256=idfF7xXDr8DOx84tV0RSFL9sGzB_AncqVUauMEIBvD4,982
44
+ praisonaiagents-0.0.77.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
45
+ praisonaiagents-0.0.77.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
46
+ praisonaiagents-0.0.77.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