fast-agent-mcp 0.2.3__py3-none-any.whl → 0.2.5__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 (43) hide show
  1. {fast_agent_mcp-0.2.3.dist-info → fast_agent_mcp-0.2.5.dist-info}/METADATA +13 -9
  2. {fast_agent_mcp-0.2.3.dist-info → fast_agent_mcp-0.2.5.dist-info}/RECORD +42 -40
  3. mcp_agent/__init__.py +2 -2
  4. mcp_agent/agents/agent.py +5 -0
  5. mcp_agent/agents/base_agent.py +152 -36
  6. mcp_agent/agents/workflow/chain_agent.py +9 -13
  7. mcp_agent/agents/workflow/evaluator_optimizer.py +3 -3
  8. mcp_agent/agents/workflow/orchestrator_agent.py +9 -7
  9. mcp_agent/agents/workflow/parallel_agent.py +2 -2
  10. mcp_agent/agents/workflow/router_agent.py +7 -5
  11. mcp_agent/cli/main.py +11 -0
  12. mcp_agent/config.py +29 -7
  13. mcp_agent/context.py +2 -0
  14. mcp_agent/core/{direct_agent_app.py → agent_app.py} +115 -15
  15. mcp_agent/core/direct_factory.py +9 -18
  16. mcp_agent/core/enhanced_prompt.py +3 -3
  17. mcp_agent/core/fastagent.py +218 -49
  18. mcp_agent/core/mcp_content.py +38 -5
  19. mcp_agent/core/prompt.py +70 -8
  20. mcp_agent/core/validation.py +1 -1
  21. mcp_agent/llm/augmented_llm.py +44 -16
  22. mcp_agent/llm/augmented_llm_passthrough.py +3 -1
  23. mcp_agent/llm/model_factory.py +16 -28
  24. mcp_agent/llm/providers/augmented_llm_openai.py +3 -3
  25. mcp_agent/llm/providers/multipart_converter_anthropic.py +8 -8
  26. mcp_agent/llm/providers/multipart_converter_openai.py +9 -9
  27. mcp_agent/mcp/helpers/__init__.py +3 -0
  28. mcp_agent/mcp/helpers/content_helpers.py +116 -0
  29. mcp_agent/mcp/interfaces.py +39 -16
  30. mcp_agent/mcp/mcp_aggregator.py +117 -13
  31. mcp_agent/mcp/prompt_message_multipart.py +29 -22
  32. mcp_agent/mcp/prompt_render.py +18 -15
  33. mcp_agent/mcp/prompt_serialization.py +42 -0
  34. mcp_agent/mcp/prompts/prompt_helpers.py +22 -112
  35. mcp_agent/mcp/prompts/prompt_load.py +51 -3
  36. mcp_agent/mcp_server/agent_server.py +62 -13
  37. mcp_agent/resources/examples/internal/agent.py +2 -2
  38. mcp_agent/resources/examples/internal/fastagent.config.yaml +5 -0
  39. mcp_agent/resources/examples/internal/history_transfer.py +35 -0
  40. mcp_agent/mcp/mcp_agent_server.py +0 -56
  41. {fast_agent_mcp-0.2.3.dist-info → fast_agent_mcp-0.2.5.dist-info}/WHEEL +0 -0
  42. {fast_agent_mcp-0.2.3.dist-info → fast_agent_mcp-0.2.5.dist-info}/entry_points.txt +0 -0
  43. {fast_agent_mcp-0.2.3.dist-info → fast_agent_mcp-0.2.5.dist-info}/licenses/LICENSE +0 -0
@@ -6,16 +6,17 @@ directly creates Agent instances without proxies.
6
6
 
7
7
  import argparse
8
8
  import asyncio
9
- import os
9
+ import sys
10
10
  from contextlib import asynccontextmanager
11
+ from importlib.metadata import version as get_version
11
12
  from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, TypeVar
12
13
 
13
14
  import yaml
14
15
 
16
+ from mcp_agent import config
15
17
  from mcp_agent.app import MCPApp
16
- from mcp_agent.config import Settings
17
18
  from mcp_agent.context import Context
