praisonaiagents 0.0.65__py3-none-any.whl → 0.0.67__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.
@@ -10,6 +10,7 @@ from .tools.tools import Tools
10
10
  from .agents.autoagents import AutoAgents
11
11
  from .knowledge.knowledge import Knowledge
12
12
  from .knowledge.chunking import Chunking
13
+ from .mcp.mcp import MCP
13
14
  from .main import (
14
15
  TaskOutput,
15
16
  ReflectionOutput,
@@ -51,5 +52,6 @@ __all__ = [
51
52
  'sync_display_callbacks',
52
53
  'async_display_callbacks',
53
54
  'Knowledge',
54
- 'Chunking'
55
+ 'Chunking',
56
+ 'MCP'
55
57
  ]
@@ -0,0 +1,6 @@
1
+ """
2
+ Model Context Protocol (MCP) integration for PraisonAI Agents.
3
+ """
4
+ from .mcp import MCP
5
+
6
+ __all__ = ["MCP"]
@@ -0,0 +1,319 @@
1
+ import asyncio
2
+ import threading
3
+ import queue
4
+ import time
5
+ import inspect
6
+ import shlex
7
+ import logging
8
+ import os
9
+ from typing import Any, List, Optional, Callable, Iterable, Union
10
+ from functools import wraps, partial
11
+
12
+ from mcp import ClientSession, StdioServerParameters
13
+ from mcp.client.stdio import stdio_client
14
+
15
+ class MCPToolRunner(threading.Thread):
16
+ """A dedicated thread for running MCP operations."""
17
+
18
+ def __init__(self, server_params):
19
+ super().__init__(daemon=True)
20
+ self.server_params = server_params
21
+ self.queue = queue.Queue()
22
+ self.result_queue = queue.Queue()
23
+ self.initialized = threading.Event()
24
+ self.tools = []
25
+ self.start()
26
+
27
+ def run(self):
28
+ """Main thread function that processes MCP requests."""
29
+ asyncio.run(self._run_async())
30
+
31
+ async def _run_async(self):
32
+ """Async entry point for MCP operations."""
33
+ try:
34
+ # Set up MCP session
35
+ async with stdio_client(self.server_params) as (read, write):
36
+ async with ClientSession(read, write) as session:
37
+ # Initialize connection
38
+ await session.initialize()
39
+
40
+ # Get tools
41
+ tools_result = await session.list_tools()
42
+ self.tools = tools_result.tools
43
+
44
+ # Signal that initialization is complete
45
+ self.initialized.set()
46
+
47
+ # Process requests
48
+ while True:
49
+ try:
50
+ # Check for new requests
51
+ try:
52
+ item = self.queue.get(block=False)
53
+ if item is None: # Shutdown signal
54
+ break
55
+
56
+ tool_name, arguments = item
57
+ try:
58
+ result = await session.call_tool(tool_name, arguments)
59
+ self.result_queue.put((True, result))
60
+ except Exception as e:
61
+ self.result_queue.put((False, str(e)))
62
+ except queue.Empty:
63
+ pass
64
+
65
+ # Give other tasks a chance to run
66
+ await asyncio.sleep(0.01)
67
+ except asyncio.CancelledError:
68
+ break
69
+ except Exception as e:
70
+ self.initialized.set() # Ensure we don't hang
71
+ self.result_queue.put((False, f"MCP initialization error: {str(e)}"))
72
+
73
+ def call_tool(self, tool_name, arguments):
74
+ """Call an MCP tool and wait for the result."""
75
+ if not self.initialized.is_set():
76
+ self.initialized.wait(timeout=30)
77
+ if not self.initialized.is_set():
78
+ return "Error: MCP initialization timed out"
79
+
80
+ # Put request in queue
81
+ self.queue.put((tool_name, arguments))
82
+
83
+ # Wait for result
84
+ success, result = self.result_queue.get()
85
+ if not success:
86
+ return f"Error: {result}"
87
+
88
+ # Process result
89
+ if hasattr(result, 'content') and result.content:
90
+ if hasattr(result.content[0], 'text'):
91
+ return result.content[0].text
92
+ return str(result.content[0])
93
+ return str(result)
94
+
95
+ def shutdown(self):
96
+ """Signal the thread to shut down."""
97
+ self.queue.put(None)
98
+
99
+
100
+ class MCP:
101
+ """
102
+ Model Context Protocol (MCP) integration for PraisonAI Agents.
103
+
104
+ This class provides a simple way to connect to MCP servers and use their tools
105
+ within PraisonAI agents.
106
+
107
+ Example:
108
+ ```python
109
+ from praisonaiagents import Agent
110
+ from praisonaiagents.mcp import MCP
111
+
112
+ # Method 1: Using command and args separately
113
+ agent = Agent(
114
+ instructions="You are a helpful assistant...",
115
+ llm="gpt-4o-mini",
116
+ tools=MCP(
117
+ command="/path/to/python",
118
+ args=["/path/to/app.py"]
119
+ )
120
+ )
121
+
122
+ # Method 2: Using a single command string
123
+ agent = Agent(
124
+ instructions="You are a helpful assistant...",
125
+ llm="gpt-4o-mini",
126
+ tools=MCP("/path/to/python /path/to/app.py")
127
+ )
128
+
129
+ agent.start("What is the stock price of Tesla?")
130
+ ```
131
+ """
132
+
133
+ def __init__(self, command_or_string=None, args=None, *, command=None, timeout=60, debug=False, **kwargs):
134
+ """
135
+ Initialize the MCP connection and get tools.
136
+
137
+ Args:
138
+ command_or_string: Either:
139
+ - The command to run the MCP server (e.g., Python path)
140
+ - A complete command string (e.g., "/path/to/python /path/to/app.py")
141
+ - For NPX: 'npx' command with args for smithery tools
142
+ args: Arguments to pass to the command (when command_or_string is the command)
143
+ command: Alternative parameter name for backward compatibility
144
+ timeout: Timeout in seconds for MCP server initialization and tool calls (default: 60)
145
+ debug: Enable debug logging for MCP operations (default: False)
146
+ **kwargs: Additional parameters for StdioServerParameters
147
+ """
148
+ # Handle backward compatibility with named parameter 'command'
149
+ if command_or_string is None and command is not None:
150
+ command_or_string = command
151
+
152
+ # Handle the single string format
153
+ if isinstance(command_or_string, str) and args is None:
154
+ # Split the string into command and args using shell-like parsing
155
+ parts = shlex.split(command_or_string)
156
+ if not parts:
157
+ raise ValueError("Empty command string")
158
+
159
+ cmd = parts[0]
160
+ arguments = parts[1:] if len(parts) > 1 else []
161
+ else:
162
+ # Use the original format with separate command and args
163
+ cmd = command_or_string
164
+ arguments = args or []
165
+
166
+ self.server_params = StdioServerParameters(
167
+ command=cmd,
168
+ args=arguments,
169
+ **kwargs
170
+ )
171
+ self.runner = MCPToolRunner(self.server_params)
172
+
173
+ # Wait for initialization
174
+ if not self.runner.initialized.wait(timeout=30):
175
+ 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
+
184
+ # Automatically detect if this is an NPX command
185
+ self.is_npx = cmd == 'npx' or (isinstance(cmd, str) and os.path.basename(cmd) == 'npx')
186
+
187
+ # For NPX-based MCP servers, use a different approach
188
+ if self.is_npx:
189
+ self._function_declarations = []
190
+ self._initialize_npx_mcp_tools(cmd, arguments)
191
+ else:
192
+ # Generate tool functions immediately and store them
193
+ self._tools = self._generate_tool_functions()
194
+
195
+ def _generate_tool_functions(self) -> List[Callable]:
196
+ """
197
+ Generate functions for each MCP tool.
198
+
199
+ Returns:
200
+ List[Callable]: Functions that can be used as tools
201
+ """
202
+ tool_functions = []
203
+
204
+ for tool in self.runner.tools:
205
+ wrapper = self._create_tool_wrapper(tool)
206
+ tool_functions.append(wrapper)
207
+
208
+ return tool_functions
209
+
210
+ def _create_tool_wrapper(self, tool):
211
+ """Create a wrapper function for an MCP tool."""
212
+ # Determine parameter names from the schema
213
+ param_names = []
214
+ param_annotations = {}
215
+ required_params = []
216
+
217
+ if hasattr(tool, 'inputSchema') and tool.inputSchema:
218
+ properties = tool.inputSchema.get("properties", {})
219
+ required = tool.inputSchema.get("required", [])
220
+
221
+ for name, prop in properties.items():
222
+ param_names.append(name)
223
+
224
+ # Set annotation based on property type
225
+ prop_type = prop.get("type", "string")
226
+ if prop_type == "string":
227
+ param_annotations[name] = str
228
+ elif prop_type == "integer":
229
+ param_annotations[name] = int
230
+ elif prop_type == "number":
231
+ param_annotations[name] = float
232
+ elif prop_type == "boolean":
233
+ param_annotations[name] = bool
234
+ elif prop_type == "array":
235
+ param_annotations[name] = list
236
+ elif prop_type == "object":
237
+ param_annotations[name] = dict
238
+ else:
239
+ param_annotations[name] = Any
240
+
241
+ if name in required:
242
+ required_params.append(name)
243
+
244
+ # Create the function signature
245
+ params = []
246
+ for name in param_names:
247
+ is_required = name in required_params
248
+ param = inspect.Parameter(
249
+ name=name,
250
+ kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
251
+ default=inspect.Parameter.empty if is_required else None,
252
+ annotation=param_annotations.get(name, Any)
253
+ )
254
+ params.append(param)
255
+
256
+ # Create function template to be properly decorated
257
+ def template_function(*args, **kwargs):
258
+ return None
259
+
260
+ # Create a proper function with the correct signature
261
+ template_function.__signature__ = inspect.Signature(params)
262
+ template_function.__annotations__ = param_annotations
263
+ template_function.__name__ = tool.name
264
+ template_function.__qualname__ = tool.name
265
+ template_function.__doc__ = tool.description
266
+
267
+ # Create the actual function using a decorator
268
+ @wraps(template_function)
269
+ def wrapper(*args, **kwargs):
270
+ # Map positional args to parameter names
271
+ all_args = {}
272
+ for i, arg in enumerate(args):
273
+ if i < len(param_names):
274
+ all_args[param_names[i]] = arg
275
+
276
+ # Add keyword args
277
+ all_args.update(kwargs)
278
+
279
+ # Call the tool
280
+ return self.runner.call_tool(tool.name, all_args)
281
+
282
+ # Make sure the wrapper has the correct signature for inspection
283
+ wrapper.__signature__ = inspect.Signature(params)
284
+
285
+ return wrapper
286
+
287
+ def _initialize_npx_mcp_tools(self, cmd, arguments):
288
+ """Initialize the NPX MCP tools by extracting tool definitions."""
289
+ try:
290
+ # For NPX tools, we'll use the same approach as regular MCP tools
291
+ # but we need to handle the initialization differently
292
+ if self.debug:
293
+ logging.debug(f"Initializing NPX MCP tools with command: {cmd} {' '.join(arguments)}")
294
+
295
+ # Generate tool functions using the regular MCP approach
296
+ self._tools = self._generate_tool_functions()
297
+
298
+ if self.debug:
299
+ logging.debug(f"Generated {len(self._tools)} NPX MCP tools")
300
+
301
+ except Exception as e:
302
+ if self.debug:
303
+ logging.error(f"Failed to initialize NPX MCP tools: {e}")
304
+ raise RuntimeError(f"Failed to initialize NPX MCP tools: {e}")
305
+
306
+
307
+
308
+ def __iter__(self) -> Iterable[Callable]:
309
+ """
310
+ Allow the MCP instance to be used directly as an iterable of tools.
311
+
312
+ This makes it possible to pass the MCP instance directly to the Agent's tools parameter.
313
+ """
314
+ return iter(self._tools)
315
+
316
+ def __del__(self):
317
+ """Clean up resources when the object is garbage collected."""
318
+ if hasattr(self, 'runner'):
319
+ self.runner.shutdown()
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: praisonaiagents
3
- Version: 0.0.65
3
+ Version: 0.0.67
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,4 +1,4 @@
1
- praisonaiagents/__init__.py,sha256=frdIvimDY-kU9j-9yXV1z4NtXypfPvyvlnac5mgBCuQ,1288
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
4
  praisonaiagents/agent/agent.py,sha256=h3s0-1M88zujllDHnKijHmYeVihD75d-K9s2Y3IHLY4,61850
@@ -11,6 +11,8 @@ praisonaiagents/knowledge/chunking.py,sha256=FzoNY0q8MkvG4gADqk4JcRhmH3lcEHbRdon
11
11
  praisonaiagents/knowledge/knowledge.py,sha256=fQNREDiwdoisfIxJBLVkteXgq_8Gbypfc3UaZbxf5QY,13210
12
12
  praisonaiagents/llm/__init__.py,sha256=ttPQQJQq6Tah-0updoEXDZFKWtJAM93rBWRoIgxRWO8,689
13
13
  praisonaiagents/llm/llm.py,sha256=6QMRW47fgFozibzaqxa3dwxlD756rMLCRjL3eNsw8QQ,74088
14
+ praisonaiagents/mcp/__init__.py,sha256=IkYdrAK1bDQDm_0t3Wjt63Zwv3_IJgqz84Wqz9GH2iQ,111
15
+ praisonaiagents/mcp/mcp.py,sha256=7EJo2Eyw89ePQzeKUzRZgW3E2ztLkMwQj2lCjFlzecQ,12281
14
16
  praisonaiagents/memory/memory.py,sha256=I8dOTkrl1i-GgQbDcrFOsSruzJ7MiI6Ys37DK27wrUs,35537
15
17
  praisonaiagents/process/__init__.py,sha256=lkYbL7Hn5a0ldvJtkdH23vfIIZLIcanK-65C0MwaorY,52
16
18
  praisonaiagents/process/process.py,sha256=HPw84OhnKQW3EyrDkpoQu0DcpxThbrzR2hWUgwQh9Pw,59955
@@ -37,7 +39,7 @@ praisonaiagents/tools/xml_tools.py,sha256=iYTMBEk5l3L3ryQ1fkUnNVYK-Nnua2Kx2S0dxN
37
39
  praisonaiagents/tools/yaml_tools.py,sha256=uogAZrhXV9O7xvspAtcTfpKSQYL2nlOTvCQXN94-G9A,14215
38
40
  praisonaiagents/tools/yfinance_tools.py,sha256=s2PBj_1v7oQnOobo2fDbQBACEHl61ftG4beG6Z979ZE,8529
39
41
  praisonaiagents/tools/train/data/generatecot.py,sha256=H6bNh-E2hqL5MW6kX3hqZ05g9ETKN2-kudSjiuU_SD8,19403
40
- praisonaiagents-0.0.65.dist-info/METADATA,sha256=yns65YUATN0vnXt8y4TMvoNUTgvp1fPkKKjqZAVSjcY,830
41
- praisonaiagents-0.0.65.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
42
- praisonaiagents-0.0.65.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
43
- praisonaiagents-0.0.65.dist-info/RECORD,,
42
+ praisonaiagents-0.0.67.dist-info/METADATA,sha256=sGyO2OxLcuZGGmq-2WZHg9RIvSB1tLPFOUpEst8VeU0,830
43
+ praisonaiagents-0.0.67.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
44
+ praisonaiagents-0.0.67.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
45
+ praisonaiagents-0.0.67.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5