29
29
LOG_DIR .mkdir (parents = True , exist_ok = True )
30
30
LOG_FILE = LOG_DIR / "mcp_plugin.log"
31
31
32
- # Configure root logger
33
- logging .basicConfig (
34
- level = logging .INFO ,
35
- format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" ,
36
- handlers = [
37
- logging .FileHandler (LOG_FILE ),
38
- logging .StreamHandler ()
39
- ]
40
- )
32
+ # Configure file logger with detailed formatting
33
+ file_handler = logging .FileHandler (LOG_FILE )
34
+ file_handler .setFormatter (logging .Formatter (
35
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
36
+ ))
37
+
38
+ # Configure console logger with simpler formatting
39
+ console_handler = logging .StreamHandler ()
40
+ console_handler .setFormatter (logging .Formatter (
41
+ '%(asctime)s - %(levelname)s - %(message)s'
42
+ ))
41
43
44
+ # Set up the logger
42
45
logger = logging .getLogger ("optillm.mcp_plugin" )
46
+ logger .setLevel (logging .DEBUG ) # Set to DEBUG for maximum detail
47
+ logger .addHandler (file_handler )
48
+ logger .addHandler (console_handler )
43
49
44
50
# Plugin identifier
45
51
SLUG = "mcp"
46
52
53
+ # Add custom logging for MCP communication
54
+ def log_mcp_message (direction : str , method : str , params : Any = None , result : Any = None , error : Any = None ):
55
+ """Log MCP communication in detail"""
56
+ message_parts = [f"MCP { direction } - Method: { method } " ]
57
+
58
+ if params :
59
+ try :
60
+ params_str = json .dumps (params , indent = 2 )
61
+ message_parts .append (f"Params: { params_str } " )
62
+ except :
63
+ message_parts .append (f"Params: { params } " )
64
+
65
+ if result :
66
+ try :
67
+ result_str = json .dumps (result , indent = 2 )
68
+ message_parts .append (f"Result: { result_str } " )
69
+ except :
70
+ message_parts .append (f"Result: { result } " )
71
+
72
+ if error :
73
+ message_parts .append (f"Error: { error } " )
74
+
75
+ logger .debug ("\n " .join (message_parts ))
76
+
47
77
def find_executable (cmd : str ) -> Optional [str ]:
48
78
"""
49
79
Find the full path to an executable command.
@@ -123,7 +153,9 @@ def load_config(self) -> bool:
123
153
return False
124
154
125
155
with open (self .config_path , 'r' ) as f :
126
- config = json .load (f )
156
+ config_data = f .read ()
157
+ logger .debug (f"Raw config data: { config_data } " )
158
+ config = json .loads (config_data )
127
159
128
160
# Set log level
129
161
self .log_level = config .get ("log_level" , "INFO" )
@@ -134,6 +166,7 @@ def load_config(self) -> bool:
134
166
servers_config = config .get ("mcpServers" , {})
135
167
for server_name , server_config in servers_config .items ():
136
168
self .servers [server_name ] = ServerConfig .from_dict (server_config )
169
+ logger .debug (f"Loaded server config for { server_name } : { server_config } " )
137
170
138
171
logger .info (f"Loaded configuration with { len (self .servers )} servers" )
139
172
return True
@@ -165,6 +198,36 @@ def create_default_config(self) -> bool:
165
198
logger .error (f"Error creating default configuration: { e } " )
166
199
return False
167
200
201
+ # Create a custom ClientSession that logs all communication
202
+ class LoggingClientSession (ClientSession ):
203
+ """A ClientSession that logs all communication"""
204
+
205
+ async def send_request (self , * args , ** kwargs ):
206
+ """Log and forward requests"""
207
+ method = args [0 ]
208
+ params = args [1 ] if len (args ) > 1 else None
209
+ log_mcp_message ("REQUEST" , method , params )
210
+
211
+ try :
212
+ result = await super ().send_request (* args , ** kwargs )
213
+ log_mcp_message ("RESPONSE" , method , result = result )
214
+ return result
215
+ except Exception as e :
216
+ log_mcp_message ("ERROR" , method , error = str (e ))
217
+ raise
218
+
219
+ async def send_notification (self , * args , ** kwargs ):
220
+ """Log and forward notifications"""
221
+ method = args [0 ]
222
+ params = args [1 ] if len (args ) > 1 else None
223
+ log_mcp_message ("NOTIFICATION" , method , params )
224
+
225
+ try :
226
+ await super ().send_notification (* args , ** kwargs )
227
+ except Exception as e :
228
+ log_mcp_message ("ERROR" , method , error = str (e ))
229
+ raise
230
+
168
231
class MCPServer :
169
232
"""Represents a connection to an MCP server"""
170
233
@@ -182,6 +245,7 @@ def __init__(self, server_name: str, config: ServerConfig):
182
245
async def connect_and_discover (self ) -> bool :
183
246
"""Connect to the server and discover capabilities using proper context management"""
184
247
logger .info (f"Connecting to MCP server: { self .server_name } " )
248
+ logger .debug (f"Server configuration: { vars (self .config )} " )
185
249
186
250
# Find the full path to the command
187
251
full_command = find_executable (self .config .command )
@@ -194,6 +258,10 @@ async def connect_and_discover(self) -> bool:
194
258
if self .config .env :
195
259
merged_env .update (self .config .env )
196
260
261
+ logger .debug (f"Using command: { full_command } " )
262
+ logger .debug (f"Arguments: { self .config .args } " )
263
+ logger .debug (f"Environment: { self .config .env } " )
264
+
197
265
# Create server parameters
198
266
server_params = StdioServerParameters (
199
267
command = full_command ,
@@ -207,7 +275,8 @@ async def connect_and_discover(self) -> bool:
207
275
full_command ,
208
276
* self .config .args ,
209
277
env = merged_env ,
210
- stderr = asyncio .subprocess .PIPE
278
+ stderr = asyncio .subprocess .PIPE ,
279
+ stdout = asyncio .subprocess .PIPE
211
280
)
212
281
213
282
# Log startup message from stderr
@@ -216,22 +285,39 @@ async def log_stderr():
216
285
line = await process .stderr .readline ()
217
286
if not line :
218
287
break
219
- logger .info (f"Server { self .server_name } stderr: { line .decode ().strip ()} " )
288
+ stderr_text = line .decode ().strip ()
289
+ logger .info (f"Server { self .server_name } stderr: { stderr_text } " )
290
+
291
+ # Log stdout too for debugging
292
+ async def log_stdout ():
293
+ while True :
294
+ line = await process .stdout .readline ()
295
+ if not line :
296
+ break
297
+ stdout_text = line .decode ().strip ()
298
+ logger .debug (f"Server { self .server_name } stdout: { stdout_text } " )
220
299
221
- # Start stderr logging task
300
+ # Start logging tasks
222
301
asyncio .create_task (log_stderr ())
302
+ asyncio .create_task (log_stdout ())
223
303
224
304
# Wait a bit for the server to start up
305
+ logger .debug (f"Waiting for server to start up..." )
225
306
await asyncio .sleep (2 )
226
307
227
308
# Use the MCP client with proper context management
309
+ logger .debug (f"Establishing MCP client connection to { self .server_name } " )
228
310
async with stdio_client (server_params ) as (read_stream , write_stream ):
229
- async with ClientSession (read_stream , write_stream ) as session :
311
+ logger .debug (f"Connection established, creating session" )
312
+ # Use our logging session instead of the regular one
313
+ async with LoggingClientSession (read_stream , write_stream ) as session :
230
314
logger .info (f"Connected to server: { self .server_name } " )
231
315
232
316
# Initialize session
317
+ logger .debug (f"Initializing MCP session for { self .server_name } " )
233
318
result = await session .initialize ()
234
319
logger .info (f"Server { self .server_name } initialized with capabilities: { result .capabilities } " )
320
+ logger .debug (f"Full initialization result: { result } " )
235
321
236
322
# Check which capabilities the server supports
237
323
server_capabilities = result .capabilities
@@ -244,6 +330,7 @@ async def log_stderr():
244
330
tools_result = await session .list_tools ()
245
331
self .tools = tools_result .tools
246
332
logger .info (f"Found { len (self .tools )} tools" )
333
+ logger .debug (f"Tools details: { [t .name for t in self .tools ]} " )
247
334
except McpError as e :
248
335
logger .warning (f"Failed to list tools: { e } " )
249
336
@@ -255,6 +342,7 @@ async def log_stderr():
255
342
resources_result = await session .list_resources ()
256
343
self .resources = resources_result .resources
257
344
logger .info (f"Found { len (self .resources )} resources" )
345
+ logger .debug (f"Resources details: { [r .uri for r in self .resources ]} " )
258
346
except McpError as e :
259
347
logger .warning (f"Failed to list resources: { e } " )
260
348
@@ -266,6 +354,7 @@ async def log_stderr():
266
354
prompts_result = await session .list_prompts ()
267
355
self .prompts = prompts_result .prompts
268
356
logger .info (f"Found { len (self .prompts )} prompts" )
357
+ logger .debug (f"Prompts details: { [p .name for p in self .prompts ]} " )
269
358
except McpError as e :
270
359
logger .warning (f"Failed to list prompts: { e } " )
271
360
@@ -304,38 +393,45 @@ async def initialize(self) -> bool:
304
393
self .servers [server_name ] = MCPServer (server_name , server_config )
305
394
306
395
# Connect to all servers and discover capabilities
396
+ connected_servers = 0
307
397
for server_name , server in self .servers .items ():
308
398
success = await server .connect_and_discover ()
309
399
if success :
400
+ connected_servers += 1
310
401
# Cache server capabilities
311
402
for tool in server .tools :
312
- self . all_tools . append ( {
403
+ tool_info = {
313
404
"server" : server_name ,
314
405
"name" : tool .name ,
315
406
"description" : tool .description ,
316
407
"input_schema" : tool .inputSchema
317
- })
408
+ }
409
+ self .all_tools .append (tool_info )
410
+ logger .debug (f"Cached tool: { tool_info } " )
318
411
319
412
for resource in server .resources :
320
- self . all_resources . append ( {
413
+ resource_info = {
321
414
"server" : server_name ,
322
415
"uri" : resource .uri ,
323
416
"name" : resource .name ,
324
417
"description" : resource .description
325
- })
418
+ }
419
+ self .all_resources .append (resource_info )
420
+ logger .debug (f"Cached resource: { resource_info } " )
326
421
327
422
for prompt in server .prompts :
328
- self . all_prompts . append ( {
423
+ prompt_info = {
329
424
"server" : server_name ,
330
425
"name" : prompt .name ,
331
426
"description" : prompt .description ,
332
427
"arguments" : prompt .arguments
333
- })
428
+ }
429
+ self .all_prompts .append (prompt_info )
430
+ logger .debug (f"Cached prompt: { prompt_info } " )
334
431
335
432
self .initialized = True
336
433
337
434
# Check if we successfully connected to any servers
338
- connected_servers = sum (1 for server in self .servers .values () if server .connected )
339
435
logger .info (f"Connected to { connected_servers } /{ len (self .servers )} MCP servers" )
340
436
return connected_servers > 0
341
437
@@ -357,6 +453,7 @@ def get_tools_for_model(self) -> List[Dict[str, Any]]:
357
453
}
358
454
}
359
455
tools .append (tool_entry )
456
+ logger .debug (f"Added tool for model: { tool_entry } " )
360
457
361
458
return tools
362
459
@@ -435,9 +532,21 @@ async def execute_tool(server_name: str, tool_name: str, arguments: Dict[str, An
435
532
)
436
533
437
534
try :
535
+ # Log the tool call in detail
536
+ logger .debug (f"Tool call details:" )
537
+ logger .debug (f" Server: { server_name } " )
538
+ logger .debug (f" Tool: { tool_name } " )
539
+ logger .debug (f" Arguments: { json .dumps (arguments , indent = 2 )} " )
540
+ logger .debug (f" Command: { full_command } " )
541
+ logger .debug (f" Args: { server_config .args } " )
542
+
438
543
# Use the MCP client with proper context management
439
544
async with stdio_client (server_params ) as (read_stream , write_stream ):
440
- async with ClientSession (read_stream , write_stream ) as session :
545
+ # Use our logging session
546
+ async with LoggingClientSession (read_stream , write_stream ) as session :
547
+ # Initialize the session
548
+ await session .initialize ()
549
+
441
550
# Call the tool and get the result
442
551
logger .info (f"Calling tool { tool_name } with arguments: { arguments } " )
443
552
result = await session .call_tool (tool_name , arguments )
@@ -450,12 +559,14 @@ async def execute_tool(server_name: str, tool_name: str, arguments: Dict[str, An
450
559
"type" : "text" ,
451
560
"text" : content .text
452
561
})
562
+ logger .debug (f"Tool result (text): { content .text [:100 ]} ..." )
453
563
elif content .type == "image" :
454
564
content_results .append ({
455
565
"type" : "image" ,
456
566
"data" : content .data ,
457
567
"mimeType" : content .mimeType
458
568
})
569
+ logger .debug (f"Tool result (image): { content .mimeType } " )
459
570
460
571
return {
461
572
"result" : content_results ,
@@ -481,6 +592,8 @@ async def run(system_prompt: str, initial_query: str, client, model: str) -> Tup
481
592
Tuple of (response text, token usage)
482
593
"""
483
594
logger .info (f"MCP Plugin run called with model: { model } " )
595
+ logger .debug (f"System prompt: { system_prompt [:100 ]} ..." )
596
+ logger .debug (f"Initial query: { initial_query } " )
484
597
485
598
try :
486
599
# Load configuration
@@ -534,6 +647,7 @@ async def run(system_prompt: str, initial_query: str, client, model: str) -> Tup
534
647
535
648
# Get capabilities description
536
649
capabilities_description = server_manager .get_capabilities_description ()
650
+ logger .debug (f"Capabilities description: { capabilities_description } " )
537
651
538
652
# Enhance system prompt with MCP capabilities
539
653
enhanced_system_prompt = f"{ system_prompt } \n \n You have access to the following MCP capabilities:\n \n { capabilities_description } "
@@ -553,10 +667,13 @@ async def run(system_prompt: str, initial_query: str, client, model: str) -> Tup
553
667
# Check if the model wants to use any tools
554
668
response_message = response .choices [0 ].message
555
669
response_content = response_message .content or ""
670
+ logger .debug (f"Initial model response: { response_content [:100 ]} ..." )
556
671
557
672
# Check for tool calls
558
673
if hasattr (response_message , "tool_calls" ) and response_message .tool_calls :
559
674
logger .info (f"Model requested tool calls: { len (response_message .tool_calls )} " )
675
+ for i , tc in enumerate (response_message .tool_calls ):
676
+ logger .debug (f"Tool call { i + 1 } : { tc .function .name } with args: { tc .function .arguments } " )
560
677
561
678
# Create new messages with the original system and user message
562
679
messages = [
@@ -587,6 +704,7 @@ async def run(system_prompt: str, initial_query: str, client, model: str) -> Tup
587
704
"tool_call_id" : tool_call_id ,
588
705
"content" : json .dumps (result )
589
706
})
707
+ logger .debug (f"Added tool result for { full_tool_name } : { json .dumps (result )[:100 ]} ..." )
590
708
except Exception as e :
591
709
logger .error (f"Error processing tool call { full_tool_name } : { e } " )
592
710
messages .append ({
@@ -614,10 +732,12 @@ async def run(system_prompt: str, initial_query: str, client, model: str) -> Tup
614
732
final_message = final_response .choices [0 ].message
615
733
response_text = final_message .content or ""
616
734
token_usage = final_response .usage .completion_tokens
735
+ logger .debug (f"Final model response: { response_text [:100 ]} ..." )
617
736
else :
618
737
# Model didn't call any tools, use its initial response
619
738
response_text = response_content
620
739
token_usage = response .usage .completion_tokens
740
+ logger .info ("Model did not request any tool calls" )
621
741
622
742
return response_text , token_usage
623
743
0 commit comments