Skip to content

Commit 1dc8687

Browse files
authored
Merge pull request #174 from codelion/fix-logging-mcp-plugin
Update mcp_plugin.py
2 parents 3b65f4c + 3add700 commit 1dc8687

File tree

3 files changed

+144
-24
lines changed

3 files changed

+144
-24
lines changed

optillm/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33

44
# Version information
5-
__version__ = "0.1.7"
5+
__version__ = "0.1.8"
66

77
# Get the path to the root optillm.py
88
spec = util.spec_from_file_location(

optillm/plugins/mcp_plugin.py

+142-22
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,51 @@
2929
LOG_DIR.mkdir(parents=True, exist_ok=True)
3030
LOG_FILE = LOG_DIR / "mcp_plugin.log"
3131

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+
))
4143

44+
# Set up the logger
4245
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)
4349

4450
# Plugin identifier
4551
SLUG = "mcp"
4652

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+
4777
def find_executable(cmd: str) -> Optional[str]:
4878
"""
4979
Find the full path to an executable command.
@@ -123,7 +153,9 @@ def load_config(self) -> bool:
123153
return False
124154

125155
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)
127159

128160
# Set log level
129161
self.log_level = config.get("log_level", "INFO")
@@ -134,6 +166,7 @@ def load_config(self) -> bool:
134166
servers_config = config.get("mcpServers", {})
135167
for server_name, server_config in servers_config.items():
136168
self.servers[server_name] = ServerConfig.from_dict(server_config)
169+
logger.debug(f"Loaded server config for {server_name}: {server_config}")
137170

138171
logger.info(f"Loaded configuration with {len(self.servers)} servers")
139172
return True
@@ -165,6 +198,36 @@ def create_default_config(self) -> bool:
165198
logger.error(f"Error creating default configuration: {e}")
166199
return False
167200

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+
168231
class MCPServer:
169232
"""Represents a connection to an MCP server"""
170233

@@ -182,6 +245,7 @@ def __init__(self, server_name: str, config: ServerConfig):
182245
async def connect_and_discover(self) -> bool:
183246
"""Connect to the server and discover capabilities using proper context management"""
184247
logger.info(f"Connecting to MCP server: {self.server_name}")
248+
logger.debug(f"Server configuration: {vars(self.config)}")
185249

186250
# Find the full path to the command
187251
full_command = find_executable(self.config.command)
@@ -194,6 +258,10 @@ async def connect_and_discover(self) -> bool:
194258
if self.config.env:
195259
merged_env.update(self.config.env)
196260

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+
197265
# Create server parameters
198266
server_params = StdioServerParameters(
199267
command=full_command,
@@ -207,7 +275,8 @@ async def connect_and_discover(self) -> bool:
207275
full_command,
208276
*self.config.args,
209277
env=merged_env,
210-
stderr=asyncio.subprocess.PIPE
278+
stderr=asyncio.subprocess.PIPE,
279+
stdout=asyncio.subprocess.PIPE
211280
)
212281

213282
# Log startup message from stderr
@@ -216,22 +285,39 @@ async def log_stderr():
216285
line = await process.stderr.readline()
217286
if not line:
218287
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}")
220299

221-
# Start stderr logging task
300+
# Start logging tasks
222301
asyncio.create_task(log_stderr())
302+
asyncio.create_task(log_stdout())
223303

224304
# Wait a bit for the server to start up
305+
logger.debug(f"Waiting for server to start up...")
225306
await asyncio.sleep(2)
226307

227308
# Use the MCP client with proper context management
309+
logger.debug(f"Establishing MCP client connection to {self.server_name}")
228310
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:
230314
logger.info(f"Connected to server: {self.server_name}")
231315

232316
# Initialize session
317+
logger.debug(f"Initializing MCP session for {self.server_name}")
233318
result = await session.initialize()
234319
logger.info(f"Server {self.server_name} initialized with capabilities: {result.capabilities}")
320+
logger.debug(f"Full initialization result: {result}")
235321

236322
# Check which capabilities the server supports
237323
server_capabilities = result.capabilities
@@ -244,6 +330,7 @@ async def log_stderr():
244330
tools_result = await session.list_tools()
245331
self.tools = tools_result.tools
246332
logger.info(f"Found {len(self.tools)} tools")
333+
logger.debug(f"Tools details: {[t.name for t in self.tools]}")
247334
except McpError as e:
248335
logger.warning(f"Failed to list tools: {e}")
249336

@@ -255,6 +342,7 @@ async def log_stderr():
255342
resources_result = await session.list_resources()
256343
self.resources = resources_result.resources
257344
logger.info(f"Found {len(self.resources)} resources")
345+
logger.debug(f"Resources details: {[r.uri for r in self.resources]}")
258346
except McpError as e:
259347
logger.warning(f"Failed to list resources: {e}")
260348

@@ -266,6 +354,7 @@ async def log_stderr():
266354
prompts_result = await session.list_prompts()
267355
self.prompts = prompts_result.prompts
268356
logger.info(f"Found {len(self.prompts)} prompts")
357+
logger.debug(f"Prompts details: {[p.name for p in self.prompts]}")
269358
except McpError as e:
270359
logger.warning(f"Failed to list prompts: {e}")
271360

@@ -304,38 +393,45 @@ async def initialize(self) -> bool:
304393
self.servers[server_name] = MCPServer(server_name, server_config)
305394

306395
# Connect to all servers and discover capabilities
396+
connected_servers = 0
307397
for server_name, server in self.servers.items():
308398
success = await server.connect_and_discover()
309399
if success:
400+
connected_servers += 1
310401
# Cache server capabilities
311402
for tool in server.tools:
312-
self.all_tools.append({
403+
tool_info = {
313404
"server": server_name,
314405
"name": tool.name,
315406
"description": tool.description,
316407
"input_schema": tool.inputSchema
317-
})
408+
}
409+
self.all_tools.append(tool_info)
410+
logger.debug(f"Cached tool: {tool_info}")
318411

319412
for resource in server.resources:
320-
self.all_resources.append({
413+
resource_info = {
321414
"server": server_name,
322415
"uri": resource.uri,
323416
"name": resource.name,
324417
"description": resource.description
325-
})
418+
}
419+
self.all_resources.append(resource_info)
420+
logger.debug(f"Cached resource: {resource_info}")
326421

327422
for prompt in server.prompts:
328-
self.all_prompts.append({
423+
prompt_info = {
329424
"server": server_name,
330425
"name": prompt.name,
331426
"description": prompt.description,
332427
"arguments": prompt.arguments
333-
})
428+
}
429+
self.all_prompts.append(prompt_info)
430+
logger.debug(f"Cached prompt: {prompt_info}")
334431

335432
self.initialized = True
336433

337434
# Check if we successfully connected to any servers
338-
connected_servers = sum(1 for server in self.servers.values() if server.connected)
339435
logger.info(f"Connected to {connected_servers}/{len(self.servers)} MCP servers")
340436
return connected_servers > 0
341437

@@ -357,6 +453,7 @@ def get_tools_for_model(self) -> List[Dict[str, Any]]:
357453
}
358454
}
359455
tools.append(tool_entry)
456+
logger.debug(f"Added tool for model: {tool_entry}")
360457

361458
return tools
362459

@@ -435,9 +532,21 @@ async def execute_tool(server_name: str, tool_name: str, arguments: Dict[str, An
435532
)
436533

437534
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+
438543
# Use the MCP client with proper context management
439544
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+
441550
# Call the tool and get the result
442551
logger.info(f"Calling tool {tool_name} with arguments: {arguments}")
443552
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
450559
"type": "text",
451560
"text": content.text
452561
})
562+
logger.debug(f"Tool result (text): {content.text[:100]}...")
453563
elif content.type == "image":
454564
content_results.append({
455565
"type": "image",
456566
"data": content.data,
457567
"mimeType": content.mimeType
458568
})
569+
logger.debug(f"Tool result (image): {content.mimeType}")
459570

460571
return {
461572
"result": content_results,
@@ -481,6 +592,8 @@ async def run(system_prompt: str, initial_query: str, client, model: str) -> Tup
481592
Tuple of (response text, token usage)
482593
"""
483594
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}")
484597

