fast-agent-mcp 0.2.4__py3-none-any.whl → 0.2.6__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.4.dist-info → fast_agent_mcp-0.2.6.dist-info}/METADATA +2 -2
- {fast_agent_mcp-0.2.4.dist-info → fast_agent_mcp-0.2.6.dist-info}/RECORD +32 -33
- mcp_agent/agents/agent.py +1 -1
- mcp_agent/agents/base_agent.py +25 -13
- mcp_agent/agents/workflow/chain_agent.py +7 -1
- mcp_agent/agents/workflow/evaluator_optimizer.py +6 -0
- mcp_agent/agents/workflow/orchestrator_agent.py +6 -1
- mcp_agent/agents/workflow/parallel_agent.py +7 -1
- mcp_agent/agents/workflow/router_agent.py +7 -2
- mcp_agent/cli/commands/setup.py +2 -2
- mcp_agent/cli/main.py +11 -0
- mcp_agent/config.py +29 -7
- mcp_agent/context.py +2 -0
- mcp_agent/core/agent_app.py +8 -19
- mcp_agent/core/agent_types.py +1 -0
- mcp_agent/core/direct_decorators.py +2 -1
- mcp_agent/core/direct_factory.py +6 -15
- mcp_agent/core/enhanced_prompt.py +3 -3
- mcp_agent/core/fastagent.py +202 -46
- mcp_agent/core/interactive_prompt.py +1 -1
- mcp_agent/llm/augmented_llm.py +8 -10
- mcp_agent/llm/augmented_llm_passthrough.py +3 -1
- mcp_agent/llm/model_factory.py +5 -7
- mcp_agent/mcp/interfaces.py +5 -0
- mcp_agent/mcp/prompt_serialization.py +42 -0
- mcp_agent/mcp/prompts/prompt_load.py +51 -3
- mcp_agent/mcp_server/agent_server.py +61 -12
- mcp_agent/resources/examples/internal/agent.py +2 -2
- mcp_agent/resources/examples/internal/fastagent.config.yaml +5 -0
- mcp_agent/mcp/mcp_agent_server.py +0 -56
- {fast_agent_mcp-0.2.4.dist-info → fast_agent_mcp-0.2.6.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.4.dist-info → fast_agent_mcp-0.2.6.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.2.4.dist-info → fast_agent_mcp-0.2.6.dist-info}/licenses/LICENSE +0 -0
mcp_agent/core/direct_factory.py
CHANGED
@@ -148,10 +148,7 @@ async def create_agents_by_type(
|
|
148
148
|
|
149
149
|
# Attach LLM to the agent
|
150
150
|
llm_factory = model_factory_func(model=config.model)
|
151
|
-
await agent.attach_llm(
|
152
|
-
llm_factory,
|
153
|
-
request_params=config.default_request_params
|
154
|
-
)
|
151
|
+
await agent.attach_llm(llm_factory, request_params=config.default_request_params)
|
155
152
|
result_agents[name] = agent
|
156
153
|
|
157
154
|
elif agent_type == AgentType.ORCHESTRATOR:
|
@@ -185,8 +182,7 @@ async def create_agents_by_type(
|
|
185
182
|
# Attach LLM to the orchestrator
|
186
183
|
llm_factory = model_factory_func(model=config.model)
|
187
184
|
await orchestrator.attach_llm(
|
188
|
-
llm_factory,
|
189
|
-
request_params=config.default_request_params
|
185
|
+
llm_factory, request_params=config.default_request_params
|
190
186
|
)
|
191
187
|
|
192
188
|
result_agents[name] = orchestrator
|
@@ -201,9 +197,7 @@ async def create_agents_by_type(
|
|
201
197
|
# Create default fan-in agent with auto-generated name
|
202
198
|
fan_in_name = f"{name}_fan_in"
|
203
199
|
fan_in_agent = await _create_default_fan_in_agent(
|
204
|
-
fan_in_name,
|
205
|
-
app_instance.context,
|
206
|
-
model_factory_func
|
200
|
+
fan_in_name, app_instance.context, model_factory_func
|
207
201
|
)
|
208
202
|
# Add to result_agents so it's registered properly
|
209
203
|
result_agents[fan_in_name] = fan_in_agent
|
@@ -242,16 +236,13 @@ async def create_agents_by_type(
|
|
242
236
|
config=config,
|
243
237
|
context=app_instance.context,
|
244
238
|
agents=router_agents,
|
245
|
-
routing_instruction=agent_data.get("
|
239
|
+
routing_instruction=agent_data.get("instruction"),
|
246
240
|
)
|
247
241
|
await router.initialize()
|
248
242
|
|
249
243
|
# Attach LLM to the router
|
250
244
|
llm_factory = model_factory_func(model=config.model)
|
251
|
-
await router.attach_llm(
|
252
|
-
llm_factory,
|
253
|
-
request_params=config.default_request_params
|
254
|
-
)
|
245
|
+
await router.attach_llm(llm_factory, request_params=config.default_request_params)
|
255
246
|
result_agents[name] = router
|
256
247
|
|
257
248
|
elif agent_type == AgentType.CHAIN:
|
@@ -459,7 +450,7 @@ async def _create_default_fan_in_agent(
|
|
459
450
|
default_config = AgentConfig(
|
460
451
|
name=fan_in_name,
|
461
452
|
model="passthrough",
|
462
|
-
instruction="You are a passthrough agent that combines outputs from parallel agents."
|
453
|
+
instruction="You are a passthrough agent that combines outputs from parallel agents.",
|
463
454
|
)
|
464
455
|
|
465
456
|
# Create and initialize the default agent
|
@@ -291,7 +291,7 @@ async def get_enhanced_input(
|
|
291
291
|
return f"SELECT_PROMPT:{cmd_parts[1].strip()}"
|
292
292
|
elif cmd == "exit":
|
293
293
|
return "EXIT"
|
294
|
-
elif cmd == "stop":
|
294
|
+
elif cmd.lower() == "stop":
|
295
295
|
return "STOP"
|
296
296
|
|
297
297
|
# Agent switching
|
@@ -420,7 +420,7 @@ async def get_argument_input(
|
|
420
420
|
prompt_session.app.exit()
|
421
421
|
|
422
422
|
|
423
|
-
async def handle_special_commands(command, agent_app=None):
|
423
|
+
async def handle_special_commands(command: str, agent_app=None):
|
424
424
|
"""Handle special input commands."""
|
425
425
|
# Quick guard for empty or None commands
|
426
426
|
if not command:
|
@@ -450,7 +450,7 @@ async def handle_special_commands(command, agent_app=None):
|
|
450
450
|
print("\033c", end="")
|
451
451
|
return True
|
452
452
|
|
453
|
-
elif command == "EXIT":
|
453
|
+
elif command.upper() == "EXIT":
|
454
454
|
raise PromptExitError("User requested to exit fast-agent session")
|
455
455
|
|
456
456
|
elif command == "LIST_AGENTS":
|
mcp_agent/core/fastagent.py
CHANGED
@@ -6,14 +6,15 @@ 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
19
|
from mcp_agent.core.agent_app import AgentApp
|
19
20
|
from mcp_agent.core.direct_decorators import (
|
@@ -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,10 +136,28 @@ 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
|
150
|
+
|
113
151
|
try:
|
152
|
+
# Load configuration directly for this instance
|
114
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
|
+
|
115
161
|
except yaml.parser.ParserError as e:
|
116
162
|
handle_error(
|
117
163
|
e,
|
@@ -119,25 +165,30 @@ class FastAgent:
|
|
119
165
|
"There was an error parsing the config or secrets YAML configuration file.",
|
120
166
|
)
|
121
167
|
raise SystemExit(1)
|
122
|
-
# Create the MCPApp with the config
|
123
|
-
self.app = MCPApp(
|
124
|
-
name=name,
|
125
|
-
settings=Settings(**self.config) if hasattr(self, "config") else None,
|
126
|
-
)
|
127
168
|
|
128
169
|
# Dictionary to store agent configurations from decorators
|
129
170
|
self.agents: Dict[str, Dict[str, Any]] = {}
|
130
171
|
|
131
172
|
def _load_config(self) -> None:
|
132
|
-
"""Load configuration from YAML file
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
141
192
|
|
142
193
|
@property
|
143
194
|
def context(self) -> Context:
|
@@ -166,16 +217,17 @@ class FastAgent:
|
|
166
217
|
quiet_mode = hasattr(self, "args") and self.args.quiet
|
167
218
|
|
168
219
|
try:
|
169
|
-
async with self.app.run()
|
220
|
+
async with self.app.run():
|
170
221
|
# Apply quiet mode if requested
|
171
222
|
if (
|
172
223
|
quiet_mode
|
173
|
-
and hasattr(
|
174
|
-
and hasattr(
|
224
|
+
and hasattr(self.app.context, "config")
|
225
|
+
and hasattr(self.app.context.config, "logger")
|
175
226
|
):
|
176
|
-
|
177
|
-
|
178
|
-
|
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
|
179
231
|
|
180
232
|
# Directly disable the progress display singleton
|
181
233
|
from mcp_agent.progress_display import progress_display
|
@@ -205,6 +257,41 @@ class FastAgent:
|
|
205
257
|
# Create a wrapper with all agents for simplified access
|
206
258
|
wrapper = AgentApp(active_agents)
|
207
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)
|
294
|
+
|
208
295
|
# Handle direct message sending if --agent and --message are provided
|
209
296
|
if hasattr(self, "args") and self.args.agent and self.args.message:
|
210
297
|
agent_name = self.args.agent
|
@@ -222,7 +309,8 @@ class FastAgent:
|
|
222
309
|
agent = active_agents[agent_name]
|
223
310
|
response = await agent.send(message)
|
224
311
|
|
225
|
-
#
|
312
|
+
# In quiet mode, just print the raw response
|
313
|
+
# The chat display should already be turned off by the configuration
|
226
314
|
if self.args.quiet:
|
227
315
|
print(f"{response}")
|
228
316
|
|
@@ -313,6 +401,58 @@ class FastAgent:
|
|
313
401
|
else:
|
314
402
|
handle_error(e, error_type or "Error", "An unexpected error occurred.")
|
315
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
|
316
456
|
async def run_with_mcp_server(
|
317
457
|
self,
|
318
458
|
transport: str = "sse",
|
@@ -323,6 +463,8 @@ class FastAgent:
|
|
323
463
|
) -> None:
|
324
464
|
"""
|
325
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.
|
326
468
|
|
327
469
|
Args:
|
328
470
|
transport: Transport protocol to use ("stdio" or "sse")
|
@@ -331,26 +473,40 @@ class FastAgent:
|
|
331
473
|
server_name: Optional custom name for the MCP server
|
332
474
|
server_description: Optional description for the MCP server
|
333
475
|
"""
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
server_description=server_description,
|
342
|
-
)
|
343
|
-
|
344
|
-
# Run the MCP server in a separate task
|
345
|
-
server_task = asyncio.create_task(
|
346
|
-
mcp_server.run_async(transport=transport, host=host, port=port)
|
347
|
-
)
|
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
|
+
)
|
348
483
|
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
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
|
@@ -77,7 +77,7 @@ class InteractivePrompt:
|
|
77
77
|
if agent not in available_agents:
|
78
78
|
raise ValueError(f"No agent named '{agent}'")
|
79
79
|
|
80
|
-
#
|
80
|
+
# Ensure we track available agents in a set for fast lookup
|
81
81
|
available_agents_set = set(available_agents)
|
82
82
|
|
83
83
|
result = ""
|
mcp_agent/llm/augmented_llm.py
CHANGED
@@ -39,7 +39,6 @@ from mcp_agent.mcp.interfaces import (
|
|
39
39
|
from mcp_agent.mcp.mcp_aggregator import MCPAggregator
|
40
40
|
from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
|
41
41
|
from mcp_agent.mcp.prompt_render import render_multipart_message
|
42
|
-
from mcp_agent.mcp.prompt_serialization import multipart_messages_to_delimited_format
|
43
42
|
from mcp_agent.ui.console_display import ConsoleDisplay
|
44
43
|
|
45
44
|
# Define type variables locally
|
@@ -148,7 +147,7 @@ class AugmentedLLM(ContextDependent, AugmentedLLMProtocol, Generic[MessageParamT
|
|
148
147
|
"""Apply the prompt and return the result as a Pydantic model, or None if coercion fails"""
|
149
148
|
try:
|
150
149
|
result: PromptMessageMultipart = await self.generate(prompt, request_params)
|
151
|
-
json_data = from_json(result.first_text(), allow_partial=True)
|
150
|
+
json_data = from_json(result.first_text().strip(), allow_partial=True)
|
152
151
|
validated_model = model.model_validate(json_data)
|
153
152
|
return cast("ModelT", validated_model), Prompt.assistant(json_data)
|
154
153
|
except Exception as e:
|
@@ -435,16 +434,15 @@ class AugmentedLLM(ContextDependent, AugmentedLLMProtocol, Generic[MessageParamT
|
|
435
434
|
|
436
435
|
async def _save_history(self, filename: str) -> None:
|
437
436
|
"""
|
438
|
-
Save the Message History to a file in a
|
437
|
+
Save the Message History to a file in a format determined by the file extension.
|
438
|
+
|
439
|
+
Uses JSON format for .json files (MCP SDK compatible format) and
|
440
|
+
delimited text format for other extensions.
|
439
441
|
"""
|
440
|
-
|
441
|
-
delimited_content = multipart_messages_to_delimited_format(
|
442
|
-
self._message_history,
|
443
|
-
)
|
442
|
+
from mcp_agent.mcp.prompt_serialization import save_messages_to_file
|
444
443
|
|
445
|
-
#
|
446
|
-
|
447
|
-
f.write("\n\n".join(delimited_content))
|
444
|
+
# Save messages using the unified save function that auto-detects format
|
445
|
+
save_messages_to_file(self._message_history, filename)
|
448
446
|
|
449
447
|
@abstractmethod
|
450
448
|
async def _apply_prompt_provider_specific(
|
@@ -143,7 +143,9 @@ class PassthroughLLM(AugmentedLLM):
|
|
143
143
|
|
144
144
|
# TODO -- improve when we support Audio/Multimodal gen
|
145
145
|
if self.is_tool_call(last_message):
|
146
|
-
|
146
|
+
result = Prompt.assistant(await self.generate_str(last_message.first_text()))
|
147
|
+
await self.show_assistant_message(result.first_text())
|
148
|
+
return result
|
147
149
|
|
148
150
|
if last_message.first_text().startswith(FIXED_RESPONSE_INDICATOR):
|
149
151
|
self._fixed_response = (
|
mcp_agent/llm/model_factory.py
CHANGED
@@ -103,7 +103,7 @@ class ModelFactory:
|
|
103
103
|
"sonnet": "claude-3-7-sonnet-latest",
|
104
104
|
"sonnet35": "claude-3-5-sonnet-latest",
|
105
105
|
"sonnet37": "claude-3-7-sonnet-latest",
|
106
|
-
"claude": "claude-3-
|
106
|
+
"claude": "claude-3-7-sonnet-latest",
|
107
107
|
"haiku": "claude-3-5-haiku-latest",
|
108
108
|
"haiku3": "claude-3-haiku-20240307",
|
109
109
|
"haiku35": "claude-3-5-haiku-latest",
|
@@ -188,24 +188,22 @@ class ModelFactory:
|
|
188
188
|
|
189
189
|
# Create a factory function matching the updated attach_llm protocol
|
190
190
|
def factory(
|
191
|
-
agent: Agent,
|
192
|
-
request_params: Optional[RequestParams] = None,
|
193
|
-
**kwargs
|
191
|
+
agent: Agent, request_params: Optional[RequestParams] = None, **kwargs
|
194
192
|
) -> AugmentedLLMProtocol:
|
195
193
|
# Create base params with parsed model name
|
196
194
|
base_params = RequestParams()
|
197
195
|
base_params.model = config.model_name # Use the parsed model name, not the alias
|
198
|
-
|
196
|
+
|
199
197
|
# Add reasoning effort if available
|
200
198
|
if config.reasoning_effort:
|
201
199
|
kwargs["reasoning_effort"] = config.reasoning_effort.value
|
202
|
-
|
200
|
+
|
203
201
|
# Forward all arguments to LLM constructor
|
204
202
|
llm_args = {
|
205
203
|
"agent": agent,
|
206
204
|
"model": config.model_name,
|
207
205
|
"request_params": request_params,
|
208
|
-
**kwargs
|
206
|
+
**kwargs,
|
209
207
|
}
|
210
208
|
|
211
209
|
llm: AugmentedLLMProtocol = llm_class(**llm_args)
|
mcp_agent/mcp/interfaces.py
CHANGED
@@ -136,6 +136,11 @@ class AgentProtocol(AugmentedLLMProtocol, Protocol):
|
|
136
136
|
|
137
137
|
name: str
|
138
138
|
|
139
|
+
@property
|
140
|
+
def agent_type(self) -> str:
|
141
|
+
"""Return the type of this agent"""
|
142
|
+
...
|
143
|
+
|
139
144
|
async def __call__(self, message: Union[str, PromptMessage, PromptMessageMultipart]) -> str:
|
140
145
|
"""Make the agent callable for sending messages directly."""
|
141
146
|
...
|
@@ -110,6 +110,48 @@ def load_messages_from_json_file(file_path: str) -> List[PromptMessageMultipart]
|
|
110
110
|
return json_to_multipart_messages(json_str)
|
111
111
|
|
112
112
|
|
113
|
+
def save_messages_to_file(messages: List[PromptMessageMultipart], file_path: str) -> None:
|
114
|
+
"""
|
115
|
+
Save PromptMessageMultipart objects to a file, with format determined by file extension.
|
116
|
+
|
117
|
+
Uses JSON format for .json files and delimited text format for other extensions.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
messages: List of PromptMessageMultipart objects
|
121
|
+
file_path: Path to save the file
|
122
|
+
"""
|
123
|
+
path_str = str(file_path).lower()
|
124
|
+
|
125
|
+
if path_str.endswith(".json"):
|
126
|
+
# Use JSON format for .json files (MCP SDK compatible format)
|
127
|
+
save_messages_to_json_file(messages, file_path)
|
128
|
+
else:
|
129
|
+
# Use delimited text format for other extensions
|
130
|
+
save_messages_to_delimited_file(messages, file_path)
|
131
|
+
|
132
|
+
|
133
|
+
def load_messages_from_file(file_path: str) -> List[PromptMessageMultipart]:
|
134
|
+
"""
|
135
|
+
Load PromptMessageMultipart objects from a file, with format determined by file extension.
|
136
|
+
|
137
|
+
Uses JSON format for .json files and delimited text format for other extensions.
|
138
|
+
|
139
|
+
Args:
|
140
|
+
file_path: Path to the file
|
141
|
+
|
142
|
+
Returns:
|
143
|
+
List of PromptMessageMultipart objects
|
144
|
+
"""
|
145
|
+
path_str = str(file_path).lower()
|
146
|
+
|
147
|
+
if path_str.endswith(".json"):
|
148
|
+
# Use JSON format for .json files (MCP SDK compatible format)
|
149
|
+
return load_messages_from_json_file(file_path)
|
150
|
+
else:
|
151
|
+
# Use delimited text format for other extensions
|
152
|
+
return load_messages_from_delimited_file(file_path)
|
153
|
+
|
154
|
+
|
113
155
|
# -------------------------------------------------------------------------
|
114
156
|
# Delimited Text Format Functions
|
115
157
|
# -------------------------------------------------------------------------
|
@@ -101,9 +101,57 @@ def create_resource_message(
|
|
101
101
|
|
102
102
|
|
103
103
|
def load_prompt(file: Path) -> List[PromptMessage]:
|
104
|
-
|
105
|
-
return
|
104
|
+
"""
|
105
|
+
Load a prompt from a file and return as PromptMessage objects.
|
106
|
+
|
107
|
+
The loader uses file extension to determine the format:
|
108
|
+
- .json files are loaded as MCP SDK compatible JSON format
|
109
|
+
- All other files are loaded using the template-based delimited format
|
110
|
+
|
111
|
+
Args:
|
112
|
+
file: Path to the prompt file
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
List of PromptMessage objects
|
116
|
+
"""
|
117
|
+
file_str = str(file).lower()
|
118
|
+
|
119
|
+
if file_str.endswith(".json"):
|
120
|
+
# JSON format (MCP SDK compatible)
|
121
|
+
from mcp_agent.mcp.prompt_serialization import load_messages_from_json_file
|
122
|
+
|
123
|
+
# Load multipart messages and convert to flat messages
|
124
|
+
multipart_messages = load_messages_from_json_file(str(file))
|
125
|
+
messages = []
|
126
|
+
for mp in multipart_messages:
|
127
|
+
messages.extend(mp.from_multipart())
|
128
|
+
return messages
|
129
|
+
else:
|
130
|
+
# Template-based format (delimited text)
|
131
|
+
template: PromptTemplate = PromptTemplateLoader().load_from_file(file)
|
132
|
+
return create_messages_with_resources(template.content_sections, [file])
|
106
133
|
|
107
134
|
|
108
135
|
def load_prompt_multipart(file: Path) -> List[PromptMessageMultipart]:
|
109
|
-
|
136
|
+
"""
|
137
|
+
Load a prompt from a file and return as PromptMessageMultipart objects.
|
138
|
+
|
139
|
+
The loader uses file extension to determine the format:
|
140
|
+
- .json files are loaded as MCP SDK compatible JSON format
|
141
|
+
- All other files are loaded using the template-based delimited format
|
142
|
+
|
143
|
+
Args:
|
144
|
+
file: Path to the prompt file
|
145
|
+
|
146
|
+
Returns:
|
147
|
+
List of PromptMessageMultipart objects
|
148
|
+
"""
|
149
|
+
file_str = str(file).lower()
|
150
|
+
|
151
|
+
if file_str.endswith(".json"):
|
152
|
+
# JSON format (MCP SDK compatible)
|
153
|
+
from mcp_agent.mcp.prompt_serialization import load_messages_from_json_file
|
154
|
+
return load_messages_from_json_file(str(file))
|
155
|
+
else:
|
156
|
+
# Template-based format (delimited text)
|
157
|
+
return PromptMessageMultipart.to_multipart(load_prompt(file))
|