fast-agent-mcp 0.1.13__py3-none-any.whl → 0.2.0__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 (147) hide show
  1. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/METADATA +3 -4
  2. fast_agent_mcp-0.2.0.dist-info/RECORD +123 -0
  3. mcp_agent/__init__.py +75 -0
  4. mcp_agent/agents/agent.py +59 -371
  5. mcp_agent/agents/base_agent.py +522 -0
  6. mcp_agent/agents/workflow/__init__.py +1 -0
  7. mcp_agent/agents/workflow/chain_agent.py +173 -0
  8. mcp_agent/agents/workflow/evaluator_optimizer.py +362 -0
  9. mcp_agent/agents/workflow/orchestrator_agent.py +591 -0
  10. mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_models.py +27 -11
  11. mcp_agent/agents/workflow/parallel_agent.py +182 -0
  12. mcp_agent/agents/workflow/router_agent.py +307 -0
  13. mcp_agent/app.py +3 -1
  14. mcp_agent/cli/commands/bootstrap.py +18 -7
  15. mcp_agent/cli/commands/setup.py +12 -4
  16. mcp_agent/cli/main.py +1 -1
  17. mcp_agent/cli/terminal.py +1 -1
  18. mcp_agent/config.py +24 -35
  19. mcp_agent/context.py +3 -1
  20. mcp_agent/context_dependent.py +3 -1
  21. mcp_agent/core/agent_types.py +10 -7
  22. mcp_agent/core/direct_agent_app.py +179 -0
  23. mcp_agent/core/direct_decorators.py +443 -0
  24. mcp_agent/core/direct_factory.py +476 -0
  25. mcp_agent/core/enhanced_prompt.py +15 -20
  26. mcp_agent/core/fastagent.py +151 -337
  27. mcp_agent/core/interactive_prompt.py +424 -0
  28. mcp_agent/core/mcp_content.py +19 -11
  29. mcp_agent/core/prompt.py +6 -2
  30. mcp_agent/core/validation.py +89 -16
  31. mcp_agent/executor/decorator_registry.py +6 -2
  32. mcp_agent/executor/temporal.py +35 -11
  33. mcp_agent/executor/workflow_signal.py +8 -2
  34. mcp_agent/human_input/handler.py +3 -1
  35. mcp_agent/llm/__init__.py +2 -0
  36. mcp_agent/{workflows/llm → llm}/augmented_llm.py +131 -256
  37. mcp_agent/{workflows/llm → llm}/augmented_llm_passthrough.py +35 -107
  38. mcp_agent/llm/augmented_llm_playback.py +83 -0
  39. mcp_agent/{workflows/llm → llm}/model_factory.py +26 -8
  40. mcp_agent/llm/providers/__init__.py +8 -0
  41. mcp_agent/{workflows/llm → llm/providers}/anthropic_utils.py +5 -1
  42. mcp_agent/{workflows/llm → llm/providers}/augmented_llm_anthropic.py +37 -141
  43. mcp_agent/llm/providers/augmented_llm_deepseek.py +53 -0
  44. mcp_agent/{workflows/llm → llm/providers}/augmented_llm_openai.py +112 -148
  45. mcp_agent/{workflows/llm → llm}/providers/multipart_converter_anthropic.py +78 -35
  46. mcp_agent/{workflows/llm → llm}/providers/multipart_converter_openai.py +73 -44
  47. mcp_agent/{workflows/llm → llm}/providers/openai_multipart.py +18 -4
  48. mcp_agent/{workflows/llm → llm/providers}/openai_utils.py +3 -3
  49. mcp_agent/{workflows/llm → llm}/providers/sampling_converter_anthropic.py +3 -3
  50. mcp_agent/{workflows/llm → llm}/providers/sampling_converter_openai.py +3 -3
  51. mcp_agent/{workflows/llm → llm}/sampling_converter.py +0 -21
  52. mcp_agent/{workflows/llm → llm}/sampling_format_converter.py +16 -1
  53. mcp_agent/logging/logger.py +2 -2
  54. mcp_agent/mcp/gen_client.py +9 -3
  55. mcp_agent/mcp/interfaces.py +67 -45
  56. mcp_agent/mcp/logger_textio.py +97 -0
  57. mcp_agent/mcp/mcp_agent_client_session.py +12 -4
  58. mcp_agent/mcp/mcp_agent_server.py +3 -1
  59. mcp_agent/mcp/mcp_aggregator.py +124 -93
  60. mcp_agent/mcp/mcp_connection_manager.py +21 -7
  61. mcp_agent/mcp/prompt_message_multipart.py +59 -1
  62. mcp_agent/mcp/prompt_render.py +77 -0
  63. mcp_agent/mcp/prompt_serialization.py +20 -13
  64. mcp_agent/mcp/prompts/prompt_constants.py +18 -0
  65. mcp_agent/mcp/prompts/prompt_helpers.py +327 -0
  66. mcp_agent/mcp/prompts/prompt_load.py +15 -5
  67. mcp_agent/mcp/prompts/prompt_server.py +154 -87
  68. mcp_agent/mcp/prompts/prompt_template.py +26 -35
  69. mcp_agent/mcp/resource_utils.py +3 -1
  70. mcp_agent/mcp/sampling.py +24 -15
  71. mcp_agent/mcp_server/agent_server.py +8 -5
  72. mcp_agent/mcp_server_registry.py +22 -9
  73. mcp_agent/resources/examples/{workflows → in_dev}/agent_build.py +1 -1
  74. mcp_agent/resources/examples/{data-analysis → in_dev}/slides.py +1 -1
  75. mcp_agent/resources/examples/internal/agent.py +4 -2
  76. mcp_agent/resources/examples/internal/fastagent.config.yaml +8 -2
  77. mcp_agent/resources/examples/prompting/image_server.py +3 -1
  78. mcp_agent/resources/examples/prompting/work_with_image.py +19 -0
  79. mcp_agent/ui/console_display.py +27 -7
  80. fast_agent_mcp-0.1.13.dist-info/RECORD +0 -164
  81. mcp_agent/core/agent_app.py +0 -570
  82. mcp_agent/core/agent_utils.py +0 -69
  83. mcp_agent/core/decorators.py +0 -448
  84. mcp_agent/core/factory.py +0 -422
  85. mcp_agent/core/proxies.py +0 -278
  86. mcp_agent/core/types.py +0 -22
  87. mcp_agent/eval/__init__.py +0 -0
  88. mcp_agent/mcp/stdio.py +0 -114
  89. mcp_agent/resources/examples/data-analysis/analysis-campaign.py +0 -188
  90. mcp_agent/resources/examples/data-analysis/analysis.py +0 -65
  91. mcp_agent/resources/examples/data-analysis/fastagent.config.yaml +0 -41
  92. mcp_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -1471
  93. mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +0 -53
  94. mcp_agent/resources/examples/researcher/fastagent.config.yaml +0 -66
  95. mcp_agent/resources/examples/researcher/researcher-eval.py +0 -53
  96. mcp_agent/resources/examples/researcher/researcher-imp.py +0 -189
  97. mcp_agent/resources/examples/researcher/researcher.py +0 -39
  98. mcp_agent/resources/examples/workflows/chaining.py +0 -45
  99. mcp_agent/resources/examples/workflows/evaluator.py +0 -79
  100. mcp_agent/resources/examples/workflows/fastagent.config.yaml +0 -24
  101. mcp_agent/resources/examples/workflows/human_input.py +0 -26
  102. mcp_agent/resources/examples/workflows/orchestrator.py +0 -74
  103. mcp_agent/resources/examples/workflows/parallel.py +0 -79
  104. mcp_agent/resources/examples/workflows/router.py +0 -54
  105. mcp_agent/resources/examples/workflows/sse.py +0 -23
  106. mcp_agent/telemetry/__init__.py +0 -0
  107. mcp_agent/telemetry/usage_tracking.py +0 -19
  108. mcp_agent/workflows/__init__.py +0 -0
  109. mcp_agent/workflows/embedding/__init__.py +0 -0
  110. mcp_agent/workflows/embedding/embedding_base.py +0 -58
  111. mcp_agent/workflows/embedding/embedding_cohere.py +0 -49
  112. mcp_agent/workflows/embedding/embedding_openai.py +0 -37
  113. mcp_agent/workflows/evaluator_optimizer/__init__.py +0 -0
  114. mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +0 -447
  115. mcp_agent/workflows/intent_classifier/__init__.py +0 -0
  116. mcp_agent/workflows/intent_classifier/intent_classifier_base.py +0 -117
  117. mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +0 -130
  118. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +0 -41
  119. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +0 -41
  120. mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +0 -150
  121. mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +0 -60
  122. mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +0 -58
  123. mcp_agent/workflows/llm/__init__.py +0 -0
  124. mcp_agent/workflows/llm/augmented_llm_playback.py +0 -111
  125. mcp_agent/workflows/llm/providers/__init__.py +0 -8
  126. mcp_agent/workflows/orchestrator/__init__.py +0 -0
  127. mcp_agent/workflows/orchestrator/orchestrator.py +0 -535
  128. mcp_agent/workflows/parallel/__init__.py +0 -0
  129. mcp_agent/workflows/parallel/fan_in.py +0 -320
  130. mcp_agent/workflows/parallel/fan_out.py +0 -181
  131. mcp_agent/workflows/parallel/parallel_llm.py +0 -149
  132. mcp_agent/workflows/router/__init__.py +0 -0
  133. mcp_agent/workflows/router/router_base.py +0 -338
  134. mcp_agent/workflows/router/router_embedding.py +0 -226
  135. mcp_agent/workflows/router/router_embedding_cohere.py +0 -59
  136. mcp_agent/workflows/router/router_embedding_openai.py +0 -59
  137. mcp_agent/workflows/router/router_llm.py +0 -304
  138. mcp_agent/workflows/swarm/__init__.py +0 -0
  139. mcp_agent/workflows/swarm/swarm.py +0 -292
  140. mcp_agent/workflows/swarm/swarm_anthropic.py +0 -42
  141. mcp_agent/workflows/swarm/swarm_openai.py +0 -41
  142. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/WHEEL +0 -0
  143. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
  144. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
  145. /mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_prompts.py +0 -0
  146. /mcp_agent/{workflows/llm → llm}/memory.py +0 -0
  147. /mcp_agent/{workflows/llm → llm}/prompt_utils.py +0 -0
