Skip to content

Commit 529ca97

Browse files
πŸ“ Add docstrings to develop
Docstrings generation was requested by @MervinPraison. * #429 (comment) The following files were modified: * `src/praisonai-agents/mcp-basic.py` * `src/praisonai-agents/mcp-test.py` * `src/praisonai-agents/praisonaiagents/mcp/mcp.py`
1 parent 16684c8 commit 529ca97

3 files changed

Lines changed: 153 additions & 18 deletions

File tree

β€Žsrc/praisonai-agents/mcp-basic.pyβ€Ž

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@
1414
# Function to get stock price using MCP
1515
async def get_stock_price(symbol):
1616
# Start server and connect client
17+
"""
18+
Retrieves the stock price asynchronously.
19+
20+
This function connects to a server via an asynchronous client session, retrieves the
21+
list of available tools, and searches for a tool capable of fetching stock prices. If a
22+
suitable tool is found, it invokes the tool with the provided stock symbol and returns
23+
its result; otherwise, it returns a message indicating that no appropriate tool was found.
24+
25+
Args:
26+
symbol: The stock ticker symbol to query.
27+
"""
1728
async with stdio_client(server_params) as (read, write):
1829
async with ClientSession(read, write) as session:
1930
# Initialize the connection
@@ -45,7 +56,19 @@ async def get_stock_price(symbol):
4556

4657
# Create a custom tool for the agent
4758
def stock_price_tool(symbol: str) -> str:
48-
"""Get the current stock price for a given symbol"""
59+
"""
60+
Retrieve and format the current stock price for a specified symbol.
61+
62+
This synchronous wrapper runs the asynchronous `get_stock_price` function using
63+
`asyncio.run` to obtain the latest stock price, and returns a formatted string
64+
displaying the price for the given stock symbol.
65+
66+
Args:
67+
symbol: The stock ticker symbol for which the price is retrieved.
68+
69+
Returns:
70+
A string representing the stock price in a human-readable format.
71+
"""
4972
# Run the async function to get the stock price
5073
result = asyncio.run(get_stock_price(symbol))
5174
return f"Stock price for {symbol}: {result}"

β€Žsrc/praisonai-agents/mcp-test.pyβ€Ž

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414

