fast-agent-mcp 0.1.13__py3-none-any.whl → 0.2.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.
Files changed (147) hide show
  1. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/METADATA +3 -4
  2. fast_agent_mcp-0.2.0.dist-info/RECORD +123 -0
  3. mcp_agent/__init__.py +75 -0
  4. mcp_agent/agents/agent.py +59 -371
  5. mcp_agent/agents/base_agent.py +522 -0
  6. mcp_agent/agents/workflow/__init__.py +1 -0
  7. mcp_agent/agents/workflow/chain_agent.py +173 -0
  8. mcp_agent/agents/workflow/evaluator_optimizer.py +362 -0
  9. mcp_agent/agents/workflow/orchestrator_agent.py +591 -0
  10. mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_models.py +27 -11
  11. mcp_agent/agents/workflow/parallel_agent.py +182 -0
  12. mcp_agent/agents/workflow/router_agent.py +307 -0
  13. mcp_agent/app.py +3 -1
  14. mcp_agent/cli/commands/bootstrap.py +18 -7
  15. mcp_agent/cli/commands/setup.py +12 -4
  16. mcp_agent/cli/main.py +1 -1
  17. mcp_agent/cli/terminal.py +1 -1
  18. mcp_agent/config.py +24 -35
  19. mcp_agent/context.py +3 -1
  20. mcp_agent/context_dependent.py +3 -1
  21. mcp_agent/core/agent_types.py +10 -7
  22. mcp_agent/core/direct_agent_app.py +179 -0
  23. mcp_agent/core/direct_decorators.py +443 -0
  24. mcp_agent/core/direct_factory.py +476 -0
  25. mcp_agent/core/enhanced_prompt.py +15 -20
  26. mcp_agent/core/fastagent.py +151 -337
  27. mcp_agent/core/interactive_prompt.py +424 -0
  28. mcp_agent/core/mcp_content.py +19 -11
  29. mcp_agent/core/prompt.py +6 -2
  30. mcp_agent/core/validation.py +89 -16
  31. mcp_agent/executor/decorator_registry.py +6 -2
  32. mcp_agent/executor/temporal.py +35 -11
  33. mcp_agent/executor/workflow_signal.py +8 -2
  34. mcp_agent/human_input/handler.py +3 -1
  35. mcp_agent/llm/__init__.py +2 -0
  36. mcp_agent/{workflows/llm → llm}/augmented_llm.py +131 -256
  37. mcp_agent/{workflows/llm → llm}/augmented_llm_passthrough.py +35 -107
  38. mcp_agent/llm/augmented_llm_playback.py +83 -0
  39. mcp_agent/{workflows/llm → llm}/model_factory.py +26 -8
  40. mcp_agent/llm/providers/__init__.py +8 -0
  41. mcp_agent/{workflows/llm → llm/providers}/anthropic_utils.py +5 -1
  42. mcp_agent/{workflows/llm → llm/providers}/augmented_llm_anthropic.py +37 -141
  43. mcp_agent/llm/providers/augmented_llm_deepseek.py +53 -0
  44. mcp_agent/{workflows/llm → llm/providers}/augmented_llm_openai.py +112 -148
  45. mcp_agent/{workflows/llm → llm}/providers/multipart_converter_anthropic.py +78 -35
  46. mcp_agent/{workflows/llm → llm}/providers/multipart_converter_openai.py +73 -44
  47. mcp_agent/{workflows/llm → llm}/providers/openai_multipart.py +18 -4
  48. mcp_agent/{workflows/llm → llm/providers}/openai_utils.py +3 -3
  49. mcp_agent/{workflows/llm → llm}/providers/sampling_converter_anthropic.py +3 -3
  50. mcp_agent/{workflows/llm → llm}/providers/sampling_converter_openai.py +3 -3
  51. mcp_agent/{workflows/llm → llm}/sampling_converter.py +0 -21
  52. mcp_agent/{workflows/llm → llm}/sampling_format_converter.py +16 -1
  53. mcp_agent/logging/logger.py +2 -2
  54. mcp_agent/mcp/gen_client.py +9 -3
  55. mcp_agent/mcp/interfaces.py +67 -45
  56. mcp_agent/mcp/logger_textio.py +97 -0
  57. mcp_agent/mcp/mcp_agent_client_session.py +12 -4
  58. mcp_agent/mcp/mcp_agent_server.py +3 -1
  59. mcp_agent/mcp/mcp_aggregator.py +124 -93
  60. mcp_agent/mcp/mcp_connection_manager.py +21 -7
  61. mcp_agent/mcp/prompt_message_multipart.py +59 -1
  62. mcp_agent/mcp/prompt_render.py +77 -0
  63. mcp_agent/mcp/prompt_serialization.py +20 -13
  64. mcp_agent/mcp/prompts/prompt_constants.py +18 -0
  65. mcp_agent/mcp/prompts/prompt_helpers.py +327 -0
  66. mcp_agent/mcp/prompts/prompt_load.py +15 -5
  67. mcp_agent/mcp/prompts/prompt_server.py +154 -87
  68. mcp_agent/mcp/prompts/prompt_template.py +26 -35
  69. mcp_agent/mcp/resource_utils.py +3 -1
  70. mcp_agent/mcp/sampling.py +24 -15
  71. mcp_agent/mcp_server/agent_server.py +8 -5
  72. mcp_agent/mcp_server_registry.py +22 -9
  73. mcp_agent/resources/examples/{workflows → in_dev}/agent_build.py +1 -1
  74. mcp_agent/resources/examples/{data-analysis → in_dev}/slides.py +1 -1
  75. mcp_agent/resources/examples/internal/agent.py +4 -2
  76. mcp_agent/resources/examples/internal/fastagent.config.yaml +8 -2
  77. mcp_agent/resources/examples/prompting/image_server.py +3 -1
  78. mcp_agent/resources/examples/prompting/work_with_image.py +19 -0
  79. mcp_agent/ui/console_display.py +27 -7
  80. fast_agent_mcp-0.1.13.dist-info/RECORD +0 -164
  81. mcp_agent/core/agent_app.py +0 -570
  82. mcp_agent/core/agent_utils.py +0 -69
  83. mcp_agent/core/decorators.py +0 -448
  84. mcp_agent/core/factory.py +0 -422
  85. mcp_agent/core/proxies.py +0 -278
  86. mcp_agent/core/types.py +0 -22
  87. mcp_agent/eval/__init__.py +0 -0
  88. mcp_agent/mcp/stdio.py +0 -114
  89. mcp_agent/resources/examples/data-analysis/analysis-campaign.py +0 -188
  90. mcp_agent/resources/examples/data-analysis/analysis.py +0 -65
  91. mcp_agent/resources/examples/data-analysis/fastagent.config.yaml +0 -41
  92. mcp_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -1471
  93. mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +0 -53
  94. mcp_agent/resources/examples/researcher/fastagent.config.yaml +0 -66
  95. mcp_agent/resources/examples/researcher/researcher-eval.py +0 -53
  96. mcp_agent/resources/examples/researcher/researcher-imp.py +0 -189
  97. mcp_agent/resources/examples/researcher/researcher.py +0 -39
  98. mcp_agent/resources/examples/workflows/chaining.py +0 -45
  99. mcp_agent/resources/examples/workflows/evaluator.py +0 -79
  100. mcp_agent/resources/examples/workflows/fastagent.config.yaml +0 -24
  101. mcp_agent/resources/examples/workflows/human_input.py +0 -26
  102. mcp_agent/resources/examples/workflows/orchestrator.py +0 -74
  103. mcp_agent/resources/examples/workflows/parallel.py +0 -79
  104. mcp_agent/resources/examples/workflows/router.py +0 -54
  105. mcp_agent/resources/examples/workflows/sse.py +0 -23
  106. mcp_agent/telemetry/__init__.py +0 -0
  107. mcp_agent/telemetry/usage_tracking.py +0 -19
  108. mcp_agent/workflows/__init__.py +0 -0
  109. mcp_agent/workflows/embedding/__init__.py +0 -0
  110. mcp_agent/workflows/embedding/embedding_base.py +0 -58
  111. mcp_agent/workflows/embedding/embedding_cohere.py +0 -49
  112. mcp_agent/workflows/embedding/embedding_openai.py +0 -37
  113. mcp_agent/workflows/evaluator_optimizer/__init__.py +0 -0
  114. mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +0 -447
  115. mcp_agent/workflows/intent_classifier/__init__.py +0 -0
  116. mcp_agent/workflows/intent_classifier/intent_classifier_base.py +0 -117
  117. mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +0 -130
  118. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +0 -41
  119. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +0 -41
  120. mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +0 -150
  121. mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +0 -60
  122. mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +0 -58
  123. mcp_agent/workflows/llm/__init__.py +0 -0
  124. mcp_agent/workflows/llm/augmented_llm_playback.py +0 -111
  125. mcp_agent/workflows/llm/providers/__init__.py +0 -8
  126. mcp_agent/workflows/orchestrator/__init__.py +0 -0
  127. mcp_agent/workflows/orchestrator/orchestrator.py +0 -535
  128. mcp_agent/workflows/parallel/__init__.py +0 -0
  129. mcp_agent/workflows/parallel/fan_in.py +0 -320
  130. mcp_agent/workflows/parallel/fan_out.py +0 -181
  131. mcp_agent/workflows/parallel/parallel_llm.py +0 -149
  132. mcp_agent/workflows/router/__init__.py +0 -0
  133. mcp_agent/workflows/router/router_base.py +0 -338
  134. mcp_agent/workflows/router/router_embedding.py +0 -226
  135. mcp_agent/workflows/router/router_embedding_cohere.py +0 -59
  136. mcp_agent/workflows/router/router_embedding_openai.py +0 -59
  137. mcp_agent/workflows/router/router_llm.py +0 -304
  138. mcp_agent/workflows/swarm/__init__.py +0 -0
  139. mcp_agent/workflows/swarm/swarm.py +0 -292
  140. mcp_agent/workflows/swarm/swarm_anthropic.py +0 -42
  141. mcp_agent/workflows/swarm/swarm_openai.py +0 -41
  142. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/WHEEL +0 -0
  143. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
  144. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
  145. /mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_prompts.py +0 -0
  146. /mcp_agent/{workflows/llm → llm}/memory.py +0 -0
  147. /mcp_agent/{workflows/llm → llm}/prompt_utils.py +0 -0