@@ -15,86 +15,84 @@ from typing import Any, Awaitable, Callable, Dict, List, Optional
15
15
 
16
16
  from mcp.server.fastmcp import FastMCP
17
17
  from mcp.server.fastmcp.prompts.base import (
18
+ AssistantMessage,
18
19
  Message,
20
+ UserMessage,
19
21
  )
20
22
  from mcp.server.fastmcp.resources import FileResource
23
+ from mcp.types import PromptMessage
21
24
  from pydantic import AnyUrl
22
25
 
23
26
  from mcp_agent.mcp import mime_utils, resource_utils
27
+ from mcp_agent.mcp.prompts.prompt_constants import (
28
+ ASSISTANT_DELIMITER as DEFAULT_ASSISTANT_DELIMITER,
29
+ )
30
+ from mcp_agent.mcp.prompts.prompt_constants import (
31
+ RESOURCE_DELIMITER as DEFAULT_RESOURCE_DELIMITER,
32
+ )
33
+ from mcp_agent.mcp.prompts.prompt_constants import (
34
+ USER_DELIMITER as DEFAULT_USER_DELIMITER,
35
+ )
24
36
  from mcp_agent.mcp.prompts.prompt_load import create_messages_with_resources
25
37
  from mcp_agent.mcp.prompts.prompt_template import (
26
38
  PromptMetadata,
27
- PromptTemplate,
28
39
  PromptTemplateLoader,
29
40
  )
