fast-agent-mcp 0.2.57__py3-none-any.whl → 0.3.0__py3-none-any.whl

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

Potentially problematic release.


This version of 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 +64 -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 +13 -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 +17 -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 +128 -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. fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
  64. fast_agent/llm/provider/bedrock/llm_bedrock.py +2192 -0
  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 -206
  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 +43 -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/elicitation_forms_server.py +25 -3
  118. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/forms_demo.py +3 -3
  119. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/game_character.py +2 -2
  120. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/game_character_handler.py +1 -1
  121. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/tool_call.py +1 -1
  122. {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/agent_one.py +1 -1
  123. {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/agent_two.py +1 -1
  124. {mcp_agent → fast_agent}/resources/examples/researcher/researcher-eval.py +1 -1
  125. {mcp_agent → fast_agent}/resources/examples/researcher/researcher-imp.py +1 -1
  126. {mcp_agent → fast_agent}/resources/examples/researcher/researcher.py +1 -1
  127. {mcp_agent → fast_agent}/resources/examples/tensorzero/agent.py +2 -2
  128. {mcp_agent → fast_agent}/resources/examples/tensorzero/image_demo.py +3 -3
  129. {mcp_agent → fast_agent}/resources/examples/tensorzero/simple_agent.py +1 -1
  130. {mcp_agent → fast_agent}/resources/examples/workflows/chaining.py +1 -1
  131. {mcp_agent → fast_agent}/resources/examples/workflows/evaluator.py +3 -3
  132. {mcp_agent → fast_agent}/resources/examples/workflows/human_input.py +5 -3
  133. {mcp_agent → fast_agent}/resources/examples/workflows/orchestrator.py +1 -1
  134. {mcp_agent → fast_agent}/resources/examples/workflows/parallel.py +2 -2
  135. {mcp_agent → fast_agent}/resources/examples/workflows/router.py +5 -2
  136. fast_agent/resources/setup/.gitignore +24 -0
  137. fast_agent/resources/setup/agent.py +18 -0
  138. fast_agent/resources/setup/fastagent.config.yaml +44 -0
  139. fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -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 +56 -39
  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.57.dist-info → fast_agent_mcp-0.3.0.dist-info}/METADATA +7 -7
  154. fast_agent_mcp-0.3.0.dist-info/RECORD +202 -0
  155. fast_agent_mcp-0.3.0.dist-info/entry_points.txt +5 -0
  156. fast_agent_mcp-0.2.57.dist-info/RECORD +0 -192
  157. fast_agent_mcp-0.2.57.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 -717
  172. mcp_agent/llm/providers/augmented_llm_bedrock.py +0 -1788
  173. mcp_agent/llm/providers/augmented_llm_google_native.py +0 -495
  174. mcp_agent/llm/providers/sampling_converter_anthropic.py +0 -57
  175. mcp_agent/llm/providers/sampling_converter_openai.py +0 -26
  176. mcp_agent/llm/sampling_format_converter.py +0 -37
  177. mcp_agent/logging/__init__.py +0 -0
  178. mcp_agent/mcp/__init__.py +0 -50
  179. mcp_agent/mcp/helpers/__init__.py +0 -25
  180. mcp_agent/mcp/helpers/content_helpers.py +0 -187
  181. mcp_agent/mcp/interfaces.py +0 -266
  182. mcp_agent/mcp/prompts/__init__.py +0 -0
  183. mcp_agent/mcp/prompts/__main__.py +0 -10
  184. mcp_agent/mcp_server_registry.py +0 -343
  185. mcp_agent/tools/tool_definition.py +0 -14
  186. mcp_agent/ui/console_display.py +0 -790
  187. mcp_agent/ui/console_display_legacy.py +0 -401
  188. {mcp_agent → fast_agent}/agents/workflow/orchestrator_prompts.py +0 -0
  189. {mcp_agent/agents → fast_agent/cli}/__init__.py +0 -0
  190. {mcp_agent → fast_agent}/cli/constants.py +0 -0
  191. {mcp_agent → fast_agent}/core/error_handling.py +0 -0
  192. {mcp_agent → fast_agent}/core/exceptions.py +0 -0
  193. {mcp_agent/cli → fast_agent/core/executor}/__init__.py +0 -0
  194. {mcp_agent → fast_agent/core}/executor/task_registry.py +0 -0
  195. {mcp_agent → fast_agent/core}/executor/workflow_signal.py +0 -0
  196. {mcp_agent → fast_agent}/human_input/form_fields.py +0 -0
  197. {mcp_agent → fast_agent}/llm/prompt_utils.py +0 -0
  198. {mcp_agent/core → fast_agent/llm}/request_params.py +0 -0
  199. {mcp_agent → fast_agent}/mcp/common.py +0 -0
  200. {mcp_agent/executor → fast_agent/mcp/prompts}/__init__.py +0 -0
  201. {mcp_agent → fast_agent}/mcp/prompts/prompt_constants.py +0 -0
  202. {mcp_agent → fast_agent}/py.typed +0 -0
  203. {mcp_agent → fast_agent}/resources/examples/data-analysis/fastagent.config.yaml +0 -0
  204. {mcp_agent → fast_agent}/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -0
  205. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_account_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.57.dist-info → fast_agent_mcp-0.3.0.dist-info}/WHEEL +0 -0
  234. {fast_agent_mcp-0.2.57.dist-info → fast_agent_mcp-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -12,6 +12,7 @@ from typing import Any, List, Literal, Optional, Union
12
12
  from mcp.types import (
13
13
  Annotations,
14
14
  BlobResourceContents,
15
+ ContentBlock,
15
16
  EmbeddedResource,
16
17
  ImageContent,
17
18
  ReadResourceResult,
@@ -21,15 +22,12 @@ from mcp.types import (
21
22
  )
22
23
  from pydantic import AnyUrl
23
24
 
24
- from mcp_agent.mcp.mime_utils import (
25
+ from fast_agent.mcp.mime_utils import (
25
26
  guess_mime_type,
26
27
  is_binary_content,
27
28
  is_image_mime_type,
28
29
  )
29
30
 
30
- # Type for all MCP content types
31
- MCPContentType = Union[TextContent, ImageContent, EmbeddedResource, ResourceContents]
32
-
33
31
 
34
32
  def MCPText(
35
33
  text: str,
@@ -152,8 +150,8 @@ def MCPFile(
152
150
 
153
151
 
154
152
  def MCPPrompt(
155
- *content_items: Union[dict, str, Path, bytes, MCPContentType, 'EmbeddedResource', 'ReadResourceResult'],
156
- role: Literal["user", "assistant"] = "user"
153
+ *content_items: Union[dict, str, Path, bytes, ContentBlock, ReadResourceResult],
154
+ role: Literal["user", "assistant"] = "user",
157
155
  ) -> List[dict]:
158
156
  """
159
157
  Create one or more prompt messages with various content types.
@@ -209,19 +207,25 @@ def MCPPrompt(
209
207
  elif isinstance(item, EmbeddedResource):
210
208
  # Already an EmbeddedResource, wrap in a message
211
209
  result.append({"role": role, "content": item})
212
- elif hasattr(item, 'type') and item.type == 'resource' and hasattr(item, 'resource'):
210
+ elif hasattr(item, "type") and item.type == "resource" and hasattr(item, "resource"):
213
211
  # Looks like an EmbeddedResource but may not be the exact class
214
- result.append({"role": role, "content": EmbeddedResource(type="resource", resource=item.resource)})
212
+ result.append(
213
+ {"role": role, "content": EmbeddedResource(type="resource", resource=item.resource)}
214
+ )
215
215
  elif isinstance(item, ResourceContents):
216
216
  # It's a ResourceContents, wrap it in an EmbeddedResource
217
- result.append({"role": role, "content": EmbeddedResource(type="resource", resource=item)})
217
+ result.append(
218
+ {"role": role, "content": EmbeddedResource(type="resource", resource=item)}
219
+ )
218
220
  elif isinstance(item, ReadResourceResult):
219
221
  # It's a ReadResourceResult, convert each resource content
220
222
  for resource_content in item.contents:
221
- result.append({
222
- "role": role,
223
- "content": EmbeddedResource(type="resource", resource=resource_content)
224
- })
223
+ result.append(
224
+ {
225
+ "role": role,
226
+ "content": EmbeddedResource(type="resource", resource=resource_content),
227
+ }
228
+ )
225
229
  else:
226
230
  # Try to convert to string
227
231
  result.append(MCPText(str(item), role=role))
@@ -229,12 +233,16 @@ def MCPPrompt(
229
233
  return result
230
234
 
231
235
 
232
- def User(*content_items: Union[dict, str, Path, bytes, MCPContentType, 'EmbeddedResource', 'ReadResourceResult']) -> List[dict]:
236
+ def User(
237
+ *content_items: Union[dict, str, Path, bytes, ContentBlock, ReadResourceResult],
238
+ ) -> List[dict]:
233
239
  """Create user message(s) with various content types."""
234
240
  return MCPPrompt(*content_items, role="user")
235
241
 
236
242
 
237
- def Assistant(*content_items: Union[dict, str, Path, bytes, MCPContentType, 'EmbeddedResource', 'ReadResourceResult']) -> List[dict]:
243
+ def Assistant(
244
+ *content_items: Union[dict, str, Path, bytes, ContentBlock, ReadResourceResult],
245
+ ) -> List[dict]:
238
246
  """Create assistant message(s) with various content types."""
239
247
  return MCPPrompt(*content_items, role="assistant")
240
248
 
@@ -67,3 +67,42 @@ def is_binary_content(mime_type: str) -> bool:
67
67
  def is_image_mime_type(mime_type: str) -> bool:
68
68
  """Check if a MIME type represents an image."""
69
69
  return mime_type.startswith("image/") and mime_type != "image/svg+xml"
70
+
71
+
72
+ # Common alias mapping and normalization helpers
73
+ _MIME_ALIASES = {
74
+ # Friendly or non-standard labels
75
+ "document/pdf": "application/pdf",
76
+ "image/jpg": "image/jpeg",
77
+ # Some providers sometimes return these variants
78
+ "application/x-pdf": "application/pdf",
79
+ }
80
+
81
+
82
+ def normalize_mime_type(mime: str | None) -> str | None:
83
+ """
84
+ Normalize a MIME-like string to a canonical MIME type.
85
+
86
+ - Lowercases and trims
87
+ - Resolves common aliases (e.g. image/jpg -> image/jpeg, document/pdf -> application/pdf)
88
+ - If input looks like a bare extension (e.g. "pdf", "png"), map via mimetypes
89
+ - Returns None for falsy inputs
90
+ """
91
+ if not mime:
92
+ return None
93
+
94
+ m = mime.strip().lower()
95
+
96
+ # If it's an alias we know about
97
+ if m in _MIME_ALIASES:
98
+ return _MIME_ALIASES[m]
99
+
100
+ # If it already looks like a full MIME type
101
+ if "/" in m:
102
+ # image/jpg -> image/jpeg etc.
103
+ return _MIME_ALIASES.get(m, m)
104
+
105
+ # Treat as a bare file extension (e.g. "pdf", "png")
106
+ if not m.startswith("."):
107
+ m = "." + m
108
+ return mimetypes.types_map.get(m, None)
@@ -0,0 +1,159 @@
1
+ """
2
+ Prompt class for easily creating and working with MCP prompt content.
3
+
4
+ This implementation lives in the fast_agent namespace as part of the
5
+ migration away from fast_agent. A compatibility shim remains at
6
+ fast_agent.core.prompt importing this Prompt.
7
+ """
8
+
9
+ from pathlib import Path
10
+ from typing import Dict, List, Literal, Union
11
+
12
+ from mcp import CallToolRequest
13
+ from mcp.types import ContentBlock, PromptMessage
14
+
15
+ from fast_agent.mcp.mcp_content import Assistant, MCPPrompt, User
16
+ from fast_agent.types import LlmStopReason, PromptMessageExtended
17
+
18
+
19
+ class Prompt:
20
+ """
21
+ A helper class for working with MCP prompt content.
22
+
23
+ This class provides static methods to create:
24
+ - PromptMessage instances
25
+ - PromptMessageExtended instances
26
+ - Lists of messages for conversations
27
+
28
+ All methods intelligently handle various content types:
29
+ - Strings become TextContent
30
+ - Image file paths become ImageContent
31
+ - Other file paths become EmbeddedResource
32
+ - TextContent objects are used directly
33
+ - ImageContent objects are used directly
34
+ - EmbeddedResource objects are used directly
35
+ - Pre-formatted messages pass through unchanged
36
+ """
37
+
38
+ @classmethod
39
+ def user(
40
+ cls,
41
+ *content_items: Union[
42
+ str, Path, bytes, dict, ContentBlock, PromptMessage, PromptMessageExtended
43
+ ],
44
+ ) -> PromptMessageExtended:
45
+ """
46
+ Create a user PromptMessageExtended with various content items.
47
+ """
48
+ # Handle PromptMessage and PromptMessageExtended directly
49
+ if len(content_items) == 1:
50
+ item = content_items[0]
51
+ if isinstance(item, PromptMessage):
52
+ return PromptMessageExtended(role="user", content=[item.content])
53
+ elif isinstance(item, PromptMessageExtended):
54
+ # Keep the content but change role to user
55
+ return PromptMessageExtended(role="user", content=item.content)
56
+
57
+ # Use the content factory for other types
58
+ messages = User(*content_items)
59
+ return PromptMessageExtended(role="user", content=[msg["content"] for msg in messages])
60
+
61
+ @classmethod
62
+ def assistant(
63
+ cls,
64
+ *content_items: Union[
65
+ str, Path, bytes, dict, ContentBlock, PromptMessage, PromptMessageExtended
66
+ ],
67
+ stop_reason: LlmStopReason | None = None,
68
+ tool_calls: Dict[str, CallToolRequest] | None = None,
69
+ ) -> PromptMessageExtended:
70
+ """
71
+ Create an assistant PromptMessageExtended with various content items.
72
+ """
73
+ # Handle PromptMessage and PromptMessageExtended directly
74
+ if len(content_items) == 1:
75
+ item = content_items[0]
76
+ if isinstance(item, PromptMessage):
77
+ return PromptMessageExtended(
78
+ role="assistant",
79
+ content=[item.content],
80
+ stop_reason=stop_reason,
81
+ tool_calls=tool_calls,
82
+ )
83
+ elif isinstance(item, PromptMessageExtended):
84
+ # Keep the content but change role to assistant
85
+ return PromptMessageExtended(
86
+ role="assistant",
87
+ content=item.content,
88
+ stop_reason=stop_reason,
89
+ tool_calls=tool_calls,
90
+ )
91
+
92
+ # Use the content factory for other types
93
+ messages = Assistant(*content_items)
94
+ return PromptMessageExtended(
95
+ role="assistant",
96
+ content=[msg["content"] for msg in messages],
97
+ stop_reason=stop_reason,
98
+ tool_calls=tool_calls,
99
+ )
100
+
101
+ @classmethod
102
+ def message(
103
+ cls,
104
+ *content_items: Union[
105
+ str, Path, bytes, dict, ContentBlock, PromptMessage, PromptMessageExtended
106
+ ],
107
+ role: Literal["user", "assistant"] = "user",
108
+ ) -> PromptMessageExtended:
109
+ """
110
+ Create a PromptMessageExtended with the specified role and content items.
111
+ """
112
+ # Handle PromptMessage and PromptMessageExtended directly
113
+ if len(content_items) == 1:
114
+ item = content_items[0]
115
+ if isinstance(item, PromptMessage):
116
+ return PromptMessageExtended(role=role, content=[item.content])
117
+ elif isinstance(item, PromptMessageExtended):
118
+ # Keep the content but change role as specified
119
+ return PromptMessageExtended(role=role, content=item.content)
120
+
121
+ # Use the content factory for other types
122
+ messages = MCPPrompt(*content_items, role=role)
123
+ return PromptMessageExtended(
124
+ role=messages[0]["role"] if messages else role,
125
+ content=[msg["content"] for msg in messages],
126
+ )
127
+
128
+ @classmethod
129
+ def conversation(cls, *messages) -> List[PromptMessage]:
130
+ """
131
+ Create a list of PromptMessages from various inputs.
132
+ """
133
+ result = []
134
+
135
+ for item in messages:
136
+ if isinstance(item, PromptMessageExtended):
137
+ # Convert PromptMessageExtended to a list of PromptMessages
138
+ result.extend(item.from_multipart())
139
+ elif isinstance(item, dict) and "role" in item and "content" in item:
140
+ # Convert a single message dict to PromptMessage
141
+ result.append(PromptMessage(**item))
142
+ elif isinstance(item, list):
143
+ # Process each item in the list
144
+ for msg in item:
145
+ if isinstance(msg, dict) and "role" in msg and "content" in msg:
146
+ result.append(PromptMessage(**msg))
147
+ # Ignore other types
148
+
149
+ return result
150
+
151
+ @classmethod
152
+ def from_multipart(cls, multipart: List[PromptMessageExtended]) -> List[PromptMessage]:
153
+ """
154
+ Convert a list of PromptMessageExtended objects to PromptMessages.
155
+ """
156
+ result = []
157
+ for mp in multipart:
158
+ result.extend(mp.from_multipart())
159
+ return result
@@ -1,6 +1,8 @@
1
- from typing import List, Optional
1
+ from typing import Dict, List, Optional
2
2
 
3
3
  from mcp.types import (
4
+ CallToolRequest,
5
+ CallToolResult,
4
6
  ContentBlock,
5
7
  GetPromptResult,
6
8
  PromptMessage,
@@ -9,21 +11,26 @@ from mcp.types import (
9
11
  )
10
12
  from pydantic import BaseModel
11
13
 
12
- from mcp_agent.mcp.helpers.content_helpers import get_text
14
+ from fast_agent.mcp.helpers.content_helpers import get_text
15
+ from fast_agent.types.llm_stop_reason import LlmStopReason
13
16
 
14
17
 
15
- class PromptMessageMultipart(BaseModel):
18
+ class PromptMessageExtended(BaseModel):
16
19
  """
17
20
  Extension of PromptMessage that handles multiple content parts.
18
21
  Internally converts to/from a sequence of standard PromptMessages.
19
22
  """
20
23
 
21
24
  role: Role
22
- content: List[ContentBlock]
25
+ content: List[ContentBlock] = []
26
+ tool_calls: Dict[str, CallToolRequest] | None = None
27
+ tool_results: Dict[str, CallToolResult] | None = None
28
+ channels: Dict[str, List[ContentBlock]] | None = None
29
+ stop_reason: LlmStopReason | None = None
23
30
 
24
31
  @classmethod
25
- def to_multipart(cls, messages: List[PromptMessage]) -> List["PromptMessageMultipart"]:
26
- """Convert a sequence of PromptMessages into PromptMessageMultipart objects."""
32
+ def to_extended(cls, messages: List[PromptMessage]) -> List["PromptMessageExtended"]:
33
+ """Convert a sequence of PromptMessages into PromptMessageExtended objects."""
27
34
  if not messages:
28
35
  return []
29
36
 
@@ -50,7 +57,7 @@ class PromptMessageMultipart(BaseModel):
50
57
  return result
51
58
 
52
59
  def from_multipart(self) -> List[PromptMessage]:
53
- """Convert this PromptMessageMultipart to a sequence of standard PromptMessages."""
60
+ """Convert this PromptMessageExtended to a sequence of standard PromptMessages."""
54
61
  return [
55
62
  PromptMessage(role=self.role, content=content_part) for content_part in self.content
56
63
  ]
@@ -60,7 +67,7 @@ class PromptMessageMultipart(BaseModel):
60
67
  Get the first available text content from a message. Note this could be tool content etc.
61
68
 
62
69
  Args:
63
- message: A PromptMessage or PromptMessageMultipart
70
+ message: A PromptMessage or PromptMessageExtended
64
71
 
65
72
  Returns:
66
73
  First text content or None if no text content exists
@@ -72,13 +79,13 @@ class PromptMessageMultipart(BaseModel):
72
79
 
73
80
  return "<no text>"
74
81
 
75
- def last_text(self) -> str:
82
+ def last_text(self) -> str | None:
76
83
  """
77
84
  Get the last available text content from a message. This will usually be the final
78
85
  generation from the Assistant.
79
86
 
80
87
  Args:
81
- message: A PromptMessage or PromptMessageMultipart
88
+ message: A PromptMessage or PromptMessageExtended
82
89
 
83
90
  Returns:
84
91
  First text content or None if no text content exists
@@ -88,14 +95,14 @@ class PromptMessageMultipart(BaseModel):
88
95
  if text is not None:
89
96
  return text
90
97
 
91
- return "<no text>"
98
+ return None
92
99
 
93
100
  def all_text(self) -> str:
94
101
  """
95
102
  Get all the text available.
96
103
 
97
104
  Args:
98
- message: A PromptMessage or PromptMessageMultipart
105
+ message: A PromptMessage or PromptMessageExtended
99
106
 
100
107
  Returns:
101
108
  First text content or None if no text content exists
@@ -114,32 +121,32 @@ class PromptMessageMultipart(BaseModel):
114
121
  return text
115
122
 
116
123
  @classmethod
117
- def parse_get_prompt_result(cls, result: GetPromptResult) -> List["PromptMessageMultipart"]:
124
+ def parse_get_prompt_result(cls, result: GetPromptResult) -> List["PromptMessageExtended"]:
118
125
  """
119
- Parse a GetPromptResult into PromptMessageMultipart objects.
126
+ Parse a GetPromptResult into PromptMessageExtended objects.
120
127
 
121
128
  Args:
122
129
  result: GetPromptResult from MCP server
123
130
 
124
131
  Returns:
125
- List of PromptMessageMultipart objects
132
+ List of PromptMessageExtended objects
126
133
  """
127
- return cls.to_multipart(result.messages)
134
+ return cls.to_extended(result.messages)
128
135
 
129
136
  @classmethod
130
137
  def from_get_prompt_result(
131
138
  cls, result: Optional[GetPromptResult]
132
- ) -> List["PromptMessageMultipart"]:
139
+ ) -> List["PromptMessageExtended"]:
133
140
  """
134
- Convert a GetPromptResult to PromptMessageMultipart objects with error handling.
141
+ Convert a GetPromptResult to PromptMessageExtended objects with error handling.
135
142
  This method safely handles None values and empty results.
136
143
 
137
144
  Args:
138
145
  result: GetPromptResult from MCP server or None
139
146
 
140
147
  Returns:
141
- List of PromptMessageMultipart objects or empty list if result is None/empty
148
+ List of PromptMessageExtended objects or empty list if result is None/empty
142
149
  """
143
150
  if not result or not result.messages:
144
151
  return []
145
- return cls.to_multipart(result.messages)
152
+ return cls.to_extended(result.messages)
@@ -1,43 +1,43 @@
1
1
  """
2
- Utilities for rendering PromptMessageMultipart objects for display.
2
+ Utilities for rendering PromptMessageExtended objects for display.
3
3
  """
4
4
 
5
5
  from typing import List
6
6
 
7
7
  from mcp.types import BlobResourceContents, TextResourceContents
8
8
 
9
- from mcp_agent.mcp.helpers.content_helpers import (
9
+ from fast_agent.mcp.helpers.content_helpers import (
10
10
  get_resource_uri,
11
11
  get_text,
12
12
  is_image_content,
13
13
  is_resource_content,
14
14
  is_text_content,
15
15
  )
16
- from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
16
+ from fast_agent.types import PromptMessageExtended
17
17
 
18
18
 
19
- def render_multipart_message(message: PromptMessageMultipart) -> str:
19
+ def render_multipart_message(message: PromptMessageExtended) -> str:
20
20
  """
21
21
  Render a multipart message for display purposes.
22
-
22
+
23
23
  This function formats the message content for user-friendly display,
24
24
  handling different content types appropriately.
25
-
25
+
26
26
  Args:
27
- message: A PromptMessageMultipart object to render
28
-
27
+ message: A PromptMessageExtended object to render
28
+
29
29
  Returns:
30
30
  A string representation of the message's content
31
31
  """
32
32
  rendered_parts: List[str] = []
33
-
33
+
34
34
  for content in message.content:
35
35
  if is_text_content(content):
36
36
  # Handle text content
37
37
  text = get_text(content)
38
38
  if text:
39
39
  rendered_parts.append(text)
40
-
40
+
41
41
  elif is_image_content(content):
42
42
  # Format details about the image
43
43
  image_data = getattr(content, "data", "")
@@ -45,36 +45,38 @@ def render_multipart_message(message: PromptMessageMultipart) -> str:
45
45
  mime_type = getattr(content, "mimeType", "unknown")
46
46
  image_info = f"[IMAGE: {mime_type}, {data_size} bytes]"
47
47
  rendered_parts.append(image_info)
48
-
48
+
49
49
  elif is_resource_content(content):
50
50
  # Handle embedded resources
51
51
  uri = get_resource_uri(content)
52
52
  resource = getattr(content, "resource", None)
53
-
53
+
54
54
  if resource and isinstance(resource, TextResourceContents):
55
55
  # Handle text resources
56
56
  text = resource.text
57
57
  text_length = len(text)
58
58
  mime_type = getattr(resource, "mimeType", "text/plain")
59
-
59
+
60
60
  # Preview with truncation for long content
61
61
  preview = text[:300] + ("..." if text_length > 300 else "")
62
- resource_info = f"[EMBEDDED TEXT RESOURCE: {mime_type}, {uri}, {text_length} chars]\n{preview}"
62
+ resource_info = (
63
+ f"[EMBEDDED TEXT RESOURCE: {mime_type}, {uri}, {text_length} chars]\n{preview}"
64
+ )
63
65
  rendered_parts.append(resource_info)
64
-
66
+
65
67
  elif resource and isinstance(resource, BlobResourceContents):
66
68
  # Handle blob resources (binary data)
67
69
  blob = getattr(resource, "blob", "")
68
70
  blob_length = len(blob) if blob else 0
69
71
  mime_type = getattr(resource, "mimeType", "application/octet-stream")
70
-
72
+
71
73
  resource_info = f"[EMBEDDED BLOB RESOURCE: {mime_type}, {uri}, {blob_length} bytes]"
72
74
  rendered_parts.append(resource_info)
73
-
75
+
74
76
  else:
75
77
  # Fallback for other content types
76
78
  text = get_text(content)
77
79
  if text is not None:
78
80
  rendered_parts.append(text)
79
-
80
- return "\n".join(rendered_parts)
81
+
82
+ return "\n".join(rendered_parts)