@@ -0,0 +1,182 @@
1
+ import asyncio
2
+ from typing import Any, List, Optional
3
+
4
+ from mcp.types import TextContent
5
+
6
+ from mcp_agent.agents.agent import Agent, AgentConfig
7
+ from mcp_agent.agents.base_agent import BaseAgent
8
+ from mcp_agent.core.request_params import RequestParams
9
+ from mcp_agent.mcp.interfaces import ModelT
10
+ from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
11
+
12
+
13
+ class ParallelAgent(BaseAgent):
14
+ """
15
+ LLMs can sometimes work simultaneously on a task (fan-out)
16
+ and have their outputs aggregated programmatically (fan-in).
17
+ This workflow performs both the fan-out and fan-in operations using LLMs.
18
+ From the user's perspective, an input is specified and the output is returned.
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ config: AgentConfig,
24
+ fan_in_agent: Agent,
25
+ fan_out_agents: List[Agent],
26
+ include_request: bool = True,
27
+ **kwargs,
28
+ ) -> None:
29
+ """
30
+ Initialize a ParallelLLM agent.
31
+
32
+ Args:
33
+ config: Agent configuration or name
34
+ fan_in_agent: Agent that aggregates results from fan-out agents
35
+ fan_out_agents: List of agents to execute in parallel
36
+ include_request: Whether to include the original request in the aggregation
37
+ **kwargs: Additional keyword arguments to pass to BaseAgent
38
+ """
39
+ super().__init__(config, **kwargs)
40
+ self.fan_in_agent = fan_in_agent
41
+ self.fan_out_agents = fan_out_agents
42
+ self.include_request = include_request
43
+
44
+ async def generate(
45
+ self,
46
+ multipart_messages: List[PromptMessageMultipart],
47
+ request_params: Optional[RequestParams] = None,
48
+ ) -> PromptMessageMultipart:
49
+ """
50
+ Execute fan-out agents in parallel and aggregate their results with the fan-in agent.
51
+
52
+ Args:
53
+ multipart_messages: List of messages to send to the fan-out agents
54
+ request_params: Optional parameters to configure the request
55
+
56
+ Returns:
57
+ The aggregated response from the fan-in agent
58
+ """
59
+ # Execute all fan-out agents in parallel
60
+ responses: List[PromptMessageMultipart] = await asyncio.gather(
61
+ *[agent.generate(multipart_messages, request_params) for agent in self.fan_out_agents]
62
+ )
63
+
64
+ # Extract the received message from the input
65
+ received_message: Optional[str] = (
66
+ multipart_messages[-1].all_text() if multipart_messages else None
67
+ )
68
+
69
+ # Convert responses to strings for aggregation
70
+ string_responses = []
71
+ for response in responses:
72
+ string_responses.append(response.all_text())
73
+
74
+ # Format the responses and send to the fan-in agent
75
+ aggregated_prompt = self._format_responses(string_responses, received_message)
76
+
77
+ # Create a new multipart message with the formatted responses
78
+ formatted_prompt = PromptMessageMultipart(
79
+ role="user", content=[TextContent(type="text", text=aggregated_prompt)]
80
+ )
81
+
82
+ # Use the fan-in agent to aggregate the responses
83
+ return await self.fan_in_agent.generate([formatted_prompt], request_params)
84
+
85
+ def _format_responses(self, responses: List[Any], message: Optional[str] = None) -> str:
86
+ """
87
+ Format a list of responses for the fan-in agent.
88
+
89
+ Args:
90
+ responses: List of responses from fan-out agents
91
+ message: Optional original message that was sent to the agents
92
+
93
+ Returns:
94
+ Formatted string with responses
95
+ """
96
+ formatted = []
97
+
98
+ # Include the original message if specified
99
+ if self.include_request and message:
100
+ formatted.append("The following request was sent to the agents:")
101
+ formatted.append(f"<fastagent:request>\n{message}\n</fastagent:request>")
102
+
103
+ # Format each agent's response
104
+ for i, response in enumerate(responses):
105
+ agent_name = self.fan_out_agents[i].name
106
+ formatted.append(
107
+ f'<fastagent:response agent="{agent_name}">\n{response}\n</fastagent:response>'
108
+ )
109
+ return "\n\n".join(formatted)
110
+
111
+ async def structured(
112
+ self,
113
+ prompt: List[PromptMessageMultipart],
114
+ model: type[ModelT],
115
+ request_params: Optional[RequestParams] = None,
116
+ ) -> Optional[ModelT]:
117
+ """
118
+ Apply the prompt and return the result as a Pydantic model.
119
+
120
+ This implementation delegates to the fan-in agent's structured method.
121
+
122
+ Args:
123
+ prompt: List of PromptMessageMultipart objects
124
+ model: The Pydantic model class to parse the result into
125
+ request_params: Optional parameters to configure the LLM request
126
+
127
+ Returns:
128
+ An instance of the specified model, or None if coercion fails
129
+ """
130
+ # Generate parallel responses first
131
+ responses: List[PromptMessageMultipart] = await asyncio.gather(
132
+ *[agent.generate(prompt, request_params) for agent in self.fan_out_agents]
133
+ )
134
+
135
+ # Extract the received message
136
+ received_message: Optional[str] = prompt[-1].all_text() if prompt else None
137
+
138
+ # Convert responses to strings
139
+ string_responses = [response.all_text() for response in responses]
140
+
141
+ # Format the responses for the fan-in agent
142
+ aggregated_prompt = self._format_responses(string_responses, received_message)
143
+
144
+ # Create a multipart message
145
+ formatted_prompt = PromptMessageMultipart(
146
+ role="user", content=[TextContent(type="text", text=aggregated_prompt)]
147
+ )
148
+
149
+ # Use the fan-in agent to parse the structured output
150
+ return await self.fan_in_agent.structured([formatted_prompt], model, request_params)
151
+
152
+ async def initialize(self) -> None:
153
+ """
154
+ Initialize the agent and its fan-in and fan-out agents.
155
+ """
156
+ await super().initialize()
157
+
158
+ # Initialize fan-in and fan-out agents if not already initialized
159
+ if not getattr(self.fan_in_agent, "initialized", False):
160
+ await self.fan_in_agent.initialize()
161
+
162
+ for agent in self.fan_out_agents:
163
+ if not getattr(agent, "initialized", False):
164
+ await agent.initialize()
165
+
166
+ async def shutdown(self) -> None:
167
+ """
168
+ Shutdown the agent and its fan-in and fan-out agents.
169
+ """
170
+ await super().shutdown()
171
+
172
+ # Shutdown fan-in and fan-out agents
173
+ try:
174
+ await self.fan_in_agent.shutdown()
175
+ except Exception as e:
176
+ self.logger.warning(f"Error shutting down fan-in agent: {str(e)}")
177
+
178
+ for agent in self.fan_out_agents:
179
+ try:
180
+ await agent.shutdown()
181
+ except Exception as e:
182
+ self.logger.warning(f"Error shutting down fan-out agent {agent.name}: {str(e)}")
@@ -0,0 +1,307 @@
1
+ """
2
+ Router agent implementation using the BaseAgent adapter pattern.
3
+
4
+ This provides a simplified implementation that routes messages to agents
5
+ by determining the best agent for a request and dispatching to it.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, List, Optional, Type
9
+
10
+ from mcp.types import TextContent
11
+ from pydantic import BaseModel
12
+
13
+ from mcp_agent.agents.agent import Agent
14
+ from mcp_agent.agents.base_agent import BaseAgent
15
+ from mcp_agent.core.agent_types import AgentConfig
16
+ from mcp_agent.core.exceptions import AgentConfigError
17
+ from mcp_agent.core.request_params import RequestParams
18
+ from mcp_agent.logging.logger import get_logger
19
+ from mcp_agent.mcp.interfaces import ModelT
20
+ from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
21
+
22
+ if TYPE_CHECKING:
23
+ from mcp_agent.context import Context
24
+
25
+ logger = get_logger(__name__)
26
+
27
+ # Simple system instruction for the router
28
+ ROUTING_SYSTEM_INSTRUCTION = """
29
+ You are a highly accurate request router that directs incoming requests to the most appropriate agent.
30
+ Analyze each request and determine which specialized agent would be best suited to handle it based on their capabilities.
31
+
32
+ Follow these guidelines:
33
+ - Carefully match the request's needs with each agent's capabilities and description
34
+ - Select the single most appropriate agent for the request
35
+ - Provide your confidence level (high, medium, low) and brief reasoning for your selection
36
+ """
37
+
38
+ # Default routing instruction with placeholders for context and request
39
+ DEFAULT_ROUTING_INSTRUCTION = """
40
+ You are a highly accurate request router that directs incoming requests to the most appropriate agent.
41
+
42
+ <fastagent:data>
43
+ <fastagent:agents>
44
+ {context}
45
+ </fastagent:agents>
46
+
47
+ <fastagent:request>
48
+ {request}
49
+ </fastagent:request>
50
+ </fastagent:data>
51
+
52
+ Your task is to analyze the request and determine the most appropriate agent from the options above.
53
+
54
+ <fastagent:instruction>
55
+ Respond in JSON format. NEVER include Code Fences:
56
+ {{
57
+ "agent": "<agent name>",
58
+ "confidence": "<high, medium or low>",
59
+ "reasoning": "<brief explanation>"
60
+ }}
61
+ </fastagent:instruction>
62
+ """
63
+
64
+
65
+ class RoutingResponse(BaseModel):
66
+ """Model for the structured routing response from the LLM."""
67
+
68
+ agent: str
69
+ confidence: str
70
+ reasoning: Optional[str] = None
71
+
72
+
73
+ class RouterResult(BaseModel):
74
+ """Router result with agent reference and confidence rating."""
75
+
76
+ result: Agent
77
+ confidence: str
78
+ reasoning: Optional[str] = None
79
+
80
+ # Allow Agent objects to be stored without serialization
81
+ model_config = {"arbitrary_types_allowed": True}
82
+
83
+
84
+ class RouterAgent(BaseAgent):
85
+ """
86
+ A simplified router that uses an LLM to determine the best agent for a request,
87
+ then dispatches the request to that agent and returns the response.
88
+ """
89
+
90
+ def __init__(
91
+ self,
92
+ config: AgentConfig,
93
+ agents: List[Agent],
94
+ routing_instruction: Optional[str] = None,
95
+ context: Optional["Context"] = None,
96
+ default_request_params: Optional[RequestParams] = None,
97
+ **kwargs,
98
+ ) -> None:
99
+ """
100
+ Initialize a RouterAgent.
101
+
102
+ Args:
103
+ config: Agent configuration or name
104
+ agents: List of agents to route between
105
+ routing_instruction: Optional custom routing instruction
106
+ context: Optional application context
107
+ default_request_params: Optional default request parameters
108
+ **kwargs: Additional keyword arguments to pass to BaseAgent
109
+ """
110
+ super().__init__(config=config, context=context, **kwargs)
111
+
112
+ if not agents:
113
+ raise AgentConfigError("At least one agent must be provided")
114
+
115
+ self.agents = agents
116
+ self.routing_instruction = routing_instruction
117
+ self.agent_map = {agent.name: agent for agent in agents}
118
+
119
+ # Set up base router request parameters
120
+ base_params = {"systemPrompt": ROUTING_SYSTEM_INSTRUCTION, "use_history": False}
121
+
122
+ # Merge with provided defaults if any
123
+ if default_request_params:
124
+ # Start with defaults and override with router-specific settings
125
+ merged_params = default_request_params.model_copy(update=base_params)
126
+ else:
127
+ merged_params = RequestParams(**base_params)
128
+
129
+ self._default_request_params = merged_params
130
+
131
+ async def initialize(self) -> None:
132
+ """Initialize the router and all agents."""
133
+ if not self.initialized:
134
+ await super().initialize()
135
+
136
+ # Initialize all agents if not already initialized
137
+ for agent in self.agents:
138
+ if not getattr(agent, "initialized", False):
139
+ await agent.initialize()
140
+
141
+ self.initialized = True
142
+
143
+ async def shutdown(self) -> None:
144
+ """Shutdown the router and all agents."""
145
+ await super().shutdown()
146
+
147
+ # Shutdown all agents
148
+ for agent in self.agents:
149
+ try:
150
+ await agent.shutdown()
151
+ except Exception as e:
152
+ logger.warning(f"Error shutting down agent: {str(e)}")
153
+
154
+ async def _get_routing_result(
155
+ self,
156
+ messages: List[PromptMessageMultipart],
157
+ ) -> Optional[RouterResult]:
158
+ """
159
+ Common method to extract request and get routing result.
160
+
161
+ Args:
162
+ messages: The messages to extract request from
163
+
164
+ Returns:
165
+ RouterResult containing the selected agent, or None if no suitable agent found
166
+ """
167
+ if not self.initialized:
168
+ await self.initialize()
169
+
170
+ # Extract the request text from the last message
171
+ request = messages[-1].all_text() if messages else ""
172
+
173
+ # Determine which agent to route to
174
+ routing_result = await self._route_request(request)
175
+
176
+ if not routing_result:
177
+ logger.warning("Could not determine appropriate agent for this request")
178
+
179
+ return routing_result
180
+
181
+ async def generate(
182
+ self,
183
+ multipart_messages: List[PromptMessageMultipart],
184
+ request_params: Optional[RequestParams] = None,
185
+ ) -> PromptMessageMultipart:
186
+ """
187
+ Route the request to the most appropriate agent and return its response.
188
+
189
+ Args:
190
+ multipart_messages: Messages to route
191
+ request_params: Optional request parameters
192
+
193
+ Returns:
194
+ The response from the selected agent
195
+ """
196
+ routing_result = await self._get_routing_result(multipart_messages)
197
+
198
+ if not routing_result:
199
+ return PromptMessageMultipart(
200
+ role="assistant",
201
+ content=[
202
+ TextContent(
203
+ type="text", text="Could not determine appropriate agent for this request."
204
+ )
205
+ ],
206
+ )
207
+
208
+ # Get the selected agent
209
+ selected_agent = routing_result.result
210
+
211
+ # Log the routing decision
212
+ logger.info(
213
+ f"Routing request to agent: {selected_agent.name} (confidence: {routing_result.confidence})"
214
+ )
215
+
216
+ # Dispatch the request to the selected agent
217
+ return await selected_agent.generate(multipart_messages, request_params)
218
+
219
+ async def structured(
220
+ self,
221
+ prompt: List[PromptMessageMultipart],
222
+ model: Type[ModelT],
223
+ request_params: Optional[RequestParams] = None,
224
+ ) -> Optional[ModelT]:
225
+ """
226
+ Route the request to the most appropriate agent and parse its response.
227
+
228
+ Args:
229
+ prompt: Messages to route
230
+ model: Pydantic model to parse the response into
231
+ request_params: Optional request parameters
232
+
233
+ Returns:
234
+ The parsed response from the selected agent, or None if parsing fails
235
+ """
236
+ routing_result = await self._get_routing_result(prompt)
237
+
238
+ if not routing_result:
239
+ return None
240
+
241
+ # Get the selected agent
242
+ selected_agent = routing_result.result
243
+
244
+ # Log the routing decision
245
+ logger.info(
246
+ f"Routing structured request to agent: {selected_agent.name} (confidence: {routing_result.confidence})"
247
+ )
248
+
249
+ # Dispatch the request to the selected agent
250
+ return await selected_agent.structured(prompt, model, request_params)
251
+
252
+ async def _route_request(self, request: str) -> Optional[RouterResult]:
253
+ """
254
+ Determine which agent to route the request to.
255
+
256
+ Args:
257
+ request: The request to route
258
+
259
+ Returns:
260
+ RouterResult containing the selected agent, or None if no suitable agent was found
261
+ """
262
+ if not self.agents:
263
+ logger.warning("No agents available for routing")
264
+ return None
265
+
266
+ # If only one agent is available, use it directly
267
+ if len(self.agents) == 1:
268
+ return RouterResult(
269
+ result=self.agents[0], confidence="high", reasoning="Only one agent available"
270
+ )
271
+
272
+ # Generate agent descriptions for the context
273
+ agent_descriptions = []
274
+ for i, agent in enumerate(self.agents, 1):
275
+ description = agent.instruction if isinstance(agent.instruction, str) else ""
276
+ agent_descriptions.append(f"{i}. Name: {agent.name} - {description}")
277
+
278
+ context = "\n\n".join(agent_descriptions)
279
+
280
+ # Format the routing prompt
281
+ routing_instruction = self.routing_instruction or DEFAULT_ROUTING_INSTRUCTION
282
+ prompt_text = routing_instruction.format(context=context, request=request)
283
+
284
+ # Create multipart message for the router
285
+ prompt = PromptMessageMultipart(
286
+ role="user", content=[TextContent(type="text", text=prompt_text)]
287
+ )
288
+
289
+ # Get structured response from LLM
290
+ response = await self._llm.structured(
291
+ [prompt], RoutingResponse, self._default_request_params
292
+ )
293
+
294
+ if not response:
295
+ logger.warning("No routing response received from LLM")
296
+ return None
297
+
298
+ # Look up the agent by name
299
+ selected_agent = self.agent_map.get(response.agent)
300
+
301
+ if not selected_agent:
302
+ logger.warning(f"Agent '{response.agent}' not found in available agents")
303
+ return None
304
+
305
+ return RouterResult(
306
+ result=selected_agent, confidence=response.confidence, reasoning=response.reasoning
307
+ )
mcp_agent/app.py CHANGED
@@ -72,7 +72,9 @@ class MCPApp:
72
72
  @property