18
- from mcp_agent.core.direct_agent_app import DirectAgentApp
19
+ from mcp_agent.core.agent_app import AgentApp
19
20
  from mcp_agent.core.direct_decorators import (
20
21
  agent as agent_decorator,
21
22
  )
@@ -70,7 +71,7 @@ class FastAgent:
70
71
  def __init__(
71
72
  self,
72
73
  name: str,
73
- config_path: Optional[str] = None,
74
+ config_path: str | None = None,
74
75
  ignore_unknown_args: bool = False,
75
76
  ) -> None:
76
77
  """
@@ -101,6 +102,33 @@ class FastAgent:
101
102
  action="store_true",
102
103
  help="Disable progress display, tool and message logging for cleaner output",
103
104
  )
105
+ parser.add_argument(
106
+ "--version",
107
+ action="store_true",
108
+ help="Show version and exit",
109
+ )
110
+ parser.add_argument(
111
+ "--server",
112
+ action="store_true",
113
+ help="Run as an MCP server",
114
+ )
115
+ parser.add_argument(
116
+ "--transport",
117
+ choices=["sse", "stdio"],
118
+ default="sse",
119
+ help="Transport protocol to use when running as a server (sse or stdio)",
120
+ )
121
+ parser.add_argument(
122
+ "--port",
123
+ type=int,
124
+ default=8000,
125
+ help="Port to use when running as a server with SSE transport",
126
+ )
127
+ parser.add_argument(
128
+ "--host",
129
+ default="0.0.0.0",
130
+ help="Host address to bind to when running as a server with SSE transport",
131
+ )
104
132
 
105
133
  if ignore_unknown_args:
106
134
  known_args, _ = parser.parse_known_args()
@@ -108,29 +136,59 @@ class FastAgent:
108
136
  else:
109
137
  self.args = parser.parse_args()
110
138
 
139
+ # Handle version flag
140
+ if self.args.version:
141
+ try:
142
+ app_version = get_version("fast-agent-mcp")
143
+ except: # noqa: E722
144
+ app_version = "unknown"
145
+ print(f"fast-agent-mcp v{app_version}")
146
+ sys.exit(0)
147
+
111
148
  self.name = name
112
149
  self.config_path = config_path
113
- self._load_config()
114
150
 
115
- # Create the MCPApp with the config
116
- self.app = MCPApp(
117
- name=name,
118
- settings=Settings(**self.config) if hasattr(self, "config") else None,
119
- )
151
+ try:
152
+ # Load configuration directly for this instance
153
+ self._load_config()
154
+
155
+ # Create the app with our local settings
156
+ self.app = MCPApp(
157
+ name=name,
158
+ settings=config.Settings(**self.config) if hasattr(self, "config") else None,
159
+ )
160
+
161
+ except yaml.parser.ParserError as e:
162
+ handle_error(
163
+ e,
164
+ "YAML Parsing Error",
165
+ "There was an error parsing the config or secrets YAML configuration file.",
166
+ )
167
+ raise SystemExit(1)
120
168
 
121
169
  # Dictionary to store agent configurations from decorators
122
170
  self.agents: Dict[str, Dict[str, Any]] = {}
123
171
 
124
172
  def _load_config(self) -> None:
125
- """Load configuration from YAML file"""
126
- if self.config_path:
127
- with open(self.config_path) as f:
128
- self.config = yaml.safe_load(f) or {}
129
- elif os.path.exists("fastagent.config.yaml"):
130
- with open("fastagent.config.yaml") as f:
131
- self.config = yaml.safe_load(f) or {}
132
- else:
133
- self.config = {}
173
+ """Load configuration from YAML file including secrets using get_settings
174
+ but without relying on the global cache."""
175
+
176
+ # Import but make a local copy to avoid affecting the global state
177
+ from mcp_agent.config import _settings, get_settings
178
+
179
+ # Temporarily clear the global settings to ensure a fresh load
180
+ old_settings = _settings
181
+ _settings = None
182
+
183
+ try:
184
+ # Use get_settings to load config - this handles all paths and secrets merging
185
+ settings = get_settings(self.config_path)
186
+
187
+ # Convert to dict for backward compatibility
188
+ self.config = settings.model_dump() if settings else {}
189
+ finally:
190
+ # Restore the original global settings
191
+ _settings = old_settings
134
192
 
135
193
  @property
136
194
  def context(self) -> Context:
@@ -159,16 +217,17 @@ class FastAgent:
159
217
  quiet_mode = hasattr(self, "args") and self.args.quiet
160
218
 
161
219
  try:
162
- async with self.app.run() as agent_app:
220
+ async with self.app.run():
163
221
  # Apply quiet mode if requested
164
222
  if (
165
223
  quiet_mode
166
- and hasattr(agent_app.context, "config")
167
- and hasattr(agent_app.context.config, "logger")
224
+ and hasattr(self.app.context, "config")
225
+ and hasattr(self.app.context.config, "logger")
168
226
  ):
169
- agent_app.context.config.logger.progress_display = False
170
- agent_app.context.config.logger.show_chat = False
171
- agent_app.context.config.logger.show_tools = False
227
+ # Update our app's config directly
228
+ self.app.context.config.logger.progress_display = False
229
+ self.app.context.config.logger.show_chat = False
230
+ self.app.context.config.logger.show_tools = False
172
231
 
173
232
  # Directly disable the progress display singleton
174
233
  from mcp_agent.progress_display import progress_display
@@ -196,7 +255,42 @@ class FastAgent:
196
255
  )
197
256
 
198
257
  # Create a wrapper with all agents for simplified access
199
- wrapper = DirectAgentApp(active_agents)
258
+ wrapper = AgentApp(active_agents)
259
+
260
+ # Handle command line options that should be processed after agent initialization
261
+
262
+ # Handle --server option
263
+ if hasattr(self, "args") and self.args.server:
264
+ try:
265
+ # Print info message if not in quiet mode
266
+ if not quiet_mode:
267
+ print(f"Starting FastAgent '{self.name}' in server mode")
268
+ print(f"Transport: {self.args.transport}")
269
+ if self.args.transport == "sse":
270
+ print(f"Listening on {self.args.host}:{self.args.port}")
271
+ print("Press Ctrl+C to stop")
272
+
273
+ # Create the MCP server
274
+ from mcp_agent.mcp_server import AgentMCPServer
275
+
276
+ mcp_server = AgentMCPServer(
277
+ agent_app=wrapper,
278
+ server_name=f"{self.name}-MCP-Server",
279
+ )
280
+
281
+ # Run the server directly (this is a blocking call)
282
+ await mcp_server.run_async(
283
+ transport=self.args.transport, host=self.args.host, port=self.args.port
284
+ )
285
+ except KeyboardInterrupt:
286
+ if not quiet_mode:
287
+ print("\nServer stopped by user (Ctrl+C)")
288
+ except Exception as e:
289
+ if not quiet_mode:
290
+ print(f"\nServer stopped with error: {e}")
291
+
292
+ # Exit after server shutdown
293
+ raise SystemExit(0)
200
294
 
201
295
  # Handle direct message sending if --agent and --message are provided
202
296
  if hasattr(self, "args") and self.args.agent and self.args.message:
@@ -215,7 +309,8 @@ class FastAgent:
215
309
  agent = active_agents[agent_name]
216
310
  response = await agent.send(message)
217
311
 
218
- # Print the response in quiet mode
312
+ # In quiet mode, just print the raw response
313
+ # The chat display should already be turned off by the configuration
219
314
  if self.args.quiet:
220
315
  print(f"{response}")
221
316
 
@@ -297,9 +392,67 @@ class FastAgent:
297
392
  e,
298
393
  "User requested exit",
299
394
  )
395
+ elif isinstance(e, asyncio.CancelledError):
396
+ handle_error(
397
+ e,
398
+ "Cancelled",
399
+ "The operation was cancelled.",
400
+ )
300
401
  else:
301
402
  handle_error(e, error_type or "Error", "An unexpected error occurred.")
302
403
 
404
+ async def start_server(
405
+ self,
406
+ transport: str = "sse",
407
+ host: str = "0.0.0.0",
408
+ port: int = 8000,
409
+ server_name: Optional[str] = None,
410
+ server_description: Optional[str] = None,
411
+ ) -> None:
412
+ """
413
+ Start the application as an MCP server.
414
+ This method initializes agents and exposes them through an MCP server.
415
+ It is a blocking method that runs until the server is stopped.
416
+
417
+ Args:
418
+ transport: Transport protocol to use ("stdio" or "sse")
419
+ host: Host address for the server when using SSE
420
+ port: Port for the server when using SSE
421
+ server_name: Optional custom name for the MCP server
422
+ server_description: Optional description for the MCP server
423
+ """
424
+ # This method simply updates the command line arguments and uses run()
425
+ # to ensure we follow the same initialization path for all operations
426
+
427
+ # Store original args
428
+ original_args = None
429
+ if hasattr(self, "args"):
430
+ original_args = self.args
431
+
432
+ # Create our own args object with server settings
433
+ from argparse import Namespace
434
+
435
+ self.args = Namespace()
436
+ self.args.server = True
437
+ self.args.transport = transport
438
+ self.args.host = host
439
+ self.args.port = port
440
+ self.args.quiet = (
441
+ original_args.quiet if original_args and hasattr(original_args, "quiet") else False
442
+ )
443
+ self.args.model = None
444
+ if hasattr(original_args, "model"):
445
+ self.args.model = original_args.model
446
+
447
+ # Run the application, which will detect the server flag and start server mode
448
+ async with self.run():
449
+ pass # This won't be reached due to SystemExit in run()
450
+
451
+ # Restore original args (if we get here)
452
+ if original_args:
453
+ self.args = original_args
454
+
455
+ # Keep run_with_mcp_server for backward compatibility
303
456
  async def run_with_mcp_server(
304
457
  self,
305
458
  transport: str = "sse",
@@ -310,6 +463,8 @@ class FastAgent:
310
463
  ) -> None:
311
464
  """