485598
try:
486599
# Load configuration
@@ -534,6 +647,7 @@ async def run(system_prompt: str, initial_query: str, client, model: str) -> Tup
534647

535648
# Get capabilities description
536649
capabilities_description = server_manager.get_capabilities_description()
650+
logger.debug(f"Capabilities description: {capabilities_description}")
537651

538652
# Enhance system prompt with MCP capabilities
539653
enhanced_system_prompt = f"{system_prompt}\n\nYou 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
553667
# Check if the model wants to use any tools
554668
response_message = response.choices[0].message
555669
response_content = response_message.content or ""
670+
logger.debug(f"Initial model response: {response_content[:100]}...")
556671

557672
# Check for tool calls
558673
if hasattr(response_message, "tool_calls") and response_message.tool_calls:
559674
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}")
560677

561678
# Create new messages with the original system and user message
562679
messages = [
@@ -587,6 +704,7 @@ async def run(system_prompt: str, initial_query: str, client, model: str) -> Tup
587704
"tool_call_id": tool_call_id,
588705
"content": json.dumps(result)
589706
})
707+
logger.debug(f"Added tool result for {full_tool_name}: {json.dumps(result)[:100]}...")
590708
except Exception as e:
591709
logger.error(f"Error processing tool call {full_tool_name}: {e}")
592710
messages.append({
@@ -614,10 +732,12 @@ async def run(system_prompt: str, initial_query: str, client, model: str) -> Tup
614732
final_message = final_response.choices[0].message
615733
response_text = final_message.content or ""
616734
token_usage = final_response.usage.completion_tokens
735+
logger.debug(f"Final model response: {response_text[:100]}...")
617736
else:
618737
# Model didn't call any tools, use its initial response
619738
response_text = response_content
620739
token_usage = response.usage.completion_tokens
740+
logger.info("Model did not request any tool calls")
621741

622742
return response_text, token_usage
623743

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
setup(
55
name="optillm",
6-
version="0.1.7",
6+
version="0.1.8",
77
packages=find_packages(include=['optillm', 'optillm.*']), # This ensures all subpackages are included
88
py_modules=['optillm'],
99
package_data={

0 commit comments

Comments
 (0)