30
41
 
31
42
  # Configure logging
32
- logging.basicConfig(level=logging.INFO)
43
+ logging.basicConfig(level=logging.ERROR)
33
44
  logger = logging.getLogger("prompt_server")
34
45
 
35
46
  # Create FastMCP server
36
47
  mcp = FastMCP("Prompt Server")
37
48
 
38
49
 
50
+ def convert_to_fastmcp_messages(prompt_messages: List[PromptMessage]) -> List[Message]:
51
+ """
52
+ Convert PromptMessage objects from prompt_load to FastMCP Message objects.
53
+ This adapter prevents double-wrapping of messages.
54
+
55
+ Args:
56
+ prompt_messages: List of PromptMessage objects from prompt_load
57
+
58
+ Returns:
59
+ List of FastMCP Message objects
60
+ """
61
+ result = []
62
+
63
+ for msg in prompt_messages:
64
+ if msg.role == "user":
65
+ result.append(UserMessage(content=msg.content))
66
+ elif msg.role == "assistant":
67
+ result.append(AssistantMessage(content=msg.content))
68
+ else:
69
+ logger.warning(f"Unknown message role: {msg.role}, defaulting to user")
70
+ result.append(UserMessage(content=msg.content))
71
+
72
+ return result
73
+
74
+
39
75
  class PromptConfig(PromptMetadata):
40
76
  """Configuration for the prompt server"""
41
77
 
42
78
  prompt_files: List[Path] = []
43
- user_delimiter: str = "---USER"
44
- assistant_delimiter: str = "---ASSISTANT"
45
- resource_delimiter: str = "---RESOURCE"
79
+ user_delimiter: str = DEFAULT_USER_DELIMITER
80
+ assistant_delimiter: str = DEFAULT_ASSISTANT_DELIMITER
81
+ resource_delimiter: str = DEFAULT_RESOURCE_DELIMITER
46
82
  http_timeout: float = 10.0
47
83
  transport: str = "stdio"
48
84
  port: int = 8000
49
85
 
50
86
 
51
- # Will be initialized with command line args
52
- config = None
53
-
54
87
  # We'll maintain registries of all exposed resources and prompts
55
88
  exposed_resources: Dict[str, Path] = {}