1515
async def execute_tool(session: ClientSession, tool_name: str, params: Dict[str, Any]) -> Any:
1616
"""
17-
Execute a tool with proper error handling and return the result.
17+
Execute a tool asynchronously and return its result.
1818
19-
This follows the pattern shown in the article for reliable tool execution.
19+
This function invokes the tool identified by the given name using the provided session
20+
and parameters. If the tool execution completes successfully, its output is returned.
21+
If an error occurs, the function prints an error message and returns a dictionary
22+
containing the error detail.
2023
"""
2124
try:
2225
result = await session.call_tool(tool_name, arguments=params)
@@ -27,6 +30,14 @@ async def execute_tool(session: ClientSession, tool_name: str, params: Dict[str,
2730

2831
async def main():
2932
# Start server and connect client
33+
"""
34+
Orchestrates an asynchronous interaction with the tool server.
35+
36+
Establishes a connection using a standard I/O client and creates a client session,
37+
initializes the session, and retrieves a list of available tools. For debugging, it
38+
prints the available tools and, if present, selects the first tool to demonstrate an
39+
example invocation by constructing parameters from its input schema and printing the response.
40+
"""
3041
async with stdio_client(server_params) as (read, write):
3142
async with ClientSession(read, write) as session:
3243
# Initialize the connection

β€Žsrc/praisonai-agents/praisonaiagents/mcp/mcp.pyβ€Ž

Lines changed: 116 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ class MCPToolRunner(threading.Thread):
1414
"""A dedicated thread for running MCP operations."""
1515

1616
def __init__(self, server_params):
17+
"""
18+
Initializes the MCPToolRunner and starts its asynchronous processing thread.
19+
20+
Sets up queues for handling tool requests and results, configures an event to signal
21+
when initialization is complete, and stores the server parameters for establishing
22+
an MCP connection. The daemon thread is started immediately to manage asynchronous
23+
tool operations.
24+
25+
Args:
26+
server_params: Configuration parameters for connecting to the MCP server.
27+
"""
1728
super().__init__(daemon=True)
1829
self.server_params = server_params
1930
self.queue = queue.Queue()
@@ -23,11 +34,25 @@ def __init__(self, server_params):
2334
self.start()
2435

2536
def run(self):
26-
"""Main thread function that processes MCP requests."""
37+
"""
38+
Starts the asynchronous loop to process MCP requests.
39+
40+
This method serves as the entry point for the dedicated thread,
41+
initializing an asyncio event loop to execute the internal
42+
asynchronous request processing routine.
43+
"""
2744
asyncio.run(self._run_async())
2845

2946
async def _run_async(self):
30-
"""Async entry point for MCP operations."""
47+
"""
48+
Initializes the MCP session and processes tool requests asynchronously.
49+
50+
Establishes a connection with the MCP server using the provided parameters, initializes
51+
the session, and retrieves available tools before signaling readiness. Then, it enters
52+
a loop to poll an internal request queue for tool invocation requests, calling the
53+
appropriate tool for each request and enqueuing the result or error message. The loop
54+
exits gracefully when a shutdown signal or cancellation is encountered.
55+
"""
3156
try:
3257
# Set up MCP session
3358
async with stdio_client(self.server_params) as (read, write):
@@ -69,7 +94,22 @@ async def _run_async(self):
6994
self.result_queue.put((False, f"MCP initialization error: {str(e)}"))
7095

7196
def call_tool(self, tool_name, arguments):
72-
"""Call an MCP tool and wait for the result."""
97+
"""
98+
Calls an MCP tool synchronously and returns its result.
99+
100+
This method waits for MCP initialization (up to 30 seconds) before proceeding. If initialization
101+
times out, it returns an error message. Otherwise, it enqueues the tool request with the specified
102+
arguments and waits for the result. If the tool execution fails, an error message is returned; if
103+
successful, the method extracts and returns the text content of the result when available, or a string
104+
representation of the result.
105+
106+
Parameters:
107+
tool_name: The name of the MCP tool to execute.
108+
arguments: The arguments to pass to the MCP tool.
109+
110+
Returns:
111+
A string containing the tool's output or an error message.
112+
"""
73113
if not self.initialized.is_set():
74114
self.initialized.wait(timeout=30)
75115
if not self.initialized.is_set():
@@ -91,7 +131,12 @@ def call_tool(self, tool_name, arguments):
91131
return str(result)
92132

93133
def shutdown(self):
94-
"""Signal the thread to shut down."""
134+
"""
135+
Signals the worker thread to terminate.
136+
137+
Inserts a sentinel value (None) into the request queue, indicating that the thread should
138+
cease processing further requests.
139+
"""
95140
self.queue.put(None)
96141

97142

@@ -130,15 +175,25 @@ class MCP:
130175

131176
def __init__(self, command_or_string=None, args=None, *, command=None, **kwargs):
132177
"""
133-
Initialize the MCP connection and get tools.
178+
Initialize the MCP instance with command parsing and dynamic tool generation.
179+
180+
This method configures the MCP connection by interpreting command inputs. It accepts
181+
either a complete command stringβ€”which is split into a command and its argumentsβ€”or
182+
separate command and argument values. An alternative parameter name 'command' is also
183+
supported for backward compatibility. The method sets up the server parameters, starts
184+
the MCP tool runner, and waits up to 30 seconds for initialization, printing a warning
185+
if the process times out.
134186
135187
Args:
136-
command_or_string: Either:
137-
- The command to run the MCP server (e.g., Python path)
138-
- A complete command string (e.g., "/path/to/python /path/to/app.py")
139-
args: Arguments to pass to the command (when command_or_string is the command)
140-
command: Alternative parameter name for backward compatibility
141-
**kwargs: Additional parameters for StdioServerParameters
188+
command_or_string: A command executable or a complete command line to launch the
189+
MCP server. When provided as a string without separate arguments, it is split
190+
into the executable and its arguments.
191+
args: A list of arguments to pass to the command when it is provided separately.
192+
command: An alternative name for 'command_or_string' for backward compatibility.
193+
**kwargs: Additional keyword arguments passed to StdioServerParameters.
194+
195+
Raises:
196+
ValueError: If the provided command string is empty after parsing.
142197
"""
143198
# Handle backward compatibility with named parameter 'command'
144199
if command_or_string is None and command is not None:
@@ -174,10 +229,14 @@ def __init__(self, command_or_string=None, args=None, *, command=None, **kwargs)
174229

175230
def _generate_tool_functions(self) -> List[Callable]:
176231
"""
177-
Generate functions for each MCP tool.
232+
Generates callable wrappers for each available MCP tool.
233+
234+
This method iterates over the tools provided by the MCP runner and creates a wrapper
235+
function for each using the _create_tool_wrapper method. The returned functions can be
236+
invoked directly to execute the corresponding MCP tool with the appropriate input schema.
178237
179238
Returns:
180-
List[Callable]: Functions that can be used as tools
239+
List[Callable]: A list of callable wrappers for MCP tools.
181240
"""
182241
tool_functions = []
183242

@@ -188,7 +247,25 @@ def _generate_tool_functions(self) -> List[Callable]:
188247
return tool_functions
189248

190249
def _create_tool_wrapper(self, tool):
191-
"""Create a wrapper function for an MCP tool."""
250+
"""
251+
Creates a dynamic wrapper for an MCP tool.
252+
253+
This function builds a callable that conforms to the tool's interface as defined
254+
by its input schema. It extracts parameter names, types, and required status from
255+
the tool's schema and constructs a wrapper with a matching signature and
256+
documentation. When invoked, the wrapper maps positional and keyword arguments
257+
to the expected parameters and calls the tool via the runner.
258+
259+
Parameters:
260+
tool: An MCP tool object with attributes 'name', 'description', and
261+
'inputSchema'. The inputSchema should include a "properties" dictionary
262+
that defines parameter types and an optional "required" list for mandatory
263+
parameters.
264+
265+
Returns:
266+
A callable that wraps the tool, allowing it to be invoked with a signature
267+
that reflects its defined input parameters.
268+
"""
192269
# Determine parameter names from the schema
193270
param_names = []
194271
param_annotations = {}
@@ -235,6 +312,11 @@ def _create_tool_wrapper(self, tool):
235312

236313
# Create function template to be properly decorated
237314
def template_function(*args, **kwargs):
315+
"""
316+
Template function that accepts arbitrary arguments.
317+
318+
This placeholder function is intended for future extension and currently does not perform any operation; it always returns None.
319+
"""
238320
return None
239321

240322
# Create a proper function with the correct signature
@@ -248,6 +330,20 @@ def template_function(*args, **kwargs):
248330
@wraps(template_function)
249331
def wrapper(*args, **kwargs):
250332
# Map positional args to parameter names
333+
"""
334+
Invokes a tool using combined positional and keyword arguments.
335+
336+
This wrapper maps positional arguments to their corresponding expected
337+
parameter names, merges them with any keyword arguments, and delegates
338+
the call to the tool via the runner using the tool's name.
339+
340+
Args:
341+
*args: Positional values for the tool's parameters.
342+
**kwargs: Keyword arguments for the tool.
343+
344+
Returns:
345+
The result of executing the tool.
346+
"""
251347
all_args = {}
252348
for i, arg in enumerate(args):
253349
if i < len(param_names):
@@ -273,6 +369,11 @@ def __iter__(self) -> Iterable[Callable]:
273369
return iter(self._tools)
274370

275371
def __del__(self):
276-
"""Clean up resources when the object is garbage collected."""
372+
"""
373+
Clean up resources when the MCP instance is garbage collected.
374+
375+
If the instance has an associated runner, its shutdown method is called to halt
376+
ongoing operations and release allocated resources.
377+
"""
277378
if hasattr(self, 'runner'):
278379
self.runner.shutdown()

0 commit comments

Comments
Β (0)