fast-agent-mcp 0.1.12__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 (169) hide show
  1. {fast_agent_mcp-0.1.12.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 +61 -415
  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 +11 -21
  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 +15 -19
  14. mcp_agent/cli/commands/bootstrap.py +19 -38
  15. mcp_agent/cli/commands/config.py +4 -4
  16. mcp_agent/cli/commands/setup.py +7 -14
  17. mcp_agent/cli/main.py +7 -10
  18. mcp_agent/cli/terminal.py +3 -3
  19. mcp_agent/config.py +25 -40
  20. mcp_agent/context.py +12 -21
  21. mcp_agent/context_dependent.py +3 -5
  22. mcp_agent/core/agent_types.py +10 -7
  23. mcp_agent/core/direct_agent_app.py +179 -0
  24. mcp_agent/core/direct_decorators.py +443 -0
  25. mcp_agent/core/direct_factory.py +476 -0
  26. mcp_agent/core/enhanced_prompt.py +23 -55
  27. mcp_agent/core/exceptions.py +8 -8
  28. mcp_agent/core/fastagent.py +145 -371
  29. mcp_agent/core/interactive_prompt.py +424 -0
  30. mcp_agent/core/mcp_content.py +17 -17
  31. mcp_agent/core/prompt.py +6 -9
  32. mcp_agent/core/request_params.py +6 -3
  33. mcp_agent/core/validation.py +92 -18
  34. mcp_agent/executor/decorator_registry.py +9 -17
  35. mcp_agent/executor/executor.py +8 -17
  36. mcp_agent/executor/task_registry.py +2 -4
  37. mcp_agent/executor/temporal.py +19 -41
  38. mcp_agent/executor/workflow.py +3 -5
  39. mcp_agent/executor/workflow_signal.py +15 -21
  40. mcp_agent/human_input/handler.py +4 -7
  41. mcp_agent/human_input/types.py +2 -3
  42. mcp_agent/llm/__init__.py +2 -0
  43. mcp_agent/llm/augmented_llm.py +450 -0
  44. mcp_agent/llm/augmented_llm_passthrough.py +162 -0
  45. mcp_agent/llm/augmented_llm_playback.py +83 -0
  46. mcp_agent/llm/memory.py +103 -0
  47. mcp_agent/{workflows/llm → llm}/model_factory.py +22 -16
  48. mcp_agent/{workflows/llm → llm}/prompt_utils.py +1 -3
  49. mcp_agent/llm/providers/__init__.py +8 -0
  50. mcp_agent/{workflows/llm → llm/providers}/anthropic_utils.py +8 -25
  51. mcp_agent/{workflows/llm → llm/providers}/augmented_llm_anthropic.py +56 -194
  52. mcp_agent/llm/providers/augmented_llm_deepseek.py +53 -0
  53. mcp_agent/{workflows/llm → llm/providers}/augmented_llm_openai.py +99 -190
  54. mcp_agent/{workflows/llm → llm}/providers/multipart_converter_anthropic.py +72 -71
  55. mcp_agent/{workflows/llm → llm}/providers/multipart_converter_openai.py +65 -71
  56. mcp_agent/{workflows/llm → llm}/providers/openai_multipart.py +16 -44
  57. mcp_agent/{workflows/llm → llm/providers}/openai_utils.py +4 -4
  58. mcp_agent/{workflows/llm → llm}/providers/sampling_converter_anthropic.py +9 -11
  59. mcp_agent/{workflows/llm → llm}/providers/sampling_converter_openai.py +8 -12
  60. mcp_agent/{workflows/llm → llm}/sampling_converter.py +3 -31
  61. mcp_agent/llm/sampling_format_converter.py +37 -0
  62. mcp_agent/logging/events.py +1 -5
  63. mcp_agent/logging/json_serializer.py +7 -6
  64. mcp_agent/logging/listeners.py +20 -23
  65. mcp_agent/logging/logger.py +17 -19
  66. mcp_agent/logging/rich_progress.py +10 -8
  67. mcp_agent/logging/tracing.py +4 -6
  68. mcp_agent/logging/transport.py +22 -22
  69. mcp_agent/mcp/gen_client.py +1 -3
  70. mcp_agent/mcp/interfaces.py +117 -110
  71. mcp_agent/mcp/logger_textio.py +97 -0
  72. mcp_agent/mcp/mcp_agent_client_session.py +7 -7
  73. mcp_agent/mcp/mcp_agent_server.py +8 -8
  74. mcp_agent/mcp/mcp_aggregator.py +102 -143
  75. mcp_agent/mcp/mcp_connection_manager.py +20 -27
  76. mcp_agent/mcp/prompt_message_multipart.py +68 -16
  77. mcp_agent/mcp/prompt_render.py +77 -0
  78. mcp_agent/mcp/prompt_serialization.py +30 -48
  79. mcp_agent/mcp/prompts/prompt_constants.py +18 -0
  80. mcp_agent/mcp/prompts/prompt_helpers.py +327 -0
  81. mcp_agent/mcp/prompts/prompt_load.py +109 -0
  82. mcp_agent/mcp/prompts/prompt_server.py +155 -195
  83. mcp_agent/mcp/prompts/prompt_template.py +35 -66
  84. mcp_agent/mcp/resource_utils.py +7 -14
  85. mcp_agent/mcp/sampling.py +17 -17
  86. mcp_agent/mcp_server/agent_server.py +13 -17
  87. mcp_agent/mcp_server_registry.py +13 -22
  88. mcp_agent/resources/examples/{workflows → in_dev}/agent_build.py +3 -2
  89. mcp_agent/resources/examples/in_dev/slides.py +110 -0
  90. mcp_agent/resources/examples/internal/agent.py +6 -3
  91. mcp_agent/resources/examples/internal/fastagent.config.yaml +8 -2
  92. mcp_agent/resources/examples/internal/job.py +2 -1
  93. mcp_agent/resources/examples/internal/prompt_category.py +1 -1
  94. mcp_agent/resources/examples/internal/prompt_sizing.py +3 -5
  95. mcp_agent/resources/examples/internal/sizer.py +2 -1
  96. mcp_agent/resources/examples/internal/social.py +2 -1
  97. mcp_agent/resources/examples/prompting/agent.py +2 -1
  98. mcp_agent/resources/examples/prompting/image_server.py +4 -8
  99. mcp_agent/resources/examples/prompting/work_with_image.py +19 -0
  100. mcp_agent/ui/console_display.py +16 -20
  101. fast_agent_mcp-0.1.12.dist-info/RECORD +0 -161
  102. mcp_agent/core/agent_app.py +0 -646
  103. mcp_agent/core/agent_utils.py +0 -71
  104. mcp_agent/core/decorators.py +0 -455
  105. mcp_agent/core/factory.py +0 -463
  106. mcp_agent/core/proxies.py +0 -269
  107. mcp_agent/core/types.py +0 -24
  108. mcp_agent/eval/__init__.py +0 -0
  109. mcp_agent/mcp/stdio.py +0 -111
  110. mcp_agent/resources/examples/data-analysis/analysis-campaign.py +0 -188
  111. mcp_agent/resources/examples/data-analysis/analysis.py +0 -65
  112. mcp_agent/resources/examples/data-analysis/fastagent.config.yaml +0 -41
  113. mcp_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -1471
  114. mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +0 -53
  115. mcp_agent/resources/examples/researcher/fastagent.config.yaml +0 -66
  116. mcp_agent/resources/examples/researcher/researcher-eval.py +0 -53
  117. mcp_agent/resources/examples/researcher/researcher-imp.py +0 -190
  118. mcp_agent/resources/examples/researcher/researcher.py +0 -38
  119. mcp_agent/resources/examples/workflows/chaining.py +0 -44
  120. mcp_agent/resources/examples/workflows/evaluator.py +0 -78
  121. mcp_agent/resources/examples/workflows/fastagent.config.yaml +0 -24
  122. mcp_agent/resources/examples/workflows/human_input.py +0 -25
  123. mcp_agent/resources/examples/workflows/orchestrator.py +0 -73
  124. mcp_agent/resources/examples/workflows/parallel.py +0 -78
  125. mcp_agent/resources/examples/workflows/router.py +0 -53
  126. mcp_agent/resources/examples/workflows/sse.py +0 -23
  127. mcp_agent/telemetry/__init__.py +0 -0
  128. mcp_agent/telemetry/usage_tracking.py +0 -18
  129. mcp_agent/workflows/__init__.py +0 -0
  130. mcp_agent/workflows/embedding/__init__.py +0 -0
  131. mcp_agent/workflows/embedding/embedding_base.py +0 -61
  132. mcp_agent/workflows/embedding/embedding_cohere.py +0 -49
  133. mcp_agent/workflows/embedding/embedding_openai.py +0 -46
  134. mcp_agent/workflows/evaluator_optimizer/__init__.py +0 -0
  135. mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +0 -481
  136. mcp_agent/workflows/intent_classifier/__init__.py +0 -0
  137. mcp_agent/workflows/intent_classifier/intent_classifier_base.py +0 -120
  138. mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +0 -134
  139. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +0 -45
  140. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +0 -45
  141. mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +0 -161
  142. mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +0 -60
  143. mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +0 -60
  144. mcp_agent/workflows/llm/__init__.py +0 -0
  145. mcp_agent/workflows/llm/augmented_llm.py +0 -753
  146. mcp_agent/workflows/llm/augmented_llm_passthrough.py +0 -241
  147. mcp_agent/workflows/llm/augmented_llm_playback.py +0 -109
  148. mcp_agent/workflows/llm/providers/__init__.py +0 -8
  149. mcp_agent/workflows/llm/sampling_format_converter.py +0 -22
  150. mcp_agent/workflows/orchestrator/__init__.py +0 -0
  151. mcp_agent/workflows/orchestrator/orchestrator.py +0 -578
  152. mcp_agent/workflows/parallel/__init__.py +0 -0
  153. mcp_agent/workflows/parallel/fan_in.py +0 -350
  154. mcp_agent/workflows/parallel/fan_out.py +0 -187
  155. mcp_agent/workflows/parallel/parallel_llm.py +0 -166
  156. mcp_agent/workflows/router/__init__.py +0 -0
  157. mcp_agent/workflows/router/router_base.py +0 -368
  158. mcp_agent/workflows/router/router_embedding.py +0 -240
  159. mcp_agent/workflows/router/router_embedding_cohere.py +0 -59
  160. mcp_agent/workflows/router/router_embedding_openai.py +0 -59
  161. mcp_agent/workflows/router/router_llm.py +0 -320
  162. mcp_agent/workflows/swarm/__init__.py +0 -0
  163. mcp_agent/workflows/swarm/swarm.py +0 -320
  164. mcp_agent/workflows/swarm/swarm_anthropic.py +0 -42
  165. mcp_agent/workflows/swarm/swarm_openai.py +0 -41
  166. {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/WHEEL +0 -0
  167. {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
  168. {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
  169. /mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_prompts.py +0 -0
mcp_agent/agents/agent.py CHANGED
@@ -1,450 +1,96 @@
1
- import asyncio
2
- import uuid
3
- from typing import Callable, Dict, List, Optional, TypeVar, Union, TYPE_CHECKING
1
+ """
2
+ Agent implementation using the clean BaseAgent adapter.
4
3
 
5
- from mcp.server.fastmcp.tools import Tool as FastTool
6
- from mcp.types import (
7
- CallToolResult,
8
- ListToolsResult,
9
- TextContent,
10
- Tool,
11
- EmbeddedResource,
12
- ReadResourceResult,
13
- )
14
- from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
4
+ This provides a streamlined implementation that adheres to AgentProtocol
5
+ while delegating LLM operations to an attached AugmentedLLMProtocol instance.
6
+ """
15
7
 
16
- from mcp_agent.core.exceptions import PromptExitError
17
- from mcp_agent.mcp.mcp_aggregator import MCPAggregator
8
+ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, TypeVar
9
+
10
+ from mcp_agent.agents.base_agent import BaseAgent
18
11
  from mcp_agent.core.agent_types import AgentConfig
19
- from mcp_agent.human_input.types import (
20
- HumanInputCallback,
21
- HumanInputRequest,
22
- HumanInputResponse,
23
- HUMAN_INPUT_SIGNAL_NAME,
24
- )
25
- from mcp_agent.workflows.llm.augmented_llm import AugmentedLLM
12
+ from mcp_agent.core.interactive_prompt import InteractivePrompt
13
+ from mcp_agent.human_input.types import HumanInputCallback
26
14
  from mcp_agent.logging.logger import get_logger
15
+ from mcp_agent.mcp.interfaces import AugmentedLLMProtocol
27
16
 
28
17
  if TYPE_CHECKING:
29
18
  from mcp_agent.context import Context
30
- import traceback
31
19
 
32
20
  logger = get_logger(__name__)
33
21
 
34
22
  # Define a TypeVar for AugmentedLLM and its subclasses
35
- LLM = TypeVar("LLM", bound=AugmentedLLM)
36
-
37
- HUMAN_INPUT_TOOL_NAME = "__human_input__"
23
+ LLM = TypeVar("LLM", bound=AugmentedLLMProtocol)
38
24
 
39
25
 
40
- class Agent(MCPAggregator):
26
+ class Agent(BaseAgent):
41
27
  """
42
28
  An Agent is an entity that has access to a set of MCP servers and can interact with them.
43
29
  Each agent should have a purpose defined by its instruction.
30
+
31
+ This implementation provides a clean adapter that adheres to AgentProtocol
32
+ while delegating LLM operations to an attached AugmentedLLMProtocol instance.
44
33
  """
45
34
 
46
35
  def __init__(
47
36
  self,
48
- config: Union[
49
- AgentConfig, str
50
- ], # Can be AgentConfig or backward compatible str name
51
- instruction: Optional[Union[str, Callable[[Dict], str]]] = None,
52
- server_names: Optional[List[str]] = None,
37
+ config: AgentConfig, # Can be AgentConfig or backward compatible str name
53
38
  functions: Optional[List[Callable]] = None,
54
39
  connection_persistence: bool = True,
55
40
  human_input_callback: Optional[HumanInputCallback] = None,
56
41
  context: Optional["Context"] = None,
57
- **kwargs,
58
- ):
59
- # Handle backward compatibility where first arg was name
60
- if isinstance(config, str):
61
- self.config = AgentConfig(
62
- name=config,
63
- instruction=instruction or "You are a helpful agent.",
64
- servers=server_names or [],
65
- )
66
- else:
67
- self.config = config
68
-
42
+ **kwargs: Dict[str, Any],
43
+ ) -> None:
44
+ # Initialize with BaseAgent constructor
69
45
  super().__init__(
70
- context=context,
71
- server_names=self.config.servers,
46
+ config=config,
47
+ functions=functions,
72
48
  connection_persistence=connection_persistence,
73
- name=self.config.name,
49
+ human_input_callback=human_input_callback,
50
+ context=context,
74
51
  **kwargs,
75
52
  )
76
53
 
77
- self.name = self.config.name
78
- self.instruction = self.config.instruction
79
- self.functions = functions or []
80
- self.executor = self.context.executor
81
- self.logger = get_logger(f"{__name__}.{self.name}")
82
-
83
- # Store the default request params from config
84
- self._default_request_params = self.config.default_request_params
85
-
86
- # Map function names to tools
87
- self._function_tool_map: Dict[str, FastTool] = {}
88
-
89
- if not self.config.human_input:
90
- self.human_input_callback = None
91
- else:
92
- self.human_input_callback: HumanInputCallback | None = human_input_callback
93
- if not human_input_callback:
94
- if self.context.human_input_handler:
95
- self.human_input_callback = self.context.human_input_handler
96
-
97
- async def initialize(self):
98
- """
99
- Initialize the agent and connect to the MCP servers.
100
- NOTE: This method is called automatically when the agent is used as an async context manager.
101
- """
102
- await (
103
- self.__aenter__()
104
- ) # This initializes the connection manager and loads the servers
105
-
106
- for function in self.functions:
107
- tool: FastTool = FastTool.from_function(function)
108
- self._function_tool_map[tool.name] = tool
109
-
110
- async def attach_llm(self, llm_factory: Callable[..., LLM]) -> LLM:
111
- """
112
- Create an LLM instance for the agent.
113
-
114
- Args:
115
- llm_factory: A callable that constructs an AugmentedLLM or its subclass.
116
- The factory should accept keyword arguments matching the
117
- AugmentedLLM constructor parameters.
118
-
119
- Returns:
120
- An instance of AugmentedLLM or one of its subclasses.
121
- """
122
- return llm_factory(
123
- agent=self, default_request_params=self._default_request_params
124
- )
125
-
126
- async def shutdown(self):
127
- """
128
- Shutdown the agent and close all MCP server connections.
129
- NOTE: This method is called automatically when the agent is used as an async context manager.
130
- """
131
- await super().close()
132
-
133
- async def request_human_input(self, request: HumanInputRequest) -> str:
54
+ async def prompt(self, default_prompt: str = "", agent_name: Optional[str] = None) -> str:
134
55
  """
135
- Request input from a human user. Pauses the workflow until input is received.
56
+ Start an interactive prompt session with this agent.
136
57
 
137
58
  Args:
138
- request: The human input request
59
+ default: Default message to use when user presses enter
60
+ agent_name: Ignored for single agents, included for API compatibility
139
61
 
140
62
  Returns:
141
- The input provided by the human
142
-
143
- Raises:
144
- TimeoutError: If the timeout is exceeded
145
- """
146
- if not self.human_input_callback:
147
- raise ValueError("Human input callback not set")
148
-
149
- # Generate a unique ID for this request to avoid signal collisions
150
- request_id = f"{HUMAN_INPUT_SIGNAL_NAME}_{self.name}_{uuid.uuid4()}"
151
- request.request_id = request_id
152
- # Use metadata as a dictionary to pass agent name
153
- request.metadata = {"agent_name": self.name}
154
- self.logger.debug("Requesting human input:", data=request)
155
-
156
- async def call_callback_and_signal():
157
- try:
158
- user_input = await self.human_input_callback(request)
159
-
160
- self.logger.debug("Received human input:", data=user_input)
161
- await self.executor.signal(signal_name=request_id, payload=user_input)
162
- except PromptExitError as e:
163
- # Propagate the exit error through the signal system
164
- self.logger.info("User requested to exit session")
165
- await self.executor.signal(
166
- signal_name=request_id,
167
- payload={"exit_requested": True, "error": str(e)},
168
- )
169
- except Exception as e:
170
- await self.executor.signal(
171
- request_id, payload=f"Error getting human input: {str(e)}"
172
- )
173
-
174
- asyncio.create_task(call_callback_and_signal())
175
-
176
- self.logger.debug("Waiting for human input signal")
177
-
178
- # Wait for signal (workflow is paused here)
179
- result = await self.executor.wait_for_signal(
180
- signal_name=request_id,
181
- request_id=request_id,
182
- workflow_id=request.workflow_id,
183
- signal_description=request.description or request.prompt,
184
- timeout_seconds=request.timeout_seconds,
185
- signal_type=HumanInputResponse, # TODO: saqadri - should this be HumanInputResponse?
186
- )
187
-
188
- if isinstance(result, dict) and result.get("exit_requested", False):
189
- raise PromptExitError(
190
- result.get("error", "User requested to exit FastAgent session")
191
- )
192
- self.logger.debug("Received human input signal", data=result)
193
- return result
194
-
195
- async def list_tools(self) -> ListToolsResult:
196
- if not self.initialized:
197
- await self.initialize()
198
-
199
- result = await super().list_tools()
200
-
201
- # Add function tools
202
- for tool in self._function_tool_map.values():
203
- result.tools.append(
204
- Tool(
205
- name=tool.name,
206
- description=tool.description,
207
- inputSchema=tool.parameters,
208
- )
209
- )
210
-
211
- # Add a human_input_callback as a tool
212
- if not self.human_input_callback:
213
- self.logger.debug("Human input callback not set")
214
- return result
215
-
216
- # Add a human_input_callback as a tool
217
- human_input_tool: FastTool = FastTool.from_function(self.request_human_input)
218
- result.tools.append(
219
- Tool(
220
- name=HUMAN_INPUT_TOOL_NAME,
221
- description=human_input_tool.description,
222
- inputSchema=human_input_tool.parameters,
223
- )
63
+ The result of the interactive session
64
+ """
65
+ # Use the agent name as a string - ensure it's not the object itself
66
+ agent_name_str = str(self.name)
67
+
68
+ # Create agent_types dictionary with just this agent
69
+ agent_types = {agent_name_str: getattr(self.config, "agent_type", "Agent")}
70
+
71
+ # Create the interactive prompt
72
+ prompt = InteractivePrompt(agent_types=agent_types)
73
+
74
+ # Define wrapper for send function
75
+ async def send_wrapper(message, agent_name):
76
+ return await self.send(message)
77
+
78
+ # Define wrapper for apply_prompt function
79
+ async def apply_prompt_wrapper(prompt_name, args, agent_name):
80
+ # Just apply the prompt directly
81
+ return await self.apply_prompt(prompt_name, args)
82
+
83
+ # Define wrapper for list_prompts function
84
+ async def list_prompts_wrapper(agent_name):
85
+ # Always call list_prompts on this agent regardless of agent_name
86
+ return await self.list_prompts()
87
+
88
+ # Start the prompt loop with just this agent
89
+ return await prompt.prompt_loop(
90
+ send_func=send_wrapper,
91
+ default_agent=agent_name_str,
92
+ available_agents=[agent_name_str], # Only this agent
93
+ apply_prompt_func=apply_prompt_wrapper,
94
+ list_prompts_func=list_prompts_wrapper,
95
+ default=default_prompt,
224
96
  )
225
-
226
- return result
227
-
228
- # todo would prefer to use tool_name to disambiguate agent name
229
- async def call_tool(
230
- self, name: str, arguments: dict | None = None
231
- ) -> CallToolResult:
232
- if name == HUMAN_INPUT_TOOL_NAME:
233
- # Call the human input tool
234
- return await self._call_human_input_tool(arguments)
235
- elif name in self._function_tool_map:
236
- # Call local function and return the result as a text response
237
- tool = self._function_tool_map[name]
238
- result = await tool.run(arguments)
239
- return CallToolResult(content=[TextContent(type="text", text=str(result))])
240
- else:
241
- return await super().call_tool(name, arguments)
242
-
243
- async def _call_human_input_tool(
244
- self, arguments: dict | None = None
245
- ) -> CallToolResult:
246
- # Handle human input request
247
- try:
248
- # Make sure arguments is not None
249
- if arguments is None:
250
- arguments = {}
251
-
252
- # Extract request data
253
- request_data = arguments.get("request")
254
-
255
- # Handle both string and dict request formats
256
- if isinstance(request_data, str):
257
- request = HumanInputRequest(prompt=request_data)
258
- elif isinstance(request_data, dict):
259
- request = HumanInputRequest(**request_data)
260
- else:
261
- # Fallback for invalid or missing request data
262
- request = HumanInputRequest(prompt="Please provide input:")
263
-
264
- result = await self.request_human_input(request=request)
265
-
266
- # Use response attribute if available, otherwise use the result directly
267
- response_text = (
268
- result.response
269
- if isinstance(result, HumanInputResponse)
270
- else str(result)
271
- )
272
-
273
- return CallToolResult(
274
- content=[
275
- TextContent(type="text", text=f"Human response: {response_text}")
276
- ]
277
- )
278
-
279
- except PromptExitError:
280
- raise
281
- except TimeoutError as e:
282
- return CallToolResult(
283
- isError=True,
284
- content=[
285
- TextContent(
286
- type="text",
287
- text=f"Error: Human input request timed out: {str(e)}",
288
- )
289
- ],
290
- )
291
- except Exception as e:
292
- print(f"Error in _call_human_input_tool: {traceback.format_exc()}")
293
-
294
- return CallToolResult(
295
- isError=True,
296
- content=[
297
- TextContent(
298
- type="text", text=f"Error requesting human input: {str(e)}"
299
- )
300
- ],
301
- )
302
-
303
- async def read_resource(
304
- self, server_name: str, resource_name: str
305
- ) -> ReadResourceResult:
306
- return None
307
-
308
- async def apply_prompt(
309
- self, prompt_name: str, arguments: dict[str, str] = None
310
- ) -> str:
311
- """
312
- Apply an MCP Server Prompt by name and return the assistant's response.
313
- Will search all available servers for the prompt if not namespaced.
314
-
315
- If the last message in the prompt is from a user, this will automatically
316
- generate an assistant response to ensure we always end with an assistant message.
317
-
318
- Args:
319
- prompt_name: The name of the prompt to apply
320
- arguments: Optional dictionary of string arguments to pass to the prompt template
321
-
322
- Returns:
323
- The assistant's response or error message
324
- """
325
- # If we don't have an LLM, we can't apply the prompt
326
- if not hasattr(self, "_llm") or not self._llm:
327
- raise RuntimeError("Agent has no attached LLM")
328
-
329
- # Get the prompt - this will search all servers if needed
330
- self.logger.debug(f"Loading prompt '{prompt_name}'")
331
- prompt_result = await self.get_prompt(prompt_name, arguments)
332
-
333
- if not prompt_result or not prompt_result.messages:
334
- error_msg = (
335
- f"Prompt '{prompt_name}' could not be found or contains no messages"
336
- )
337
- self.logger.warning(error_msg)
338
- return error_msg
339
-
340
- # Get the display name (namespaced version)
341
- display_name = getattr(prompt_result, "namespaced_name", prompt_name)
342
-
343
- # Apply the prompt template and get the result
344
- # The LLM will automatically generate a response if needed
345
- result = await self._llm.apply_prompt_template(prompt_result, display_name)
346
- return result
347
-
348
- async def get_resource(self, server_name: str, resource_name: str):
349
- """
350
- Get a resource directly from an MCP server by name.
351
-
352
- Args:
353
- server_name: Name of the MCP server to retrieve the resource from
354
- resource_name: Name of the resource to retrieve
355
-
356
- Returns:
357
- The resource object from the MCP server
358
-
359
- Raises:
360
- ValueError: If the server doesn't exist or the resource couldn't be found
361
- """
362
- if not self.initialized:
363
- await self.initialize()
364
-
365
- # Get the specified server connection
366
- server = self.get_server(server_name)
367
- if not server:
368
- raise ValueError(f"Server '{server_name}' not found or not connected")
369
-
370
- # Request the resource directly from the server
371
- try:
372
- resource_result = await server.get_resource(resource_name)
373
- return resource_result
374
- except Exception as e:
375
- self.logger.error(
376
- f"Error retrieving resource '{resource_name}' from server '{server_name}': {str(e)}"
377
- )
378
- raise ValueError(
379
- f"Failed to retrieve resource '{resource_name}' from server '{server_name}': {str(e)}"
380
- )
381
-
382
- async def get_embedded_resources(
383
- self, server_name: str, resource_name: str
384
- ) -> List[EmbeddedResource]:
385
- """
386
- Get a resource from an MCP server and return it as a list of embedded resources ready for use in prompts.
387
-
388
- Args:
389
- server_name: Name of the MCP server to retrieve the resource from
390
- resource_name: Name or URI of the resource to retrieve
391
-
392
- Returns:
393
- List of EmbeddedResource objects ready to use in a PromptMessageMultipart
394
-
395
- Raises:
396
- ValueError: If the server doesn't exist or the resource couldn't be found
397
- """
398
- # Get the raw resource result
399
- result: ReadResourceResult = await super().get_resource(
400
- server_name, resource_name
401
- )
402
-
403
- # Convert each resource content to an EmbeddedResource
404
- embedded_resources: List[EmbeddedResource] = []
405
- for resource_content in result.contents:
406
- embedded_resource = EmbeddedResource(
407
- type="resource", resource=resource_content, annotations=None
408
- )
409
- embedded_resources.append(embedded_resource)
410
-
411
- return embedded_resources
412
-
413
- async def with_resource(
414
- self,
415
- prompt_content: Union[str, PromptMessageMultipart],
416
- server_name: str,
417
- resource_name: str,
418
- ) -> str:
419
- """
420
- Create a prompt with the given content and resource, then send it to the agent.
421
-
422
- Args:
423
- prompt_content: Either a string message or an existing PromptMessageMultipart
424
- server_name: Name of the MCP server to retrieve the resource from
425
- resource_name: Name or URI of the resource to retrieve
426
-
427
- Returns:
428
- The agent's response as a string
429
- """
430
- # Get the embedded resources
431
- embedded_resources: List[EmbeddedResource] = await self.get_embedded_resources(
432
- server_name, resource_name
433
- )
434
-
435
- # Create or update the prompt message
436
- prompt: PromptMessageMultipart
437
- if isinstance(prompt_content, str):
438
- # Create a new prompt with the text and resources
439
- content = [TextContent(type="text", text=prompt_content)]
440
- content.extend(embedded_resources)
441
- prompt = PromptMessageMultipart(role="user", content=content)
442
- elif isinstance(prompt_content, PromptMessageMultipart):
443
- # Add resources to the existing prompt
444
- prompt = prompt_content
445
- prompt.content.extend(embedded_resources)
446
- else:
447
- raise TypeError("prompt_content must be a string or PromptMessageMultipart")
448
-
449
- # Send the prompt to the agent and return the response
450
- return await self._llm.generate_prompt(prompt, None)