fast-agent-mcp 0.2.58__py3-none-any.whl → 0.3.1__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (234) hide show
  1. fast_agent/__init__.py +127 -0
  2. fast_agent/agents/__init__.py +36 -0
  3. {mcp_agent/core → fast_agent/agents}/agent_types.py +2 -1
  4. fast_agent/agents/llm_agent.py +217 -0
  5. fast_agent/agents/llm_decorator.py +486 -0
  6. mcp_agent/agents/base_agent.py → fast_agent/agents/mcp_agent.py +377 -385
  7. fast_agent/agents/tool_agent.py +168 -0
  8. {mcp_agent → fast_agent}/agents/workflow/chain_agent.py +43 -33
  9. {mcp_agent → fast_agent}/agents/workflow/evaluator_optimizer.py +31 -35
  10. {mcp_agent → fast_agent}/agents/workflow/iterative_planner.py +56 -47
  11. {mcp_agent → fast_agent}/agents/workflow/orchestrator_models.py +4 -4
  12. {mcp_agent → fast_agent}/agents/workflow/parallel_agent.py +34 -41
  13. {mcp_agent → fast_agent}/agents/workflow/router_agent.py +54 -39
  14. {mcp_agent → fast_agent}/cli/__main__.py +5 -3
  15. {mcp_agent → fast_agent}/cli/commands/check_config.py +95 -66
  16. {mcp_agent → fast_agent}/cli/commands/go.py +20 -11
  17. {mcp_agent → fast_agent}/cli/commands/quickstart.py +4 -4
  18. {mcp_agent → fast_agent}/cli/commands/server_helpers.py +1 -1
  19. {mcp_agent → fast_agent}/cli/commands/setup.py +75 -134
  20. {mcp_agent → fast_agent}/cli/commands/url_parser.py +9 -8
  21. {mcp_agent → fast_agent}/cli/main.py +36 -16
  22. {mcp_agent → fast_agent}/cli/terminal.py +2 -2
  23. {mcp_agent → fast_agent}/config.py +10 -2
  24. fast_agent/constants.py +8 -0
  25. {mcp_agent → fast_agent}/context.py +24 -19
  26. {mcp_agent → fast_agent}/context_dependent.py +9 -5
  27. fast_agent/core/__init__.py +52 -0
  28. {mcp_agent → fast_agent}/core/agent_app.py +39 -36
  29. fast_agent/core/core_app.py +135 -0
  30. {mcp_agent → fast_agent}/core/direct_decorators.py +12 -26
  31. {mcp_agent → fast_agent}/core/direct_factory.py +95 -73
  32. {mcp_agent → fast_agent/core}/executor/executor.py +4 -5
  33. {mcp_agent → fast_agent}/core/fastagent.py +32 -32
  34. fast_agent/core/logging/__init__.py +5 -0
  35. {mcp_agent → fast_agent/core}/logging/events.py +3 -3
  36. {mcp_agent → fast_agent/core}/logging/json_serializer.py +1 -1
  37. {mcp_agent → fast_agent/core}/logging/listeners.py +85 -7
  38. {mcp_agent → fast_agent/core}/logging/logger.py +7 -7
  39. {mcp_agent → fast_agent/core}/logging/transport.py +10 -11
  40. fast_agent/core/prompt.py +9 -0
  41. {mcp_agent → fast_agent}/core/validation.py +4 -4
  42. fast_agent/event_progress.py +61 -0
  43. fast_agent/history/history_exporter.py +44 -0
  44. {mcp_agent → fast_agent}/human_input/__init__.py +9 -12
  45. {mcp_agent → fast_agent}/human_input/elicitation_handler.py +26 -8
  46. {mcp_agent → fast_agent}/human_input/elicitation_state.py +7 -7
  47. {mcp_agent → fast_agent}/human_input/simple_form.py +6 -4
  48. {mcp_agent → fast_agent}/human_input/types.py +1 -18
  49. fast_agent/interfaces.py +228 -0
  50. fast_agent/llm/__init__.py +9 -0
  51. mcp_agent/llm/augmented_llm.py → fast_agent/llm/fastagent_llm.py +127 -218
  52. fast_agent/llm/internal/passthrough.py +137 -0
  53. mcp_agent/llm/augmented_llm_playback.py → fast_agent/llm/internal/playback.py +29 -25
  54. mcp_agent/llm/augmented_llm_silent.py → fast_agent/llm/internal/silent.py +10 -17
  55. fast_agent/llm/internal/slow.py +38 -0
  56. {mcp_agent → fast_agent}/llm/memory.py +40 -30
  57. {mcp_agent → fast_agent}/llm/model_database.py +35 -2
  58. {mcp_agent → fast_agent}/llm/model_factory.py +103 -77
  59. fast_agent/llm/model_info.py +126 -0
  60. {mcp_agent/llm/providers → fast_agent/llm/provider/anthropic}/anthropic_utils.py +7 -7
  61. fast_agent/llm/provider/anthropic/llm_anthropic.py +603 -0
  62. {mcp_agent/llm/providers → fast_agent/llm/provider/anthropic}/multipart_converter_anthropic.py +79 -86
  63. {mcp_agent/llm/providers → fast_agent/llm/provider/bedrock}/bedrock_utils.py +3 -1
  64. mcp_agent/llm/providers/augmented_llm_bedrock.py → fast_agent/llm/provider/bedrock/llm_bedrock.py +833 -717
  65. {mcp_agent/llm/providers → fast_agent/llm/provider/google}/google_converter.py +66 -14
  66. fast_agent/llm/provider/google/llm_google_native.py +431 -0
  67. mcp_agent/llm/providers/augmented_llm_aliyun.py → fast_agent/llm/provider/openai/llm_aliyun.py +6 -7
  68. mcp_agent/llm/providers/augmented_llm_azure.py → fast_agent/llm/provider/openai/llm_azure.py +4 -4
  69. mcp_agent/llm/providers/augmented_llm_deepseek.py → fast_agent/llm/provider/openai/llm_deepseek.py +10 -11
  70. mcp_agent/llm/providers/augmented_llm_generic.py → fast_agent/llm/provider/openai/llm_generic.py +4 -4
  71. mcp_agent/llm/providers/augmented_llm_google_oai.py → fast_agent/llm/provider/openai/llm_google_oai.py +4 -4
  72. mcp_agent/llm/providers/augmented_llm_groq.py → fast_agent/llm/provider/openai/llm_groq.py +14 -16
  73. mcp_agent/llm/providers/augmented_llm_openai.py → fast_agent/llm/provider/openai/llm_openai.py +133 -207
  74. mcp_agent/llm/providers/augmented_llm_openrouter.py → fast_agent/llm/provider/openai/llm_openrouter.py +6 -6
  75. mcp_agent/llm/providers/augmented_llm_tensorzero_openai.py → fast_agent/llm/provider/openai/llm_tensorzero_openai.py +17 -16
  76. mcp_agent/llm/providers/augmented_llm_xai.py → fast_agent/llm/provider/openai/llm_xai.py +6 -6
  77. {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/multipart_converter_openai.py +125 -63
  78. {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/openai_multipart.py +12 -12
  79. {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/openai_utils.py +18 -16
  80. {mcp_agent → fast_agent}/llm/provider_key_manager.py +2 -2
  81. {mcp_agent → fast_agent}/llm/provider_types.py +2 -0
  82. {mcp_agent → fast_agent}/llm/sampling_converter.py +15 -12
  83. {mcp_agent → fast_agent}/llm/usage_tracking.py +23 -5
  84. fast_agent/mcp/__init__.py +54 -0
  85. {mcp_agent → fast_agent}/mcp/elicitation_factory.py +3 -3
  86. {mcp_agent → fast_agent}/mcp/elicitation_handlers.py +19 -10
  87. {mcp_agent → fast_agent}/mcp/gen_client.py +3 -3
  88. fast_agent/mcp/helpers/__init__.py +36 -0
  89. fast_agent/mcp/helpers/content_helpers.py +183 -0
  90. {mcp_agent → fast_agent}/mcp/helpers/server_config_helpers.py +8 -8
  91. {mcp_agent → fast_agent}/mcp/hf_auth.py +25 -23
  92. fast_agent/mcp/interfaces.py +93 -0
  93. {mcp_agent → fast_agent}/mcp/logger_textio.py +4 -4
  94. {mcp_agent → fast_agent}/mcp/mcp_agent_client_session.py +49 -44
  95. {mcp_agent → fast_agent}/mcp/mcp_aggregator.py +66 -115
  96. {mcp_agent → fast_agent}/mcp/mcp_connection_manager.py +16 -23
  97. {mcp_agent/core → fast_agent/mcp}/mcp_content.py +23 -15
  98. {mcp_agent → fast_agent}/mcp/mime_utils.py +39 -0
  99. fast_agent/mcp/prompt.py +159 -0
  100. mcp_agent/mcp/prompt_message_multipart.py → fast_agent/mcp/prompt_message_extended.py +27 -20
  101. {mcp_agent → fast_agent}/mcp/prompt_render.py +21 -19
  102. {mcp_agent → fast_agent}/mcp/prompt_serialization.py +46 -46
  103. fast_agent/mcp/prompts/__main__.py +7 -0
  104. {mcp_agent → fast_agent}/mcp/prompts/prompt_helpers.py +31 -30
  105. {mcp_agent → fast_agent}/mcp/prompts/prompt_load.py +8 -8
  106. {mcp_agent → fast_agent}/mcp/prompts/prompt_server.py +11 -19
  107. {mcp_agent → fast_agent}/mcp/prompts/prompt_template.py +18 -18
  108. {mcp_agent → fast_agent}/mcp/resource_utils.py +1 -1
  109. {mcp_agent → fast_agent}/mcp/sampling.py +31 -26
  110. {mcp_agent/mcp_server → fast_agent/mcp/server}/__init__.py +1 -1
  111. {mcp_agent/mcp_server → fast_agent/mcp/server}/agent_server.py +5 -6
  112. fast_agent/mcp/ui_agent.py +48 -0
  113. fast_agent/mcp/ui_mixin.py +209 -0
  114. fast_agent/mcp_server_registry.py +90 -0
  115. {mcp_agent → fast_agent}/resources/examples/data-analysis/analysis-campaign.py +5 -4
  116. {mcp_agent → fast_agent}/resources/examples/data-analysis/analysis.py +1 -1
  117. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/forms_demo.py +3 -3
  118. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/game_character.py +2 -2
  119. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/game_character_handler.py +1 -1
  120. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/tool_call.py +1 -1
  121. {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/agent_one.py +1 -1
  122. {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/agent_two.py +1 -1
  123. {mcp_agent → fast_agent}/resources/examples/researcher/researcher-eval.py +1 -1
  124. {mcp_agent → fast_agent}/resources/examples/researcher/researcher-imp.py +1 -1
  125. {mcp_agent → fast_agent}/resources/examples/researcher/researcher.py +1 -1
  126. {mcp_agent → fast_agent}/resources/examples/tensorzero/agent.py +2 -2
  127. {mcp_agent → fast_agent}/resources/examples/tensorzero/image_demo.py +3 -3
  128. {mcp_agent → fast_agent}/resources/examples/tensorzero/simple_agent.py +1 -1
  129. {mcp_agent → fast_agent}/resources/examples/workflows/chaining.py +1 -1
  130. {mcp_agent → fast_agent}/resources/examples/workflows/evaluator.py +3 -3
  131. {mcp_agent → fast_agent}/resources/examples/workflows/human_input.py +5 -3
  132. {mcp_agent → fast_agent}/resources/examples/workflows/orchestrator.py +1 -1
  133. {mcp_agent → fast_agent}/resources/examples/workflows/parallel.py +2 -2
  134. {mcp_agent → fast_agent}/resources/examples/workflows/router.py +5 -2
  135. fast_agent/resources/setup/.gitignore +24 -0
  136. fast_agent/resources/setup/agent.py +18 -0
  137. fast_agent/resources/setup/fastagent.config.yaml +44 -0
  138. fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
  139. fast_agent/resources/setup/pyproject.toml.tmpl +17 -0
  140. fast_agent/tools/elicitation.py +369 -0
  141. fast_agent/types/__init__.py +32 -0
  142. fast_agent/types/llm_stop_reason.py +77 -0
  143. fast_agent/ui/__init__.py +38 -0
  144. fast_agent/ui/console_display.py +1005 -0
  145. {mcp_agent/human_input → fast_agent/ui}/elicitation_form.py +17 -12
  146. mcp_agent/human_input/elicitation_forms.py → fast_agent/ui/elicitation_style.py +1 -1
  147. {mcp_agent/core → fast_agent/ui}/enhanced_prompt.py +96 -25
  148. {mcp_agent/core → fast_agent/ui}/interactive_prompt.py +330 -125
  149. fast_agent/ui/mcp_ui_utils.py +224 -0
  150. {mcp_agent → fast_agent/ui}/progress_display.py +2 -2
  151. {mcp_agent/logging → fast_agent/ui}/rich_progress.py +4 -4
  152. {mcp_agent/core → fast_agent/ui}/usage_display.py +3 -8
  153. {fast_agent_mcp-0.2.58.dist-info → fast_agent_mcp-0.3.1.dist-info}/METADATA +7 -7
  154. fast_agent_mcp-0.3.1.dist-info/RECORD +203 -0
  155. fast_agent_mcp-0.3.1.dist-info/entry_points.txt +5 -0
  156. fast_agent_mcp-0.2.58.dist-info/RECORD +0 -193
  157. fast_agent_mcp-0.2.58.dist-info/entry_points.txt +0 -6
  158. mcp_agent/__init__.py +0 -114
  159. mcp_agent/agents/agent.py +0 -92
  160. mcp_agent/agents/workflow/__init__.py +0 -1
  161. mcp_agent/agents/workflow/orchestrator_agent.py +0 -597
  162. mcp_agent/app.py +0 -175
  163. mcp_agent/core/__init__.py +0 -26
  164. mcp_agent/core/prompt.py +0 -191
  165. mcp_agent/event_progress.py +0 -134
  166. mcp_agent/human_input/handler.py +0 -81
  167. mcp_agent/llm/__init__.py +0 -2
  168. mcp_agent/llm/augmented_llm_passthrough.py +0 -232
  169. mcp_agent/llm/augmented_llm_slow.py +0 -53
  170. mcp_agent/llm/providers/__init__.py +0 -8
  171. mcp_agent/llm/providers/augmented_llm_anthropic.py +0 -718
  172. mcp_agent/llm/providers/augmented_llm_google_native.py +0 -496
  173. mcp_agent/llm/providers/sampling_converter_anthropic.py +0 -57
  174. mcp_agent/llm/providers/sampling_converter_openai.py +0 -26
  175. mcp_agent/llm/sampling_format_converter.py +0 -37
  176. mcp_agent/logging/__init__.py +0 -0
  177. mcp_agent/mcp/__init__.py +0 -50
  178. mcp_agent/mcp/helpers/__init__.py +0 -25
  179. mcp_agent/mcp/helpers/content_helpers.py +0 -187
  180. mcp_agent/mcp/interfaces.py +0 -266
  181. mcp_agent/mcp/prompts/__init__.py +0 -0
  182. mcp_agent/mcp/prompts/__main__.py +0 -10
  183. mcp_agent/mcp_server_registry.py +0 -343
  184. mcp_agent/tools/tool_definition.py +0 -14
  185. mcp_agent/ui/console_display.py +0 -790
  186. mcp_agent/ui/console_display_legacy.py +0 -401
  187. {mcp_agent → fast_agent}/agents/workflow/orchestrator_prompts.py +0 -0
  188. {mcp_agent/agents → fast_agent/cli}/__init__.py +0 -0
  189. {mcp_agent → fast_agent}/cli/constants.py +0 -0
  190. {mcp_agent → fast_agent}/core/error_handling.py +0 -0
  191. {mcp_agent → fast_agent}/core/exceptions.py +0 -0
  192. {mcp_agent/cli → fast_agent/core/executor}/__init__.py +0 -0
  193. {mcp_agent → fast_agent/core}/executor/task_registry.py +0 -0
  194. {mcp_agent → fast_agent/core}/executor/workflow_signal.py +0 -0
  195. {mcp_agent → fast_agent}/human_input/form_fields.py +0 -0
  196. {mcp_agent → fast_agent}/llm/prompt_utils.py +0 -0
  197. {mcp_agent/core → fast_agent/llm}/request_params.py +0 -0
  198. {mcp_agent → fast_agent}/mcp/common.py +0 -0
  199. {mcp_agent/executor → fast_agent/mcp/prompts}/__init__.py +0 -0
  200. {mcp_agent → fast_agent}/mcp/prompts/prompt_constants.py +0 -0
  201. {mcp_agent → fast_agent}/py.typed +0 -0
  202. {mcp_agent → fast_agent}/resources/examples/data-analysis/fastagent.config.yaml +0 -0
  203. {mcp_agent → fast_agent}/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -0
  204. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_account_server.py +0 -0
  205. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_forms_server.py +0 -0
  206. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_game_server.py +0 -0
  207. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/fastagent.config.yaml +0 -0
  208. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +0 -0
  209. {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/fastagent.config.yaml +0 -0
  210. {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +0 -0
  211. {mcp_agent → fast_agent}/resources/examples/researcher/fastagent.config.yaml +0 -0
  212. {mcp_agent → fast_agent}/resources/examples/tensorzero/.env.sample +0 -0
  213. {mcp_agent → fast_agent}/resources/examples/tensorzero/Makefile +0 -0
  214. {mcp_agent → fast_agent}/resources/examples/tensorzero/README.md +0 -0
  215. {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
  216. {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/crab.png +0 -0
  217. {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
  218. {mcp_agent → fast_agent}/resources/examples/tensorzero/docker-compose.yml +0 -0
  219. {mcp_agent → fast_agent}/resources/examples/tensorzero/fastagent.config.yaml +0 -0
  220. {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/Dockerfile +0 -0
  221. {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/entrypoint.sh +0 -0
  222. {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/mcp_server.py +0 -0
  223. {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/pyproject.toml +0 -0
  224. {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/system_schema.json +0 -0
  225. {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +0 -0
  226. {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +0 -0
  227. {mcp_agent → fast_agent}/resources/examples/workflows/fastagent.config.yaml +0 -0
  228. {mcp_agent → fast_agent}/resources/examples/workflows/graded_report.md +0 -0
  229. {mcp_agent → fast_agent}/resources/examples/workflows/short_story.md +0 -0
  230. {mcp_agent → fast_agent}/resources/examples/workflows/short_story.txt +0 -0
  231. {mcp_agent → fast_agent/ui}/console.py +0 -0
  232. {mcp_agent/core → fast_agent/ui}/mermaid_utils.py +0 -0
  233. {fast_agent_mcp-0.2.58.dist-info → fast_agent_mcp-0.3.1.dist-info}/WHEEL +0 -0
  234. {fast_agent_mcp-0.2.58.dist-info → fast_agent_mcp-0.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -9,13 +9,24 @@ import time
9
9
  from typing import List, Optional, Union
10
10
 
11
11
  # Proper type imports for each provider
12
- from anthropic.types import Usage as AnthropicUsage
13
- from google.genai.types import GenerateContentResponseUsageMetadata as GoogleUsage
14
- from openai.types.completion_usage import CompletionUsage as OpenAIUsage
12
+ try:
13
+ from anthropic.types import Usage as AnthropicUsage
14
+ except Exception: # pragma: no cover - optional dependency
15
+ AnthropicUsage = object # type: ignore
16
+
17
+ try:
18
+ from google.genai.types import GenerateContentResponseUsageMetadata as GoogleUsage
19
+ except Exception: # pragma: no cover - optional dependency
20
+ GoogleUsage = object # type: ignore
21
+
22
+ try:
23
+ from openai.types.completion_usage import CompletionUsage as OpenAIUsage
24
+ except Exception: # pragma: no cover - optional dependency
25
+ OpenAIUsage = object # type: ignore
15
26
  from pydantic import BaseModel, Field, computed_field
16
27
 
17
- from mcp_agent.llm.model_database import ModelDatabase
18
- from mcp_agent.llm.provider_types import Provider
28
+ from fast_agent.llm.model_database import ModelDatabase
29
+ from fast_agent.llm.provider_types import Provider
19
30
 
20
31
 
21
32
  # Fast-agent specific usage type for synthetic providers
@@ -230,6 +241,13 @@ class UsageAccumulator(BaseModel):
230
241
  if self.model is None:
231
242
  self.model = turn.model
232
243
 
244
+ # add tool call count to the last turn (if present)
245
+ # not ideal way to do it, but works well enough. full history would be available through the
246
+ # message_history; maybe we consolidate there and put turn_usage on the turn.
247
+ def count_tools(self, tool_calls: int) -> None:
248
+ if self.turns and self.turns[-1]:
249
+ self.turns[-1].tool_calls = tool_calls
250
+
233
251
  @computed_field
234
252
  @property
235
253
  def cumulative_input_tokens(self) -> int:
@@ -0,0 +1,54 @@
1
+ """
2
+ MCP utilities and types for fast-agent.
3
+
4
+ Public API:
5
+ - `Prompt`: helper for constructing MCP prompts/messages.
6
+ - `PromptMessageExtended`: canonical message container used internally by providers.
7
+ - Helpers from `fast_agent.mcp.helpers` (re-exported for convenience).
8
+
9
+ Note: Backward compatibility for legacy `PromptMessageMultipart` imports is handled
10
+ via `fast_agent.mcp.prompt_message_multipart`, which subclasses `PromptMessageExtended`.
11
+ """
12
+
13
+ from .helpers import (
14
+ ensure_multipart_messages,
15
+ get_image_data,
16
+ get_resource_text,
17
+ get_resource_uri,
18
+ get_text,
19
+ is_image_content,
20
+ is_resource_content,
21
+ is_resource_link,
22
+ is_text_content,
23
+ normalize_to_extended_list,
24
+ split_thinking_content,
25
+ text_content,
26
+ )
27
+ from .prompt_message_extended import PromptMessageExtended
28
+
29
+ __all__ = [
30
+ "Prompt",
31
+ "PromptMessageExtended",
32
+ # Helpers
33
+ "get_text",
34
+ "get_image_data",
35
+ "get_resource_uri",
36
+ "is_text_content",
37
+ "is_image_content",
38
+ "is_resource_content",
39
+ "is_resource_link",
40
+ "get_resource_text",
41
+ "ensure_multipart_messages",
42
+ "normalize_to_extended_list",
43
+ "split_thinking_content",
44
+ "text_content",
45
+ ]
46
+
47
+
48
+ def __getattr__(name: str):
49
+ # Lazily import to avoid circular imports with fast_agent.types
50
+ if name == "Prompt":
51
+ from .prompt import Prompt # local import
52
+
53
+ return Prompt
54
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
@@ -6,9 +6,9 @@ from typing import Any, Optional
6
6
 
7
7
  from mcp.client.session import ElicitationFnT
8
8
 
9
- from mcp_agent.core.agent_types import AgentConfig
10
- from mcp_agent.logging.logger import get_logger
11
- from mcp_agent.mcp.elicitation_handlers import (
9
+ from fast_agent.agents.agent_types import AgentConfig
10
+ from fast_agent.core.logging.logger import get_logger
11
+ from fast_agent.mcp.elicitation_handlers import (
12
12
  auto_cancel_elicitation_handler,
13
13
  forms_elicitation_handler,
14
14
  )
@@ -8,10 +8,10 @@ from typing import TYPE_CHECKING, Any
8
8
  from mcp.shared.context import RequestContext
9
9
  from mcp.types import ElicitRequestParams, ElicitResult, ErrorData
10
10
 
11
- from mcp_agent.human_input.elicitation_handler import elicitation_input_callback
12
- from mcp_agent.human_input.types import HumanInputRequest
13
- from mcp_agent.logging.logger import get_logger
14
- from mcp_agent.mcp.helpers.server_config_helpers import get_server_config
11
+ from fast_agent.core.logging.logger import get_logger
12
+ from fast_agent.human_input.elicitation_handler import elicitation_input_callback
13
+ from fast_agent.human_input.types import HumanInputRequest
14
+ from fast_agent.mcp.helpers.server_config_helpers import get_server_config
15
15
 
16
16
  if TYPE_CHECKING:
17
17
  from mcp import ClientSession
@@ -24,7 +24,7 @@ async def auto_cancel_elicitation_handler(
24
24
  params: ElicitRequestParams,
25
25
  ) -> ElicitResult | ErrorData:
26
26
  """Handler that automatically cancels all elicitation requests.
27
-
27
+
28
28
  Useful for production deployments where you want to advertise elicitation
29
29
  capability but automatically decline all requests.
30
30
  """
@@ -51,7 +51,8 @@ async def forms_elicitation_handler(
51
51
  agent_name: str | None = None
52
52
 
53
53
  # 1. Check if we have an MCPAgentClientSession in the context
54
- from mcp_agent.mcp.mcp_agent_client_session import MCPAgentClientSession
54
+ from fast_agent.mcp.mcp_agent_client_session import MCPAgentClientSession
55
+
55
56
  if hasattr(context, "session") and isinstance(context.session, MCPAgentClientSession):
56
57
  agent_name = context.session.agent_name
57
58
 
@@ -90,9 +91,17 @@ async def forms_elicitation_handler(
90
91
  elif response_data == "__CANCELLED__":
91
92
  return ElicitResult(action="cancel")
92
93
  elif response_data == "__DISABLE_SERVER__":
93
- # Log that user wants to disable elicitation for this server
94
- logger.warning(f"User requested to disable elicitation for server: {server_name}")
95
- # For now, just cancel - in a full implementation, this would update server config
94
+ # Respect user's request: disable elicitation for this server for this session
95
+ logger.warning(
96
+ f"User requested to disable elicitation for server: {server_name} disabling for session"
97
+ )
98
+ try:
99
+ from fast_agent.human_input.elicitation_state import elicitation_state
100
+
101
+ elicitation_state.disable_server(server_name)
102
+ except Exception:
103
+ # Do not fail the flow if state update fails
104
+ pass
96
105
  return ElicitResult(action="cancel")
97
106
 
98
107
  # Parse response based on schema if provided
@@ -152,4 +161,4 @@ async def forms_elicitation_handler(
152
161
  return ElicitResult(action="accept", content=content)
153
162
  except (KeyboardInterrupt, EOFError, TimeoutError):
154
163
  # User cancelled or timeout
155
- return ElicitResult(action="cancel")
164
+ return ElicitResult(action="cancel")
@@ -5,9 +5,9 @@ from typing import AsyncGenerator, Callable
5
5
  from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
6
6
  from mcp import ClientSession
7
7
 
8
- from mcp_agent.logging.logger import get_logger
9
- from mcp_agent.mcp.interfaces import ServerRegistryProtocol
10
- from mcp_agent.mcp.mcp_agent_client_session import MCPAgentClientSession
8
+ from fast_agent.core.logging.logger import get_logger
9
+ from fast_agent.mcp.interfaces import ServerRegistryProtocol
10
+ from fast_agent.mcp.mcp_agent_client_session import MCPAgentClientSession
11
11
 
12
12
  logger = get_logger(__name__)
13
13
 
@@ -0,0 +1,36 @@
1
+ """
2
+ Helper modules for working with MCP content (Fast Agent namespace).
3
+
4
+ This mirrors the legacy fast_agent.mcp.helpers API to provide a stable,
5
+ cycle-free import path now that PromptMessageExtended lives in fast_agent.mcp.
6
+ """
7
+
8
+ from .content_helpers import (
9
+ ensure_multipart_messages,
10
+ get_image_data,
11
+ get_resource_text,
12
+ get_resource_uri,
13
+ get_text,
14
+ is_image_content,
15
+ is_resource_content,
16
+ is_resource_link,
17
+ is_text_content,
18
+ normalize_to_extended_list,
19
+ split_thinking_content,
20
+ text_content,
21
+ )
22
+
23
+ __all__ = [
24
+ "get_text",
25
+ "get_image_data",
26
+ "get_resource_uri",
27
+ "is_text_content",
28
+ "is_image_content",
29
+ "is_resource_content",
30
+ "is_resource_link",
31
+ "get_resource_text",
32
+ "ensure_multipart_messages",
33
+ "normalize_to_extended_list",
34
+ "split_thinking_content",
35
+ "text_content",
36
+ ]
@@ -0,0 +1,183 @@
1
+ """
2
+ Helper functions for working with content objects (Fast Agent namespace).
3
+
4
+ """
5
+
6
+ from typing import TYPE_CHECKING, List, Optional, Sequence, Union
7
+
8
+ if TYPE_CHECKING:
9
+ from fast_agent.mcp.prompt_message_extended import PromptMessageExtended
10
+
11
+ from mcp.types import (
12
+ BlobResourceContents,
13
+ ContentBlock,
14
+ EmbeddedResource,
15
+ ImageContent,
16
+ PromptMessage,
17
+ ReadResourceResult,
18
+ ResourceLink,
19
+ TextContent,
20
+ TextResourceContents,
21
+ )
22
+
23
+
24
+ def get_text(content: ContentBlock) -> Optional[str]:
25
+ """Extract text content from a content object if available."""
26
+ if isinstance(content, TextContent):
27
+ return content.text
28
+
29
+ if isinstance(content, TextResourceContents):
30
+ return content.text
31
+
32
+ if isinstance(content, EmbeddedResource):
33
+ if isinstance(content.resource, TextResourceContents):
34
+ return content.resource.text
35
+
36
+ if isinstance(content, ResourceLink):
37
+ name = content.name or "unknown"
38
+ uri_str = str(content.uri)
39
+ mime_type = content.mimeType or "unknown"
40
+ description = content.description or "No description"
41
+
42
+ return (
43
+ f"Linked Resource ${name} MIME type {mime_type}>\n"
44
+ f"Resource Link: {uri_str}\n"
45
+ f"${description}\n"
46
+ )
47
+
48
+ return None
49
+
50
+
51
+ def get_image_data(content: ContentBlock) -> Optional[str]:
52
+ """Extract image data from a content object if available."""
53
+ if isinstance(content, ImageContent):
54
+ return content.data
55
+
56
+ if isinstance(content, EmbeddedResource):
57
+ if isinstance(content.resource, BlobResourceContents):
58
+ return content.resource.blob
59
+
60
+ return None
61
+
62
+
63
+ def get_resource_uri(content: ContentBlock) -> Optional[str]:
64
+ """Extract resource URI from an EmbeddedResource if available."""
65
+ if isinstance(content, EmbeddedResource):
66
+ return str(content.resource.uri)
67
+ return None
68
+
69
+
70
+ def is_text_content(content: ContentBlock) -> bool:
71
+ """Check if the content is text content."""
72
+ return isinstance(content, TextContent) or isinstance(content, TextResourceContents)
73
+
74
+
75
+ def is_image_content(content: Union[TextContent, ImageContent, EmbeddedResource]) -> bool:
76
+ """Check if the content is image content."""
77
+ return isinstance(content, ImageContent)
78
+
79
+
80
+ def is_resource_content(content: ContentBlock) -> bool:
81
+ """Check if the content is an embedded resource."""
82
+ return isinstance(content, EmbeddedResource)
83
+
84
+
85
+ def is_resource_link(content: ContentBlock) -> bool:
86
+ """Check if the content is a resource link."""
87
+ return isinstance(content, ResourceLink)
88
+
89
+
90
+ def get_resource_text(result: ReadResourceResult, index: int = 0) -> Optional[str]:
91
+ """Extract text content from a ReadResourceResult at the specified index."""
92
+ if index >= len(result.contents):
93
+ raise IndexError(
94
+ f"Index {index} out of bounds for contents list of length {len(result.contents)}"
95
+ )
96
+ content = result.contents[index]
97
+ if isinstance(content, TextResourceContents):
98
+ return content.text
99
+ return None
100
+
101
+
102
+ def split_thinking_content(message: str) -> tuple[Optional[str], str]:
103
+ """Split a message into thinking and content parts."""
104
+ import re
105
+
106
+ pattern = r"^<think>(.*?)</think>\s*(.*)$"
107
+ match = re.match(pattern, message, re.DOTALL)
108
+
109
+ if match:
110
+ thinking_content = match.group(1).strip()
111
+ main_content = match.group(2).strip()
112
+ return (thinking_content, main_content)
113
+ else:
114
+ return (None, message)
115
+
116
+
117
+ def text_content(text: str) -> TextContent:
118
+ """Convenience to create a TextContent block from a string."""
119
+ return TextContent(type="text", text=text)
120
+
121
+
122
+ def ensure_multipart_messages(
123
+ messages: List[Union["PromptMessageExtended", PromptMessage]],
124
+ ) -> List["PromptMessageExtended"]:
125
+ """Ensure all messages in a list are PromptMessageExtended objects."""
126
+ # Import here to avoid circular dependency
127
+ from fast_agent.mcp.prompt_message_extended import PromptMessageExtended
128
+
129
+ if not messages:
130
+ return []
131
+
132
+ result = []
133
+ for message in messages:
134
+ if isinstance(message, PromptMessage):
135
+ result.append(PromptMessageExtended(role=message.role, content=[message.content]))
136
+ else:
137
+ result.append(message)
138
+
139
+ return result
140
+
141
+
142
+ def normalize_to_extended_list(
143
+ messages: Union[
144
+ str,
145
+ PromptMessage,
146
+ "PromptMessageExtended",
147
+ Sequence[Union[str, PromptMessage, "PromptMessageExtended"]],
148
+ ],
149
+ ) -> List["PromptMessageExtended"]:
150
+ """Normalize various input types to a list of PromptMessageExtended objects."""
151
+ # Import here to avoid circular dependency
152
+ from fast_agent.mcp.prompt_message_extended import PromptMessageExtended
153
+
154
+ if messages is None:
155
+ return []
156
+
157
+ # Single string → convert to user PromptMessageExtended
158
+ if isinstance(messages, str):
159
+ return [
160
+ PromptMessageExtended(role="user", content=[TextContent(type="text", text=messages)])
161
+ ]
162
+
163
+ # Single PromptMessage → convert to PromptMessageExtended
164
+ if isinstance(messages, PromptMessage):
165
+ return [PromptMessageExtended(role=messages.role, content=[messages.content])]
166
+
167
+ # Single PromptMessageExtended → wrap in a list
168
+ if isinstance(messages, PromptMessageExtended):
169
+ return [messages]
170
+
171
+ # List of mixed types → convert each element
172
+ result: List[PromptMessageExtended] = []
173
+ for item in messages:
174
+ if isinstance(item, str):
175
+ result.append(
176
+ PromptMessageExtended(role="user", content=[TextContent(type="text", text=item)])
177
+ )
178
+ elif isinstance(item, PromptMessage):
179
+ result.append(PromptMessageExtended(role=item.role, content=[item.content]))
180
+ else:
181
+ result.append(item)
182
+
183
+ return result
@@ -3,23 +3,23 @@
3
3
  from typing import TYPE_CHECKING, Any, Optional
4
4
 
5
5
  if TYPE_CHECKING:
6
- from mcp_agent.config import MCPServerSettings
6
+ from fast_agent.config import MCPServerSettings
7
7
 
8
8
 
9
9
  def get_server_config(ctx: Any) -> Optional["MCPServerSettings"]:
10
10
  """Extract server config from context if available.
11
-
11
+
12
12
  Type guard helper that safely accesses server_config with proper type checking.
13
13
  """
14
14
  # Import here to avoid circular import
15
- from mcp_agent.mcp.mcp_agent_client_session import MCPAgentClientSession
16
-
15
+ from fast_agent.mcp.mcp_agent_client_session import MCPAgentClientSession
16
+
17
17
  # Check if ctx has a session attribute (RequestContext case)
18
18
  if hasattr(ctx, "session"):
19
- if isinstance(ctx.session, MCPAgentClientSession) and hasattr(ctx.session, 'server_config'):
19
+ if isinstance(ctx.session, MCPAgentClientSession) and hasattr(ctx.session, "server_config"):
20
20
  return ctx.session.server_config
21
21
  # Also check if ctx itself is MCPAgentClientSession (direct call case)
22
- elif isinstance(ctx, MCPAgentClientSession) and hasattr(ctx, 'server_config'):
22
+ elif isinstance(ctx, MCPAgentClientSession) and hasattr(ctx, "server_config"):
23
23
  return ctx.server_config
24
-
25
- return None
24
+
25
+ return None
@@ -8,10 +8,10 @@ from urllib.parse import urlparse
8
8
  def is_huggingface_url(url: str) -> bool:
9
9
  """
10
10
  Check if a URL is a HuggingFace URL that should receive HF_TOKEN authentication.
11
-
11
+
12
12
  Args:
13
13
  url: The URL to check
14
-
14
+
15
15
  Returns:
16
16
  True if the URL is a HuggingFace URL, False otherwise
17
17
  """
@@ -20,11 +20,11 @@ def is_huggingface_url(url: str) -> bool:
20
20
  hostname = parsed.hostname
21
21
  if hostname is None:
22
22
  return False
23
-
23
+
24
24
  # Check for HuggingFace domains
25
25
  if hostname in {"hf.co", "huggingface.co"}:
26
26
  return True
27
-
27
+
28
28
  # Check for HuggingFace Spaces (*.hf.space)
29
29
  # Use endswith to match subdomains like space-name.hf.space
30
30
  # but ensure exact match to prevent spoofing like evil.hf.space.com
@@ -35,12 +35,14 @@ def is_huggingface_url(url: str) -> bool:
35
35
  if len(parts) == 3 and parts[-2:] == ["hf", "space"]:
36
36
  space_name = parts[0]
37
37
  # Validate space name: not empty, not just hyphens/dots, no spaces
38
- return (len(space_name) > 0 and
39
- space_name != "-" and
40
- not space_name.startswith(".") and
41
- not space_name.endswith(".") and
42
- " " not in space_name)
43
-
38
+ return (
39
+ len(space_name) > 0
40
+ and space_name != "-"
41
+ and not space_name.startswith(".")
42
+ and not space_name.endswith(".")
43
+ and " " not in space_name
44
+ )
45
+
44
46
  return False
45
47
  except Exception:
46
48
  return False
@@ -49,7 +51,7 @@ def is_huggingface_url(url: str) -> bool:
49
51
  def get_hf_token_from_env() -> Optional[str]:
50
52
  """
51
53
  Get the HuggingFace token from the HF_TOKEN environment variable.
52
-
54
+
53
55
  Returns:
54
56
  The HF_TOKEN value if set, None otherwise
55
57
  """
@@ -59,11 +61,11 @@ def get_hf_token_from_env() -> Optional[str]:
59
61
  def should_add_hf_auth(url: str, existing_headers: Optional[Dict[str, str]]) -> bool:
60
62
  """
61
63
  Determine if HuggingFace authentication should be added to the headers.
62
-
64
+
63
65
  Args:
64
66
  url: The URL to check
65
67
  existing_headers: Existing headers dictionary (may be None)
66
-
68
+
67
69
  Returns:
68
70
  True if HF auth should be added, False otherwise
69
71
  """
@@ -71,10 +73,10 @@ def should_add_hf_auth(url: str, existing_headers: Optional[Dict[str, str]]) ->
71
73
  # 1. URL is a HuggingFace URL
72
74
  # 2. No existing Authorization/X-HF-Authorization header is set
73
75
  # 3. HF_TOKEN environment variable is available
74
-
76
+
75
77
  if not is_huggingface_url(url):
76
78
  return False
77
-
79
+
78
80
  if existing_headers:
79
81
  # Check if this is a .hf.space domain
80
82
  try:
@@ -92,31 +94,31 @@ def should_add_hf_auth(url: str, existing_headers: Optional[Dict[str, str]]) ->
92
94
  # Fallback to checking Authorization header
93
95
  if "Authorization" in existing_headers:
94
96
  return False
95
-
97
+
96
98
  return get_hf_token_from_env() is not None
97
99
 
98
100
 
99
101
  def add_hf_auth_header(url: str, headers: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]:
100
102
  """
101
103
  Add HuggingFace authentication header if appropriate.
102
-
104
+
103
105
  Args:
104
106
  url: The URL to check
105
107
  headers: Existing headers dictionary (may be None)
106
-
108
+
107
109
  Returns:
108
110
  Updated headers dictionary with HF auth if appropriate, or original headers
109
111
  """
110
112
  if not should_add_hf_auth(url, headers):
111
113
  return headers
112
-
114
+
113
115
  hf_token = get_hf_token_from_env()
114
116
  if hf_token is None:
115
117
  return headers
116
-
118
+
117
119
  # Create new headers dict or copy existing one
118
120
  result_headers = dict(headers) if headers else {}
119
-
121
+
120
122
  # Check if this is a .hf.space domain
121
123
  try:
122
124
  parsed = urlparse(url)
@@ -130,5 +132,5 @@ def add_hf_auth_header(url: str, headers: Optional[Dict[str, str]]) -> Optional[
130
132
  except Exception:
131
133
  # Fallback to standard Authorization header
132
134
  result_headers["Authorization"] = f"Bearer {hf_token}"
133
-
134
- return result_headers
135
+
136
+ return result_headers
@@ -0,0 +1,93 @@
1
+ """
2
+ Interface definitions to prevent circular imports.
3
+ This module defines protocols (interfaces) that can be used to break circular dependencies.
4
+ """
5
+
6
+ from datetime import timedelta
7
+ from typing import (
8
+ AsyncContextManager,
9
+ Callable,
10
+ Optional,
11
+ Protocol,
12
+ runtime_checkable,
13
+ )
14
+
15
+ from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
16
+ from mcp import ClientSession
17
+
18
+ from fast_agent.interfaces import (
19
+ AgentProtocol,
20
+ FastAgentLLMProtocol,
21
+ LlmAgentProtocol,
22
+ LLMFactoryProtocol,
23
+ ModelFactoryFunctionProtocol,
24
+ ModelT,
25
+ )
26
+
27
+ __all__ = [
28
+ "MCPConnectionManagerProtocol",
29
+ "ServerRegistryProtocol",
30
+ "ServerConnection",
31
+ "FastAgentLLMProtocol",
32
+ "AgentProtocol",
33
+ "LlmAgentProtocol",
34
+ "LLMFactoryProtocol",
35
+ "ModelFactoryFunctionProtocol",
36
+ "ModelT",
37
+ ]
38
+
39
+
40
+ @runtime_checkable
41
+ class MCPConnectionManagerProtocol(Protocol):
42
+ """Protocol for MCPConnectionManager functionality needed by ServerRegistry."""
43
+
44
+ async def get_server(
45
+ self,
46
+ server_name: str,
47
+ client_session_factory: Optional[
48
+ Callable[
49
+ [
50
+ MemoryObjectReceiveStream,
51
+ MemoryObjectSendStream,
52
+ Optional[timedelta],
53
+ ],
54
+ ClientSession,
55
+ ]
56
+ ] = None,
57
+ ) -> "ServerConnection": ...
58
+
59
+ async def disconnect_server(self, server_name: str) -> None: ...
60
+
61
+ async def disconnect_all_servers(self) -> None: ...
62
+
63
+
64
+ @runtime_checkable
65
+ class ServerRegistryProtocol(Protocol):
66
+ """Protocol defining the minimal interface of ServerRegistry needed by gen_client."""
67
+
68
+ @property
69
+ def connection_manager(self) -> MCPConnectionManagerProtocol: ...
70
+
71
+ def initialize_server(
72
+ self,
73
+ server_name: str,
74
+ client_session_factory: Optional[
75
+ Callable[
76
+ [
77
+ MemoryObjectReceiveStream,
78
+ MemoryObjectSendStream,
79
+ Optional[timedelta],
80
+ ],
81
+ ClientSession,
82
+ ]
83
+ ] = None,
84
+ ) -> AsyncContextManager[ClientSession]:
85
+ """Initialize a server and yield a client session."""
86
+ ...
87
+
88
+
89
+ class ServerConnection(Protocol):
90
+ """Protocol for server connection objects returned by MCPConnectionManager."""
91
+
92
+ @property
93
+ def session(self) -> ClientSession: ...
@@ -6,7 +6,7 @@ import io
6
6
  import os
7
7
  from typing import TextIO
8
8
 
9
- from mcp_agent.logging.logger import get_logger
9
+ from fast_agent.core.logging.logger import get_logger
10
10
 
11
11
  logger = get_logger(__name__)
12
12
 
@@ -82,13 +82,13 @@ class LoggerTextIO(TextIO):
82
82
  This prevents output from showing on the terminal
83
83
  while still allowing our write() method to capture it for logging.
84
84
  """
85
- if not hasattr(self, '_devnull_fd'):
85
+ if not hasattr(self, "_devnull_fd"):
86
86
  self._devnull_fd = os.open(os.devnull, os.O_WRONLY)
87
87
  return self._devnull_fd
88
-
88
+
89
89
  def __del__(self):
90
90
  """Clean up the devnull file descriptor."""
91
- if hasattr(self, '_devnull_fd'):
91
+ if hasattr(self, "_devnull_fd"):
92
92
  try:
93
93
  os.close(self._devnull_fd)
94
94
  except (OSError, AttributeError):