73
73
  def context(self) -> Context:
74
74
  if self._context is None:
75
- raise RuntimeError("MCPApp not initialized, please call initialize() first, or use async with app.run().")
75
+ raise RuntimeError(
76
+ "MCPApp not initialized, please call initialize() first, or use async with app.run()."
77
+ )
76
78
  return self._context
77
79
 
78
80
  @property
@@ -70,8 +70,9 @@ def copy_example_files(example_type: str, target_dir: Path, force: bool = False)
70
70
  mount_point_dir.mkdir(parents=True)
71
71
  console.print(f"Created mount-point directory: {mount_point_dir}")
72
72
 
73
- # Use the resources directory from the package
74
- source_dir = Path(__file__).parent.parent.parent / "resources" / "examples" / ("workflows" if example_type == "workflow" else f"{example_type}")
73
+ # Use examples from top-level directory
74
+ package_dir = Path(__file__).parent.parent.parent.parent.parent
75
+ source_dir = package_dir / "examples" / ("workflows" if example_type == "workflow" else f"{example_type}")
75
76
 
76
77
  if not source_dir.exists():
77
78
  console.print(f"[red]Error: Source directory not found: {source_dir}[/red]")
@@ -110,7 +111,9 @@ def copy_example_files(example_type: str, target_dir: Path, force: bool = False)
110
111
  continue