312
465
  Run the application and expose agents through an MCP server.
466
+ This method is kept for backward compatibility.
467
+ For new code, use start_server() instead.
313
468
 
314
469
  Args:
315
470
  transport: Transport protocol to use ("stdio" or "sse")
@@ -318,26 +473,40 @@ class FastAgent:
318
473
  server_name: Optional custom name for the MCP server
319
474
  server_description: Optional description for the MCP server
320
475
  """
321
- from mcp_agent.mcp_server import AgentMCPServer
322
-
323
- async with self.run() as agent_app:
324
- # Create the MCP server
325
- mcp_server = AgentMCPServer(
326
- agent_app=agent_app,
327
- server_name=server_name or f"{self.name}-MCP-Server",
328
- server_description=server_description,
329
- )
330
-
331
- # Run the MCP server in a separate task
332
- server_task = asyncio.create_task(
333
- mcp_server.run_async(transport=transport, host=host, port=port)
334
- )
476
+ await self.start_server(
477
+ transport=transport,
478
+ host=host,
479
+ port=port,
480
+ server_name=server_name,
481
+ server_description=server_description,
482
+ )
335
483
 
336
- try:
337
- # Wait for the server task to complete (or be cancelled)
338
- await server_task
339
- except asyncio.CancelledError:
340
- # Propagate cancellation
341
- server_task.cancel()
342
- await asyncio.gather(server_task, return_exceptions=True)
343
- raise
484
+ async def main(self):
485
+ """
486
+ Helper method for checking if server mode was requested.
487
+
488
+ Usage:
489
+ ```python
490
+ fast = FastAgent("My App")
491
+
492
+ @fast.agent(...)
493
+ async def app_main():
494
+ # Check if server mode was requested
495
+ # This doesn't actually do anything - the check happens in run()
496
+ # But it provides a way for application code to know if server mode
497
+ # was requested for conditionals
498
+ is_server_mode = hasattr(self, "args") and self.args.server
499
+
500
+ # Normal run - this will handle server mode automatically if requested
501
+ async with fast.run() as agent:
502
+ # This code only executes for normal mode
503
+ # Server mode will exit before reaching here
504
+ await agent.send("Hello")
505
+ ```
506
+
507
+ Returns:
508
+ bool: True if --server flag is set, False otherwise
509
+ """
510
+ # Just check if the flag is set, no action here
511
+ # The actual server code will be handled by run()
512
+ return hasattr(self, "args") and self.args.server
@@ -14,6 +14,8 @@ from mcp.types import (
14
14
  BlobResourceContents,
15
15
  EmbeddedResource,
16
16
  ImageContent,
17
+ ReadResourceResult,
18
+ ResourceContents,
17
19
  TextContent,
18
20
  TextResourceContents,
19
21
  )
@@ -25,6 +27,9 @@ from mcp_agent.mcp.mime_utils import (
25
27
  is_image_mime_type,
26
28
  )
27
29
 
30
+ # Type for all MCP content types
31
+ MCPContentType = Union[TextContent, ImageContent, EmbeddedResource, ResourceContents]
32
+
28
33
 
29
34
  def MCPText(
30
35
  text: str,
@@ -147,7 +152,8 @@ def MCPFile(
147
152
 
148
153
 
149
154
  def MCPPrompt(
150
- *content_items: Union[dict, str, Path, bytes], role: Literal["user", "assistant"] = "user"
155
+ *content_items: Union[dict, str, Path, bytes, MCPContentType, 'EmbeddedResource', 'ReadResourceResult'],
156
+ role: Literal["user", "assistant"] = "user"
151
157
  ) -> List[dict]:
152
158
  """
