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.
Files changed (33) hide show
  1. {fast_agent_mcp-0.2.4.dist-info → fast_agent_mcp-0.2.6.dist-info}/METADATA +2 -2
  2. {fast_agent_mcp-0.2.4.dist-info → fast_agent_mcp-0.2.6.dist-info}/RECORD +32 -33
  3. mcp_agent/agents/agent.py +1 -1
  4. mcp_agent/agents/base_agent.py +25 -13
  5. mcp_agent/agents/workflow/chain_agent.py +7 -1
  6. mcp_agent/agents/workflow/evaluator_optimizer.py +6 -0
  7. mcp_agent/agents/workflow/orchestrator_agent.py +6 -1
  8. mcp_agent/agents/workflow/parallel_agent.py +7 -1
  9. mcp_agent/agents/workflow/router_agent.py +7 -2
  10. mcp_agent/cli/commands/setup.py +2 -2
  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/agent_app.py +8 -19
  15. mcp_agent/core/agent_types.py +1 -0
  16. mcp_agent/core/direct_decorators.py +2 -1
  17. mcp_agent/core/direct_factory.py +6 -15
  18. mcp_agent/core/enhanced_prompt.py +3 -3
  19. mcp_agent/core/fastagent.py +202 -46
  20. mcp_agent/core/interactive_prompt.py +1 -1
  21. mcp_agent/llm/augmented_llm.py +8 -10
  22. mcp_agent/llm/augmented_llm_passthrough.py +3 -1
  23. mcp_agent/llm/model_factory.py +5 -7
  24. mcp_agent/mcp/interfaces.py +5 -0
  25. mcp_agent/mcp/prompt_serialization.py +42 -0
  26. mcp_agent/mcp/prompts/prompt_load.py +51 -3
  27. mcp_agent/mcp_server/agent_server.py +61 -12
  28. mcp_agent/resources/examples/internal/agent.py +2 -2
  29. mcp_agent/resources/examples/internal/fastagent.config.yaml +5 -0
  30. mcp_agent/mcp/mcp_agent_server.py +0 -56
  31. {fast_agent_mcp-0.2.4.dist-info → fast_agent_mcp-0.2.6.dist-info}/WHEEL +0 -0
  32. {fast_agent_mcp-0.2.4.dist-info → fast_agent_mcp-0.2.6.dist-info}/entry_points.txt +0 -0
  33. {fast_agent_mcp-0.2.4.dist-info → fast_agent_mcp-0.2.6.dist-info}/licenses/LICENSE +0 -0
@@ -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("routing_instruction"),
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":
@@ -6,14 +6,15 @@ 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
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: 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,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
- if self.config_path:
134
- with open(self.config_path) as f:
135
- self.config = yaml.safe_load(f) or {}
136
- elif os.path.exists("fastagent.config.yaml"):
137
- with open("fastagent.config.yaml") as f:
138
- self.config = yaml.safe_load(f) or {}
139
- else:
140
- 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
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() as agent_app:
220
+ async with self.app.run():
170
221
  # Apply quiet mode if requested
171
222
  if (
172
223
  quiet_mode
173
- and hasattr(agent_app.context, "config")
174
- and hasattr(agent_app.context.config, "logger")
224
+ and hasattr(self.app.context, "config")
225
+ and hasattr(self.app.context.config, "logger")
175
226
  ):
176
- agent_app.context.config.logger.progress_display = False
177
- agent_app.context.config.logger.show_chat = False
178
- 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
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
- # 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
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
- from mcp_agent.mcp_server import AgentMCPServer
335
-
336
- async with self.run() as agent_app:
337
- # Create the MCP server
338
- mcp_server = AgentMCPServer(
339
- agent_app=agent_app,
340
- server_name=server_name or f"{self.name}-MCP-Server",
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
- try:
350
- # Wait for the server task to complete (or be cancelled)
351
- await server_task
352
- except asyncio.CancelledError:
353
- # Propagate cancellation
354
- server_task.cancel()
355
- await asyncio.gather(server_task, return_exceptions=True)
356
- 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
@@ -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
- # Create agent_types dictionary if not provided
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 = ""
@@ -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 simple delimeted format.
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
- # Convert to delimited format
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
- # Write to file
446
- with open(filename, "w", encoding="utf-8") as f:
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
- return Prompt.assistant(await self.generate_str(last_message.first_text()))
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 = (
@@ -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-5-sonnet-latest",
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)
@@ -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
- template: PromptTemplate = PromptTemplateLoader().load_from_file(file)
105
- return create_messages_with_resources(template.content_sections, [file])
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
- return PromptMessageMultipart.to_multipart(load_prompt(file))
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))