111
112
 
112
113
  if target.exists() and not force:
113
- console.print(f"[yellow]Skipping[/yellow] mount-point/{filename} (already exists)")
114
+ console.print(
115
+ f"[yellow]Skipping[/yellow] mount-point/{filename} (already exists)"
116
+ )
114
117
  continue
115
118
 
116
119
  shutil.copy2(source, target)
@@ -137,7 +140,9 @@ def show_overview() -> None:
137
140
  for name, info in EXAMPLE_TYPES.items():
138
141
  files_list = "\n".join(f"• {f}" for f in info["files"])
139
142
  if "mount_point_files" in info:
140
- files_list += "\n[blue]mount-point:[/blue]\n" + "\n".join(f"• {f}" for f in info["mount_point_files"])
143
+ files_list += "\n[blue]mount-point:[/blue]\n" + "\n".join(
144
+ f"• {f}" for f in info["mount_point_files"]
145
+ )
141
146
  table.add_row(f"[green]{name}[/green]", info["description"], files_list)
142
147
 
143
148
  console.print(table)
@@ -229,17 +234,23 @@ def _show_completion_message(example_type: str, created: list[str]) -> None:
229
234
  console.print(" - evaluator.py: Add evaluation capabilities")
230
235
  console.print(" - human_input.py: Incorporate human feedback")
