fast-agent-mcp 0.1.7__py3-none-any.whl → 0.1.9__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 (56) hide show
  1. {fast_agent_mcp-0.1.7.dist-info → fast_agent_mcp-0.1.9.dist-info}/METADATA +37 -9
  2. {fast_agent_mcp-0.1.7.dist-info → fast_agent_mcp-0.1.9.dist-info}/RECORD +53 -31
  3. {fast_agent_mcp-0.1.7.dist-info → fast_agent_mcp-0.1.9.dist-info}/entry_points.txt +1 -0
  4. mcp_agent/agents/agent.py +5 -11
  5. mcp_agent/core/agent_app.py +125 -44
  6. mcp_agent/core/decorators.py +3 -2
  7. mcp_agent/core/enhanced_prompt.py +106 -20
  8. mcp_agent/core/factory.py +28 -66
  9. mcp_agent/core/fastagent.py +13 -3
  10. mcp_agent/core/mcp_content.py +222 -0
  11. mcp_agent/core/prompt.py +132 -0
  12. mcp_agent/core/proxies.py +41 -36
  13. mcp_agent/human_input/handler.py +4 -1
  14. mcp_agent/logging/transport.py +30 -3
  15. mcp_agent/mcp/mcp_aggregator.py +27 -22
  16. mcp_agent/mcp/mime_utils.py +69 -0
  17. mcp_agent/mcp/prompt_message_multipart.py +64 -0
  18. mcp_agent/mcp/prompt_serialization.py +447 -0
  19. mcp_agent/mcp/prompts/__init__.py +0 -0
  20. mcp_agent/mcp/prompts/__main__.py +10 -0
  21. mcp_agent/mcp/prompts/prompt_server.py +508 -0
  22. mcp_agent/mcp/prompts/prompt_template.py +469 -0
  23. mcp_agent/mcp/resource_utils.py +203 -0
  24. mcp_agent/resources/examples/internal/agent.py +1 -1
  25. mcp_agent/resources/examples/internal/fastagent.config.yaml +2 -2
  26. mcp_agent/resources/examples/internal/sizer.py +0 -5
  27. mcp_agent/resources/examples/prompting/__init__.py +3 -0
  28. mcp_agent/resources/examples/prompting/agent.py +23 -0
  29. mcp_agent/resources/examples/prompting/fastagent.config.yaml +44 -0
  30. mcp_agent/resources/examples/prompting/image_server.py +56 -0
  31. mcp_agent/resources/examples/researcher/researcher-eval.py +1 -1
  32. mcp_agent/resources/examples/workflows/orchestrator.py +5 -4
  33. mcp_agent/resources/examples/workflows/router.py +0 -2
  34. mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +57 -87
  35. mcp_agent/workflows/llm/anthropic_utils.py +101 -0
  36. mcp_agent/workflows/llm/augmented_llm.py +155 -141
  37. mcp_agent/workflows/llm/augmented_llm_anthropic.py +135 -281
  38. mcp_agent/workflows/llm/augmented_llm_openai.py +175 -337
  39. mcp_agent/workflows/llm/augmented_llm_passthrough.py +104 -0
  40. mcp_agent/workflows/llm/augmented_llm_playback.py +109 -0
  41. mcp_agent/workflows/llm/model_factory.py +25 -6
  42. mcp_agent/workflows/llm/openai_utils.py +65 -0
  43. mcp_agent/workflows/llm/providers/__init__.py +8 -0
  44. mcp_agent/workflows/llm/providers/multipart_converter_anthropic.py +348 -0
  45. mcp_agent/workflows/llm/providers/multipart_converter_openai.py +426 -0
  46. mcp_agent/workflows/llm/providers/openai_multipart.py +197 -0
  47. mcp_agent/workflows/llm/providers/sampling_converter_anthropic.py +258 -0
  48. mcp_agent/workflows/llm/providers/sampling_converter_openai.py +229 -0
  49. mcp_agent/workflows/llm/sampling_format_converter.py +39 -0
  50. mcp_agent/workflows/orchestrator/orchestrator.py +62 -153
  51. mcp_agent/workflows/router/router_llm.py +18 -24
  52. mcp_agent/core/server_validation.py +0 -44
  53. mcp_agent/core/simulator_registry.py +0 -22
  54. mcp_agent/workflows/llm/enhanced_passthrough.py +0 -70
  55. {fast_agent_mcp-0.1.7.dist-info → fast_agent_mcp-0.1.9.dist-info}/WHEEL +0 -0
  56. {fast_agent_mcp-0.1.7.dist-info → fast_agent_mcp-0.1.9.dist-info}/licenses/LICENSE +0 -0
