From 3f86bd40096586ac964086690b6a160516152d43 Mon Sep 17 00:00:00 2001 From: Richard Tang Date: Tue, 3 Mar 2026 20:39:04 -0800 Subject: [PATCH] chore: lint fix --- core/framework/graph/conversation.py | 11 +- core/framework/graph/event_loop_node.py | 68 +++++------ core/framework/graph/executor.py | 44 ++++--- core/framework/server/session_manager.py | 5 +- core/framework/tools/queen_lifecycle_tools.py | 11 +- core/tests/test_event_loop_node.py | 8 +- core/tests/test_node_conversation.py | 115 ++++++++++++------ examples/templates/email_reply_agent/agent.py | 6 +- .../templates/email_reply_agent/config.py | 4 +- .../email_reply_agent/tests/test_structure.py | 2 - tools/coder_tools_server.py | 18 +-- 11 files changed, 168 insertions(+), 124 deletions(-) diff --git a/core/framework/graph/conversation.py b/core/framework/graph/conversation.py index 4a7e1aa0..3f63daa6 100644 --- a/core/framework/graph/conversation.py +++ b/core/framework/graph/conversation.py @@ -214,9 +214,7 @@ def extract_tool_call_history(messages: list[Message], max_entries: int = 30) -> unique = list(dict.fromkeys(outputs_set)) parts.append("OUTPUTS SET: " + ", ".join(unique)) if errors: - parts.append( - "ERRORS (do NOT retry these):\n" + "\n".join(f" - {e}" for e in errors[:10]) - ) + parts.append("ERRORS (do NOT retry these):\n" + "\n".join(f" - {e}" for e in errors[:10])) return "\n\n".join(parts) @@ -809,8 +807,7 @@ class NodeConversation: if msg.role != "assistant" or not msg.tool_calls: continue has_protected = any( - tc.get("function", {}).get("name") == "set_output" - for tc in msg.tool_calls + tc.get("function", {}).get("name") == "set_output" for tc in msg.tool_calls ) tc_ids = {tc.get("id", "") for tc in msg.tool_calls} if has_protected: @@ -935,9 +932,7 @@ class NodeConversation: # messages, so we delete everything before the recent boundary and # rewrite only what we want to keep. if self._store: - recent_boundary = ( - recent_messages[0].seq if recent_messages else self._next_seq - ) + recent_boundary = recent_messages[0].seq if recent_messages else self._next_seq await self._store.delete_parts_before(recent_boundary) # Write the reference message await self._store.write_part(ref_msg.seq, ref_msg.to_storage_dict()) diff --git a/core/framework/graph/event_loop_node.py b/core/framework/graph/event_loop_node.py index 68c689c2..70ead812 100644 --- a/core/framework/graph/event_loop_node.py +++ b/core/framework/graph/event_loop_node.py @@ -576,8 +576,10 @@ class EventLoopNode(NodeProtocol): # 6b2. Dynamic tool refresh (mode switching) if ctx.dynamic_tools_provider is not None: _synthetic_names = { - "set_output", "ask_user", - "delegate_to_sub_agent", "report_to_parent", + "set_output", + "ask_user", + "delegate_to_sub_agent", + "report_to_parent", } synthetic = [t for t in tools if t.name in _synthetic_names] tools.clear() @@ -889,8 +891,7 @@ class EventLoopNode(NodeProtocol): # path but without failing (no outputs to demand). _consecutive_empty_turns += 1 logger.warning( - "[%s] iter=%d: empty response on node with no output_keys " - "(consecutive=%d)", + "[%s] iter=%d: empty response on node with no output_keys (consecutive=%d)", node_id, iteration, _consecutive_empty_turns, @@ -899,8 +900,7 @@ class EventLoopNode(NodeProtocol): # Persistent ghost — but since this is a forever-alive # node, block for user input instead of crashing. logger.warning( - "[%s] iter=%d: %d consecutive empty responses, " - "blocking for user input", + "[%s] iter=%d: %d consecutive empty responses, blocking for user input", node_id, iteration, _consecutive_empty_turns, @@ -3000,17 +3000,16 @@ class EventLoopNode(NodeProtocol): return # --- Step 3: LLM compaction at >95% (recursive binary-search) --- - if ( - conversation.usage_ratio() > self._LLM_COMPACT_THRESHOLD - and ctx.llm is not None - ): + if conversation.usage_ratio() > self._LLM_COMPACT_THRESHOLD and ctx.llm is not None: logger.info( "LLM compaction triggered (%.0f%% usage)", conversation.usage_ratio() * 100, ) try: summary = await self._llm_compact( - ctx, list(conversation.messages), accumulator, + ctx, + list(conversation.messages), + accumulator, ) await conversation.compact( summary, @@ -3031,7 +3030,9 @@ class EventLoopNode(NodeProtocol): ) summary = self._build_emergency_summary(ctx, accumulator, conversation) await conversation.compact( - summary, keep_recent=1, phase_graduated=phase_grad, + summary, + keep_recent=1, + phase_graduated=phase_grad, ) await self._log_compaction(ctx, conversation, ratio_before) @@ -3054,20 +3055,23 @@ class EventLoopNode(NodeProtocol): from framework.graph.conversation import extract_tool_call_history if _depth > self._LLM_COMPACT_MAX_DEPTH: - raise RuntimeError( - f"LLM compaction recursion limit ({self._LLM_COMPACT_MAX_DEPTH})" - ) + raise RuntimeError(f"LLM compaction recursion limit ({self._LLM_COMPACT_MAX_DEPTH})") formatted = self._format_messages_for_summary(messages) # Proactive split: avoid wasting an API call on oversized input if len(formatted) > self._LLM_COMPACT_CHAR_LIMIT and len(messages) > 1: summary = await self._llm_compact_split( - ctx, messages, accumulator, _depth, + ctx, + messages, + accumulator, + _depth, ) else: prompt = self._build_llm_compaction_prompt( - ctx, accumulator, formatted, + ctx, + accumulator, + formatted, ) summary_budget = max(1024, self._config.max_history_tokens // 2) try: @@ -3090,7 +3094,10 @@ class EventLoopNode(NodeProtocol): len(messages), ) summary = await self._llm_compact_split( - ctx, messages, accumulator, _depth, + ctx, + messages, + accumulator, + _depth, ) else: raise @@ -3114,7 +3121,10 @@ class EventLoopNode(NodeProtocol): mid = max(1, len(messages) // 2) s1 = await self._llm_compact(ctx, messages[:mid], None, _depth + 1) s2 = await self._llm_compact( - ctx, messages[mid:], accumulator, _depth + 1, + ctx, + messages[mid:], + accumulator, + _depth + 1, ) return s1 + "\n\n" + s2 @@ -3131,10 +3141,7 @@ class EventLoopNode(NodeProtocol): content += "..." lines.append(f"[tool result]: {content}") elif m.role == "assistant" and m.tool_calls: - names = [ - tc.get("function", {}).get("name", "?") - for tc in m.tool_calls - ] + names = [tc.get("function", {}).get("name", "?") for tc in m.tool_calls] text = m.content[:200] if m.content else "" lines.append(f"[assistant (calls: {', '.join(names)})]: {text}") else: @@ -3162,16 +3169,12 @@ class EventLoopNode(NodeProtocol): if done: ctx_lines.append( "OUTPUTS ALREADY SET:\n" - + "\n".join( - f" {k}: {str(v)[:150]}" for k, v in done.items() - ) + + "\n".join(f" {k}: {str(v)[:150]}" for k, v in done.items()) ) if todo: ctx_lines.append(f"OUTPUTS STILL NEEDED: {', '.join(todo)}") elif spec.output_keys: - ctx_lines.append( - f"OUTPUTS STILL NEEDED: {', '.join(spec.output_keys)}" - ) + ctx_lines.append(f"OUTPUTS STILL NEEDED: {', '.join(spec.output_keys)}") target_tokens = self._config.max_history_tokens // 2 target_chars = target_tokens * 4 @@ -3224,11 +3227,9 @@ class EventLoopNode(NodeProtocol): node_id=ctx.node_id, node_type="event_loop", step_index=-1, - llm_text=f"Context compacted ({level}): " - f"{before_pct}% \u2192 {after_pct}%", + llm_text=f"Context compacted ({level}): {before_pct}% \u2192 {after_pct}%", verdict="COMPACTION", - verdict_feedback=f"level={level} " - f"before={before_pct}% after={after_pct}%", + verdict_feedback=f"level={level} before={before_pct}% after={after_pct}%", ) if self._event_bus: @@ -3247,7 +3248,6 @@ class EventLoopNode(NodeProtocol): ) ) - def _build_emergency_summary( self, ctx: NodeContext, diff --git a/core/framework/graph/executor.py b/core/framework/graph/executor.py index a1d5847f..24669d55 100644 --- a/core/framework/graph/executor.py +++ b/core/framework/graph/executor.py @@ -161,7 +161,8 @@ class GraphExecutor: accounts_prompt: Connected accounts block for system prompt injection accounts_data: Raw account data for per-node prompt generation tool_provider_map: Tool name to provider name mapping for account routing - dynamic_tools_provider: Optional callback returning current tool list (for mode switching) + dynamic_tools_provider: Optional callback returning current + tool list (for mode switching) """ self.runtime = runtime self.llm = llm @@ -317,12 +318,11 @@ class GraphExecutor: c = m.content[:500] + ("..." if len(m.content) > 500 else "") lines.append(f"[tool result]: {c}") elif m.role == "assistant" and m.tool_calls: - names = [ - tc.get("function", {}).get("name", "?") - for tc in m.tool_calls - ] - lines.append(f"[assistant (calls: {', '.join(names)})]: " - f"{m.content[:200] if m.content else ''}") + names = [tc.get("function", {}).get("name", "?") for tc in m.tool_calls] + lines.append( + f"[assistant (calls: {', '.join(names)})]: " + f"{m.content[:200] if m.content else ''}" + ) else: lines.append(f"[{m.role}]: {m.content}") formatted = "\n\n".join(lines) @@ -330,7 +330,10 @@ class GraphExecutor: # Proactive split if len(formatted) > self._PHASE_LLM_CHAR_LIMIT and len(messages) > 1: summary = await self._phase_llm_compact_split( - conversation, next_spec, messages, _depth, + conversation, + next_spec, + messages, + _depth, ) else: max_tokens = getattr(conversation, "_max_history_tokens", 32000) @@ -367,7 +370,10 @@ class GraphExecutor: except Exception as e: if _is_context_too_large_error(e) and len(messages) > 1: summary = await self._phase_llm_compact_split( - conversation, next_spec, messages, _depth, + conversation, + next_spec, + messages, + _depth, ) else: raise @@ -390,10 +396,16 @@ class GraphExecutor: """Split messages in half and summarise each half.""" mid = max(1, len(messages) // 2) s1 = await self._phase_llm_compact( - conversation, next_spec, messages[:mid], _depth + 1, + conversation, + next_spec, + messages[:mid], + _depth + 1, ) s2 = await self._phase_llm_compact( - conversation, next_spec, messages[mid:], _depth + 1, + conversation, + next_spec, + messages[mid:], + _depth + 1, ) return s1 + "\n\n" + s2 @@ -1414,9 +1426,7 @@ class GraphExecutor: _phase_ratio * 100, ) _data_dir = ( - str(self._storage_path / "data") - if self._storage_path - else None + str(self._storage_path / "data") if self._storage_path else None ) # Step 1: Structural compaction (>=80%) if _data_dir: @@ -1440,8 +1450,7 @@ class GraphExecutor: and self._llm is not None ): self.logger.info( - " LLM phase-boundary compaction " - "(%.0f%% usage)", + " LLM phase-boundary compaction (%.0f%% usage)", continuous_conversation.usage_ratio() * 100, ) try: @@ -1457,7 +1466,8 @@ class GraphExecutor: ) except Exception as e: self.logger.warning( - " Phase LLM compaction failed: %s", e, + " Phase LLM compaction failed: %s", + e, ) # Step 3: Emergency (only if still over budget) diff --git a/core/framework/server/session_manager.py b/core/framework/server/session_manager.py index f6b93f25..844ef3f7 100644 --- a/core/framework/server/session_manager.py +++ b/core/framework/server/session_manager.py @@ -426,7 +426,10 @@ class SessionManager: logger.warning("Queen: MCP config failed to load", exc_info=True) # Mode state for building/running mode switching - from framework.tools.queen_lifecycle_tools import QueenModeState, register_queen_lifecycle_tools + from framework.tools.queen_lifecycle_tools import ( + QueenModeState, + register_queen_lifecycle_tools, + ) # Start in staging when the caller provided an agent, building otherwise. initial_mode = "staging" if worker_identity else "building" diff --git a/core/framework/tools/queen_lifecycle_tools.py b/core/framework/tools/queen_lifecycle_tools.py index 75dd1c7c..508f77e8 100644 --- a/core/framework/tools/queen_lifecycle_tools.py +++ b/core/framework/tools/queen_lifecycle_tools.py @@ -119,13 +119,15 @@ class QueenModeState: msg = ( "[MODE CHANGE] The user clicked Run in the UI. Switched to RUNNING mode. " "Worker is now executing. You have monitoring/lifecycle tools: " - + ", ".join(tool_names) + "." + + ", ".join(tool_names) + + "." ) else: msg = ( "[MODE CHANGE] Switched to RUNNING mode. " "Worker is executing. You now have monitoring/lifecycle tools: " - + ", ".join(tool_names) + "." + + ", ".join(tool_names) + + "." ) await self.inject_notification(msg) @@ -144,8 +146,9 @@ class QueenModeState: if self.inject_notification: if source == "frontend": msg = ( - "[MODE CHANGE] The user stopped the worker from the UI. Switched to STAGING mode. " - "Agent is still loaded. Available tools: " + ", ".join(tool_names) + "." + "[MODE CHANGE] The user stopped the worker from the UI. " + "Switched to STAGING mode. Agent is still loaded. " + "Available tools: " + ", ".join(tool_names) + "." ) elif source == "auto": msg = ( diff --git a/core/tests/test_event_loop_node.py b/core/tests/test_event_loop_node.py index 39416de8..2ec4b6ec 100644 --- a/core/tests/test_event_loop_node.py +++ b/core/tests/test_event_loop_node.py @@ -2069,9 +2069,7 @@ class TestSubagentAccumulatorMemory: subagent_input_keys = ["tweet_content"] read_keys = set(parent_data.keys()) | set(subagent_input_keys) - scoped = subagent_memory.with_permissions( - read_keys=list(read_keys), write_keys=[] - ) + scoped = subagent_memory.with_permissions(read_keys=list(read_keys), write_keys=[]) # This would have raised PermissionError before the fix assert scoped.read("tweet_content") == "Hello world!" @@ -2090,9 +2088,7 @@ class TestSubagentAccumulatorMemory: # input_keys includes "tweet_content" which isn't in parent_data read_keys = set(parent_data.keys()) | {"tweet_content"} - scoped = subagent_memory.with_permissions( - read_keys=list(read_keys), write_keys=[] - ) + scoped = subagent_memory.with_permissions(read_keys=list(read_keys), write_keys=[]) # Should return None (not raise PermissionError) assert scoped.read("tweet_content") is None diff --git a/core/tests/test_node_conversation.py b/core/tests/test_node_conversation.py index a4daed55..e5c9117c 100644 --- a/core/tests/test_node_conversation.py +++ b/core/tests/test_node_conversation.py @@ -960,17 +960,25 @@ async def _build_tool_heavy_conversation( for i in range(5): args = {"filename": "output.html", "content": "x" * 500} tc = [_make_tool_call(f"call_{i}", "append_data", args)] - conv._messages.append(Message( - seq=conv._next_seq, role="assistant", - content=f"Appending part {i}", tool_calls=tc, - )) + conv._messages.append( + Message( + seq=conv._next_seq, + role="assistant", + content=f"Appending part {i}", + tool_calls=tc, + ) + ) if store: await store.write_part(conv._next_seq, conv._messages[-1].to_storage_dict()) conv._next_seq += 1 - conv._messages.append(Message( - seq=conv._next_seq, role="tool", - content='{"success": true}', tool_use_id=f"call_{i}", - )) + conv._messages.append( + Message( + seq=conv._next_seq, + role="tool", + content='{"success": true}', + tool_use_id=f"call_{i}", + ) + ) if store: await store.write_part(conv._next_seq, conv._messages[-1].to_storage_dict()) conv._next_seq += 1 @@ -983,11 +991,14 @@ async def _build_tool_heavy_conversation( if store: await store.write_part(conv._next_seq, conv._messages[-1].to_storage_dict()) conv._next_seq += 1 - conv._messages.append(Message( - seq=conv._next_seq, role="tool", - content="Output 'result' set successfully.", - tool_use_id="call_so", - )) + conv._messages.append( + Message( + seq=conv._next_seq, + role="tool", + content="Output 'result' set successfully.", + tool_use_id="call_so", + ) + ) if store: await store.write_part(conv._next_seq, conv._messages[-1].to_storage_dict()) conv._next_seq += 1 @@ -1011,7 +1022,9 @@ class TestAggressiveStructuralCompaction: spill = str(tmp_path) await conv.compact_preserving_structure( - spillover_dir=spill, keep_recent=2, aggressive=True, + spillover_dir=spill, + keep_recent=2, + aggressive=True, ) # The 5 append_data pairs (10 msgs) + 1 user msg should be collapsed. @@ -1038,7 +1051,9 @@ class TestAggressiveStructuralCompaction: spill = str(tmp_path) await conv.compact_preserving_structure( - spillover_dir=spill, keep_recent=2, aggressive=True, + spillover_dir=spill, + keep_recent=2, + aggressive=True, ) # Find all tool calls in remaining messages @@ -1077,8 +1092,11 @@ class TestAggressiveStructuralCompaction: conv._next_seq += 1 conv._messages.append( Message( - seq=conv._next_seq, role="tool", content="Connection timeout", - tool_use_id="call_err", is_error=True, + seq=conv._next_seq, + role="tool", + content="Connection timeout", + tool_use_id="call_err", + is_error=True, ) ) conv._next_seq += 1 @@ -1088,7 +1106,9 @@ class TestAggressiveStructuralCompaction: spill = str(tmp_path) await conv.compact_preserving_structure( - spillover_dir=spill, keep_recent=2, aggressive=True, + spillover_dir=spill, + keep_recent=2, + aggressive=True, ) # Error pair should be preserved @@ -1103,7 +1123,9 @@ class TestAggressiveStructuralCompaction: spill = str(tmp_path) await conv.compact_preserving_structure( - spillover_dir=spill, keep_recent=2, aggressive=False, + spillover_dir=spill, + keep_recent=2, + aggressive=False, ) # All 6 tool pairs (12 msgs) should be kept as structural. @@ -1118,14 +1140,17 @@ class TestAggressiveStructuralCompaction: # Pass 1: standard await conv.compact_preserving_structure( - spillover_dir=spill, keep_recent=2, + spillover_dir=spill, + keep_recent=2, ) after_standard = conv.message_count assert after_standard == 15 # all structural kept # Pass 2: aggressive await conv.compact_preserving_structure( - spillover_dir=spill, keep_recent=2, aggressive=True, + spillover_dir=spill, + keep_recent=2, + aggressive=True, ) after_aggressive = conv.message_count assert after_aggressive < after_standard @@ -1140,7 +1165,9 @@ class TestAggressiveStructuralCompaction: spill = str(tmp_path) await conv.compact_preserving_structure( - spillover_dir=spill, keep_recent=2, aggressive=True, + spillover_dir=spill, + keep_recent=2, + aggressive=True, ) # Verify store state matches in-memory state @@ -1151,13 +1178,25 @@ class TestAggressiveStructuralCompaction: class TestExtractToolCallHistory: def test_basic_extraction(self): msgs = [ - Message(seq=0, role="assistant", content="", tool_calls=[ - _make_tool_call("c1", "web_search", {"query": "python async"}), - ]), + Message( + seq=0, + role="assistant", + content="", + tool_calls=[ + _make_tool_call("c1", "web_search", {"query": "python async"}), + ], + ), Message(seq=1, role="tool", content="results", tool_use_id="c1"), - Message(seq=2, role="assistant", content="", tool_calls=[ - _make_tool_call("c2", "save_data", {"filename": "output.txt", "content": "data"}), - ]), + Message( + seq=2, + role="assistant", + content="", + tool_calls=[ + _make_tool_call( + "c2", "save_data", {"filename": "output.txt", "content": "data"} + ), + ], + ), Message(seq=3, role="tool", content="saved", tool_use_id="c2"), ] result = extract_tool_call_history(msgs) @@ -1168,8 +1207,11 @@ class TestExtractToolCallHistory: def test_errors_included(self): msgs = [ Message( - seq=0, role="tool", content="Connection refused", - is_error=True, tool_use_id="c1", + seq=0, + role="tool", + content="Connection refused", + is_error=True, + tool_use_id="c1", ), ] result = extract_tool_call_history(msgs) @@ -1197,9 +1239,7 @@ class TestIsContextTooLargeError: def test_openai_context_length(self): from framework.graph.event_loop_node import _is_context_too_large_error - err = RuntimeError( - "This model's maximum context length is 128000 tokens" - ) + err = RuntimeError("This model's maximum context length is 128000 tokens") assert _is_context_too_large_error(err) def test_anthropic_too_long(self): @@ -1352,9 +1392,7 @@ class TestLlmCompact: # First call with full messages → fail # Subsequent calls with smaller chunks → succeed if call_count == 1: - raise RuntimeError( - "This model's maximum context length is 128000 tokens" - ) + raise RuntimeError("This model's maximum context length is 128000 tokens") resp = MagicMock() resp.content = f"Summary part {call_count}" return resp @@ -1362,10 +1400,7 @@ class TestLlmCompact: ctx = self._make_ctx() ctx.llm.acomplete = mock_acomplete - msgs = [ - Message(seq=i, role="user", content=f"Message {i}") - for i in range(10) - ] + msgs = [Message(seq=i, role="user", content=f"Message {i}") for i in range(10)] result = await node._llm_compact(ctx, msgs, None) # Should have split and produced two summaries assert "Summary part" in result diff --git a/examples/templates/email_reply_agent/agent.py b/examples/templates/email_reply_agent/agent.py index 19eef166..f4cef4b9 100644 --- a/examples/templates/email_reply_agent/agent.py +++ b/examples/templates/email_reply_agent/agent.py @@ -8,7 +8,7 @@ from framework.graph.executor import ExecutionResult from framework.graph.checkpoint_config import CheckpointConfig from framework.llm import LiteLLMProvider from framework.runner.tool_registry import ToolRegistry -from framework.runtime.agent_runtime import AgentRuntime, create_agent_runtime +from framework.runtime.agent_runtime import create_agent_runtime from framework.runtime.execution_stream import EntryPointSpec from .config import default_config, metadata @@ -251,9 +251,7 @@ class EmailReplyAgent: errors.append(f"Terminal node '{t}' not found") for ep_id, nid in self.entry_points.items(): if nid not in node_ids: - errors.append( - f"Entry point '{ep_id}' references unknown node '{nid}'" - ) + errors.append(f"Entry point '{ep_id}' references unknown node '{nid}'") return {"valid": len(errors) == 0, "errors": errors, "warnings": warnings} diff --git a/examples/templates/email_reply_agent/config.py b/examples/templates/email_reply_agent/config.py index cd822134..a637b52b 100644 --- a/examples/templates/email_reply_agent/config.py +++ b/examples/templates/email_reply_agent/config.py @@ -36,7 +36,9 @@ default_config = RuntimeConfig() class AgentMetadata: name: str = "Email Reply Agent" version: str = "1.0.0" - description: str = "Filter unreplied emails, confirm recipients, send personalized replies." + description: str = ( + "Filter unreplied emails, confirm recipients, send personalized replies." + ) intro_message: str = "Tell me which emails you want to reply to (e.g., 'emails from @company.com in the last week')." diff --git a/examples/templates/email_reply_agent/tests/test_structure.py b/examples/templates/email_reply_agent/tests/test_structure.py index aaac7780..ec5f05c9 100644 --- a/examples/templates/email_reply_agent/tests/test_structure.py +++ b/examples/templates/email_reply_agent/tests/test_structure.py @@ -1,7 +1,5 @@ """Structural tests for Email Reply Agent.""" -import pytest - class TestAgentStructure: """Test agent graph structure.""" diff --git a/tools/coder_tools_server.py b/tools/coder_tools_server.py index cbc326b2..22e20160 100644 --- a/tools/coder_tools_server.py +++ b/tools/coder_tools_server.py @@ -271,7 +271,9 @@ def list_agent_tools( JSON with tools grouped by prefix (e.g. gmail_*, slack_*). """ if output_schema not in ("simple", "full"): - return json.dumps({"error": f"Invalid output_schema: {output_schema!r}. Use 'simple' or 'full'."}) + return json.dumps( + {"error": f"Invalid output_schema: {output_schema!r}. Use 'simple' or 'full'."} + ) # Resolve config path if not server_config_path: @@ -324,12 +326,14 @@ def list_agent_tools( client = MCPClient(config) client.connect() for tool in client.list_tools(): - all_tools.append({ - "server": server_name, - "name": tool.name, - "description": tool.description, - "input_schema": tool.input_schema, - }) + all_tools.append( + { + "server": server_name, + "name": tool.name, + "description": tool.description, + "input_schema": tool.input_schema, + } + ) client.disconnect() except Exception as e: errors.append({"server": server_name, "error": str(e)})