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.
- {fast_agent_mcp-0.2.3.dist-info → fast_agent_mcp-0.2.5.dist-info}/METADATA +13 -9
- {fast_agent_mcp-0.2.3.dist-info → fast_agent_mcp-0.2.5.dist-info}/RECORD +42 -40
- mcp_agent/__init__.py +2 -2
- mcp_agent/agents/agent.py +5 -0
- mcp_agent/agents/base_agent.py +152 -36
- mcp_agent/agents/workflow/chain_agent.py +9 -13
- mcp_agent/agents/workflow/evaluator_optimizer.py +3 -3
- mcp_agent/agents/workflow/orchestrator_agent.py +9 -7
- mcp_agent/agents/workflow/parallel_agent.py +2 -2
- mcp_agent/agents/workflow/router_agent.py +7 -5
- mcp_agent/cli/main.py +11 -0
- mcp_agent/config.py +29 -7
- mcp_agent/context.py +2 -0
- mcp_agent/core/{direct_agent_app.py → agent_app.py} +115 -15
- mcp_agent/core/direct_factory.py +9 -18
- mcp_agent/core/enhanced_prompt.py +3 -3
- mcp_agent/core/fastagent.py +218 -49
- mcp_agent/core/mcp_content.py +38 -5
- mcp_agent/core/prompt.py +70 -8
- mcp_agent/core/validation.py +1 -1
- mcp_agent/llm/augmented_llm.py +44 -16
- mcp_agent/llm/augmented_llm_passthrough.py +3 -1
- mcp_agent/llm/model_factory.py +16 -28
- mcp_agent/llm/providers/augmented_llm_openai.py +3 -3
- mcp_agent/llm/providers/multipart_converter_anthropic.py +8 -8
- mcp_agent/llm/providers/multipart_converter_openai.py +9 -9
- mcp_agent/mcp/helpers/__init__.py +3 -0
- mcp_agent/mcp/helpers/content_helpers.py +116 -0
- mcp_agent/mcp/interfaces.py +39 -16
- mcp_agent/mcp/mcp_aggregator.py +117 -13
- mcp_agent/mcp/prompt_message_multipart.py +29 -22
- mcp_agent/mcp/prompt_render.py +18 -15
- mcp_agent/mcp/prompt_serialization.py +42 -0
- mcp_agent/mcp/prompts/prompt_helpers.py +22 -112
- mcp_agent/mcp/prompts/prompt_load.py +51 -3
- mcp_agent/mcp_server/agent_server.py +62 -13
- mcp_agent/resources/examples/internal/agent.py +2 -2
- mcp_agent/resources/examples/internal/fastagent.config.yaml +5 -0
- mcp_agent/resources/examples/internal/history_transfer.py +35 -0
- mcp_agent/mcp/mcp_agent_server.py +0 -56
- {fast_agent_mcp-0.2.3.dist-info → fast_agent_mcp-0.2.5.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.3.dist-info → fast_agent_mcp-0.2.5.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.2.3.dist-info → fast_agent_mcp-0.2.5.dist-info}/licenses/LICENSE +0 -0
mcp_agent/core/fastagent.py
CHANGED
@@ -6,16 +6,17 @@ directly creates Agent instances without proxies.
|
|
6
6
|
|
7
7
|
import argparse
|
8
8
|
import asyncio
|
9
|
-
import
|
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.
|
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:
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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()
|
220
|
+
async with self.app.run():
|
163
221
|
# Apply quiet mode if requested
|
164
222
|
if (
|
165
223
|
quiet_mode
|
166
|
-
and hasattr(
|
167
|
-
and hasattr(
|
224
|
+
and hasattr(self.app.context, "config")
|
225
|
+
and hasattr(self.app.context.config, "logger")
|
168
226
|
):
|
169
|
-
|
170
|
-
|
171
|
-
|
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 =
|
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
|
-
#
|
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
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
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
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
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
|
mcp_agent/core/mcp_content.py
CHANGED
@@ -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
|
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
|
187
|
+
# Simple text content
|
177
188
|
result.append(MCPText(item, role=role))
|
178
|
-
elif isinstance(item, Path)
|
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
|
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
|
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
|
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
|
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
|
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,
|
mcp_agent/core/validation.py
CHANGED
@@ -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("
|
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", []))
|