@@ -2,13 +2,13 @@
2
2
  Enhanced prompt functionality with advanced prompt_toolkit features.
3
3
  """
4
4
 
5
- from typing import List
5
+ from typing import List, Optional
6
6
  from importlib.metadata import version
7
7
  from prompt_toolkit import PromptSession
8
8
  from prompt_toolkit.formatted_text import HTML
9
9
  from prompt_toolkit.history import InMemoryHistory
10
10
  from prompt_toolkit.key_binding import KeyBindings
11
- from prompt_toolkit.completion import Completer, Completion
11
+ from prompt_toolkit.completion import Completer, Completion, WordCompleter
12
12
  from prompt_toolkit.lexers import PygmentsLexer
13
13
  from prompt_toolkit.filters import Condition
14
14
  from prompt_toolkit.styles import Style
@@ -330,6 +330,110 @@ async def get_enhanced_input(
330
330
  # Log and gracefully handle other exceptions
331
331
  print(f"\nInput error: {type(e).__name__}: {e}")
332
332
  return "STOP"
333
+ finally:
334
+ # Ensure the prompt session is properly cleaned up
335
+ # This is especially important on Windows to prevent resource leaks
336
+ if session.app.is_running:
337
+ session.app.exit()
338
+
339
+
340
+ async def get_selection_input(
341
+ prompt_text: str,
342
+ options: List[str] = None,
343
+ default: str = None,
344
+ allow_cancel: bool = True,
345
+ complete_options: bool = True,
346
+ ) -> Optional[str]:
347
+ """
348
+ Display a selection prompt and return the user's selection.
349
+
350
+ Args:
351
+ prompt_text: Text to display as the prompt
352
+ options: List of valid options (for auto-completion)
353
+ default: Default value if user presses enter
354
+ allow_cancel: Whether to allow cancellation with empty input
355
+ complete_options: Whether to use the options for auto-completion
356
+
357
+ Returns:
358
+ Selected value, or None if cancelled
359
+ """
360
+ try:
361
+ # Initialize completer if options provided and completion requested
362
+ completer = WordCompleter(options) if options and complete_options else None
363
+
364
+ # Create prompt session
365
+ prompt_session = PromptSession(completer=completer)
366
+
367
+ try:
368
+ # Get user input
369
+ selection = await prompt_session.prompt_async(
370
+ prompt_text, default=default or ""
371
+ )
372
+
373
+ # Handle cancellation
374
+ if allow_cancel and not selection.strip():
375
+ return None
376
+
377
+ return selection
378
+ finally:
379
+ # Ensure prompt session cleanup
380
+ if prompt_session.app.is_running:
381
+ prompt_session.app.exit()
382
+ except (KeyboardInterrupt, EOFError):
383
+ return None
384
+ except Exception as e:
385
+ rich_print(f"\n[red]Error getting selection: {e}[/red]")
386
+ return None
387
+
388
+
389
+ async def get_argument_input(
390
+ arg_name: str,
391
+ description: str = None,
392
+ required: bool = True,
393
+ ) -> Optional[str]:
394
+ """
395
+ Prompt for an argument value with formatting and help text.
396
+
397
+ Args:
398
+ arg_name: Name of the argument
399
+ description: Optional description of the argument
400
+ required: Whether this argument is required
401
+
402
+ Returns:
403
+ Input value, or None if cancelled/skipped
404
+ """
405
+ # Format the prompt differently based on whether it's required
406
+ required_text = "(required)" if required else "(optional, press Enter to skip)"
407
+
408
+ # Show description if available
409
+ if description:
410
+ rich_print(f" [dim]{arg_name}: {description}[/dim]")
411
+
412
+ prompt_text = HTML(
413
+ f"Enter value for <ansibrightcyan>{arg_name}</ansibrightcyan> {required_text}: "
414
+ )
415
+
416
+ # Create prompt session
417
+ prompt_session = PromptSession()
418
+
419
+ try:
420
+ # Get user input
421
+ arg_value = await prompt_session.prompt_async(prompt_text)
422
+
423
+ # For optional arguments, empty input means skip
424
+ if not required and not arg_value:
425
+ return None
426
+
427
+ return arg_value
428
+ except (KeyboardInterrupt, EOFError):
429
+ return None
430
+ except Exception as e:
431
+ rich_print(f"\n[red]Error getting input: {e}[/red]")
432
+ return None
433
+ finally:
434
+ # Ensure prompt session cleanup
435
+ if prompt_session.app.is_running:
436
+ prompt_session.app.exit()
333
437
 
334
438
 
335
439
  async def handle_special_commands(command, agent_app=None):
@@ -408,24 +512,6 @@ async def handle_special_commands(command, agent_app=None):
408
512
  )
409
513
  return True
410
514
 
411
- elif command == "SELECT_PROMPT" or (
412
- isinstance(command, str) and command.startswith("SELECT_PROMPT:")
413
- ):
414
- # Handle prompt selection UI (previously named "list_prompts" action)
415
- if agent_app:
416
- # If it's a specific prompt, extract the name
417
- prompt_name = None
418
- if isinstance(command, str) and command.startswith("SELECT_PROMPT:"):
419
- prompt_name = command.split(":", 1)[1].strip()
420
-
421
- # Return a dictionary with a select_prompt action to be handled by the caller
422
- return {"select_prompt": True, "prompt_name": prompt_name}
423
- else:
424
- rich_print(
425
- "[yellow]Prompt selection is not available outside of an agent context[/yellow]"
426
- )
427
- return True
428
-
429
515
  elif isinstance(command, str) and command.startswith("SWITCH:"):
430
516
  agent_name = command.split(":", 1)[1]
431
517
  if agent_name in available_agents:
mcp_agent/core/factory.py CHANGED
@@ -34,10 +34,7 @@ T = TypeVar("T") # For the wrapper classes
34
34
 
35
35
 
36
36
  def create_proxy(
37
- app: MCPApp,
38
- name: str,
39
- instance: AgentOrWorkflow,
40
- agent_type: str
37
+ app: MCPApp, name: str, instance: AgentOrWorkflow, agent_type: str
41
38
  ) -> BaseAgentProxy:
42
39
  """Create appropriate proxy type based on agent type and validate instance type
