1
+ import os
2
+ import asyncio
3
+ import json
4
+ import sys
5
+ from contextlib import AsyncExitStack
6
+ from typing import Any , Dict , List
7
+
8
+ # --- MCP Imports ---
9
+ try :
10
+ from mcp import ClientSession , StdioServerParameters
11
+ from mcp .client .stdio import stdio_client
12
+ MCP_AVAILABLE = True
13
+ except ImportError :
14
+ print ("FATAL: 'mcp' library is required. Install it (`pip install mcp`)." )
15
+ exit (1 )
16
+
17
+ # --- Synchronous MCP Tool Execution Helper (Uses asyncio.run) ---
18
+
19
+ async def _async_call_mcp_tool (
20
+ abs_server_path : str ,
21
+ tool_name : str ,
22
+ tool_args : Dict ,
23
+ debug : bool = True # Add debug flag back
24
+ ) -> Any :
25
+ if not MCP_AVAILABLE :
26
+ raise ImportError ("MCP library not installed." )
27
+
28
+ result_content : Any = {"error" : "MCP call failed to complete" } # Default error
29
+ server_name = os .path .basename (abs_server_path ) # For logging
30
+
31
+ # Define log helper inside
32
+ def _log (msg ):
33
+ if debug : print (f"[_async_call_mcp: { server_name } /{ tool_name } ] { msg } " )
34
+
35
+ _log (f"Attempting to connect to { abs_server_path } ..." )
36
+ command = "python" if abs_server_path .endswith ('.py' ) else "node"
37
+ server_params = StdioServerParameters (
38
+ command = command ,
39
+ args = [abs_server_path ],
40
+ env = os .environ .copy ()
41
+ )
42
+ timeout_seconds = 30.0
43
+
44
+ try :
45
+ async with AsyncExitStack () as stack :
46
+ _log (f"Awaiting connect_and_call() with timeout { timeout_seconds } s..." )
47
+ async def connect_and_call ():
48
+ nonlocal result_content
49
+ _log (f"Entering stdio_client context..." )
50
+ stdio_transport = await stack .enter_async_context (stdio_client (server_params ))
51
+ _log (f"Entering ClientSession context..." )
52
+ session = await stack .enter_async_context (ClientSession (* stdio_transport ))
53
+ _log (f"Awaiting session.initialize()..." )
54
+ await session .initialize ()
55
+ _log (f"Session initialized. Awaiting session.call_tool({ tool_name } , { tool_args } )..." )
56
+ call_result = await session .call_tool (tool_name , tool_args )
57
+ _log (f"session.call_tool completed. Raw result: { call_result } " ) # Log raw result
58
+
59
+ content = call_result .content
60
+ _log (f"Extracted content type: { type (content )} " )
61
+
62
+ # --- Corrected Content Handling ---
63
+ if isinstance (content , list ) and content and all (hasattr (item , 'text' ) for item in content ):
64
+ result_content = [item .text for item in content ]
65
+ _log (f"Processed list of TextContent: { result_content } " )
66
+ elif isinstance (content , list ) and len (content ) == 1 and hasattr (content [0 ], 'text' ):
67
+ result_content = content [0 ].text
68
+ _log (f"Processed single TextContent in list: { result_content !r} " )
69
+ elif hasattr (content , 'text' ):
70
+ result_content = content .text
71
+ _log (f"Processed direct TextContent: { result_content !r} " )
72
+ else :
73
+ result_content = content
74
+ _log (f"Using content directly (not TextContent): { str (result_content )[:200 ]} ..." )
75
+ # --- End Corrected Content Handling ---
76
+
77
+ await asyncio .wait_for (connect_and_call (), timeout = timeout_seconds )
78
+ _log (f"connect_and_call() finished successfully." )
79
+
80
+ except asyncio .TimeoutError :
81
+ _log (f"Timeout Error!" )
82
+ result_content = {"error" : f"Timeout executing MCP tool '{ tool_name } '" }
83
+ except Exception as e :
84
+ _log (f"Exception: { type (e ).__name__ } - { e } " )
85
+ # import traceback # Optional for more detail
86
+ # traceback.print_exc() # Optional
87
+ result_content = {"error" : f"Error executing MCP tool '{ tool_name } ': { type (e ).__name__ } - { e } " }
88
+
89
+ return result_content
90
+
91
+ # --- execute_mcp_tool_sync (Passes debug flag) ---
92
+ def execute_mcp_tool_sync (
93
+ server_path : str ,
94
+ tool_name : str ,
95
+ tool_args : Dict ,
96
+ debug : bool = True # Add debug flag
97
+ ) -> Any :
98
+ if not MCP_AVAILABLE :
99
+ return {"error" : "MCP library not installed." }
100
+
101
+ abs_server_path = os .path .abspath (server_path )
102
+ if not os .path .exists (abs_server_path ):
103
+ return {"error" : f"Server path not found: { abs_server_path } " }
104
+ if not (abs_server_path .endswith ('.py' ) or abs_server_path .endswith ('.js' )):
105
+ return {"error" : f"Server path must be .py or .js: { abs_server_path } " }
106
+
107
+ try :
108
+ asyncio .get_running_loop ()
109
+ if debug : print ("[execute_mcp_tool_sync] Error: Cannot run sync within active async loop." )
110
+ return {"error" : "Cannot run MCP tool sync within active async context." }
111
+ except RuntimeError :
112
+ pass # No loop running
113
+
114
+ if debug : print (f"[execute_mcp_tool_sync] Calling asyncio.run for { tool_name } on { os .path .basename (abs_server_path )} ..." )
115
+ try :
116
+ # Pass debug flag down
117
+ result = asyncio .run (_async_call_mcp_tool (abs_server_path , tool_name , tool_args , debug ))
118
+ if debug : print (f"[execute_mcp_tool_sync] asyncio.run completed." )
119
+ return result
120
+ except Exception as e :
121
+ if debug : print (f"[execute_mcp_tool_sync] Error during asyncio.run: { e } " )
122
+ return {"error" : f"Failed to run MCP tool '{ tool_name } ' synchronously: { e } " }
123
+
124
+
125
+ # --- Example Usage (Passes debug=True) ---
126
+ if __name__ == "__main__" :
127
+ if len (sys .argv ) != 4 :
128
+ print (f"Usage: python { sys .argv [0 ]} <mcp_server_script_path> <tool_name> '<json_arguments>'" )
129
+ print (f"Example using your server: python { sys .argv [0 ]} ../npcpy/work/mcp_server.py get_available_tables '{{\" db_path\" : \" ~/npcsh_history.db\" }}'" )
130
+ sys .exit (1 )
131
+
132
+ server_script_path = sys .argv [1 ]
133
+ tool_to_call = sys .argv [2 ]
134
+ args_json_string = sys .argv [3 ]
135
+
136
+ try :
137
+ tool_arguments = json .loads (args_json_string )
138
+ if not isinstance (tool_arguments , dict ):
139
+ raise ValueError ("Arguments must be a JSON object." )
140
+ except Exception as e :
141
+ print (f"Error parsing arguments JSON: { e } " )
142
+ sys .exit (1 )
143
+
144
+ print (f"--- Attempting Synchronous MCP Tool Call ---" )
145
+ print (f"Server: { server_script_path } " )
146
+ print (f"Tool: { tool_to_call } " )
147
+ print (f"Args: { tool_arguments } " )
148
+ print (f"---------------------------------------------" )
149
+
150
+ # Execute with debug=True to see internal logs
151
+ result = execute_mcp_tool_sync (server_script_path , tool_to_call , tool_arguments , debug = True )
152
+
153
+ # --- Result Printing (Remains the same) ---
154
+ print ("\n --- Result ---" )
155
+ print (f"Type: { type (result )} " )
156
+ print ("Content:" )
157
+ if isinstance (result , (dict , list )):
158
+ print (json .dumps (result , indent = 2 ))
159
+ elif isinstance (result , str ):
160
+ print (repr (result ))
161
+ else :
162
+ print (result )
0 commit comments