fast-agent-mcp 0.1.11__py3-none-any.whl → 0.1.13__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.
Files changed (131) hide show
  1. {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/METADATA +1 -1
  2. fast_agent_mcp-0.1.13.dist-info/RECORD +164 -0
  3. mcp_agent/agents/agent.py +37 -102
  4. mcp_agent/app.py +16 -27
  5. mcp_agent/cli/commands/bootstrap.py +22 -52
  6. mcp_agent/cli/commands/config.py +4 -4
  7. mcp_agent/cli/commands/setup.py +11 -26
  8. mcp_agent/cli/main.py +6 -9
  9. mcp_agent/cli/terminal.py +2 -2
  10. mcp_agent/config.py +1 -5
  11. mcp_agent/context.py +13 -26
  12. mcp_agent/context_dependent.py +3 -7
  13. mcp_agent/core/agent_app.py +46 -122
  14. mcp_agent/core/agent_types.py +29 -2
  15. mcp_agent/core/agent_utils.py +3 -5
  16. mcp_agent/core/decorators.py +6 -14
  17. mcp_agent/core/enhanced_prompt.py +25 -52
  18. mcp_agent/core/error_handling.py +1 -1
  19. mcp_agent/core/exceptions.py +8 -8
  20. mcp_agent/core/factory.py +30 -72
  21. mcp_agent/core/fastagent.py +48 -88
  22. mcp_agent/core/mcp_content.py +10 -19
  23. mcp_agent/core/prompt.py +8 -15
  24. mcp_agent/core/proxies.py +34 -25
  25. mcp_agent/core/request_params.py +46 -0
  26. mcp_agent/core/types.py +6 -6
  27. mcp_agent/core/validation.py +16 -16
  28. mcp_agent/executor/decorator_registry.py +11 -23
  29. mcp_agent/executor/executor.py +8 -17
  30. mcp_agent/executor/task_registry.py +2 -4
  31. mcp_agent/executor/temporal.py +28 -74
  32. mcp_agent/executor/workflow.py +3 -5
  33. mcp_agent/executor/workflow_signal.py +17 -29
  34. mcp_agent/human_input/handler.py +4 -9
  35. mcp_agent/human_input/types.py +2 -3
  36. mcp_agent/logging/events.py +1 -5
  37. mcp_agent/logging/json_serializer.py +7 -6
  38. mcp_agent/logging/listeners.py +20 -23
  39. mcp_agent/logging/logger.py +15 -17
  40. mcp_agent/logging/rich_progress.py +10 -8
  41. mcp_agent/logging/tracing.py +4 -6
  42. mcp_agent/logging/transport.py +24 -24
  43. mcp_agent/mcp/gen_client.py +4 -12
  44. mcp_agent/mcp/interfaces.py +107 -88
  45. mcp_agent/mcp/mcp_agent_client_session.py +11 -19
  46. mcp_agent/mcp/mcp_agent_server.py +8 -10
  47. mcp_agent/mcp/mcp_aggregator.py +49 -122
  48. mcp_agent/mcp/mcp_connection_manager.py +16 -37
  49. mcp_agent/mcp/prompt_message_multipart.py +12 -18
  50. mcp_agent/mcp/prompt_serialization.py +13 -38
  51. mcp_agent/mcp/prompts/prompt_load.py +99 -0
  52. mcp_agent/mcp/prompts/prompt_server.py +21 -128
  53. mcp_agent/mcp/prompts/prompt_template.py +20 -42
  54. mcp_agent/mcp/resource_utils.py +8 -17
  55. mcp_agent/mcp/sampling.py +62 -64
  56. mcp_agent/mcp/stdio.py +11 -8
  57. mcp_agent/mcp_server/__init__.py +1 -1
  58. mcp_agent/mcp_server/agent_server.py +10 -17
  59. mcp_agent/mcp_server_registry.py +13 -35
  60. mcp_agent/resources/examples/data-analysis/analysis-campaign.py +1 -1
  61. mcp_agent/resources/examples/data-analysis/analysis.py +1 -1
  62. mcp_agent/resources/examples/data-analysis/slides.py +110 -0
  63. mcp_agent/resources/examples/internal/agent.py +2 -1
  64. mcp_agent/resources/examples/internal/job.py +2 -1
  65. mcp_agent/resources/examples/internal/prompt_category.py +1 -1
  66. mcp_agent/resources/examples/internal/prompt_sizing.py +3 -5
  67. mcp_agent/resources/examples/internal/sizer.py +2 -1
  68. mcp_agent/resources/examples/internal/social.py +2 -1
  69. mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +1 -1
  70. mcp_agent/resources/examples/prompting/__init__.py +1 -1
  71. mcp_agent/resources/examples/prompting/agent.py +2 -1
  72. mcp_agent/resources/examples/prompting/image_server.py +5 -11
  73. mcp_agent/resources/examples/researcher/researcher-eval.py +1 -1
  74. mcp_agent/resources/examples/researcher/researcher-imp.py +3 -4
  75. mcp_agent/resources/examples/researcher/researcher.py +2 -1
  76. mcp_agent/resources/examples/workflows/agent_build.py +2 -1
  77. mcp_agent/resources/examples/workflows/chaining.py +2 -1
  78. mcp_agent/resources/examples/workflows/evaluator.py +2 -1
  79. mcp_agent/resources/examples/workflows/human_input.py +2 -1
  80. mcp_agent/resources/examples/workflows/orchestrator.py +2 -1
  81. mcp_agent/resources/examples/workflows/parallel.py +2 -1
  82. mcp_agent/resources/examples/workflows/router.py +2 -1
  83. mcp_agent/resources/examples/workflows/sse.py +1 -1
  84. mcp_agent/telemetry/usage_tracking.py +2 -1
  85. mcp_agent/ui/console_display.py +17 -41
  86. mcp_agent/workflows/embedding/embedding_base.py +1 -4
  87. mcp_agent/workflows/embedding/embedding_cohere.py +2 -2
  88. mcp_agent/workflows/embedding/embedding_openai.py +4 -13
  89. mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +23 -57
  90. mcp_agent/workflows/intent_classifier/intent_classifier_base.py +5 -8
  91. mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +7 -11
  92. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +4 -8
  93. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +4 -8
  94. mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +11 -22
  95. mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +3 -3
  96. mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +4 -6
  97. mcp_agent/workflows/llm/anthropic_utils.py +8 -29
  98. mcp_agent/workflows/llm/augmented_llm.py +94 -332
  99. mcp_agent/workflows/llm/augmented_llm_anthropic.py +43 -76
  100. mcp_agent/workflows/llm/augmented_llm_openai.py +46 -100
  101. mcp_agent/workflows/llm/augmented_llm_passthrough.py +42 -20
  102. mcp_agent/workflows/llm/augmented_llm_playback.py +8 -6
  103. mcp_agent/workflows/llm/memory.py +103 -0
  104. mcp_agent/workflows/llm/model_factory.py +9 -21
  105. mcp_agent/workflows/llm/openai_utils.py +1 -1
  106. mcp_agent/workflows/llm/prompt_utils.py +39 -27
  107. mcp_agent/workflows/llm/providers/multipart_converter_anthropic.py +246 -184
  108. mcp_agent/workflows/llm/providers/multipart_converter_openai.py +212 -202
  109. mcp_agent/workflows/llm/providers/openai_multipart.py +19 -61
  110. mcp_agent/workflows/llm/providers/sampling_converter_anthropic.py +11 -212
  111. mcp_agent/workflows/llm/providers/sampling_converter_openai.py +13 -215
  112. mcp_agent/workflows/llm/sampling_converter.py +117 -0
  113. mcp_agent/workflows/llm/sampling_format_converter.py +12 -29
  114. mcp_agent/workflows/orchestrator/orchestrator.py +24 -67
  115. mcp_agent/workflows/orchestrator/orchestrator_models.py +14 -40
  116. mcp_agent/workflows/parallel/fan_in.py +17 -47
  117. mcp_agent/workflows/parallel/fan_out.py +6 -12
  118. mcp_agent/workflows/parallel/parallel_llm.py +9 -26
  119. mcp_agent/workflows/router/router_base.py +29 -59
  120. mcp_agent/workflows/router/router_embedding.py +11 -25
  121. mcp_agent/workflows/router/router_embedding_cohere.py +2 -2
  122. mcp_agent/workflows/router/router_embedding_openai.py +2 -2
  123. mcp_agent/workflows/router/router_llm.py +12 -28
  124. mcp_agent/workflows/swarm/swarm.py +20 -48
  125. mcp_agent/workflows/swarm/swarm_anthropic.py +2 -2
  126. mcp_agent/workflows/swarm/swarm_openai.py +2 -2
  127. fast_agent_mcp-0.1.11.dist-info/RECORD +0 -160
  128. mcp_agent/workflows/llm/llm_selector.py +0 -345
  129. {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/WHEEL +0 -0
  130. {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/entry_points.txt +0 -0
  131. {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,5 @@
1
1
  import contextlib
2
- from typing import Callable, Dict, List, Optional, Type, TYPE_CHECKING
2
+ from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type
3
3
 
4
4
  from mcp_agent.agents.agent import Agent
5
5
  from mcp_agent.context_dependent import ContextDependent
@@ -43,7 +43,7 @@ class FanIn(ContextDependent):
43
43
  llm_factory: Callable[[Agent], AugmentedLLM[MessageParamT, MessageT]] = None,
44
44
  context: Optional["Context"] = None,
45
45
  **kwargs,
46
- ):
46
+ ) -> None:
47
47
  """
48
48
  Initialize the FanIn with an Agent responsible for processing multiple responses into a single aggregated one.
49
49
  """
@@ -67,9 +67,7 @@ class FanIn(ContextDependent):
67
67
  Request fan-in agent generation from a list of messages from multiple sources/agents.
68
68
  Internally aggregates the messages and then calls the aggregator agent to generate a response.
69
69
  """
70
- message: (
71
- str | MessageParamT | List[MessageParamT]
72
- ) = await self.aggregate_messages(messages)
70
+ message: str | MessageParamT | List[MessageParamT] = await self.aggregate_messages(messages)
73
71
 
74
72
  async with contextlib.AsyncExitStack() as stack:
75
73
  if isinstance(self.aggregator_agent, AugmentedLLM):
@@ -95,9 +93,7 @@ class FanIn(ContextDependent):
95
93
  response, which is returned as a string.
96
94
  """
97
95
 
98
- message: (
99
- str | MessageParamT | List[MessageParamT]
100
- ) = await self.aggregate_messages(messages)
96
+ message: str | MessageParamT | List[MessageParamT] = await self.aggregate_messages(messages)
101
97
 
102
98
  async with contextlib.AsyncExitStack() as stack:
103
99
  if isinstance(self.aggregator_agent, AugmentedLLM):
@@ -107,9 +103,7 @@ class FanIn(ContextDependent):
107
103
  ctx_agent = await stack.enter_async_context(self.aggregator_agent)
108
104
  llm = await ctx_agent.attach_llm(self.llm_factory)
109
105
 
110
- return await llm.generate_str(
111
- message=message, request_params=request_params
112
- )
106
+ return await llm.generate_str(message=message, request_params=request_params)
113
107
 
114
108
  async def generate_structured(
115
109
  self,
@@ -123,9 +117,7 @@ class FanIn(ContextDependent):
123
117
  the aggregator agent to generate a response, which is returned as a Pydantic model.
124
118
  """
125
119
 
126
- message: (
127
- str | MessageParamT | List[MessageParamT]
128
- ) = await self.aggregate_messages(messages)
120
+ message: str | MessageParamT | List[MessageParamT] = await self.aggregate_messages(messages)
129
121
 
130
122
  async with contextlib.AsyncExitStack() as stack:
131
123
  if isinstance(self.aggregator_agent, AugmentedLLM):
@@ -141,9 +133,7 @@ class FanIn(ContextDependent):
141
133
  request_params=request_params,
142
134
  )
143
135
 
144
- async def aggregate_messages(
145
- self, messages: FanInInput
146
- ) -> str | MessageParamT | List[MessageParamT]:
136
+ async def aggregate_messages(self, messages: FanInInput) -> str | MessageParamT | List[MessageParamT]:
147
137
  """
148
138
  Aggregate messages from multiple sources/agents into a single message to
149
139
  use with the aggregator agent generation.
@@ -187,9 +177,7 @@ class FanIn(ContextDependent):
187
177
  return await self.aggregate_agent_message_strings(messages)
188
178
 
189
179
  else:
190
- raise ValueError(
191
- "Dictionary values must be either lists of messages or strings"
192
- )
180
+ raise ValueError("Dictionary values must be either lists of messages or strings")
193
181
 
194
182
  # Handle list inputs
195
183
  elif isinstance(messages, list):
@@ -214,19 +202,13 @@ class FanIn(ContextDependent):
214
202
  return await self.aggregate_message_strings(messages)
215
203
 
216
204
  else:
217
- raise ValueError(
218
- "List items must be either lists of messages or strings"
219
- )
205
+ raise ValueError("List items must be either lists of messages or strings")
220
206
 
221
207
  else:
222
- raise ValueError(
223
- "Input must be either a dictionary of agent messages or a list of messages"
224
- )
208
+ raise ValueError("Input must be either a dictionary of agent messages or a list of messages")
225
209
 
226
210
  # Helper methods for processing different types of inputs
227
- async def aggregate_agent_messages(
228
- self, messages: Dict[str, List[MessageT] | List[MessageParamT]]
229
- ) -> str | MessageParamT | List[MessageParamT]:
211
+ async def aggregate_agent_messages(self, messages: Dict[str, List[MessageT] | List[MessageParamT]]) -> str | MessageParamT | List[MessageParamT]:
230
212
  """
231
213
  Aggregate message lists with agent names.
232
214
 
@@ -254,9 +236,7 @@ class FanIn(ContextDependent):
254
236
  else:
255
237
  # Assume it's a Message/MessageParamT and add attribution
256
238
  # TODO -- this should really unpack the text from the message
257
- agent_message_strings.append(
258
- f"Agent {agent_name}: {str(msg.content[0])}"
259
- )
239
+ agent_message_strings.append(f"Agent {agent_name}: {str(msg.content[0])}")
260
240
 
261
241
  aggregated_messages.append("\n".join(agent_message_strings))
262
242
 
@@ -279,18 +259,14 @@ class FanIn(ContextDependent):
279
259
  return ""
280
260
 
281
261
  # Format each agent's message with agent attribution
282
- aggregated_messages = [
283
- f"Agent {agent_name}: {message}" for agent_name, message in messages.items()
284
- ]
262
+ aggregated_messages = [f"Agent {agent_name}: {message}" for agent_name, message in messages.items()]
285
263
 
286
264
  # Combine all messages with clear separation
287
265
  final_message = "\n\n".join(aggregated_messages)
288
266
  final_message = f"Aggregated responses from multiple Agents:\n\n{final_message}"
289
267
  return final_message
290
268
 
291
- async def aggregate_message_lists(
292
- self, messages: List[List[MessageT] | List[MessageParamT]]
293
- ) -> str | MessageParamT | List[MessageParamT]:
269
+ async def aggregate_message_lists(self, messages: List[List[MessageT] | List[MessageParamT]]) -> str | MessageParamT | List[MessageParamT]:
294
270
  """
295
271
  Aggregate message lists without agent names.
296
272
 
@@ -319,9 +295,7 @@ class FanIn(ContextDependent):
319
295
 
320
296
  # Combine all messages with clear separation
321
297
  final_message = "\n\n".join(aggregated_messages)
322
- final_message = (
323
- f"Aggregated responses from multiple sources:\n\n{final_message}"
324
- )
298
+ final_message = f"Aggregated responses from multiple sources:\n\n{final_message}"
325
299
  return final_message
326
300
 
327
301
  async def aggregate_message_strings(self, messages: List[str]) -> str:
@@ -338,13 +312,9 @@ class FanIn(ContextDependent):
338
312
  return ""
339
313
 
340
314
  # Format each source's message with attribution
341
- aggregated_messages = [
342
- f"Source {i}: {message}" for i, message in enumerate(messages, 1)
343
- ]
315
+ aggregated_messages = [f"Source {i}: {message}" for i, message in enumerate(messages, 1)]
344
316
 
345
317
  # Combine all messages with clear separation
346
318
  final_message = "\n\n".join(aggregated_messages)
347
- final_message = (
348
- f"Aggregated responses from multiple sources:\n\n{final_message}"
349
- )
319
+ final_message = f"Aggregated responses from multiple sources:\n\n{final_message}"
350
320
  return final_message
@@ -1,9 +1,10 @@
1
1
  import contextlib
2
2
  import functools
3
- from typing import Any, Callable, Coroutine, Dict, List, Optional, Type, TYPE_CHECKING
3
+ from typing import TYPE_CHECKING, Any, Callable, Coroutine, Dict, List, Optional, Type
4
4
 
5
5
  from mcp_agent.agents.agent import Agent
6
6
  from mcp_agent.context_dependent import ContextDependent
7
+ from mcp_agent.logging.logger import get_logger
7
8
  from mcp_agent.workflows.llm.augmented_llm import (
8
9
  AugmentedLLM,
9
10
  MessageParamT,
@@ -11,7 +12,6 @@ from mcp_agent.workflows.llm.augmented_llm import (
11
12
  ModelT,
12
13
  RequestParams,
13
14
  )
14
- from mcp_agent.logging.logger import get_logger
15
15
 
16
16
  if TYPE_CHECKING:
17
17
  from mcp_agent.context import Context
@@ -34,7 +34,7 @@ class FanOut(ContextDependent):
34
34
  llm_factory: Callable[[Agent], AugmentedLLM[MessageParamT, MessageT]] = None,
35
35
  context: Optional["Context"] = None,
36
36
  **kwargs,
37
- ):
37
+ ) -> None:
38
38
  """
39
39
  Initialize the FanOut with a list of agents, functions, or LLMs.
40
40
  If agents are provided, they will be wrapped in an AugmentedLLM using llm_factory if not already done so.
@@ -47,9 +47,7 @@ class FanOut(ContextDependent):
47
47
  self.functions: List[Callable[[MessageParamT], MessageT]] = functions or []
48
48
 
49
49
  if not self.agents and not self.functions:
50
- raise ValueError(
51
- "At least one agent or function must be provided for fan-out to work"
52
- )
50
+ raise ValueError("At least one agent or function must be provided for fan-out to work")
53
51
 
54
52
  if not self.llm_factory:
55
53
  for agent in self.agents:
@@ -65,9 +63,7 @@ class FanOut(ContextDependent):
65
63
  Request fan-out agent/function generations, and return the results as a dictionary.
66
64
  The keys are the names of the agents or functions that generated the results.
67
65
  """
68
- tasks: List[
69
- Callable[..., List[MessageT]] | Coroutine[Any, Any, List[MessageT]]
70
- ] = []
66
+ tasks: List[Callable[..., List[MessageT]] | Coroutine[Any, Any, List[MessageT]]] = []
71
67
  task_names: List[str] = []
72
68
  task_results = []
73
69
 
@@ -97,9 +93,7 @@ class FanOut(ContextDependent):
97
93
  logger.debug("Running fan-out tasks:", data=task_names)
98
94
  task_results = await self.executor.execute(*tasks)
99
95
 
100
- logger.debug(
101
- "Fan-out tasks completed:", data=dict(zip(task_names, task_results))
102
- )
96
+ logger.debug("Fan-out tasks completed:", data=dict(zip(task_names, task_results)))
103
97
  return dict(zip(task_names, task_results))
104
98
 
105
99
  async def generate_str(
@@ -1,5 +1,5 @@
1
- from typing import Any, Callable, List, Optional, Type, TYPE_CHECKING, Union
2
1
  import asyncio
2
+ from typing import TYPE_CHECKING, Any, Callable, List, Optional, Type, Union
3
3
 
4
4
  from mcp_agent.agents.agent import Agent
5
5
  from mcp_agent.workflows.llm.augmented_llm import (
@@ -30,7 +30,7 @@ class ParallelLLM(AugmentedLLM[MessageParamT, MessageT]):
30
30
  context: Optional["Context"] = None,
31
31
  include_request: bool = True,
32
32
  **kwargs,
33
- ):
33
+ ) -> None:
34
34
  super().__init__(context=context, **kwargs)
35
35
  self.fan_in_agent = fan_in_agent
36
36
  self.fan_out_agents = fan_out_agents
@@ -63,14 +63,10 @@ class ParallelLLM(AugmentedLLM[MessageParamT, MessageT]):
63
63
  fan_in_llm = await self.ensure_llm(self.fan_in_agent)
64
64
 
65
65
  # Run fan-out operations in parallel
66
- responses = await asyncio.gather(
67
- *[llm.generate(message, request_params) for llm in fan_out_llms]
68
- )
66
+ responses = await asyncio.gather(*[llm.generate(message, request_params) for llm in fan_out_llms])
69
67
 
70
68
  # Get message string for inclusion in formatted output
71
- message_str = (
72
- str(message) if isinstance(message, (str, MessageParamT)) else None
73
- )
69
+ message_str = str(message) if isinstance(message, (str, MessageParamT)) else None
74
70
 
75
71
  # Run fan-in to aggregate results
76
72
  result = await fan_in_llm.generate(
@@ -95,14 +91,10 @@ class ParallelLLM(AugmentedLLM[MessageParamT, MessageT]):
95
91
  fan_in_llm = await self.ensure_llm(self.fan_in_agent)
96
92
 
97
93
  # Run fan-out operations in parallel
98
- responses = await asyncio.gather(
99
- *[llm.generate_str(message, request_params) for llm in fan_out_llms]
100
- )
94
+ responses = await asyncio.gather(*[llm.generate_str(message, request_params) for llm in fan_out_llms])
101
95
 
102
96
  # Get message string for inclusion in formatted output
103
- message_str = (
104
- str(message) if isinstance(message, (str, MessageParamT)) else None
105
- )
97
+ message_str = str(message) if isinstance(message, (str, MessageParamT)) else None
106
98
 
107
99
  # Run fan-in to aggregate results
108
100
  result = await fan_in_llm.generate_str(
@@ -128,17 +120,10 @@ class ParallelLLM(AugmentedLLM[MessageParamT, MessageT]):
128
120
  fan_in_llm = await self.ensure_llm(self.fan_in_agent)
129
121
 
130
122
  # Run fan-out operations in parallel
131
- responses = await asyncio.gather(
132
- *[
133
- llm.generate_structured(message, response_model, request_params)
134
- for llm in fan_out_llms
135
- ]
136
- )
123
+ responses = await asyncio.gather(*[llm.generate_structured(message, response_model, request_params) for llm in fan_out_llms])
137
124
 
138
125
  # Get message string for inclusion in formatted output
139
- message_str = (
140
- str(message) if isinstance(message, (str, MessageParamT)) else None
141
- )
126
+ message_str = str(message) if isinstance(message, (str, MessageParamT)) else None
142
127
 
143
128
  # Run fan-in to aggregate results
144
129
  result = await fan_in_llm.generate_structured(
@@ -160,7 +145,5 @@ class ParallelLLM(AugmentedLLM[MessageParamT, MessageT]):
160
145
 
161
146
  for i, response in enumerate(responses):
162
147
  agent_name = self.fan_out_agents[i].name
163
- formatted.append(
164
- f'<fastagent:response agent="{agent_name}">\n{response}\n</fastagent:response>'
165
- )
148
+ formatted.append(f'<fastagent:response agent="{agent_name}">\n{response}\n</fastagent:response>')
166
149
  return "\n\n".join(formatted)
@@ -1,8 +1,8 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import Callable, Dict, Generic, List, Optional, TypeVar, TYPE_CHECKING
2
+ from typing import TYPE_CHECKING, Callable, Dict, Generic, List, Optional, TypeVar
3
3
 
4
- from pydantic import BaseModel, Field, ConfigDict
5
4
  from mcp.server.fastmcp.tools import Tool as FastTool
5
+ from pydantic import BaseModel, ConfigDict, Field
6
6
 
7
7
  from mcp_agent.agents.agent import Agent
8
8
  from mcp_agent.context_dependent import ContextDependent
@@ -98,7 +98,7 @@ class Router(ABC, ContextDependent):
98
98
  routing_instruction: str | None = None,
99
99
  context: Optional["Context"] = None,
100
100
  **kwargs,
101
- ):
101
+ ) -> None:
102
102
  super().__init__(context=context, **kwargs)
103
103
  self.routing_instruction = routing_instruction
104
104
  self.server_names = server_names or []
@@ -115,19 +115,13 @@ class Router(ABC, ContextDependent):
115
115
  self.initialized: bool = False
116
116
 
117
117
  if not self.server_names and not self.agents and not self.functions:
118
- raise ValueError(
119
- "At least one of mcp_servers_names, agents, or functions must be provided."
120
- )
118
+ raise ValueError("At least one of mcp_servers_names, agents, or functions must be provided.")
121
119
 
122
120
  if self.server_names and not self.server_registry:
123
- raise ValueError(
124
- "server_registry must be provided if mcp_servers_names are provided."
125
- )
121
+ raise ValueError("server_registry must be provided if mcp_servers_names are provided.")
126
122
 
127
123
  @abstractmethod
128
- async def route(
129
- self, request: str, top_k: int = 1
130
- ) -> List[RouterResult[str | Agent | Callable]]:
124
+ async def route(self, request: str, top_k: int = 1) -> List[RouterResult[str | Agent | Callable]]:
131
125
  """
132
126
  Route the input request to one or more MCP servers, agents, or functions.
133
127
  If no routing decision can be made, returns an empty list.
@@ -138,21 +132,15 @@ class Router(ABC, ContextDependent):
138
132
  """
139
133
 
140
134
  @abstractmethod
141
- async def route_to_server(
142
- self, request: str, top_k: int = 1
143
- ) -> List[RouterResult[str]]:
135
+ async def route_to_server(self, request: str, top_k: int = 1) -> List[RouterResult[str]]:
144
136
  """Route the input to one or more MCP servers."""
145
137
 
146
138
  @abstractmethod
147
- async def route_to_agent(
148
- self, request: str, top_k: int = 1
149
- ) -> List[RouterResult[Agent]]:
139
+ async def route_to_agent(self, request: str, top_k: int = 1) -> List[RouterResult[Agent]]:
150
140
  """Route the input to one or more agents."""
151
141
 
152
142
  @abstractmethod
153
- async def route_to_function(
154
- self, request: str, top_k: int = 1
155
- ) -> List[RouterResult[Callable]]:
143
+ async def route_to_function(self, request: str, top_k: int = 1) -> List[RouterResult[Callable]]:
156
144
  """
157
145
  Route the input to one or more functions.
158
146
 
@@ -160,30 +148,20 @@ class Router(ABC, ContextDependent):
160
148
  input: The input to route.
161
149
  """
162
150
 
163
- async def initialize(self):
151
+ async def initialize(self) -> None:
164
152
  """Initialize the router categories."""
165
153
 
166
154
  if self.initialized:
167
155
  return
168
156
 
169
- server_categories = [
170
- self.get_server_category(server_name) for server_name in self.server_names
171
- ]
172
- self.server_categories = {
173
- category.name: category for category in server_categories
174
- }
157
+ server_categories = [self.get_server_category(server_name) for server_name in self.server_names]
158
+ self.server_categories = {category.name: category for category in server_categories}
175
159
 
176
160
  agent_categories = [self.get_agent_category(agent) for agent in self.agents]
177
- self.agent_categories = {
178
- category.name: category for category in agent_categories
179
- }
161
+ self.agent_categories = {category.name: category for category in agent_categories}
180
162
 
181
- function_categories = [
182
- self.get_function_category(function) for function in self.functions
183
- ]
184
- self.function_categories = {
185
- category.name: category for category in function_categories
186
- }
163
+ function_categories = [self.get_function_category(function) for function in self.functions]
164
+ self.function_categories = {category.name: category for category in function_categories}
187
165
 
188
166
  all_categories = server_categories + agent_categories + function_categories
189
167
 
@@ -204,15 +182,11 @@ class Router(ABC, ContextDependent):
204
182
  )
205
183
 
206
184
  def get_agent_category(self, agent: Agent) -> AgentRouterCategory:
207
- agent_description = (
208
- agent.instruction({}) if callable(agent.instruction) else agent.instruction
209
- )
185
+ agent_description = agent.instruction({}) if callable(agent.instruction) else agent.instruction
210
186
 
211
187
  # Just get server categories without attempting to access tools
212
188
  # This is a simpler approach that avoids potential issues with uninitialized agents
213
- server_categories = [
214
- self.get_server_category(server_name) for server_name in agent.server_names
215
- ]
189
+ server_categories = [self.get_server_category(server_name) for server_name in agent.server_names]
216
190
 
217
191
  return AgentRouterCategory(
218
192
  category=agent,
@@ -230,9 +204,7 @@ class Router(ABC, ContextDependent):
230
204
  description=tool.description,
231
205
  )
232
206
 
233
- def format_category(
234
- self, category: RouterCategory, index: int | None = None
235
- ) -> str:
207
+ def format_category(self, category: RouterCategory, index: int | None = None) -> str:
236
208
  """Format a category into a readable string."""
237
209
 
238
210
  if isinstance(category, ServerRouterCategory):
@@ -265,16 +237,16 @@ class Router(ABC, ContextDependent):
265
237
  # Check if we have any content (description or tools)
266
238
  has_description = bool(category.description)
267
239
  has_tools = bool(category.tools)
268
-
240
+
269
241
  # If no content at all, use self-closing tag
270
242
  if not has_description and not has_tools:
271
243
  return f'<fastagent:server-category name="{category.name}" />'
272
-
244
+
273
245
  # Otherwise, build the content
274
246
  description_section = ""
275
247
  if has_description:
276
248
  description_section = f"\n<fastagent:description>{category.description}</fastagent:description>"
277
-
249
+
278
250
  # Add tools section if we have tool information
279
251
  if has_tools:
280
252
  tools = self._format_tools(category.tools)
@@ -293,11 +265,11 @@ class Router(ABC, ContextDependent):
293
265
  # Check if we have any content (description or servers)
294
266
  has_description = bool(category.description)
295
267
  has_servers = bool(category.servers)
296
-
268
+
297
269
  # If no content at all, use self-closing tag
298
270
  if not has_description and not has_servers:
299
271
  return f'<fastagent:agent-category name="{category.name}" />'
300
-
272
+
301
273
  # Build description section if needed
302
274
  description_section = ""
303
275
  if has_description:
@@ -314,13 +286,13 @@ class Router(ABC, ContextDependent):
314
286
  # Check if this server has any content
315
287
  has_server_description = bool(server.description)
316
288
  has_server_tools = bool(server.tools)
317
-
289
+
318
290
  # Use self-closing tag if server has no content
319
291
  if not has_server_description and not has_server_tools:
320
292
  server_section = f'<fastagent:server name="{server.name}" />'
321
293
  server_sections.append(server_section)
322
294
  continue
323
-
295
+
324
296
  # Build server description if needed
325
297
  server_desc_section = ""
326
298
  if has_server_description:
@@ -331,9 +303,7 @@ class Router(ABC, ContextDependent):
331
303
  tool_items = []
332
304
  for tool in server.tools:
333
305
  tool_desc = tool.description if tool.description else ""
334
- tool_items.append(
335
- f'<fastagent:tool name="{tool.name}">{tool_desc}</fastagent:tool>'
336
- )
306
+ tool_items.append(f'<fastagent:tool name="{tool.name}">{tool_desc}</fastagent:tool>')
337
307
 
338
308
  tools_section = f"\n<fastagent:tools>\n{chr(10).join(tool_items)}\n</fastagent:tools>"
339
309
  server_section = f"""<fastagent:server name="{server.name}">{server_desc_section}{tools_section}
@@ -342,7 +312,7 @@ class Router(ABC, ContextDependent):
342
312
  # Just description, no tools
343
313
  server_section = f"""<fastagent:server name="{server.name}">{server_desc_section}
344
314
  </fastagent:server>"""
345
-
315
+
346
316
  server_sections.append(server_section)
347
317
 
348
318
  servers = "\n".join(server_sections)
@@ -357,11 +327,11 @@ class Router(ABC, ContextDependent):
357
327
  """Format a function category into a readable string."""
358
328
  # Check if we have a description
359
329
  has_description = bool(category.description)
360
-
330
+
361
331
  # If no description, use self-closing tag
362
332
  if not has_description:
363
333
  return f'<fastagent:function-category name="{category.name}" />'
364
-
334
+
365
335
  # Include description
366
336
  return f"""<fastagent:function-category name="{category.name}">
367
337
  <fastagent:description>{category.description}</fastagent:description>
@@ -1,4 +1,4 @@
1
- from typing import Callable, List, Optional, TYPE_CHECKING
1
+ from typing import TYPE_CHECKING, Callable, List, Optional
2
2
 
3
3
  from numpy import mean
4
4
 
@@ -6,8 +6,8 @@ from mcp_agent.agents.agent import Agent
6
6
  from mcp_agent.workflows.embedding.embedding_base import (
7
7
  EmbeddingModel,
8
8
  FloatArray,
9
- compute_similarity_scores,
10
9
  compute_confidence,
10
+ compute_similarity_scores,
11
11
  )
12
12
  from mcp_agent.workflows.router.router_base import (
13
13
  Router,
@@ -56,7 +56,7 @@ class EmbeddingRouter(Router):
56
56
  functions: List[Callable] | None = None,
57
57
  context: Optional["Context"] = None,
58
58
  **kwargs,
59
- ):
59
+ ) -> None:
60
60
  super().__init__(
61
61
  server_names=server_names,
62
62
  agents=agents,
@@ -90,7 +90,7 @@ class EmbeddingRouter(Router):
90
90
  await instance.initialize()
91
91
  return instance
92
92
 
93
- async def initialize(self):
93
+ async def initialize(self) -> None:
94
94
  """Initialize by computing embeddings for all categories"""
95
95
 
96
96
  async def create_category_with_embedding(
@@ -99,9 +99,7 @@ class EmbeddingRouter(Router):
99
99
  # Get formatted text representation of category
100
100
  category_text = self.format_category(category)
101
101
  embedding = self._compute_embedding([category_text])
102
- category_with_embedding = EmbeddingRouterCategory(
103
- **category, embedding=embedding
104
- )
102
+ category_with_embedding = EmbeddingRouterCategory(**category, embedding=embedding)
105
103
 
106
104
  return category_with_embedding
107
105
 
@@ -129,18 +127,14 @@ class EmbeddingRouter(Router):
129
127
 
130
128
  self.initialized = True
131
129
 
132
- async def route(
133
- self, request: str, top_k: int = 1
134
- ) -> List[RouterResult[str | Agent | Callable]]:
130
+ async def route(self, request: str, top_k: int = 1) -> List[RouterResult[str | Agent | Callable]]:
135
131
  """Route the request based on embedding similarity"""
136
132
  if not self.initialized:
137
133
  await self.initialize()
138
134
 
139
135
  return await self._route_with_embedding(request, top_k)
140
136
 
141
- async def route_to_server(
142
- self, request: str, top_k: int = 1
143
- ) -> List[RouterResult[str]]:
137
+ async def route_to_server(self, request: str, top_k: int = 1) -> List[RouterResult[str]]:
144
138
  """Route specifically to server categories"""
145
139
  if not self.initialized:
146
140
  await self.initialize()
@@ -154,9 +148,7 @@ class EmbeddingRouter(Router):
154
148
  )
155
149
  return [r.result for r in results[:top_k]]
156
150
 
157
- async def route_to_agent(
158
- self, request: str, top_k: int = 1
159
- ) -> List[RouterResult[Agent]]:
151
+ async def route_to_agent(self, request: str, top_k: int = 1) -> List[RouterResult[Agent]]:
160
152
  """Route specifically to agent categories"""
161
153
  if not self.initialized:
162
154
  await self.initialize()
@@ -170,9 +162,7 @@ class EmbeddingRouter(Router):
170
162
  )
171
163
  return [r.result for r in results[:top_k]]
172
164
 
173
- async def route_to_function(
174
- self, request: str, top_k: int = 1
175
- ) -> List[RouterResult[Callable]]:
165
+ async def route_to_function(self, request: str, top_k: int = 1) -> List[RouterResult[Callable]]:
176
166
  """Route specifically to function categories"""
177
167
  if not self.initialized:
178
168
  await self.initialize()
@@ -198,13 +188,9 @@ class EmbeddingRouter(Router):
198
188
  if category.embedding is None:
199
189
  return None
200
190
 
201
- similarity = compute_similarity_scores(
202
- request_embedding, category.embedding
203
- )
191
+ similarity = compute_similarity_scores(request_embedding, category.embedding)
204
192
 
205
- return RouterResult(
206
- p_score=compute_confidence(similarity), result=category.category
207
- )
193
+ return RouterResult(p_score=compute_confidence(similarity), result=category.category)
208
194
 
209
195
  request_embedding = self._compute_embedding([request])
210
196
 
@@ -1,4 +1,4 @@
1
- from typing import Callable, List, Optional, TYPE_CHECKING
1
+ from typing import TYPE_CHECKING, Callable, List, Optional
2
2
 
3
3
  from mcp_agent.agents.agent import Agent
4
4
  from mcp_agent.workflows.embedding.embedding_cohere import CohereEmbeddingModel
@@ -23,7 +23,7 @@ class CohereEmbeddingRouter(EmbeddingRouter):
23
23
  embedding_model: CohereEmbeddingModel | None = None,
24
24
  context: Optional["Context"] = None,
25
25
  **kwargs,
26
- ):
26
+ ) -> None:
27
27
  embedding_model = embedding_model or CohereEmbeddingModel()
28
28
 
29
29
  super().__init__(
@@ -1,4 +1,4 @@
1
- from typing import Callable, List, Optional, TYPE_CHECKING
1
+ from typing import TYPE_CHECKING, Callable, List, Optional
2
2
 
3
3
  from mcp_agent.agents.agent import Agent
4
4
  from mcp_agent.workflows.embedding.embedding_openai import OpenAIEmbeddingModel
@@ -23,7 +23,7 @@ class OpenAIEmbeddingRouter(EmbeddingRouter):
23
23
  embedding_model: OpenAIEmbeddingModel | None = None,
24
24
  context: Optional["Context"] = None,
25
25
  **kwargs,
26
- ):
26
+ ) -> None:
27
27
  embedding_model = embedding_model or OpenAIEmbeddingModel()
28
28
 
29
29
  super().__init__(