hanzo-mcp 0.6.13__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hanzo-mcp might be problematic. Click here for more details.

Files changed (62) hide show
  1. hanzo_mcp/analytics/__init__.py +5 -0
  2. hanzo_mcp/analytics/posthog_analytics.py +364 -0
  3. hanzo_mcp/cli.py +3 -3
  4. hanzo_mcp/cli_enhanced.py +3 -3
  5. hanzo_mcp/config/settings.py +1 -1
  6. hanzo_mcp/config/tool_config.py +18 -4
  7. hanzo_mcp/server.py +34 -1
  8. hanzo_mcp/tools/__init__.py +65 -2
  9. hanzo_mcp/tools/agent/__init__.py +84 -3
  10. hanzo_mcp/tools/agent/agent_tool.py +102 -4
  11. hanzo_mcp/tools/agent/agent_tool_v2.py +459 -0
  12. hanzo_mcp/tools/agent/clarification_protocol.py +220 -0
  13. hanzo_mcp/tools/agent/clarification_tool.py +68 -0
  14. hanzo_mcp/tools/agent/claude_cli_tool.py +125 -0
  15. hanzo_mcp/tools/agent/claude_desktop_auth.py +508 -0
  16. hanzo_mcp/tools/agent/cli_agent_base.py +191 -0
  17. hanzo_mcp/tools/agent/code_auth.py +436 -0
  18. hanzo_mcp/tools/agent/code_auth_tool.py +194 -0
  19. hanzo_mcp/tools/agent/codex_cli_tool.py +123 -0
  20. hanzo_mcp/tools/agent/critic_tool.py +376 -0
  21. hanzo_mcp/tools/agent/gemini_cli_tool.py +128 -0
  22. hanzo_mcp/tools/agent/grok_cli_tool.py +128 -0
  23. hanzo_mcp/tools/agent/iching_tool.py +380 -0
  24. hanzo_mcp/tools/agent/network_tool.py +273 -0
  25. hanzo_mcp/tools/agent/prompt.py +62 -20
  26. hanzo_mcp/tools/agent/review_tool.py +433 -0
  27. hanzo_mcp/tools/agent/swarm_tool.py +535 -0
  28. hanzo_mcp/tools/agent/swarm_tool_v2.py +594 -0
  29. hanzo_mcp/tools/common/base.py +1 -0
  30. hanzo_mcp/tools/common/batch_tool.py +102 -10
  31. hanzo_mcp/tools/common/fastmcp_pagination.py +369 -0
  32. hanzo_mcp/tools/common/forgiving_edit.py +243 -0
  33. hanzo_mcp/tools/common/paginated_base.py +230 -0
  34. hanzo_mcp/tools/common/paginated_response.py +307 -0
  35. hanzo_mcp/tools/common/pagination.py +226 -0
  36. hanzo_mcp/tools/common/tool_list.py +3 -0
  37. hanzo_mcp/tools/common/truncate.py +101 -0
  38. hanzo_mcp/tools/filesystem/__init__.py +29 -0
  39. hanzo_mcp/tools/filesystem/ast_multi_edit.py +562 -0
  40. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +338 -0
  41. hanzo_mcp/tools/lsp/__init__.py +5 -0
  42. hanzo_mcp/tools/lsp/lsp_tool.py +512 -0
  43. hanzo_mcp/tools/memory/__init__.py +76 -0
  44. hanzo_mcp/tools/memory/knowledge_tools.py +518 -0
  45. hanzo_mcp/tools/memory/memory_tools.py +456 -0
  46. hanzo_mcp/tools/search/__init__.py +6 -0
  47. hanzo_mcp/tools/search/find_tool.py +581 -0
  48. hanzo_mcp/tools/search/unified_search.py +953 -0
  49. hanzo_mcp/tools/shell/__init__.py +5 -0
  50. hanzo_mcp/tools/shell/auto_background.py +203 -0
  51. hanzo_mcp/tools/shell/base_process.py +53 -27
  52. hanzo_mcp/tools/shell/bash_tool.py +17 -33
  53. hanzo_mcp/tools/shell/npx_tool.py +15 -32
  54. hanzo_mcp/tools/shell/streaming_command.py +594 -0
  55. hanzo_mcp/tools/shell/uvx_tool.py +15 -32
  56. hanzo_mcp/types.py +23 -0
  57. {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/METADATA +228 -71
  58. {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/RECORD +61 -24
  59. hanzo_mcp-0.6.13.dist-info/licenses/LICENSE +0 -21
  60. {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/WHEEL +0 -0
  61. {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/entry_points.txt +0 -0
  62. {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,594 @@
1
+ """Swarm tool implementation using hanzo-agents SDK.
2
+
3
+ This module implements the SwarmTool that leverages the hanzo-agents SDK
4
+ for sophisticated multi-agent orchestration with flexible network topologies.
5
+ """
6
+
7
+ import asyncio
8
+ import json
9
+ import os
10
+ from typing import Annotated, Any, TypedDict, Unpack, final, override, Optional, Dict, List
11
+ from pathlib import Path
12
+
13
+ from mcp.server import FastMCP
14
+ from mcp.server.fastmcp import Context as MCPContext
15
+ from pydantic import Field
16
+
17
+ # Import hanzo-agents SDK
18
+ try:
19
+ from hanzo_agents import (
20
+ Agent, State, Network, Tool, History,
21
+ ModelRegistry, InferenceResult, ToolCall,
22
+ Router, DeterministicRouter, LLMRouter, HybridRouter,
23
+ create_memory_kv, create_memory_vector,
24
+ sequential_router, conditional_router, state_based_router,
25
+ )
26
+ from hanzo_agents.core.cli_agent import (
27
+ ClaudeCodeAgent, OpenAICodexAgent,
28
+ GeminiAgent, GrokAgent
29
+ )
30
+ HANZO_AGENTS_AVAILABLE = True
31
+ except ImportError:
32
+ HANZO_AGENTS_AVAILABLE = False
33
+
34
+ from hanzo_mcp.tools.agent.agent_tool_v2 import MCPAgent, MCPToolAdapter
35
+ from hanzo_mcp.tools.common.base import BaseTool
36
+ from hanzo_mcp.tools.common.context import create_tool_context
37
+ from hanzo_mcp.tools.common.permissions import PermissionManager
38
+ from hanzo_mcp.tools.filesystem import get_read_only_filesystem_tools, Edit, MultiEdit
39
+ from hanzo_mcp.tools.jupyter import get_read_only_jupyter_tools
40
+ from hanzo_mcp.tools.common.batch_tool import BatchTool
41
+
42
+
43
+ class AgentNode(TypedDict):
44
+ """Node in the agent network."""
45
+ id: str
46
+ query: str
47
+ model: Optional[str]
48
+ role: Optional[str]
49
+ connections: Optional[List[str]]
50
+ receives_from: Optional[List[str]]
51
+ file_path: Optional[str]
52
+
53
+
54
+ class SwarmConfig(TypedDict):
55
+ """Configuration for an agent network."""
56
+ agents: Dict[str, AgentNode]
57
+ entry_point: Optional[str]
58
+ topology: Optional[str]
59
+
60
+
61
+ class SwarmToolParams(TypedDict):
62
+ """Parameters for the SwarmTool."""
63
+ config: SwarmConfig
64
+ query: str
65
+ context: Optional[str]
66
+ max_concurrent: Optional[int]
67
+ use_memory: Optional[bool]
68
+ memory_backend: Optional[str]
69
+
70
+
71
+ class SwarmState(State):
72
+ """State for swarm execution."""
73
+
74
+ def __init__(self,
75
+ config: SwarmConfig,
76
+ initial_query: str,
77
+ context: Optional[str] = None):
78
+ """Initialize swarm state."""
79
+ super().__init__()
80
+ self.config = config
81
+ self.initial_query = initial_query
82
+ self.context = context
83
+ self.agent_results = {}
84
+ self.completed_agents = set()
85
+ self.current_agent = None
86
+ self.execution_order = []
87
+
88
+ def to_dict(self) -> Dict[str, Any]:
89
+ """Convert to dictionary."""
90
+ base_dict = super().to_dict()
91
+ base_dict.update({
92
+ "config": self.config,
93
+ "initial_query": self.initial_query,
94
+ "context": self.context,
95
+ "agent_results": self.agent_results,
96
+ "completed_agents": list(self.completed_agents),
97
+ "current_agent": self.current_agent,
98
+ "execution_order": self.execution_order
99
+ })
100
+ return base_dict
101
+
102
+ @classmethod
103
+ def from_dict(cls, data: Dict[str, Any]) -> "SwarmState":
104
+ """Create from dictionary."""
105
+ state = cls(
106
+ config=data.get("config", {}),
107
+ initial_query=data.get("initial_query", ""),
108
+ context=data.get("context")
109
+ )
110
+ state.agent_results = data.get("agent_results", {})
111
+ state.completed_agents = set(data.get("completed_agents", []))
112
+ state.current_agent = data.get("current_agent")
113
+ state.execution_order = data.get("execution_order", [])
114
+ return state
115
+
116
+
117
+ class SwarmAgent(MCPAgent):
118
+ """Agent that executes within a swarm network."""
119
+
120
+ def __init__(self,
121
+ agent_id: str,
122
+ agent_config: AgentNode,
123
+ available_tools: List[BaseTool],
124
+ permission_manager: PermissionManager,
125
+ ctx: MCPContext,
126
+ **kwargs):
127
+ """Initialize swarm agent."""
128
+ # Set name and description from config
129
+ self.name = agent_id
130
+ self.description = agent_config.get("role", f"Agent {agent_id}")
131
+ self.agent_config = agent_config
132
+
133
+ # Initialize with specified model
134
+ model = agent_config.get("model")
135
+ if model:
136
+ model = self._normalize_model(model)
137
+ else:
138
+ model = "model://anthropic/claude-3-5-sonnet-20241022"
139
+
140
+ super().__init__(
141
+ available_tools=available_tools,
142
+ permission_manager=permission_manager,
143
+ ctx=ctx,
144
+ model=model,
145
+ **kwargs
146
+ )
147
+
148
+ def _normalize_model(self, model: str) -> str:
149
+ """Normalize model names to full format."""
150
+ model_map = {
151
+ "claude-3-5-sonnet": "model://anthropic/claude-3-5-sonnet-20241022",
152
+ "claude-3-opus": "model://anthropic/claude-3-opus-20240229",
153
+ "gpt-4o": "model://openai/gpt-4o",
154
+ "gpt-4": "model://openai/gpt-4",
155
+ "gemini-1.5-pro": "model://google/gemini-1.5-pro",
156
+ "gemini-1.5-flash": "model://google/gemini-1.5-flash",
157
+ }
158
+
159
+ # Check if it's already a model:// URI
160
+ if model.startswith("model://"):
161
+ return model
162
+
163
+ # Check mapping
164
+ if model in model_map:
165
+ return model_map[model]
166
+
167
+ # Assume it's a provider/model format
168
+ if "/" in model:
169
+ return f"model://{model}"
170
+
171
+ # Default to anthropic
172
+ return f"model://anthropic/{model}"
173
+
174
+ async def run(self, state: SwarmState, history: History, network: Network) -> InferenceResult:
175
+ """Execute the swarm agent."""
176
+ # Build prompt with context
177
+ prompt_parts = []
178
+
179
+ # Add role context
180
+ if self.agent_config.get("role"):
181
+ prompt_parts.append(f"Your role: {self.agent_config['role']}")
182
+
183
+ # Add shared context
184
+ if state.context:
185
+ prompt_parts.append(f"Context:\n{state.context}")
186
+
187
+ # Add inputs from connected agents
188
+ receives_from = self.agent_config.get("receives_from", [])
189
+ if receives_from:
190
+ inputs = {}
191
+ for agent_id in receives_from:
192
+ if agent_id in state.agent_results:
193
+ inputs[agent_id] = state.agent_results[agent_id]
194
+
195
+ if inputs:
196
+ prompt_parts.append("Input from previous agents:")
197
+ for input_agent, input_result in inputs.items():
198
+ prompt_parts.append(f"\n--- From {input_agent} ---\n{input_result}")
199
+
200
+ # Add file context if specified
201
+ if self.agent_config.get("file_path"):
202
+ prompt_parts.append(f"\nFile to work on: {self.agent_config['file_path']}")
203
+
204
+ # Add the main query
205
+ prompt_parts.append(f"\nTask: {self.agent_config['query']}")
206
+
207
+ # Add initial query if this is entry point
208
+ if state.current_agent == state.config.get("entry_point"):
209
+ prompt_parts.append(f"\nMain objective: {state.initial_query}")
210
+
211
+ full_prompt = "\n\n".join(prompt_parts)
212
+
213
+ # Execute using base class
214
+ messages = [
215
+ {"role": "system", "content": self._get_system_prompt()},
216
+ {"role": "user", "content": full_prompt}
217
+ ]
218
+
219
+ # Call model
220
+ from hanzo_agents import ModelRegistry
221
+ adapter = ModelRegistry.get_adapter(self.model)
222
+ response = await adapter.chat(messages)
223
+
224
+ # Store result in state
225
+ state.agent_results[self.name] = response
226
+ state.completed_agents.add(self.name)
227
+ state.execution_order.append(self.name)
228
+
229
+ # Return result
230
+ return InferenceResult(
231
+ agent=self.name,
232
+ content=response,
233
+ metadata={
234
+ "agent_id": self.name,
235
+ "role": self.agent_config.get("role"),
236
+ "connections": self.agent_config.get("connections", [])
237
+ }
238
+ )
239
+
240
+
241
+ class SwarmRouter(DeterministicRouter):
242
+ """Router for swarm agent orchestration."""
243
+
244
+ def __init__(self, swarm_config: SwarmConfig):
245
+ """Initialize swarm router."""
246
+ self.swarm_config = swarm_config
247
+ self.agents_config = swarm_config["agents"]
248
+ self.entry_point = swarm_config.get("entry_point")
249
+
250
+ # Build dependency graph
251
+ self.dependencies = {}
252
+ self.dependents = {}
253
+
254
+ for agent_id, config in self.agents_config.items():
255
+ # Dependencies (agents this one waits for)
256
+ self.dependencies[agent_id] = set(config.get("receives_from", []))
257
+
258
+ # Dependents (agents that wait for this one)
259
+ connections = config.get("connections", [])
260
+ for conn in connections:
261
+ if conn not in self.dependents:
262
+ self.dependents[conn] = set()
263
+ self.dependents[conn].add(agent_id)
264
+
265
+ def route(self, network, call_count, last_result, agent_stack):
266
+ """Determine next agent to execute."""
267
+ state = network.state
268
+
269
+ # First call - start with entry point or roots
270
+ if call_count == 0:
271
+ if self.entry_point:
272
+ state.current_agent = self.entry_point
273
+ return self._get_agent_class(self.entry_point, agent_stack)
274
+ else:
275
+ # Find roots (no dependencies)
276
+ roots = [aid for aid, deps in self.dependencies.items() if not deps]
277
+ if roots:
278
+ state.current_agent = roots[0]
279
+ return self._get_agent_class(roots[0], agent_stack)
280
+
281
+ # Find next agent to execute
282
+ for agent_id in self.agents_config:
283
+ if agent_id in state.completed_agents:
284
+ continue
285
+
286
+ # Check if all dependencies are met
287
+ deps = self.dependencies.get(agent_id, set())
288
+ if deps.issubset(state.completed_agents):
289
+ state.current_agent = agent_id
290
+ return self._get_agent_class(agent_id, agent_stack)
291
+
292
+ # No more agents to execute
293
+ return None
294
+
295
+ def _get_agent_class(self, agent_id: str, agent_stack: List[type[Agent]]) -> type[Agent]:
296
+ """Get agent class for given agent ID."""
297
+ # Find matching agent by name
298
+ for agent_class in agent_stack:
299
+ if hasattr(agent_class, "name") and agent_class.name == agent_id:
300
+ return agent_class
301
+
302
+ # Not found - this shouldn't happen
303
+ return None
304
+
305
+
306
+ @final
307
+ class SwarmTool(BaseTool):
308
+ """Tool for executing agent networks using hanzo-agents SDK."""
309
+
310
+ @property
311
+ @override
312
+ def name(self) -> str:
313
+ """Get the tool name."""
314
+ return "swarm"
315
+
316
+ @property
317
+ @override
318
+ def description(self) -> str:
319
+ """Get the tool description."""
320
+ if not HANZO_AGENTS_AVAILABLE:
321
+ return "Swarm tool (hanzo-agents SDK not available - using fallback)"
322
+
323
+ return """Execute a network of AI agents with flexible connection topologies.
324
+
325
+ This tool enables sophisticated agent orchestration where agents can be connected
326
+ in various network patterns. Each agent can pass results to connected agents,
327
+ enabling complex workflows.
328
+
329
+ Features:
330
+ - Flexible agent networks (tree, DAG, pipeline, star, mesh)
331
+ - Each agent can use different models (Claude, GPT-4, Gemini, etc.)
332
+ - Agents automatically pass results to connected agents
333
+ - Parallel execution with dependency management
334
+ - Full editing capabilities for each agent
335
+ - Memory and state management via hanzo-agents SDK
336
+
337
+ Common Topologies:
338
+ 1. Tree (Architect pattern):
339
+ architect → [frontend, backend, database] → reviewer
340
+
341
+ 2. Pipeline (Sequential processing):
342
+ analyzer → planner → implementer → tester → reviewer
343
+
344
+ 3. Star (Central coordinator):
345
+ coordinator ← → [agent1, agent2, agent3, agent4]
346
+
347
+ 4. DAG (Complex dependencies):
348
+ Multiple agents with custom connections
349
+
350
+ Models can be specified as:
351
+ - Full: 'anthropic/claude-3-5-sonnet-20241022'
352
+ - Short: 'claude-3-5-sonnet', 'gpt-4o', 'gemini-1.5-pro'
353
+ - CLI tools: 'claude_cli', 'codex_cli', 'gemini_cli', 'grok_cli'
354
+ - Model URIs: 'model://anthropic/claude-3-opus'
355
+ """
356
+
357
+ def __init__(
358
+ self,
359
+ permission_manager: PermissionManager,
360
+ model: str | None = None,
361
+ api_key: str | None = None,
362
+ base_url: str | None = None,
363
+ max_tokens: int | None = None,
364
+ agent_max_iterations: int = 10,
365
+ agent_max_tool_uses: int = 30,
366
+ ):
367
+ """Initialize the swarm tool."""
368
+ self.permission_manager = permission_manager
369
+ # Default to latest Claude Sonnet if no model specified
370
+ from hanzo_mcp.tools.agent.code_auth import get_latest_claude_model
371
+ self.model = model or f"anthropic/{get_latest_claude_model()}"
372
+ self.api_key = api_key or os.environ.get("ANTHROPIC_API_KEY")
373
+ self.base_url = base_url
374
+ self.max_tokens = max_tokens
375
+ self.agent_max_iterations = agent_max_iterations
376
+ self.agent_max_tool_uses = agent_max_tool_uses
377
+
378
+ # Set up available tools for agents
379
+ self.available_tools: list[BaseTool] = []
380
+ self.available_tools.extend(
381
+ get_read_only_filesystem_tools(self.permission_manager)
382
+ )
383
+ self.available_tools.extend(
384
+ get_read_only_jupyter_tools(self.permission_manager)
385
+ )
386
+
387
+ # Add edit tools
388
+ self.available_tools.append(Edit(self.permission_manager))
389
+ self.available_tools.append(MultiEdit(self.permission_manager))
390
+
391
+ # Add batch tool
392
+ self.available_tools.append(
393
+ BatchTool({t.name: t for t in self.available_tools})
394
+ )
395
+
396
+ @override
397
+ async def call(
398
+ self,
399
+ ctx: MCPContext,
400
+ **params: Unpack[SwarmToolParams],
401
+ ) -> str:
402
+ """Execute the swarm tool."""
403
+ tool_ctx = create_tool_context(ctx)
404
+ await tool_ctx.set_tool_info(self.name)
405
+
406
+ # Extract parameters
407
+ config = params.get("config", {})
408
+ initial_query = params.get("query", "")
409
+ context = params.get("context", "")
410
+ max_concurrent = params.get("max_concurrent", 10)
411
+ use_memory = params.get("use_memory", False)
412
+ memory_backend = params.get("memory_backend", "sqlite")
413
+
414
+ agents_config = config.get("agents", {})
415
+
416
+ if not agents_config:
417
+ await tool_ctx.error("No agents provided")
418
+ return "Error: At least one agent must be provided."
419
+
420
+ # Check if hanzo-agents is available
421
+ if not HANZO_AGENTS_AVAILABLE:
422
+ # Fall back to original implementation
423
+ await tool_ctx.warning("hanzo-agents SDK not available, using fallback")
424
+ from hanzo_mcp.tools.agent.swarm_tool import SwarmTool as OriginalSwarmTool
425
+ original_tool = OriginalSwarmTool(
426
+ permission_manager=self.permission_manager,
427
+ model=self.model,
428
+ api_key=self.api_key,
429
+ base_url=self.base_url,
430
+ max_tokens=self.max_tokens,
431
+ agent_max_iterations=self.agent_max_iterations,
432
+ agent_max_tool_uses=self.agent_max_tool_uses,
433
+ )
434
+ return await original_tool.call(ctx, **params)
435
+
436
+ await tool_ctx.info(f"Starting swarm execution with {len(agents_config)} agents using hanzo-agents SDK")
437
+
438
+ # Create state
439
+ state = SwarmState(
440
+ config=config,
441
+ initial_query=initial_query,
442
+ context=context
443
+ )
444
+
445
+ # Create agent classes dynamically
446
+ agent_classes = []
447
+ for agent_id, agent_config in agents_config.items():
448
+ # Check for CLI agents
449
+ model = agent_config.get("model", self.model)
450
+
451
+ cli_agents = {
452
+ "claude_cli": ClaudeCodeAgent,
453
+ "codex_cli": OpenAICodexAgent,
454
+ "gemini_cli": GeminiAgent,
455
+ "grok_cli": GrokAgent,
456
+ }
457
+
458
+ if model in cli_agents:
459
+ # Use CLI agent
460
+ agent_class = type(f"Swarm{agent_id}", (cli_agents[model],), {
461
+ "name": agent_id,
462
+ "description": agent_config.get("role", f"Agent {agent_id}"),
463
+ "agent_config": agent_config
464
+ })
465
+ else:
466
+ # Create dynamic SwarmAgent class
467
+ agent_class = type(f"Swarm{agent_id}", (SwarmAgent,), {
468
+ "name": agent_id,
469
+ "__init__": lambda self, aid=agent_id, acfg=agent_config: SwarmAgent.__init__(
470
+ self,
471
+ agent_id=aid,
472
+ agent_config=acfg,
473
+ available_tools=self.available_tools,
474
+ permission_manager=self.permission_manager,
475
+ ctx=ctx
476
+ )
477
+ })
478
+
479
+ agent_classes.append(agent_class)
480
+
481
+ # Create memory if requested
482
+ memory_kv = None
483
+ memory_vector = None
484
+ if use_memory:
485
+ memory_kv = create_memory_kv(memory_backend)
486
+ memory_vector = create_memory_vector("simple")
487
+
488
+ # Create router
489
+ router = SwarmRouter(config)
490
+
491
+ # Create network
492
+ network = Network(
493
+ state=state,
494
+ agents=agent_classes,
495
+ router=router,
496
+ memory_kv=memory_kv,
497
+ memory_vector=memory_vector,
498
+ max_steps=self.agent_max_iterations * len(agents_config),
499
+ )
500
+
501
+ # Execute
502
+ try:
503
+ final_state = await network.run()
504
+
505
+ # Format results
506
+ return self._format_network_results(
507
+ agents_config,
508
+ final_state.agent_results,
509
+ final_state.execution_order,
510
+ config.get("entry_point")
511
+ )
512
+
513
+ except Exception as e:
514
+ await tool_ctx.error(f"Swarm execution failed: {str(e)}")
515
+ return f"Error: {str(e)}"
516
+
517
+ def _format_network_results(
518
+ self,
519
+ agents_config: Dict[str, Any],
520
+ results: Dict[str, str],
521
+ execution_order: List[str],
522
+ entry_point: Optional[str]
523
+ ) -> str:
524
+ """Format results from agent network execution."""
525
+ output = ["Agent Network Execution Results (hanzo-agents SDK)"]
526
+ output.append("=" * 80)
527
+ output.append(f"Total agents: {len(agents_config)}")
528
+ output.append(f"Completed: {len(results)}")
529
+ output.append(f"Failed: {len([r for r in results.values() if r.startswith('Error:')])}")
530
+
531
+ if entry_point:
532
+ output.append(f"Entry point: {entry_point}")
533
+
534
+ output.append(f"\nExecution Order: {' → '.join(execution_order)}")
535
+ output.append("-" * 40)
536
+
537
+ # Detailed results
538
+ output.append("\n\nDetailed Results:")
539
+ output.append("=" * 80)
540
+
541
+ for agent_id in execution_order:
542
+ if agent_id in results:
543
+ config = agents_config.get(agent_id, {})
544
+ role = config.get("role", "Agent")
545
+ model = config.get("model", "default")
546
+
547
+ output.append(f"\n### {agent_id} ({role}) [{model}]")
548
+ output.append("-" * 40)
549
+
550
+ result = results[agent_id]
551
+ if result.startswith("Error:"):
552
+ output.append(result)
553
+ else:
554
+ # Show first part of result
555
+ lines = result.split('\n')
556
+ preview_lines = lines[:10]
557
+ output.extend(preview_lines)
558
+
559
+ if len(lines) > 10:
560
+ output.append(f"... ({len(lines) - 10} more lines)")
561
+
562
+ return "\n".join(output)
563
+
564
+ @override
565
+ def register(self, mcp_server: FastMCP) -> None:
566
+ """Register this swarm tool with the MCP server."""
567
+ tool_self = self
568
+
569
+ @mcp_server.tool(name=self.name, description=self.description)
570
+ async def swarm(
571
+ ctx: MCPContext,
572
+ config: dict[str, Any],
573
+ query: str,
574
+ context: Optional[str] = None,
575
+ max_concurrent: int = 10,
576
+ use_memory: bool = False,
577
+ memory_backend: str = "sqlite"
578
+ ) -> str:
579
+ # Convert to typed format
580
+ typed_config = SwarmConfig(
581
+ agents=config.get("agents", {}),
582
+ entry_point=config.get("entry_point"),
583
+ topology=config.get("topology")
584
+ )
585
+
586
+ return await tool_self.call(
587
+ ctx,
588
+ config=typed_config,
589
+ query=query,
590
+ context=context,
591
+ max_concurrent=max_concurrent,
592
+ use_memory=use_memory,
593
+ memory_backend=memory_backend
594
+ )
@@ -19,6 +19,7 @@ from hanzo_mcp.tools.common.validation import (
19
19
  ValidationResult,
20
20
  validate_path_parameter,
21
21
  )
22
+ from hanzo_mcp.tools.common.truncate import truncate_response
22
23
 
23
24
 
24
25
  def handle_connection_errors(