231
236
  console.print("3. Run an example with: uv run <example>.py")
232
- console.print("4. Try a different model with --model=<model>, or update the agent config")
237
+ console.print(
238
+ "4. Try a different model with --model=<model>, or update the agent config"
239
+ )
233
240
 
234
241
  elif example_type == "researcher":
235
- console.print("1. Set up the Brave MCP Server (get an API key from https://brave.com/search/api/)")
242
+ console.print(
243
+ "1. Set up the Brave MCP Server (get an API key from https://brave.com/search/api/)"
244
+ )
236
245
  console.print("2. Try `uv run researcher.py` for the basic version")
237
246
  console.print("3. Try `uv run researcher-eval.py` for the eval/optimize version")
238
247
  elif example_type == "data-analysis":
239
248
  console.print("1. Run uv `analysis.py` to perform data analysis and visualization")
240
249
  console.print("2. The dataset is available in the mount-point directory:")
241
250
  console.print(" - mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv")
242
- console.print("On Windows platforms, please edit the fastagent.config.yaml and adjust the volume mount point.")
251
+ console.print(
252
+ "On Windows platforms, please edit the fastagent.config.yaml and adjust the volume mount point."
253
+ )
243
254
  else:
244
255
  console.print("\n[yellow]No files were created.[/yellow]")