56
89
  prompt_registry: Dict[str, PromptMetadata] = {}
57
90
 
58
- # Default delimiter values
59
- DEFAULT_USER_DELIMITER = "---USER"
60
- DEFAULT_ASSISTANT_DELIMITER = "---ASSISTANT"
61
- DEFAULT_RESOURCE_DELIMITER = "---RESOURCE"
62
-
63
91
 
64
92
  # Define a single type for prompt handlers to avoid mypy issues
65
93
  PromptHandler = Callable[..., Awaitable[List[Message]]]
66
94
 
67
95
 
68
- def create_prompt_handler(template: "PromptTemplate", template_vars: List[str], prompt_files: List[Path]) -> PromptHandler:
69
- """Create a prompt handler function for the given template"""
70
- if template_vars:
71
- # With template variables
72
- docstring = f"Prompt with template variables: {', '.join(template_vars)}"
73
-
74
- async def prompt_handler(**kwargs: Any) -> List[Message]:
75
- # Build context from parameters
76
- context = {var: kwargs.get(var) for var in template_vars if var in kwargs and kwargs[var] is not None}
77
-
78
- content_sections = template.apply_substitutions(context)
79
-
80
- # Convert to MCP Message objects, handling resources properly
81
- return create_messages_with_resources(content_sections, prompt_files)
82
-
83
- else:
84
- # No template variables
85
- docstring = "Get a prompt with no variable substitution"
86
-
87
- async def prompt_handler(**kwargs: Any) -> List[Message]:
88
- content_sections = template.content_sections
89
-
90
- # Convert to MCP Message objects, handling resources properly
91
- return create_messages_with_resources(content_sections, prompt_files)
92
-
93
- # Set the docstring
94
- prompt_handler.__doc__ = docstring
95
- return prompt_handler
96
-
97
-
98
96
  # Type for resource handler
99
97
  ResourceHandler = Callable[[], Awaitable[str | bytes]]
100
98
 
@@ -117,7 +115,9 @@ def create_resource_handler(resource_path: Path, mime_type: str) -> ResourceHand
117
115
  return get_resource
118
116
 
119
117
 
120
- def get_delimiter_config(file_path: Optional[Path] = None) -> Dict[str, Any]:
118
+ def get_delimiter_config(
119
+ config: Optional[PromptConfig] = None, file_path: Optional[Path] = None
120
+ ) -> Dict[str, Any]:
121
121
  """Get delimiter configuration, falling back to defaults if config is None"""
122
122
  # Set defaults