153
159
  Create one or more prompt messages with various content types.
@@ -158,6 +164,11 @@ def MCPPrompt(
158
164
  - File paths with text mime types or other mime types become EmbeddedResource
159
165
  - Dicts with role and content are passed through unchanged
160
166
  - Raw bytes become ImageContent
167
+ - TextContent objects are used directly
168
+ - ImageContent objects are used directly
169
+ - EmbeddedResource objects are used directly
170
+ - ResourceContent objects are wrapped in EmbeddedResource
171
+ - ReadResourceResult objects are expanded into multiple messages
161
172
 
162
173
  Args:
163
174
  *content_items: Content items of various types
@@ -173,9 +184,9 @@ def MCPPrompt(
173
184
  # Already a fully formed message
174
185
  result.append(item)
175
186
  elif isinstance(item, str):
176
- # Simple text content (that's not a file path)
187
+ # Simple text content
177
188
  result.append(MCPText(item, role=role))
178
- elif isinstance(item, Path) or isinstance(item, str):
189
+ elif isinstance(item, Path):
179
190
  # File path - determine the content type based on mime type
180
191
  path_str = str(item)
181
192
  mime_type = guess_mime_type(path_str)
@@ -189,6 +200,28 @@ def MCPPrompt(
189
200
  elif isinstance(item, bytes):
190
201
  # Raw binary data, assume image
191
202
  result.append(MCPImage(data=item, role=role))
203
+ elif isinstance(item, TextContent):
204
+ # Already a TextContent, wrap in a message
205
+ result.append({"role": role, "content": item})
206
+ elif isinstance(item, ImageContent):
207
+ # Already an ImageContent, wrap in a message
208
+ result.append({"role": role, "content": item})
209
+ elif isinstance(item, EmbeddedResource):
210
+ # Already an EmbeddedResource, wrap in a message
211
+ result.append({"role": role, "content": item})
212
+ elif hasattr(item, 'type') and item.type == 'resource' and hasattr(item, 'resource'):
213
+ # Looks like an EmbeddedResource but may not be the exact class
214
+ result.append({"role": role, "content": EmbeddedResource(type="resource", resource=item.resource)})
215
+ elif isinstance(item, ResourceContents):
216
+ # It's a ResourceContents, wrap it in an EmbeddedResource
217
+ result.append({"role": role, "content": EmbeddedResource(type="resource", resource=item)})
218
+ elif isinstance(item, ReadResourceResult):
219
+ # It's a ReadResourceResult, convert each resource content
220
+ for resource_content in item.contents:
221
+ result.append({
222
+ "role": role,
223
+ "content": EmbeddedResource(type="resource", resource=resource_content)
224
+ })
192
225
  else:
193
226
  # Try to convert to string
194
227
  result.append(MCPText(str(item), role=role))
@@ -196,12 +229,12 @@ def MCPPrompt(
196
229
  return result
197
230
 
198
231
 
199
- def User(*content_items) -> List[dict]:
232
+ def User(*content_items: Union[dict, str, Path, bytes, MCPContentType, 'EmbeddedResource', 'ReadResourceResult']) -> List[dict]:
200
233
  """Create user message(s) with various content types."""
201
234
  return MCPPrompt(*content_items, role="user")
202
235
 
203
236
 
204
- def Assistant(*content_items) -> List[dict]:
237
+ def Assistant(*content_items: Union[dict, str, Path, bytes, MCPContentType, 'EmbeddedResource', 'ReadResourceResult']) -> List[dict]:
205
238
  """Create assistant message(s) with various content types."""
206
239
  return MCPPrompt(*content_items, role="assistant")
207
240
 
mcp_agent/core/prompt.py CHANGED
@@ -2,14 +2,15 @@
2
2
  Prompt class for easily creating and working with MCP prompt content.
3
3
  """
4
4
 
5
- from typing import List, Literal
5
+ from pathlib import Path
6
+ from typing import List, Literal, Union
6
7
 
7
8
  from mcp.types import PromptMessage
8
9
 
9
10
  from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
10
11
 
11
12
  # Import our content helper functions
12
- from .mcp_content import Assistant, MCPPrompt, User
13
+ from .mcp_content import Assistant, MCPContentType, MCPPrompt, User
13
14
 
14
15
 
15
16
  class Prompt:
@@ -25,34 +26,75 @@ class Prompt:
25
26
  - Strings become TextContent
26
27
  - Image file paths become ImageContent
27
28
  - Other file paths become EmbeddedResource
29
+ - TextContent objects are used directly
30
+ - ImageContent objects are used directly
31
+ - EmbeddedResource objects are used directly
28
32
  - Pre-formatted messages pass through unchanged
29
33
  """
30
34
 
31
35
  @classmethod
32
- def user(cls, *content_items) -> PromptMessageMultipart:
36
+ def user(cls, *content_items: Union[str, Path, bytes, dict, MCPContentType, PromptMessage, PromptMessageMultipart]) -> PromptMessageMultipart:
33
37
  """
34
38
  Create a user PromptMessageMultipart with various content items.
35
39
 
36
40
  Args:
37
- *content_items: Content items (strings, file paths, etc.)
41
+ *content_items: Content items in various formats:
42
+ - Strings: Converted to TextContent
43
+ - Path objects: Converted based on file type (image/text/binary)
44
+ - Bytes: Treated as image data
45
+ - Dicts with role/content: Content extracted
46
+ - TextContent: Used directly
47
+ - ImageContent: Used directly
48
+ - EmbeddedResource: Used directly
49
+ - PromptMessage: Content extracted
50
+ - PromptMessageMultipart: Content extracted with role changed to user
38
51
 
39
52
  Returns:
40
53
  A PromptMessageMultipart with user role and the specified content
41
54
  """
55
+ # Handle PromptMessage and PromptMessageMultipart directly
56
+ if len(content_items) == 1:
57
+ item = content_items[0]
58
+ if isinstance(item, PromptMessage):
59
+ return PromptMessageMultipart(role="user", content=[item.content])
60
+ elif isinstance(item, PromptMessageMultipart):
61
+ # Keep the content but change role to user
62
+ return PromptMessageMultipart(role="user", content=item.content)
63
+
64
+ # Use the original implementation for other types
42
65
  messages = User(*content_items)
43
66
  return PromptMessageMultipart(role="user", content=[msg["content"] for msg in messages])
44
67
 
45
68
  @classmethod
46
- def assistant(cls, *content_items) -> PromptMessageMultipart:
69
+ def assistant(cls, *content_items: Union[str, Path, bytes, dict, MCPContentType, PromptMessage, PromptMessageMultipart]) -> PromptMessageMultipart:
47
70
  """
48
71
  Create an assistant PromptMessageMultipart with various content items.
49
72
 
50
73
  Args:
51
- *content_items: Content items (strings, file paths, etc.)
74
+ *content_items: Content items in various formats:
75
+ - Strings: Converted to TextContent
76
+ - Path objects: Converted based on file type (image/text/binary)
77
+ - Bytes: Treated as image data
78
+ - Dicts with role/content: Content extracted
79
+ - TextContent: Used directly
80
+ - ImageContent: Used directly
81
+ - EmbeddedResource: Used directly
82
+ - PromptMessage: Content extracted
83
+ - PromptMessageMultipart: Content extracted with role changed to assistant
52
84
 
53
85
  Returns:
54
86
  A PromptMessageMultipart with assistant role and the specified content
55
87
  """
88
+ # Handle PromptMessage and PromptMessageMultipart directly
89
+ if len(content_items) == 1:
90
+ item = content_items[0]
91
+ if isinstance(item, PromptMessage):
92
+ return PromptMessageMultipart(role="assistant", content=[item.content])
93
+ elif isinstance(item, PromptMessageMultipart):
94
+ # Keep the content but change role to assistant
95
+ return PromptMessageMultipart(role="assistant", content=item.content)
96
+
97
+ # Use the original implementation for other types
56
98
  messages = Assistant(*content_items)
57
99
  return PromptMessageMultipart(
58
100
  role="assistant", content=[msg["content"] for msg in messages]
@@ -60,18 +102,38 @@ class Prompt:
60
102
 
61
103
  @classmethod
62
104
  def message(
63
- cls, *content_items, role: Literal["user", "assistant"] = "user"
105
+ cls, *content_items: Union[str, Path, bytes, dict, MCPContentType, PromptMessage, PromptMessageMultipart],
106
+ role: Literal["user", "assistant"] = "user"
64
107
  ) -> PromptMessageMultipart:
65
108
  """
66
109
  Create a PromptMessageMultipart with the specified role and content items.
67
110
 
68
111
  Args:
69
- *content_items: Content items (strings, file paths, etc.)
112
+ *content_items: Content items in various formats:
113
+ - Strings: Converted to TextContent
114
+ - Path objects: Converted based on file type (image/text/binary)
115
+ - Bytes: Treated as image data
116
+ - Dicts with role/content: Content extracted
117
+ - TextContent: Used directly
118
+ - ImageContent: Used directly
119
+ - EmbeddedResource: Used directly
120
+ - PromptMessage: Content extracted
121
+ - PromptMessageMultipart: Content extracted with role changed as specified
70
122
  role: Role for the message (user or assistant)
71
123
 
72
124
  Returns:
73
125
  A PromptMessageMultipart with the specified role and content
74
126
  """
127
+ # Handle PromptMessage and PromptMessageMultipart directly
128
+ if len(content_items) == 1:
129
+ item = content_items[0]
130
+ if isinstance(item, PromptMessage):
131
+ return PromptMessageMultipart(role=role, content=[item.content])
132
+ elif isinstance(item, PromptMessageMultipart):
133
+ # Keep the content but change role as specified
134
+ return PromptMessageMultipart(role=role, content=item.content)
135
+
136
+ # Use the original implementation for other types
75
137
  messages = MCPPrompt(*content_items, role=role)
76
138
  return PromptMessageMultipart(
77
139
  role=messages[0]["role"] if messages else role,
@@ -231,7 +231,7 @@ def get_dependencies_groups(
231
231
  dependencies[name].update(agent_data.get("parallel_agents", []))
232
232
  elif agent_type == AgentType.CHAIN.value:
233
233
  # Chain agents depend on the agents in their sequence
234
- dependencies[name].update(agent_data.get("chain_agents", []))
234
+ dependencies[name].update(agent_data.get("sequence", []))
235
235
  elif agent_type == AgentType.ROUTER.value:
236
236
  # Router agents depend on the agents they route to
237
237
  dependencies[name].update(agent_data.get("router_agents", []))