245
256
 
@@ -173,7 +173,9 @@ def init(
173
173
 
174
174
  config_path = Path(config_dir).resolve()
175
175
  if not config_path.exists():
176
- should_create = Confirm.ask(f"Directory {config_path} does not exist. Create it?", default=True)
176
+ should_create = Confirm.ask(
177
+ f"Directory {config_path} does not exist. Create it?", default=True
178
+ )
177
179
  if should_create:
178
180
  config_path.mkdir(parents=True)
179
181
  else:
@@ -212,9 +214,15 @@ def init(
212
214
  console.print("\n[green]Setup completed successfully![/green]")
213
215
  if "fastagent.secrets.yaml" in created:
214
216
  console.print("\n[yellow]Important:[/yellow] Remember to:")
215
- console.print("1. Add your API keys to fastagent-secrets.yaml or set OPENAI_API_KEY and ANTHROPIC_API_KEY environment variables")
216
- console.print("2. Keep fastagent.secrets.yaml secure and never commit it to version control")
217
- console.print("3. Update fastagent.config.yaml to set a default model (currently system default is 'haiku')")
217
+ console.print(
218
+ "1. Add your API keys to fastagent-secrets.yaml or set OPENAI_API_KEY and ANTHROPIC_API_KEY environment variables"
219
+ )
220
+ console.print(
221
+ "2. Keep fastagent.secrets.yaml secure and never commit it to version control"
222
+ )
223
+ console.print(
224
+ "3. Update fastagent.config.yaml to set a default model (currently system default is 'haiku')"
225
+ )
218
226
  console.print("\nTo get started, run:")
219
227
  console.print(" uv run agent.py")
220
228
  else:
mcp_agent/cli/main.py CHANGED
@@ -8,7 +8,7 @@ from mcp_agent.cli.commands import bootstrap, setup
8
8
  from mcp_agent.cli.terminal import Application
9
9
 
10
10
  app = typer.Typer(
11
- help="MCP Agent CLI - Build effective agents using Model Context Protocol",
11
+ help="FastAgent CLI - Build effective agents using Model Context Protocol",
12
12
  add_completion=False, # We'll add this later when we have more commands
13
13
  )
14
14
 
mcp_agent/cli/terminal.py CHANGED
@@ -14,7 +14,7 @@ class Application:
14
14
  self.error_console = error_console
15
15
 
16
16
  def log(self, message: str, level: str = "info") -> None:
17
- if level == "info" or (level == "debug" and self.verbosity > 0):
17
+ if (level == "info" or (level == "debug" and self.verbosity > 0) or level == "error"):
18
18
  if level == "error":
19
19
  self.error_console.print(f"[{level.upper()}] {message}")
20
20
  else: