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,535 @@
1
+ """Swarm tool implementation for parallel and hierarchical agent execution.
2
+
3
+ This module implements the SwarmTool that enables both parallel execution of multiple
4
+ agent instances and hierarchical workflows with specialized roles.
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
+ from hanzo_mcp.tools.agent.agent_tool import AgentTool
18
+ from hanzo_mcp.tools.common.base import BaseTool
19
+ from hanzo_mcp.tools.common.context import create_tool_context
20
+ from hanzo_mcp.tools.common.permissions import PermissionManager
21
+
22
+
23
+ class AgentNode(TypedDict):
24
+ """Node in the agent network.
25
+
26
+ Attributes:
27
+ id: Unique identifier for this agent
28
+ query: The specific query/task for this agent
29
+ model: Optional model override (e.g., 'claude-3-5-sonnet', 'gpt-4o')
30
+ role: Optional role description (e.g., 'architect', 'frontend', 'reviewer')
31
+ connections: List of agent IDs this agent connects to (sends results to)
32
+ receives_from: Optional list of agent IDs this agent receives input from
33
+ file_path: Optional specific file for the agent to work on
34
+ """
35
+ id: str
36
+ query: str
37
+ model: Optional[str]
38
+ role: Optional[str]
39
+ connections: Optional[List[str]]
40
+ receives_from: Optional[List[str]]
41
+ file_path: Optional[str]
42
+
43
+
44
+ class SwarmConfig(TypedDict):
45
+ """Configuration for an agent network.
46
+
47
+ Attributes:
48
+ agents: Dictionary of agent configurations keyed by ID
49
+ entry_point: ID of the first agent to execute (optional, defaults to finding roots)
50
+ topology: Optional topology type (tree, dag, pipeline, star, mesh)
51
+ """
52
+ agents: Dict[str, AgentNode]
53
+ entry_point: Optional[str]
54
+ topology: Optional[str]
55
+
56
+
57
+ class SwarmToolParams(TypedDict):
58
+ """Parameters for the SwarmTool.
59
+
60
+ Attributes:
61
+ config: Agent network configuration
62
+ query: Initial query to send to entry point agent(s)
63
+ context: Optional context shared by all agents
64
+ max_concurrent: Maximum number of concurrent agents (default: 10)
65
+ """
66
+ config: SwarmConfig
67
+ query: str
68
+ context: Optional[str]
69
+ max_concurrent: Optional[int]
70
+
71
+
72
+ @final
73
+ class SwarmTool(BaseTool):
74
+ """Tool for executing multiple agent tasks in parallel.
75
+
76
+ The SwarmTool enables efficient parallel processing of multiple files or tasks
77
+ by spawning independent agent instances for each task.
78
+ """
79
+
80
+ @property
81
+ @override
82
+ def name(self) -> str:
83
+ """Get the tool name."""
84
+ return "swarm"
85
+
86
+ @property
87
+ @override
88
+ def description(self) -> str:
89
+ """Get the tool description."""
90
+ return """Execute a network of AI agents with flexible connection topologies.
91
+
92
+ This tool enables sophisticated agent orchestration where agents can be connected
93
+ in various network patterns. Each agent can pass results to connected agents,
94
+ enabling complex workflows.
95
+
96
+ Features:
97
+ - Flexible agent networks (tree, DAG, pipeline, star, mesh)
98
+ - Each agent can use different models (Claude, GPT-4, Gemini, etc.)
99
+ - Agents automatically pass results to connected agents
100
+ - Parallel execution with dependency management
101
+ - Full editing capabilities for each agent
102
+
103
+ Common Topologies:
104
+
105
+ 1. Tree (Architect pattern):
106
+ architect → [frontend, backend, database] → reviewer
107
+
108
+ 2. Pipeline (Sequential processing):
109
+ analyzer → planner → implementer → tester → reviewer
110
+
111
+ 3. Star (Central coordinator):
112
+ coordinator ← → [agent1, agent2, agent3, agent4]
113
+
114
+ 4. DAG (Complex dependencies):
115
+ Multiple agents with custom connections
116
+
117
+ Usage Example:
118
+
119
+ swarm(
120
+ config={
121
+ "agents": {
122
+ "architect": {
123
+ "id": "architect",
124
+ "query": "Analyze codebase and create refactoring plan",
125
+ "model": "claude-3-5-sonnet",
126
+ "connections": ["frontend", "backend", "database"]
127
+ },
128
+ "frontend": {
129
+ "id": "frontend",
130
+ "query": "Refactor UI components based on architect's plan",
131
+ "role": "Frontend Developer",
132
+ "connections": ["reviewer"]
133
+ },
134
+ "backend": {
135
+ "id": "backend",
136
+ "query": "Refactor API endpoints based on architect's plan",
137
+ "role": "Backend Developer",
138
+ "connections": ["reviewer"]
139
+ },
140
+ "database": {
141
+ "id": "database",
142
+ "query": "Optimize database schema based on architect's plan",
143
+ "role": "Database Expert",
144
+ "connections": ["reviewer"]
145
+ },
146
+ "reviewer": {
147
+ "id": "reviewer",
148
+ "query": "Review all changes and ensure consistency",
149
+ "model": "gpt-4o",
150
+ "receives_from": ["frontend", "backend", "database"]
151
+ }
152
+ },
153
+ "entry_point": "architect"
154
+ },
155
+ query="Refactor the authentication system for better security and performance"
156
+ )
157
+
158
+ Models can be specified as:
159
+ - Full: 'anthropic/claude-3-5-sonnet-20241022'
160
+ - Short: 'claude-3-5-sonnet', 'gpt-4o', 'gemini-1.5-pro'
161
+ - CLI tools: 'claude_cli', 'codex_cli', 'gemini_cli', 'grok_cli'
162
+ """
163
+
164
+ def __init__(
165
+ self,
166
+ permission_manager: PermissionManager,
167
+ model: str | None = None,
168
+ api_key: str | None = None,
169
+ base_url: str | None = None,
170
+ max_tokens: int | None = None,
171
+ agent_max_iterations: int = 10,
172
+ agent_max_tool_uses: int = 30,
173
+ ):
174
+ """Initialize the swarm tool.
175
+
176
+ Args:
177
+ permission_manager: Permission manager for access control
178
+ model: Optional model name override (defaults to Claude Sonnet)
179
+ api_key: Optional API key for the model provider
180
+ base_url: Optional base URL for the model provider
181
+ max_tokens: Optional maximum tokens for model responses
182
+ agent_max_iterations: Max iterations per agent (default: 10)
183
+ agent_max_tool_uses: Max tool uses per agent (default: 30)
184
+ """
185
+ self.permission_manager = permission_manager
186
+ # Default to latest Claude Sonnet if no model specified
187
+ from hanzo_mcp.tools.agent.code_auth import get_latest_claude_model
188
+ self.model = model or f"anthropic/{get_latest_claude_model()}"
189
+ self.api_key = api_key or os.environ.get("ANTHROPIC_API_KEY") or os.environ.get("CLAUDE_API_KEY")
190
+ self.base_url = base_url
191
+ self.max_tokens = max_tokens
192
+ self.agent_max_iterations = agent_max_iterations
193
+ self.agent_max_tool_uses = agent_max_tool_uses
194
+
195
+ @override
196
+ async def call(
197
+ self,
198
+ ctx: MCPContext,
199
+ **params: Unpack[SwarmToolParams],
200
+ ) -> str:
201
+ """Execute the swarm tool.
202
+
203
+ Args:
204
+ ctx: MCP context
205
+ **params: Tool parameters
206
+
207
+ Returns:
208
+ Combined results from all agents
209
+ """
210
+ tool_ctx = create_tool_context(ctx)
211
+ await tool_ctx.set_tool_info(self.name)
212
+
213
+ # Extract parameters
214
+ agents = params.get("agents", [])
215
+ manager_query = params.get("manager_query")
216
+ reviewer_query = params.get("reviewer_query")
217
+ common_context = params.get("common_context", "")
218
+ max_concurrent = params.get("max_concurrent", 10)
219
+
220
+ if not agents:
221
+ await tool_ctx.error("No agents provided")
222
+ return "Error: At least one agent must be provided."
223
+
224
+ # Extract parameters
225
+ config = params.get("config", {})
226
+ initial_query = params.get("query", "")
227
+ context = params.get("context", "")
228
+
229
+ agents_config = config.get("agents", {})
230
+ entry_point = config.get("entry_point")
231
+
232
+ await tool_ctx.info(f"Starting swarm execution with {len(agents_config)} agents")
233
+
234
+ # Build agent network
235
+ agent_instances = {}
236
+ agent_results = {}
237
+ execution_queue = asyncio.Queue()
238
+ completed_agents = set()
239
+
240
+ # Create agent instances
241
+ for agent_id, agent_config in agents_config.items():
242
+ model = agent_config.get("model", self.model)
243
+
244
+ # Support CLI tools
245
+ cli_tools = {
246
+ "claude_cli": self._get_cli_tool("claude_cli"),
247
+ "codex_cli": self._get_cli_tool("codex_cli"),
248
+ "gemini_cli": self._get_cli_tool("gemini_cli"),
249
+ "grok_cli": self._get_cli_tool("grok_cli"),
250
+ }
251
+
252
+ if model in cli_tools:
253
+ agent = cli_tools[model]
254
+ else:
255
+ # Regular agent with model
256
+ agent = AgentTool(
257
+ permission_manager=self.permission_manager,
258
+ model=self._normalize_model(model),
259
+ api_key=self.api_key,
260
+ base_url=self.base_url,
261
+ max_tokens=self.max_tokens,
262
+ max_iterations=self.agent_max_iterations,
263
+ max_tool_uses=self.agent_max_tool_uses,
264
+ )
265
+
266
+ agent_instances[agent_id] = agent
267
+
268
+ # Find entry points (agents with no incoming connections)
269
+ if entry_point:
270
+ await execution_queue.put((entry_point, initial_query, {}))
271
+ else:
272
+ # Find root agents (no receives_from)
273
+ roots = []
274
+ for agent_id, agent_config in agents_config.items():
275
+ if not agent_config.get("receives_from"):
276
+ # Check if any other agent connects to this one
277
+ has_incoming = False
278
+ for other_config in agents_config.values():
279
+ if other_config.get("connections") and agent_id in other_config["connections"]:
280
+ has_incoming = True
281
+ break
282
+ if not has_incoming:
283
+ roots.append(agent_id)
284
+
285
+ if not roots:
286
+ await tool_ctx.error("No entry point found in agent network")
287
+ return "Error: Could not determine entry point for agent network"
288
+
289
+ for root in roots:
290
+ await execution_queue.put((root, initial_query, {}))
291
+
292
+ # Execute agents in network order
293
+ async def execute_agent(agent_id: str, query: str, inputs: Dict[str, str]) -> str:
294
+ """Execute a single agent in the network."""
295
+ async with semaphore:
296
+ try:
297
+ agent_config = agents_config[agent_id]
298
+ agent = agent_instances[agent_id]
299
+
300
+ await tool_ctx.info(f"Executing agent: {agent_id} ({agent_config.get('role', 'Agent')})")
301
+
302
+ # Build prompt with context and inputs
303
+ prompt_parts = []
304
+
305
+ # Add role context
306
+ if agent_config.get("role"):
307
+ prompt_parts.append(f"Your role: {agent_config['role']}")
308
+
309
+ # Add shared context
310
+ if context:
311
+ prompt_parts.append(f"Context:\n{context}")
312
+
313
+ # Add inputs from connected agents
314
+ if inputs:
315
+ prompt_parts.append("Input from previous agents:")
316
+ for input_agent, input_result in inputs.items():
317
+ prompt_parts.append(f"\n--- From {input_agent} ---\n{input_result}")
318
+
319
+ # Add file context if specified
320
+ if agent_config.get("file_path"):
321
+ prompt_parts.append(f"\nFile to work on: {agent_config['file_path']}")
322
+
323
+ # Add the main query
324
+ prompt_parts.append(f"\nTask: {agent_config['query']}")
325
+
326
+ # Combine query with initial query if this is entry point
327
+ if query and query != agent_config['query']:
328
+ prompt_parts.append(f"\nMain objective: {query}")
329
+
330
+ full_prompt = "\n\n".join(prompt_parts)
331
+
332
+ # Execute the agent
333
+ result = await agent.call(ctx, prompts=full_prompt)
334
+
335
+ await tool_ctx.info(f"Agent {agent_id} completed")
336
+ return result
337
+
338
+ except Exception as e:
339
+ error_msg = f"Agent {agent_id} failed: {str(e)}"
340
+ await tool_ctx.error(error_msg)
341
+ return f"Error: {error_msg}"
342
+
343
+ # Process agent network
344
+ running_tasks = set()
345
+
346
+ while not execution_queue.empty() or running_tasks:
347
+ # Start new tasks up to concurrency limit
348
+ while not execution_queue.empty() and len(running_tasks) < max_concurrent:
349
+ agent_id, query, inputs = await execution_queue.get()
350
+
351
+ if agent_id not in completed_agents:
352
+ # Check if all dependencies are met
353
+ agent_config = agents_config[agent_id]
354
+ receives_from = agent_config.get("receives_from", [])
355
+
356
+ # Collect inputs from dependencies
357
+ ready = True
358
+ for dep in receives_from:
359
+ if dep not in agent_results:
360
+ ready = False
361
+ # Re-queue for later
362
+ await execution_queue.put((agent_id, query, inputs))
363
+ break
364
+ else:
365
+ inputs[dep] = agent_results[dep]
366
+
367
+ if ready:
368
+ # Execute agent
369
+ task = asyncio.create_task(execute_agent(agent_id, query, inputs))
370
+ running_tasks.add(task)
371
+
372
+ async def handle_completion(task, agent_id=agent_id):
373
+ result = await task
374
+ agent_results[agent_id] = result
375
+ completed_agents.add(agent_id)
376
+ running_tasks.discard(task)
377
+
378
+ # Queue connected agents
379
+ agent_config = agents_config[agent_id]
380
+ connections = agent_config.get("connections", [])
381
+ for next_agent in connections:
382
+ if next_agent in agents_config:
383
+ await execution_queue.put((next_agent, "", {agent_id: result}))
384
+
385
+ asyncio.create_task(handle_completion(task))
386
+
387
+ # Wait a bit if we're at capacity
388
+ if running_tasks:
389
+ await asyncio.sleep(0.1)
390
+
391
+ # Wait for all tasks to complete
392
+ if running_tasks:
393
+ await asyncio.gather(*running_tasks, return_exceptions=True)
394
+
395
+ # Format results
396
+ return self._format_network_results(agents_config, agent_results, entry_point)
397
+
398
+ def _normalize_model(self, model: str) -> str:
399
+ """Normalize model names to full format."""
400
+ model_map = {
401
+ "claude-3-5-sonnet": "anthropic/claude-3-5-sonnet-20241022",
402
+ "claude-3-opus": "anthropic/claude-3-opus-20240229",
403
+ "gpt-4o": "openai/gpt-4o",
404
+ "gpt-4": "openai/gpt-4",
405
+ "gemini-1.5-pro": "google/gemini-1.5-pro",
406
+ "gemini-1.5-flash": "google/gemini-1.5-flash",
407
+ }
408
+ return model_map.get(model, model)
409
+
410
+ def _get_cli_tool(self, tool_name: str):
411
+ """Get CLI tool instance."""
412
+ # Import here to avoid circular imports
413
+ if tool_name == "claude_cli":
414
+ from hanzo_mcp.tools.agent.claude_cli_tool import ClaudeCLITool
415
+ return ClaudeCLITool(self.permission_manager)
416
+ elif tool_name == "codex_cli":
417
+ from hanzo_mcp.tools.agent.codex_cli_tool import CodexCLITool
418
+ return CodexCLITool(self.permission_manager)
419
+ elif tool_name == "gemini_cli":
420
+ from hanzo_mcp.tools.agent.gemini_cli_tool import GeminiCLITool
421
+ return GeminiCLITool(self.permission_manager)
422
+ elif tool_name == "grok_cli":
423
+ from hanzo_mcp.tools.agent.grok_cli_tool import GrokCLITool
424
+ return GrokCLITool(self.permission_manager)
425
+ return None
426
+
427
+ def _format_network_results(
428
+ self,
429
+ agents_config: Dict[str, Any],
430
+ results: Dict[str, str],
431
+ entry_point: Optional[str]
432
+ ) -> str:
433
+ """Format results from agent network execution."""
434
+ output = ["Agent Network Execution Results"]
435
+ output.append("=" * 80)
436
+ output.append(f"Total agents: {len(agents_config)}")
437
+ output.append(f"Completed: {len(results)}")
438
+ output.append(f"Failed: {len([r for r in results.values() if r.startswith('Error:')])}")
439
+
440
+ if entry_point:
441
+ output.append(f"Entry point: {entry_point}")
442
+
443
+ output.append("\nExecution Flow:")
444
+ output.append("-" * 40)
445
+
446
+ # Show results in execution order
447
+ def format_agent_tree(agent_id: str, level: int = 0) -> List[str]:
448
+ lines = []
449
+ indent = " " * level
450
+
451
+ if agent_id in agents_config:
452
+ config = agents_config[agent_id]
453
+ role = config.get("role", "Agent")
454
+ model = config.get("model", "default")
455
+
456
+ status = "✅" if agent_id in results and not results[agent_id].startswith("Error:") else "❌"
457
+ lines.append(f"{indent}{status} {agent_id} ({role}) [{model}]")
458
+
459
+ # Show connections
460
+ connections = config.get("connections", [])
461
+ for conn in connections:
462
+ if conn in agents_config:
463
+ lines.extend(format_agent_tree(conn, level + 1))
464
+
465
+ return lines
466
+
467
+ # Start from entry point or roots
468
+ if entry_point:
469
+ output.extend(format_agent_tree(entry_point))
470
+ else:
471
+ # Find roots
472
+ roots = []
473
+ for agent_id in agents_config:
474
+ has_incoming = False
475
+ for config in agents_config.values():
476
+ if config.get("connections") and agent_id in config["connections"]:
477
+ has_incoming = True
478
+ break
479
+ if not has_incoming:
480
+ roots.append(agent_id)
481
+
482
+ for root in roots:
483
+ output.extend(format_agent_tree(root))
484
+
485
+ # Detailed results
486
+ output.append("\n\nDetailed Results:")
487
+ output.append("=" * 80)
488
+
489
+ for agent_id, result in results.items():
490
+ config = agents_config.get(agent_id, {})
491
+ role = config.get("role", "Agent")
492
+
493
+ output.append(f"\n### {agent_id} ({role})")
494
+ output.append("-" * 40)
495
+
496
+ if result.startswith("Error:"):
497
+ output.append(result)
498
+ else:
499
+ # Show first part of result
500
+ lines = result.split('\n')
501
+ preview_lines = lines[:10]
502
+ output.extend(preview_lines)
503
+
504
+ if len(lines) > 10:
505
+ output.append(f"... ({len(lines) - 10} more lines)")
506
+
507
+ return "\n".join(output)
508
+
509
+ @override
510
+ def register(self, mcp_server: FastMCP) -> None:
511
+ """Register this swarm tool with the MCP server."""
512
+ tool_self = self
513
+
514
+ @mcp_server.tool(name=self.name, description=self.description)
515
+ async def swarm(
516
+ ctx: MCPContext,
517
+ config: dict[str, Any],
518
+ query: str,
519
+ context: Optional[str] = None,
520
+ max_concurrent: int = 10,
521
+ ) -> str:
522
+ # Convert to typed format
523
+ typed_config = SwarmConfig(
524
+ agents=config.get("agents", {}),
525
+ entry_point=config.get("entry_point"),
526
+ topology=config.get("topology")
527
+ )
528
+
529
+ return await tool_self.call(
530
+ ctx,
531
+ config=typed_config,
532
+ query=query,
533
+ context=context,
534
+ max_concurrent=max_concurrent
535
+ )