43
40
 
@@ -61,9 +58,7 @@ def create_proxy(
61
58
  log_agent_load(app, name)
62
59
  if agent_type == AgentType.BASIC.value:
63
60
  if not isinstance(instance, Agent):
64
- raise TypeError(
65
- f"Expected Agent instance for {name}, got {type(instance)}"
66
- )
61
+ raise TypeError(f"Expected Agent instance for {name}, got {type(instance)}")
67
62
  return LLMAgentProxy(app, name, instance)
68
63
  elif agent_type == AgentType.ORCHESTRATOR.value:
69
64
  if not isinstance(instance, Orchestrator):
@@ -177,42 +172,18 @@ async def create_agents_by_type(
177
172
  if agent_type == AgentType.BASIC:
178
173
  # Get the agent name for special handling
179
174
  agent_name = agent_data["config"].name
175
+ agent = Agent(config=config, context=app_instance.context)
180
176
 
181
- # Check if this is an agent that should use the PassthroughLLM
182
- if agent_name.endswith("_fan_in") or agent_name.startswith(
183
- "passthrough"
184
- ):
185
- # Import here to avoid circular imports
186
- from mcp_agent.workflows.llm.augmented_llm import PassthroughLLM
187
-
188
- # Create basic agent with configuration
189
- agent = Agent(config=config, context=app_instance.context)
190
-
191
- # Set up a PassthroughLLM directly
192
- async with agent:
193
- agent._llm = PassthroughLLM(
194
- name=f"{config.name}_llm",
195
- context=app_instance.context,
196
- agent=agent,
197
- default_request_params=config.default_request_params,
198
- )
199
-
200
- # Store the agent
201
- instance = agent
202
- else:
203
- # Standard basic agent with LLM
204
- agent = Agent(config=config, context=app_instance.context)
205
-
206
- # Set up LLM with proper configuration
207
- async with agent:
208
- llm_factory = model_factory_func(
209
- model=config.model,
210
- request_params=config.default_request_params,
211
- )
212
- agent._llm = await agent.attach_llm(llm_factory)
177
+ # Set up LLM with proper configuration
178
+ async with agent:
179
+ llm_factory = model_factory_func(
180
+ model=config.model,
181
+ request_params=config.default_request_params,
182
+ )
183
+ agent._llm = await agent.attach_llm(llm_factory)
213
184
 
214
- # Store the agent
215
- instance = agent
185
+ # Store the agent
186
+ instance = agent
216
187
 
217
188
  elif agent_type == AgentType.ORCHESTRATOR:
218
189
  # Get base params configured with model settings
@@ -276,12 +247,8 @@ async def create_agents_by_type(
276
247
 
277
248
  elif agent_type == AgentType.EVALUATOR_OPTIMIZER:
278
249
  # Get the referenced agents - unwrap from proxies
279
- generator = unwrap_proxy(
280
- active_agents[agent_data["generator"]]
281
- )
282
- evaluator = unwrap_proxy(
283
- active_agents[agent_data["evaluator"]]
284
- )
250
+ generator = unwrap_proxy(active_agents[agent_data["generator"]])
251
+ evaluator = unwrap_proxy(active_agents[agent_data["evaluator"]])
285
252
 
286
253
  if not generator or not evaluator:
287
254
  raise ValueError(
@@ -294,7 +261,9 @@ async def create_agents_by_type(
294
261
  optimizer_model = None
295
262
  if isinstance(generator, Agent):
296
263
  optimizer_model = generator.config.model
297
- elif hasattr(generator, '_sequence') and hasattr(generator, '_agent_proxies'):
264
+ elif hasattr(generator, "_sequence") and hasattr(
265
+ generator, "_agent_proxies"
266
+ ):
298
267
  # For ChainProxy, use the config model directly
299
268
  optimizer_model = config.model
300
269
 
@@ -311,9 +280,7 @@ async def create_agents_by_type(
311
280
 
312
281
  elif agent_type == AgentType.ROUTER:
313
282
  # Get the router's agents - unwrap proxies
314
- router_agents = get_agent_instances(
315
- agent_data["agents"], active_agents
316
- )
283
+ router_agents = get_agent_instances(agent_data["agents"], active_agents)
317
284
 
318
285
  # Create the router with proper configuration
319
286
  llm_factory = model_factory_func(
@@ -376,20 +343,15 @@ async def create_agents_by_type(
376
343
  "continue_with_final", True
377
344
  )
378
345
  # Set cumulative behavior from configuration
379
- instance._cumulative = agent_data.get(
380
- "cumulative", False
381
- )
346
+ instance._cumulative = agent_data.get("cumulative", False)
382
347
 
383
348
  elif agent_type == AgentType.PARALLEL:
384
- # Get fan-out agents (could be basic agents or other parallels)
385
349
  fan_out_agents = get_agent_instances(
386
350
  agent_data["fan_out"], active_agents
387
351
  )
388
352
 
389
353
  # Get fan-in agent - unwrap proxy
390
- fan_in_agent = unwrap_proxy(
391
- active_agents[agent_data["fan_in"]]
392
- )
354
+ fan_in_agent = unwrap_proxy(active_agents[agent_data["fan_in"]])
393
355
 
394
356
  # Create the parallel workflow
395
357
  llm_factory = model_factory_func(config.model)
@@ -416,7 +378,7 @@ async def create_agents_by_type(
416
378
 
417
379
 
418
380
  async def create_basic_agents(
419
- app_instance: MCPApp,
381
+ app_instance: MCPApp,
420
382
  agents_dict: Dict[str, Dict[str, Any]],
421
383
  model_factory_func: Callable,
422
384
  ) -> ProxyDict:
@@ -432,17 +394,17 @@ async def create_basic_agents(
432
394
  Dictionary of initialized basic agents wrapped in appropriate proxies
433
395
  """
434
396
  return await create_agents_by_type(
435
- app_instance,
436
- agents_dict,
437
- AgentType.BASIC,
438
- model_factory_func=model_factory_func
397
+ app_instance,
398
+ agents_dict,
399
+ AgentType.BASIC,
400
+ model_factory_func=model_factory_func,
439
401
  )
440
402
 
441
403
 
442
404
  async def create_agents_in_dependency_order(
443
- app_instance: MCPApp,
405
+ app_instance: MCPApp,
444
406
  agents_dict: Dict[str, Dict[str, Any]],
445
- active_agents: ProxyDict,
407
+ active_agents: ProxyDict,
446
408
  agent_type: AgentType,
447
409
  model_factory_func: Callable,
448
410
  ) -> ProxyDict:
@@ -498,4 +460,4 @@ async def create_agents_in_dependency_order(
498
460
  if agent_name in agent_result:
499
461
  result_agents[agent_name] = agent_result[agent_name]
500
462
 
501
- return result_agents
463
+ return result_agents
@@ -70,7 +70,12 @@ class FastAgent(ContextDependent):
70
70
  Provides a simplified way to create and manage agents using decorators.
71
71
  """
72
72
 
73
- def __init__(self, name: str, config_path: Optional[str] = None):
73
+ def __init__(
74
+ self,
75
+ name: str,
76
+ config_path: Optional[str] = None,
77
+ ignore_unknown_args: bool = False,
78
+ ):
74
79
  """
75
80
  Initialize the decorator interface.
76
81
 
@@ -101,7 +106,12 @@ class FastAgent(ContextDependent):
101
106
  action="store_true",
102
107
  help="Disable progress display, tool and message logging for cleaner output",
103
108
  )
104
- self.args = parser.parse_args()
109
+
110
+ if ignore_unknown_args:
111
+ known_args, _ = parser.parse_known_args()
112
+ self.args = known_args
113
+ else:
114
+ self.args = parser.parse_args()
105
115
 
106
116
  # Quiet mode will be handled in _load_config()
107
117
 
@@ -372,7 +382,7 @@ class FastAgent(ContextDependent):
372
382
 
373
383
  # Create wrapper with all agents
374
384
  wrapper = AgentApp(agent_app, active_agents)
375
-
385
+
376
386
  # Store reference to AgentApp in MCPApp for proxies to access
377
387
  agent_app._agent_app = wrapper
378
388
 
@@ -0,0 +1,222 @@
1
+ """
2
+ Helper functions for creating MCP content types with minimal code.
3
+
4
+ This module provides simple functions to create TextContent, ImageContent,
5
+ EmbeddedResource, and other MCP content types with minimal boilerplate.
6
+ """
7
+
8
+ import base64
9
+ from pathlib import Path
10
+ from typing import Literal, Optional, Union, List, Any
11
+
12
+ from mcp.types import (
13
+ TextContent,
14
+ ImageContent,
15
+ EmbeddedResource,
16
+ TextResourceContents,
17
+ BlobResourceContents,
18
+ )
19
+
20
+ from mcp_agent.mcp.mime_utils import (
21
+ guess_mime_type,
22
+ is_binary_content,
23
+ is_image_mime_type,
24
+ )
25
+
26
+
27
+ def MCPText(
28
+ text: str,
29
+ role: Literal["user", "assistant"] = "user",
30
+ annotations: Optional[dict] = None,
31
+ ) -> dict:
32
+ """
33
+ Create a message with text content.
34
+
35
+ Args:
36
+ text: The text content
37
+ role: Role of the message, defaults to "user"
38
+ annotations: Optional annotations
39
+
40
+ Returns:
41
+ A dictionary with role and content that can be used in a prompt
42
+ """
43
+ return {
44
+ "role": role,
45
+ "content": TextContent(type="text", text=text, annotations=annotations),
46
+ }
47
+
48
+
49
+ def MCPImage(
50
+ path: Union[str, Path] = None,
51
+ data: bytes = None,
52
+ mime_type: Optional[str] = None,
53
+ role: Literal["user", "assistant"] = "user",
54
+ annotations: Optional[dict] = None,
55
+ ) -> dict:
56
+ """
57
+ Create a message with image content.
58
+
59
+ Args:
60
+ path: Path to the image file
61
+ data: Raw image data bytes (alternative to path)
62
+ mime_type: Optional mime type, will be guessed from path if not provided
63
+ role: Role of the message, defaults to "user"
64
+ annotations: Optional annotations
65
+
66
+ Returns:
67
+ A dictionary with role and content that can be used in a prompt
68
+ """
69
+ if path is None and data is None:
70
+ raise ValueError("Either path or data must be provided")
71
+
72
+ if path is not None and data is not None:
73
+ raise ValueError("Only one of path or data can be provided")
74
+
75
+ if path is not None:
76
+ path = Path(path)
77
+ if not mime_type:
78
+ mime_type = guess_mime_type(str(path))
79
+ with open(path, "rb") as f:
80
+ data = f.read()
81
+
82
+ if not mime_type:
83
+ mime_type = "image/png" # Default
84
+
85
+ b64_data = base64.b64encode(data).decode("ascii")
86
+
87
+ return {
88
+ "role": role,
89
+ "content": ImageContent(
90
+ type="image", data=b64_data, mimeType=mime_type, annotations=annotations
91
+ ),
92
+ }
93
+
94
+
95
+ def MCPFile(
96
+ path: Union[str, Path],
97
+ mime_type: Optional[str] = None,
98
+ role: Literal["user", "assistant"] = "user",
99
+ annotations: Optional[dict] = None,
100
+ ) -> dict:
101
+ """
102
+ Create a message with an embedded resource from a file.
103
+
104
+ Args:
105
+ path: Path to the resource file
106
+ mime_type: Optional mime type, will be guessed from path if not provided
107
+ role: Role of the message, defaults to "user"
108
+ annotations: Optional annotations
109
+
110
+ Returns:
111
+ A dictionary with role and content that can be used in a prompt
112
+ """
113
+ path = Path(path)
114
+ uri = f"file://{path.absolute()}"
115
+
116
+ if not mime_type:
117
+ mime_type = guess_mime_type(str(path))
118
+
119
+ # Determine if this is text or binary content
120
+ is_binary = is_binary_content(mime_type)
121
+
122
+ if is_binary:
123
+ # Read as binary
124
+ binary_data = path.read_bytes()
125
+ b64_data = base64.b64encode(binary_data).decode("ascii")
126
+
127
+ resource = BlobResourceContents(uri=uri, blob=b64_data, mimeType=mime_type)
128
+ else:
129
+ # Read as text
130
+ try:
131
+ text_data = path.read_text(encoding="utf-8")
132
+ resource = TextResourceContents(uri=uri, text=text_data, mimeType=mime_type)
133
+ except UnicodeDecodeError:
134
+ # Fallback to binary if text read fails
135
+ binary_data = path.read_bytes()
136
+ b64_data = base64.b64encode(binary_data).decode("ascii")
137
+ resource = BlobResourceContents(
138
+ uri=uri, blob=b64_data, mimeType=mime_type or "application/octet-stream"
139
+ )
140
+
141
+ return {
142
+ "role": role,
143
+ "content": EmbeddedResource(
144
+ type="resource", resource=resource, annotations=annotations
145
+ ),
146
+ }
147
+
148
+
149
+
150
+ def MCPPrompt(
151
+ *content_items, role: Literal["user", "assistant"] = "user"
152
+ ) -> List[dict]:
153
+ """
154
+ Create one or more prompt messages with various content types.
155
+
156
+ This function intelligently creates different content types:
157
+ - Strings become TextContent
158
+ - File paths with image mime types become ImageContent
159
+ - File paths with text mime types or other mime types become EmbeddedResource
160
+ - Dicts with role and content are passed through unchanged
161
+ - Raw bytes become ImageContent
162
+
163
+ Args:
164
+ *content_items: Content items of various types
165
+ role: Role for all items (user or assistant)
166
+
167
+ Returns:
168
+ List of messages that can be used in a prompt
169
+ """
170
+ result = []
171
+
172
+ for item in content_items:
173
+ if isinstance(item, dict) and "role" in item and "content" in item:
174
+ # Already a fully formed message
175
+ result.append(item)
176
+ elif isinstance(item, str) and not Path(item).exists():
177
+ # Simple text content (that's not a file path)
178
+ result.append(MCPText(item, role=role))
179
+ elif isinstance(item, Path) or isinstance(item, str):
180
+ # File path - determine the content type based on mime type
181
+ path_str = str(item)
182
+ mime_type = guess_mime_type(path_str)
183
+
184
+ if is_image_mime_type(mime_type):
185
+ # Image files (except SVG which is handled as text)
186
+ result.append(MCPImage(path=item, role=role))
187
+ else:
188
+ # All other file types (text documents, PDFs, SVGs, etc.)
189
+ result.append(MCPFile(path=item, role=role))
190
+ elif isinstance(item, bytes):
191
+ # Raw binary data, assume image
192
+ result.append(MCPImage(data=item, role=role))
193
+ else:
194
+ # Try to convert to string
195
+ result.append(MCPText(str(item), role=role))
196
+
197
+ return result
198
+
199
+
200
+ def User(*content_items) -> List[dict]:
201
+ """Create user message(s) with various content types."""
202
+ return MCPPrompt(*content_items, role="user")
203
+
204
+
205
+ def Assistant(*content_items) -> List[dict]:
206
+ """Create assistant message(s) with various content types."""
207
+ return MCPPrompt(*content_items, role="assistant")
208
+
209
+
210
+ def create_message(content: Any, role: Literal["user", "assistant"] = "user") -> dict:
211
+ """
212
+ Create a single prompt message from content of various types.
213
+
214
+ Args:
215
+ content: Content of various types (str, Path, bytes, etc.)
216
+ role: Role of the message
217
+
218
+ Returns:
219
+ A dictionary with role and content that can be used in a prompt
220
+ """
221
+ messages = MCPPrompt(content, role=role)
222
+ return messages[0] if messages else {}