123
123
  config_values = {
@@ -137,11 +137,11 @@ def get_delimiter_config(file_path: Optional[Path] = None) -> Dict[str, Any]:
137
137
  return config_values
138
138
 
139
139
 
140
- def register_prompt(file_path: Path) -> None:
140
+ def register_prompt(file_path: Path, config: Optional[PromptConfig] = None) -> None:
141
141
  """Register a prompt file"""
142
142
  try:
143
143
  # Get delimiter configuration
144
- config_values = get_delimiter_config(file_path)
144
+ config_values = get_delimiter_config(config, file_path)
145
145
 
146
146
  # Use our prompt template loader to analyze the file
147
147
  loader = PromptTemplateLoader(
@@ -171,8 +171,61 @@ def register_prompt(file_path: Path) -> None:
171
171
 
172
172
  # Create and register prompt handler
173
173
  template_vars = list(metadata.template_variables)
174
- handler = create_prompt_handler(template, template_vars, config_values["prompt_files"])
175
- mcp.prompt(name=metadata.name, description=metadata.description)(handler)
174
+
175
+ from mcp.server.fastmcp.prompts.base import Prompt, PromptArgument
176
+
177
+ # For prompts with variables, create arguments list for FastMCP
178
+ if template_vars:
179
+ # Create a function with properly typed parameters
180
+ async def template_handler_with_vars(**kwargs):
181
+ # Extract template variables from kwargs
182
+ context = {var: kwargs.get(var) for var in template_vars if var in kwargs}
183
+
184
+ # Check for missing variables
185
+ missing_vars = [var for var in template_vars if var not in context]
186
+ if missing_vars:
187
+ raise ValueError(
188
+ f"Missing required template variables: {', '.join(missing_vars)}"
189
+ )
190
+
191
+ # Apply template and create messages
192
+ content_sections = template.apply_substitutions(context)
193
+ prompt_messages = create_messages_with_resources(
194
+ content_sections, config_values["prompt_files"]
195
+ )
196
+ return convert_to_fastmcp_messages(prompt_messages)
197
+
198
+ # Create a Prompt directly
199
+ arguments = [
200
+ PromptArgument(name=var, description=f"Template variable: {var}", required=True)
201
+ for var in template_vars
202
+ ]
203
+
204
+ # Create and add the prompt directly to the prompt manager
205
+ prompt = Prompt(
206
+ name=metadata.name,
207
+ description=metadata.description,
208
+ arguments=arguments,
209
+ fn=template_handler_with_vars,
210
+ )
211
+ mcp._prompt_manager.add_prompt(prompt)
212
+ else:
213
+ # Create a simple prompt without variables
214
+ async def template_handler_without_vars() -> list[Message]:
215
+ content_sections = template.content_sections
216
+ prompt_messages = create_messages_with_resources(
217
+ content_sections, config_values["prompt_files"]
218
+ )
219
+ return convert_to_fastmcp_messages(prompt_messages)
220
+
221
+ # Create a Prompt object directly instead of using the decorator
222
+ prompt = Prompt(
223
+ name=metadata.name,
224
+ description=metadata.description,
225
+ arguments=[],
226
+ fn=template_handler_without_vars,
227
+ )
228
+ mcp._prompt_manager.add_prompt(prompt)
176
229
 
177
230
  # Register any referenced resources in the prompt
178
231
  for resource_path in metadata.resource_paths:
@@ -242,12 +295,46 @@ def parse_args():
242
295
  default=8000,
243
296
  help="Port to use for SSE transport (default: 8000)",
244
297
  )
245
- parser.add_argument("--test", type=str, help="Test a specific prompt without starting the server")
298
+ parser.add_argument(
299
+ "--test", type=str, help="Test a specific prompt without starting the server"
300
+ )
246
301
 
247
302
  return parser.parse_args()
248
303
 
249
304
 
250
- async def register_file_resource_handler() -> None:
305
+ def initialize_config(args) -> PromptConfig:
306
+ """Initialize configuration from command line arguments"""
307
+ # Resolve file paths
308
+ prompt_files = []
309
+ for file_path in args.prompt_files:
310
+ path = Path(file_path)
311
+ if not path.exists():
312
+ logger.warning(f"File not found: {path}")
313
+ continue
314
+ prompt_files.append(path.resolve())
315
+
316
+ if not prompt_files:
317
+ logger.error("No valid prompt files specified")
318
+ raise ValueError("No valid prompt files specified")
319
+
320
+ # Initialize configuration
321
+ return PromptConfig(
322
+ name="prompt_server",
323
+ description="FastMCP Prompt Server",
324
+ template_variables=set(),
325
+ resource_paths=[],
326
+ file_path=Path(__file__),
327
+ prompt_files=prompt_files,
328
+ user_delimiter=args.user_delimiter,
329
+ assistant_delimiter=args.assistant_delimiter,
330
+ resource_delimiter=args.resource_delimiter,
331
+ http_timeout=args.http_timeout,
332
+ transport=args.transport,
333
+ port=args.port,
334
+ )
335
+
336
+
337
+ async def register_file_resource_handler(config: PromptConfig) -> None:
251
338
  """Register the general file resource handler"""
252
339
 
253
340
  @mcp.resource("file://{path}")
@@ -279,14 +366,14 @@ async def register_file_resource_handler() -> None:
279
366
  raise
280
367
 
281
368
 
282
- async def test_prompt(prompt_name: str) -> int:
369
+ async def test_prompt(prompt_name: str, config: PromptConfig) -> int:
283
370
  """Test a prompt and print its details"""
284
371
  if prompt_name not in prompt_registry:
285
372
  logger.error(f"Test prompt not found: {prompt_name}")
286
373
  return 1
287
374
 
288
375
  # Get delimiter configuration with reasonable defaults
289
- config_values = get_delimiter_config()
376
+ config_values = get_delimiter_config(config)
290
377
 
291
378
  metadata = prompt_registry[prompt_name]
292
379
  print(f"\nTesting prompt: {prompt_name}")
@@ -326,64 +413,44 @@ async def test_prompt(prompt_name: str) -> int:
326
413
  return 0
327
414
 
328
415
 
329
- async def async_main():
416
+ async def async_main() -> int:
330
417
  """Run the FastMCP server (async version)"""
331
- global config
332
-
333
418
  # Parse command line arguments
334
419
  args = parse_args()
335
420
 
336
- # Resolve file paths
337
- prompt_files = []
338
- for file_path in args.prompt_files:
339
- path = Path(file_path)
340
- if not path.exists():
341
- logger.warning(f"File not found: {path}")
342
- continue
343
- prompt_files.append(path.resolve())
344
-
345
- if not prompt_files:
346
- logger.error("No valid prompt files specified")
421
+ try:
422
+ # Initialize configuration
423
+ config = initialize_config(args)
424
+ except ValueError as e:
425
+ logger.error(str(e))
347
426
  return 1
348
427
 
349
- # Initialize configuration
350
- config = PromptConfig(
351
- name="prompt_server",
352
- description="FastMCP Prompt Server",
353
- template_variables=set(),
354
- resource_paths=[],
355
- file_path=Path(__file__),
356
- prompt_files=prompt_files,
357
- user_delimiter=args.user_delimiter,
358
- assistant_delimiter=args.assistant_delimiter,
359
- resource_delimiter=args.resource_delimiter,
360
- http_timeout=args.http_timeout,
361
- transport=args.transport,
362
- port=args.port,
363
- )
364
-
365
428
  # Register resource handlers
366
- await register_file_resource_handler()
429
+ await register_file_resource_handler(config)
367
430
 
368
431
  # Register all prompts
369
432
  for file_path in config.prompt_files:
370
- register_prompt(file_path)
433
+ register_prompt(file_path, config)
371
434
 
372
435
  # Print startup info
373
436
  logger.info("Starting prompt server")
374
437
  logger.info(f"Registered {len(prompt_registry)} prompts")
375
438
  logger.info(f"Registered {len(exposed_resources)} resources")
376
- logger.info(f"Using delimiters: {config.user_delimiter}, {config.assistant_delimiter}, {config.resource_delimiter}")
439
+ logger.info(
440
+ f"Using delimiters: {config.user_delimiter}, {config.assistant_delimiter}, {config.resource_delimiter}"
441
+ )
377
442
 
378
443
  # If a test prompt was specified, print it and exit
379
444
  if args.test:
380
- return await test_prompt(args.test)
445
+ return await test_prompt(args.test, config)
381
446
 
382
447
  # Start the server with the specified transport
383
448
  if config.transport == "stdio":
384
449
  await mcp.run_stdio_async()
385
450
  else: # sse
386
- await mcp.run_sse_async(port=config.port)
451
+ # TODO update to 2025-03-26 specification and test config.
452
+ await mcp.run_sse_async()
453
+ return 0
387
454
 
388
455
 
389
456
  def main() -> int:
@@ -20,6 +20,12 @@ from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
20
20
  from mcp_agent.mcp.prompt_serialization import (
21
21
  multipart_messages_to_delimited_format,
22
22
  )
23
+ from mcp_agent.mcp.prompts.prompt_constants import (
24
+ ASSISTANT_DELIMITER,
25
+ DEFAULT_DELIMITER_MAP,
26
+ RESOURCE_DELIMITER,
27
+ USER_DELIMITER,
28
+ )
23
29
 
24
30
 
25
31
  class PromptMetadata(BaseModel):
@@ -95,11 +101,7 @@ class PromptTemplate:
95
101
  """
96
102
  self.template_text = template_text
97
103
  self.template_file_path = template_file_path
98
- self.delimiter_map = delimiter_map or {
99
- "---USER": "user",
100
- "---ASSISTANT": "assistant",
101
- "---RESOURCE": "resource",
102
- }
104
+ self.delimiter_map = delimiter_map or DEFAULT_DELIMITER_MAP
103
105
  self._template_variables = self._extract_template_variables(template_text)
104
106
  self._parsed_content = self._parse_template()
105
107
 
@@ -120,19 +122,17 @@ class PromptTemplate:
120
122
  A new PromptTemplate object
121
123
  """
122
124
  # Use default delimiter map if none provided
123
- delimiter_map = delimiter_map or {
124
- "---USER": "user",
125
- "---ASSISTANT": "assistant",
126
- "---RESOURCE": "resource",
127
- }
125
+ delimiter_map = delimiter_map or DEFAULT_DELIMITER_MAP
128
126
 
129
127
  # Convert to delimited format
130
128
  delimited_content = multipart_messages_to_delimited_format(
131
129
  messages,
132
- user_delimiter=next((k for k, v in delimiter_map.items() if v == "user"), "---USER"),
130
+ user_delimiter=next(
131
+ (k for k, v in delimiter_map.items() if v == "user"), USER_DELIMITER
132
+ ),
133
133
  assistant_delimiter=next(
134
134
  (k for k, v in delimiter_map.items() if v == "assistant"),
135
- "---ASSISTANT",
135
+ ASSISTANT_DELIMITER,
136
136
  ),
137
137
  )
138
138
 
@@ -162,12 +162,12 @@ class PromptTemplate:
162
162
  Returns:
163
163
  List of PromptContent with substitutions applied
164
164
  """
165
- result = []
166
- for section in self._parsed_content:
167
- result.append(section.apply_substitutions(context))
168
- return result
165
+ # Create a new list with substitutions applied to each section
166
+ return [section.apply_substitutions(context) for section in self._parsed_content]
169
167
 
170
- def apply_substitutions_to_multipart(self, context: Dict[str, Any]) -> List[PromptMessageMultipart]:
168
+ def apply_substitutions_to_multipart(
169
+ self, context: Dict[str, Any]
170
+ ) -> List[PromptMessageMultipart]:
171
171
  """
172
172
  Apply variable substitutions to the template and return PromptMessageMultipart objects.
173
173
 
@@ -177,9 +177,11 @@ class PromptTemplate:
177
177
  Returns:
178
178
  List of PromptMessageMultipart objects with substitutions applied
179
179
  """
180
+ # First create a substituted template
180
181
  content_sections = self.apply_substitutions(context)
181
- multiparts = []
182
182
 
183
+ # Convert content sections to multipart messages
184
+ multiparts = []
183
185
  for section in content_sections:
184
186
  # Handle text content
185
187
  content_items = [TextContent(type="text", text=section.text)]
@@ -329,11 +331,7 @@ class PromptTemplateLoader:
329
331
  Args:
330
332
  delimiter_map: Optional map of delimiters to roles
331
333
  """
332
- self.delimiter_map = delimiter_map or {
333
- "---USER": "user",
334
- "---ASSISTANT": "assistant",
335
- "---RESOURCE": "resource",
336
- }
334
+ self.delimiter_map = delimiter_map or DEFAULT_DELIMITER_MAP
337
335
 
338
336
  def load_from_file(self, file_path: Path) -> PromptTemplate:
339
337
  """
@@ -360,17 +358,8 @@ class PromptTemplateLoader:
360
358
  Returns:
361
359
  A PromptTemplate object
362
360
  """
363
- delimited_content = multipart_messages_to_delimited_format(
364
- messages,
365
- user_delimiter=next((k for k, v in self.delimiter_map.items() if v == "user"), "---USER"),
366
- assistant_delimiter=next(
367
- (k for k, v in self.delimiter_map.items() if v == "assistant"),
368
- "---ASSISTANT",
369
- ),
370
- )
371
-
372
- content = "\n".join(delimited_content)
373
- return PromptTemplate(content, self.delimiter_map)
361
+ # Use the class method directly to avoid code duplication
362
+ return PromptTemplate.from_multipart_messages(messages, self.delimiter_map)
374
363
 
375
364
  def get_metadata(self, file_path: Path) -> PromptMetadata:
376
365
  """
@@ -432,7 +421,9 @@ class PromptTemplateLoader:
432
421
 
433
422
  # Extract resource paths from all sections that come after RESOURCE delimiters
434
423
  resource_paths = []
435
- resource_delimiter = next((k for k, v in self.delimiter_map.items() if v == "resource"), "---RESOURCE")
424
+ resource_delimiter = next(
425
+ (k for k, v in self.delimiter_map.items() if v == "resource"), RESOURCE_DELIMITER
426
+ )
436
427
  for i, line in enumerate(lines):
437
428
  if line.strip() == resource_delimiter:
438
429
  if i + 1 < len(lines) and lines[i + 1].strip():
@@ -98,7 +98,9 @@ def create_resource_reference(uri: str, mime_type: str) -> "EmbeddedResource":
98
98
  return EmbeddedResource(type="resource", resource=resource_contents)
99
99
 
100
100
 
101
- def create_embedded_resource(resource_path: str, content: str, mime_type: str, is_binary: bool = False) -> EmbeddedResource:
101
+ def create_embedded_resource(
102
+ resource_path: str, content: str, mime_type: str, is_binary: bool = False
103
+ ) -> EmbeddedResource:
102
104
  """Create an embedded resource content object"""
103
105
  # Format a valid resource URI string
104
106
  resource_uri_str = create_resource_uri(resource_path)
mcp_agent/mcp/sampling.py CHANGED
@@ -2,21 +2,25 @@
2
2
  This simplified implementation directly converts between MCP types and PromptMessageMultipart.
3
3
  """
4
4
 
5
+ from typing import TYPE_CHECKING
6
+
5
7
  from mcp import ClientSession
6
- from mcp.types import (
7
- CreateMessageRequestParams,
8
- CreateMessageResult,
9
- )
8
+ from mcp.types import CreateMessageRequestParams, CreateMessageResult, TextContent
10
9
 
11
10
  from mcp_agent.core.agent_types import AgentConfig
11
+ from mcp_agent.llm.sampling_converter import SamplingConverter
12
12
  from mcp_agent.logging.logger import get_logger
13
13
  from mcp_agent.mcp.interfaces import AugmentedLLMProtocol
14
- from mcp_agent.workflows.llm.sampling_converter import SamplingConverter
14
+
15
+ if TYPE_CHECKING:
16
+ from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
15
17
 
16
18
  logger = get_logger(__name__)
17
19
 
18
20
 
19
- def create_sampling_llm(params: CreateMessageRequestParams, model_string: str) -> AugmentedLLMProtocol:
21
+ def create_sampling_llm(
22
+ params: CreateMessageRequestParams, model_string: str
23
+ ) -> AugmentedLLMProtocol:
20
24
  """
21
25
  Create an LLM instance for sampling without tools support.
22
26
  This utility function creates a minimal LLM instance based on the model string.
@@ -29,7 +33,7 @@ def create_sampling_llm(params: CreateMessageRequestParams, model_string: str) -
29
33
  An initialized LLM instance ready to use
30
34
  """
31
35
  from mcp_agent.agents.agent import Agent
32
- from mcp_agent.workflows.llm.model_factory import ModelFactory
36
+ from mcp_agent.llm.model_factory import ModelFactory
33
37
 
34
38
  app_context = None
35
39
  try:
@@ -100,19 +104,24 @@ async def sample(mcp_ctx: ClientSession, params: CreateMessageRequestParams) ->
100
104
  # Extract request parameters using our converter
101
105
  request_params = SamplingConverter.extract_request_params(params)
102
106
 
103
- # Use the new public apply_prompt method which is cleaner than calling the protected method
104
- llm_response = await llm.apply_prompt(conversation, request_params)
105
- logger.info(f"Complete sampling request : {llm_response[:50]}...")
107
+ llm_response: PromptMessageMultipart = await llm.generate(conversation, request_params)
108
+ logger.info(f"Complete sampling request : {llm_response.first_text()[:50]}...")
106
109
 
107
- # Create result using our converter
108
- return SamplingConverter.create_message_result(response=llm_response, model=model)
110
+ return CreateMessageResult(
111
+ role=llm_response.role,
112
+ content=TextContent(type="text", text=llm_response.first_text()),
113
+ model=model,
114
+ stopReason="endTurn",
115
+ )
109
116
  except Exception as e:
110
117
  logger.error(f"Error in sampling: {str(e)}")
111
- return SamplingConverter.error_result(error_message=f"Error in sampling: {str(e)}", model=model)
118
+ return SamplingConverter.error_result(
119
+ error_message=f"Error in sampling: {str(e)}", model=model
120
+ )
112
121
 
113
122
 
114
123
  def sampling_agent_config(
115
- params: CreateMessageRequestParams = None,
124
+ params: CreateMessageRequestParams | None = None,
116
125
  ) -> AgentConfig:
117
126
  """
118
127
  Build a sampling AgentConfig based on request parameters.
@@ -125,7 +134,7 @@ def sampling_agent_config(
125
134
  """
126
135
  # Use systemPrompt from params if available, otherwise use default
127
136
  instruction = "You are a helpful AI Agent."
128
- if params and hasattr(params, "systemPrompt") and params.systemPrompt is not None:
137
+ if params and params.systemPrompt is not None:
129
138
  instruction = params.systemPrompt
130
139
 
131
140
  return AgentConfig(name="sampling_agent", instruction=instruction, servers=[])
@@ -3,8 +3,8 @@
3
3
  from mcp.server.fastmcp import Context as MCPContext
4
4
  from mcp.server.fastmcp import FastMCP
5
5
 
6
- # Remove circular import
7
- from mcp_agent.core.agent_app import AgentApp
6
+ # Import the DirectAgentApp instead of AgentApp
7
+ from mcp_agent.core.direct_agent_app import DirectAgentApp
8
8
 
9
9
 
10
10
  class AgentMCPServer:
@@ -12,14 +12,15 @@ class AgentMCPServer:
12
12
 
13
13
  def __init__(
14
14
  self,
15
- agent_app: AgentApp,
15
+ agent_app: DirectAgentApp,
16
16
  server_name: str = "FastAgent-MCP-Server",
17
17
  server_description: str = None,
18
18
  ) -> None:
19
19
  self.agent_app = agent_app
20
20
  self.mcp_server = FastMCP(
21
21
  name=server_name,
22
- instructions=server_description or f"This server provides access to {len(agent_app.agents)} agents",
22
+ instructions=server_description
23
+ or f"This server provides access to {len(agent_app._agents)} agents",
23
24
  )
24
25
  self.setup_tools()
25
26
 
@@ -63,7 +64,9 @@ class AgentMCPServer:
63
64
 
64
65
  self.mcp_server.run(transport=transport)
65
66
 
66
- async def run_async(self, transport: str = "sse", host: str = "0.0.0.0", port: int = 8000) -> None:
67
+ async def run_async(
68
+ self, transport: str = "sse", host: str = "0.0.0.0", port: int = 8000
69
+ ) -> None:
67
70
  """Run the MCP server asynchronously."""
68
71
  if transport == "sse":
69
72
  self.mcp_server.settings.host = host