fast-agent-mcp 0.1.12__py3-none-any.whl → 0.1.13__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 (126) hide show
  1. {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.1.13.dist-info}/METADATA +1 -1
  2. fast_agent_mcp-0.1.13.dist-info/RECORD +164 -0
  3. mcp_agent/agents/agent.py +37 -79
  4. mcp_agent/app.py +16 -22
  5. mcp_agent/cli/commands/bootstrap.py +22 -52
  6. mcp_agent/cli/commands/config.py +4 -4
  7. mcp_agent/cli/commands/setup.py +11 -26
  8. mcp_agent/cli/main.py +6 -9
  9. mcp_agent/cli/terminal.py +2 -2
  10. mcp_agent/config.py +1 -5
  11. mcp_agent/context.py +13 -24
  12. mcp_agent/context_dependent.py +3 -7
  13. mcp_agent/core/agent_app.py +45 -121
  14. mcp_agent/core/agent_utils.py +3 -5
  15. mcp_agent/core/decorators.py +5 -12
  16. mcp_agent/core/enhanced_prompt.py +25 -52
  17. mcp_agent/core/exceptions.py +8 -8
  18. mcp_agent/core/factory.py +29 -70
  19. mcp_agent/core/fastagent.py +48 -88
  20. mcp_agent/core/mcp_content.py +8 -16
  21. mcp_agent/core/prompt.py +8 -15
  22. mcp_agent/core/proxies.py +34 -25
  23. mcp_agent/core/request_params.py +6 -3
  24. mcp_agent/core/types.py +4 -6
  25. mcp_agent/core/validation.py +4 -3
  26. mcp_agent/executor/decorator_registry.py +11 -23
  27. mcp_agent/executor/executor.py +8 -17
  28. mcp_agent/executor/task_registry.py +2 -4
  29. mcp_agent/executor/temporal.py +28 -74
  30. mcp_agent/executor/workflow.py +3 -5
  31. mcp_agent/executor/workflow_signal.py +17 -29
  32. mcp_agent/human_input/handler.py +4 -9
  33. mcp_agent/human_input/types.py +2 -3
  34. mcp_agent/logging/events.py +1 -5
  35. mcp_agent/logging/json_serializer.py +7 -6
  36. mcp_agent/logging/listeners.py +20 -23
  37. mcp_agent/logging/logger.py +15 -17
  38. mcp_agent/logging/rich_progress.py +10 -8
  39. mcp_agent/logging/tracing.py +4 -6
  40. mcp_agent/logging/transport.py +22 -22
  41. mcp_agent/mcp/gen_client.py +4 -12
  42. mcp_agent/mcp/interfaces.py +71 -86
  43. mcp_agent/mcp/mcp_agent_client_session.py +11 -19
  44. mcp_agent/mcp/mcp_agent_server.py +8 -10
  45. mcp_agent/mcp/mcp_aggregator.py +45 -117
  46. mcp_agent/mcp/mcp_connection_manager.py +16 -37
  47. mcp_agent/mcp/prompt_message_multipart.py +12 -18
  48. mcp_agent/mcp/prompt_serialization.py +13 -38
  49. mcp_agent/mcp/prompts/prompt_load.py +99 -0
  50. mcp_agent/mcp/prompts/prompt_server.py +21 -128
  51. mcp_agent/mcp/prompts/prompt_template.py +20 -42
  52. mcp_agent/mcp/resource_utils.py +8 -17
  53. mcp_agent/mcp/sampling.py +5 -14
  54. mcp_agent/mcp/stdio.py +11 -8
  55. mcp_agent/mcp_server/agent_server.py +10 -17
  56. mcp_agent/mcp_server_registry.py +13 -35
  57. mcp_agent/resources/examples/data-analysis/analysis-campaign.py +1 -1
  58. mcp_agent/resources/examples/data-analysis/analysis.py +1 -1
  59. mcp_agent/resources/examples/data-analysis/slides.py +110 -0
  60. mcp_agent/resources/examples/internal/agent.py +2 -1
  61. mcp_agent/resources/examples/internal/job.py +2 -1
  62. mcp_agent/resources/examples/internal/prompt_category.py +1 -1
  63. mcp_agent/resources/examples/internal/prompt_sizing.py +3 -5
  64. mcp_agent/resources/examples/internal/sizer.py +2 -1
  65. mcp_agent/resources/examples/internal/social.py +2 -1
  66. mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +1 -1
  67. mcp_agent/resources/examples/prompting/agent.py +2 -1
  68. mcp_agent/resources/examples/prompting/image_server.py +5 -11
  69. mcp_agent/resources/examples/researcher/researcher-eval.py +1 -1
  70. mcp_agent/resources/examples/researcher/researcher-imp.py +3 -4
  71. mcp_agent/resources/examples/researcher/researcher.py +2 -1
  72. mcp_agent/resources/examples/workflows/agent_build.py +2 -1
  73. mcp_agent/resources/examples/workflows/chaining.py +2 -1
  74. mcp_agent/resources/examples/workflows/evaluator.py +2 -1
  75. mcp_agent/resources/examples/workflows/human_input.py +2 -1
  76. mcp_agent/resources/examples/workflows/orchestrator.py +2 -1
  77. mcp_agent/resources/examples/workflows/parallel.py +2 -1
  78. mcp_agent/resources/examples/workflows/router.py +2 -1
  79. mcp_agent/resources/examples/workflows/sse.py +1 -1
  80. mcp_agent/telemetry/usage_tracking.py +2 -1
  81. mcp_agent/ui/console_display.py +15 -39
  82. mcp_agent/workflows/embedding/embedding_base.py +1 -4
  83. mcp_agent/workflows/embedding/embedding_cohere.py +2 -2
  84. mcp_agent/workflows/embedding/embedding_openai.py +4 -13
  85. mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +23 -57
  86. mcp_agent/workflows/intent_classifier/intent_classifier_base.py +5 -8
  87. mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +7 -11
  88. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +4 -8
  89. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +4 -8
  90. mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +11 -22
  91. mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +3 -3
  92. mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +4 -6
  93. mcp_agent/workflows/llm/anthropic_utils.py +8 -29
  94. mcp_agent/workflows/llm/augmented_llm.py +69 -247
  95. mcp_agent/workflows/llm/augmented_llm_anthropic.py +39 -73
  96. mcp_agent/workflows/llm/augmented_llm_openai.py +42 -97
  97. mcp_agent/workflows/llm/augmented_llm_passthrough.py +13 -20
  98. mcp_agent/workflows/llm/augmented_llm_playback.py +8 -6
  99. mcp_agent/workflows/llm/memory.py +103 -0
  100. mcp_agent/workflows/llm/model_factory.py +8 -20
  101. mcp_agent/workflows/llm/openai_utils.py +1 -1
  102. mcp_agent/workflows/llm/prompt_utils.py +1 -3
  103. mcp_agent/workflows/llm/providers/multipart_converter_anthropic.py +47 -89
  104. mcp_agent/workflows/llm/providers/multipart_converter_openai.py +20 -55
  105. mcp_agent/workflows/llm/providers/openai_multipart.py +19 -61
  106. mcp_agent/workflows/llm/providers/sampling_converter_anthropic.py +10 -12
  107. mcp_agent/workflows/llm/providers/sampling_converter_openai.py +7 -11
  108. mcp_agent/workflows/llm/sampling_converter.py +4 -11
  109. mcp_agent/workflows/llm/sampling_format_converter.py +12 -12
  110. mcp_agent/workflows/orchestrator/orchestrator.py +24 -67
  111. mcp_agent/workflows/orchestrator/orchestrator_models.py +14 -40
  112. mcp_agent/workflows/parallel/fan_in.py +17 -47
  113. mcp_agent/workflows/parallel/fan_out.py +6 -12
  114. mcp_agent/workflows/parallel/parallel_llm.py +9 -26
  115. mcp_agent/workflows/router/router_base.py +19 -49
  116. mcp_agent/workflows/router/router_embedding.py +11 -25
  117. mcp_agent/workflows/router/router_embedding_cohere.py +2 -2
  118. mcp_agent/workflows/router/router_embedding_openai.py +2 -2
  119. mcp_agent/workflows/router/router_llm.py +12 -28
  120. mcp_agent/workflows/swarm/swarm.py +20 -48
  121. mcp_agent/workflows/swarm/swarm_anthropic.py +2 -2
  122. mcp_agent/workflows/swarm/swarm_openai.py +2 -2
  123. fast_agent_mcp-0.1.12.dist-info/RECORD +0 -161
  124. {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.1.13.dist-info}/WHEEL +0 -0
  125. {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.1.13.dist-info}/entry_points.txt +0 -0
  126. {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.1.13.dist-info}/licenses/LICENSE +0 -0
@@ -19,9 +19,9 @@ and PromptMessageMultipart objects. It includes functionality for:
19
19
  import json
20
20
  from typing import List
21
21
 
22
- from mcp.types import TextContent, EmbeddedResource, ImageContent, TextResourceContents
23
- from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
22
+ from mcp.types import EmbeddedResource, ImageContent, TextContent, TextResourceContents
24
23
 
24
+ from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
25
25
 
26
26
  # -------------------------------------------------------------------------
27
27
  # JSON Serialization Functions
@@ -43,10 +43,7 @@ def multipart_messages_to_json(messages: List[PromptMessageMultipart]) -> str:
43
43
  """
44
44
  # Convert to dictionaries using model_dump with proper JSON mode
45
45
  # The mode="json" parameter ensures proper handling of AnyUrl objects
46
- message_dicts = [
47
- message.model_dump(by_alias=True, mode="json", exclude_none=True)
48
- for message in messages
49
- ]
46
+ message_dicts = [message.model_dump(by_alias=True, mode="json", exclude_none=True) for message in messages]
50
47
 
51
48
  # Convert to JSON string
52
49
  return json.dumps(message_dicts, indent=2)
@@ -76,9 +73,7 @@ def json_to_multipart_messages(json_str: str) -> List[PromptMessageMultipart]:
76
73
  return messages
77
74
 
78
75
 
79
- def save_messages_to_json_file(
80
- messages: List[PromptMessageMultipart], file_path: str
81
- ) -> None:
76
+ def save_messages_to_json_file(messages: List[PromptMessageMultipart], file_path: str) -> None:
82
77
  """
83
78
  Save PromptMessageMultipart objects to a JSON file.
84
79
 
@@ -169,9 +164,7 @@ def multipart_messages_to_delimited_format(
169
164
  delimited_content.append(resource_delimiter)
170
165
 
171
166
  # Convert to dictionary using proper JSON mode
172
- content_dict = content.model_dump(
173
- by_alias=True, mode="json", exclude_none=True
174
- )
167
+ content_dict = content.model_dump(by_alias=True, mode="json", exclude_none=True)
175
168
 
176
169
  # Add to delimited content as JSON
177
170
  delimited_content.append(json.dumps(content_dict, indent=2))
@@ -186,9 +179,7 @@ def multipart_messages_to_delimited_format(
186
179
  delimited_content.append(resource_delimiter)
187
180
 
188
181
  # Convert to dictionary using proper JSON mode
189
- content_dict = content.model_dump(
190
- by_alias=True, mode="json", exclude_none=True
191
- )
182
+ content_dict = content.model_dump(by_alias=True, mode="json", exclude_none=True)
192
183
 
193
184
  # Add to delimited content as JSON
194
185
  delimited_content.append(json.dumps(content_dict, indent=2))
@@ -237,31 +228,23 @@ def delimited_format_to_multipart_messages(
237
228
  collecting_text = True
238
229
 
239
230
  # Process each line
240
- for line in (
241
- lines[1:] if lines else []
242
- ): # Skip the first line if already processed above
231
+ for line in lines[1:] if lines else []: # Skip the first line if already processed above
243
232
  line_stripped = line.strip()
244
233
 
245
234
  # Handle role delimiters
246
235
  if line_stripped == user_delimiter or line_stripped == assistant_delimiter:
247
236
  # Save previous message if it exists
248
- if current_role is not None and (
249
- text_contents or resource_contents or text_lines
250
- ):
237
+ if current_role is not None and (text_contents or resource_contents or text_lines):
251
238
  # If we were collecting text, add it to the text contents
252
239
  if collecting_text and text_lines:
253
- text_contents.append(
254
- TextContent(type="text", text="\n".join(text_lines))
255
- )
240
+ text_contents.append(TextContent(type="text", text="\n".join(text_lines)))
256
241
  text_lines = []
257
242
 
258
243
  # Create content list with text parts first, then resource parts
259
244
  combined_content = []
260
245
 
261
246
  # Filter out any empty text content items
262
- filtered_text_contents = [
263
- tc for tc in text_contents if tc.text.strip() != ""
264
- ]
247
+ filtered_text_contents = [tc for tc in text_contents if tc.text.strip() != ""]
265
248
 
266
249
  combined_content.extend(filtered_text_contents)
267
250
  combined_content.extend(resource_contents)
@@ -286,9 +269,7 @@ def delimited_format_to_multipart_messages(
286
269
  elif line_stripped == resource_delimiter:
287
270
  # If we were collecting text, add it to text contents
288
271
  if collecting_text and text_lines:
289
- text_contents.append(
290
- TextContent(type="text", text="\n".join(text_lines))
291
- )
272
+ text_contents.append(TextContent(type="text", text="\n".join(text_lines)))
292
273
  text_lines = []
293
274
 
294
275
  # Switch to collecting JSON or legacy format
@@ -303,11 +284,7 @@ def delimited_format_to_multipart_messages(
303
284
  json_lines.append(line)
304
285
 
305
286
  # For legacy format or files where resources are just plain text
306
- if (
307
- legacy_format
308
- and line_stripped
309
- and not line_stripped.startswith("{")
310
- ):
287
+ if legacy_format and line_stripped and not line_stripped.startswith("{"):
311
288
  # This is probably a legacy resource reference like a filename
312
289
  resource_uri = line_stripped
313
290
  if not resource_uri.startswith("resource://"):
@@ -370,9 +347,7 @@ def delimited_format_to_multipart_messages(
370
347
  combined_content = []
371
348
 
372
349
  # Filter out any empty text content items
373
- filtered_text_contents = [
374
- tc for tc in text_contents if tc.text.strip() != ""
375
- ]
350
+ filtered_text_contents = [tc for tc in text_contents if tc.text.strip() != ""]
376
351
 
377
352
  combined_content.extend(filtered_text_contents)
378
353
  combined_content.extend(resource_contents)
@@ -0,0 +1,99 @@
1
+ from pathlib import Path
2
+ from typing import List, Literal
3
+
4
+ from mcp.server.fastmcp.prompts.base import (
5
+ AssistantMessage,
6
+ Message,
7
+ UserMessage,
8
+ )
9
+ from mcp.types import PromptMessage, TextContent
10
+
11
+ from mcp_agent.logging.logger import get_logger
12
+ from mcp_agent.mcp import mime_utils, resource_utils
13
+ from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
14
+ from mcp_agent.mcp.prompts.prompt_template import (
15
+ PromptContent,
16
+ PromptTemplate,
17
+ PromptTemplateLoader,
18
+ )
19
+
20
+ # Define message role type
21
+ MessageRole = Literal["user", "assistant"]
22
+ logger = get_logger("prompt_load")
23
+
24
+
25
+ def cast_message_role(role: str) -> MessageRole:
26
+ """Cast a string role to a MessageRole literal type"""
27
+ if role == "user" or role == "assistant":
28
+ return role # type: ignore
29
+ # Default to user if the role is invalid
30
+ logger.warning(f"Invalid message role: {role}, defaulting to 'user'")
31
+ return "user"
32
+
33
+
34
+ def create_messages_with_resources(content_sections: List[PromptContent], prompt_files: List[Path]) -> List[PromptMessage]:
35
+ """
36
+ Create a list of messages from content sections, with resources properly handled.
37
+
38
+ This implementation produces one message for each content section's text,
39
+ followed by separate messages for each resource (with the same role type
40
+ as the section they belong to).
41
+
42
+ Args:
43
+ content_sections: List of PromptContent objects
44
+ prompt_files: List of prompt files (to help locate resource files)
45
+
46
+ Returns:
47
+ List of Message objects
48
+ """
49
+
50
+ messages = []
51
+
52
+ for section in content_sections:
53
+ # Convert to our literal type for role
54
+ role = cast_message_role(section.role)
55
+
56
+ # Add the text message
57
+ messages.append(create_content_message(section.text, role))
58
+
59
+ # Add resource messages with the same role type as the section
60
+ for resource_path in section.resources:
61
+ try:
62
+ # Load resource with information about its type
63
+ resource_content, mime_type, is_binary = resource_utils.load_resource_content(resource_path, prompt_files)
64
+
65
+ # Create and add the resource message
66
+ resource_message = create_resource_message(resource_path, resource_content, mime_type, is_binary, role)
67
+ messages.append(resource_message)
68
+ except Exception as e:
69
+ logger.error(f"Error loading resource {resource_path}: {e}")
70
+
71
+ return messages
72
+
73
+
74
+ def create_content_message(text: str, role: MessageRole) -> PromptMessage:
75
+ """Create a text content message with the specified role"""
76
+ return PromptMessage(role=role, content=TextContent(type="text", text=text))
77
+
78
+
79
+ def create_resource_message(resource_path: str, content: str, mime_type: str, is_binary: bool, role: MessageRole) -> Message:
80
+ """Create a resource message with the specified content and role"""
81
+ message_class = UserMessage if role == "user" else AssistantMessage
82
+
83
+ if mime_utils.is_image_mime_type(mime_type):
84
+ # For images, create an ImageContent
85
+ image_content = resource_utils.create_image_content(data=content, mime_type=mime_type)
86
+ return message_class(content=image_content)
87
+ else:
88
+ # For other resources, create an EmbeddedResource
89
+ embedded_resource = resource_utils.create_embedded_resource(resource_path, content, mime_type, is_binary)
90
+ return message_class(content=embedded_resource)
91
+
92
+
93
+ def load_prompt(file: Path) -> List[PromptMessage]:
94
+ template: PromptTemplate = PromptTemplateLoader().load_from_file(file)
95
+ return create_messages_with_resources(template.content_sections, [file])
96
+
97
+
98
+ def load_prompt_multipart(file: Path) -> List[PromptMessageMultipart]:
99
+ return PromptMessageMultipart.to_multipart(load_prompt(file))
@@ -5,33 +5,27 @@ A server that loads prompts from text files with simple delimiters and serves th
5
5
  Uses the prompt_template module for clean, testable handling of prompt templates.
6
6
  """
7
7
 
8
- import asyncio
9
8
  import argparse
9
+ import asyncio
10
10
  import base64
11
11
  import logging
12
12
  import sys
13
13
  from pathlib import Path
14
- from typing import List, Dict, Optional, Callable, Awaitable, Literal, Any
15
- from mcp.server.fastmcp.resources import FileResource
16
- from pydantic import AnyUrl
17
-
18
- from mcp_agent.mcp import mime_utils, resource_utils
14
+ from typing import Any, Awaitable, Callable, Dict, List, Optional
19
15
 
20
16
  from mcp.server.fastmcp import FastMCP
21
17
  from mcp.server.fastmcp.prompts.base import (
22
- UserMessage,
23
- AssistantMessage,
24
18
  Message,
25
19
  )
26
- from mcp.types import (
27
- TextContent,
28
- )
20
+ from mcp.server.fastmcp.resources import FileResource
21
+ from pydantic import AnyUrl
29
22
 
23
+ from mcp_agent.mcp import mime_utils, resource_utils
24
+ from mcp_agent.mcp.prompts.prompt_load import create_messages_with_resources
30
25
  from mcp_agent.mcp.prompts.prompt_template import (
31
- PromptTemplateLoader,
32
26
  PromptMetadata,
33
- PromptContent,
34
27
  PromptTemplate,
28
+ PromptTemplateLoader,
35
29
  )
36
30
 
37
31
  # Configure logging
@@ -61,97 +55,17 @@ config = None
61
55
  exposed_resources: Dict[str, Path] = {}
62
56
  prompt_registry: Dict[str, PromptMetadata] = {}
63
57
 
64
- # Define message role type
65
- MessageRole = Literal["user", "assistant"]
66
-
67
-
68
- def create_content_message(text: str, role: MessageRole) -> Message:
69
- """Create a text content message with the specified role"""
70
- message_class = UserMessage if role == "user" else AssistantMessage
71
- return message_class(content=TextContent(type="text", text=text))
72
-
73
-
74
- def create_resource_message(
75
- resource_path: str, content: str, mime_type: str, is_binary: bool, role: MessageRole
76
- ) -> Message:
77
- """Create a resource message with the specified content and role"""
78
- message_class = UserMessage if role == "user" else AssistantMessage
79
-
80
- if mime_utils.is_image_mime_type(mime_type):
81
- # For images, create an ImageContent
82
- image_content = resource_utils.create_image_content(
83
- data=content, mime_type=mime_type
84
- )
85
- return message_class(content=image_content)
86
- else:
87
- # For other resources, create an EmbeddedResource
88
- embedded_resource = resource_utils.create_embedded_resource(
89
- resource_path, content, mime_type, is_binary
90
- )
91
- return message_class(content=embedded_resource)
92
-
93
-
94
- def create_messages_with_resources(
95
- content_sections: List[PromptContent], prompt_files: List[Path]
96
- ) -> List[Message]:
97
- """
98
- Create a list of messages from content sections, with resources properly handled.
99
-
100
- This implementation produces one message for each content section's text,
101
- followed by separate messages for each resource (with the same role type
102
- as the section they belong to).
103
-
104
- Args:
105
- content_sections: List of PromptContent objects
106
- prompt_files: List of prompt files (to help locate resource files)
107
-
108
- Returns:
109
- List of Message objects
110
- """
111
- messages = []
112
-
113
- for section in content_sections:
114
- # Convert to our literal type for role
115
- role = cast_message_role(section.role)
116
-
117
- # Add the text message
118
- messages.append(create_content_message(section.text, role))
119
-
120
- # Add resource messages with the same role type as the section
121
- for resource_path in section.resources:
122
- try:
123
- # Load resource with information about its type
124
- resource_content, mime_type, is_binary = (
125
- resource_utils.load_resource_content(resource_path, prompt_files)
126
- )
127
-
128
- # Create and add the resource message
129
- resource_message = create_resource_message(
130
- resource_path, resource_content, mime_type, is_binary, role
131
- )
132
- messages.append(resource_message)
133
- except Exception as e:
134
- logger.error(f"Error loading resource {resource_path}: {e}")
135
-
136
- return messages
137
-
138
-
139
- def cast_message_role(role: str) -> MessageRole:
140
- """Cast a string role to a MessageRole literal type"""
141
- if role == "user" or role == "assistant":
142
- return role # type: ignore
143
- # Default to user if the role is invalid
144
- logger.warning(f"Invalid message role: {role}, defaulting to 'user'")
145
- return "user"
58
+ # Default delimiter values
59
+ DEFAULT_USER_DELIMITER = "---USER"
60
+ DEFAULT_ASSISTANT_DELIMITER = "---ASSISTANT"
61
+ DEFAULT_RESOURCE_DELIMITER = "---RESOURCE"
146
62
 
147
63
 
148
64
  # Define a single type for prompt handlers to avoid mypy issues
149
65
  PromptHandler = Callable[..., Awaitable[List[Message]]]
150
66
 
151
67
 
152
- def create_prompt_handler(
153
- template: "PromptTemplate", template_vars: List[str], prompt_files: List[Path]
154
- ) -> PromptHandler:
68
+ def create_prompt_handler(template: "PromptTemplate", template_vars: List[str], prompt_files: List[Path]) -> PromptHandler:
155
69
  """Create a prompt handler function for the given template"""
156
70
  if template_vars:
157
71
  # With template variables
@@ -159,23 +73,18 @@ def create_prompt_handler(
159
73
 
160
74
  async def prompt_handler(**kwargs: Any) -> List[Message]:
161
75
  # Build context from parameters
162
- context = {
163
- var: kwargs.get(var)
164
- for var in template_vars
165
- if var in kwargs and kwargs[var] is not None
166
- }
76
+ context = {var: kwargs.get(var) for var in template_vars if var in kwargs and kwargs[var] is not None}
167
77
 
168
- # Apply substitutions to the template
169
78
  content_sections = template.apply_substitutions(context)
170
79
 
171
80
  # Convert to MCP Message objects, handling resources properly
172
81
  return create_messages_with_resources(content_sections, prompt_files)
82
+
173
83
  else:
174
84
  # No template variables
175
85
  docstring = "Get a prompt with no variable substitution"
176
86
 
177
87
  async def prompt_handler(**kwargs: Any) -> List[Message]:
178
- # Get the content sections
179
88
  content_sections = template.content_sections
180
89
 
181
90
  # Convert to MCP Message objects, handling resources properly
@@ -208,12 +117,6 @@ def create_resource_handler(resource_path: Path, mime_type: str) -> ResourceHand
208
117
  return get_resource
209
118
 
210
119
 
211
- # Default delimiter values
212
- DEFAULT_USER_DELIMITER = "---USER"
213
- DEFAULT_ASSISTANT_DELIMITER = "---ASSISTANT"
214
- DEFAULT_RESOURCE_DELIMITER = "---RESOURCE"
215
-
216
-
217
120
  def get_delimiter_config(file_path: Optional[Path] = None) -> Dict[str, Any]:
218
121
  """Get delimiter configuration, falling back to defaults if config is None"""
219
122
  # Set defaults
@@ -234,7 +137,7 @@ def get_delimiter_config(file_path: Optional[Path] = None) -> Dict[str, Any]:
234
137
  return config_values
235
138
 
236
139
 
237
- def register_prompt(file_path: Path):
140
+ def register_prompt(file_path: Path) -> None:
238
141
  """Register a prompt file"""
239
142
  try:
240
143
  # Get delimiter configuration
@@ -268,9 +171,7 @@ def register_prompt(file_path: Path):
268
171
 
269
172
  # Create and register prompt handler
270
173
  template_vars = list(metadata.template_variables)
271
- handler = create_prompt_handler(
272
- template, template_vars, config_values["prompt_files"]
273
- )
174
+ handler = create_prompt_handler(template, template_vars, config_values["prompt_files"])
274
175
  mcp.prompt(name=metadata.name, description=metadata.description)(handler)
275
176
 
276
177
  # Register any referenced resources in the prompt
@@ -295,9 +196,7 @@ def register_prompt(file_path: Path):
295
196
  )
296
197
  )
297
198
 
298
- logger.info(
299
- f"Registered resource: {resource_id} ({resource_file})"
300
- )
199
+ logger.info(f"Registered resource: {resource_id} ({resource_file})")
301
200
  except Exception as e:
302
201
  logger.error(f"Error registering prompt {file_path}: {e}", exc_info=True)
303
202
 
@@ -305,9 +204,7 @@ def register_prompt(file_path: Path):
305
204
  def parse_args():
306
205
  """Parse command line arguments"""
307
206
  parser = argparse.ArgumentParser(description="FastMCP Prompt Server")
308
- parser.add_argument(
309
- "prompt_files", nargs="+", type=str, help="Prompt files to serve"
310
- )
207
+ parser.add_argument("prompt_files", nargs="+", type=str, help="Prompt files to serve")
311
208
  parser.add_argument(
312
209
  "--user-delimiter",
313
210
  type=str,
@@ -345,14 +242,12 @@ def parse_args():
345
242
  default=8000,
346
243
  help="Port to use for SSE transport (default: 8000)",
347
244
  )
348
- parser.add_argument(
349
- "--test", type=str, help="Test a specific prompt without starting the server"
350
- )
245
+ parser.add_argument("--test", type=str, help="Test a specific prompt without starting the server")
351
246
 
352
247
  return parser.parse_args()
353
248
 
354
249
 
355
- async def register_file_resource_handler():
250
+ async def register_file_resource_handler() -> None:
356
251
  """Register the general file resource handler"""
357
252
 
358
253
  @mcp.resource("file://{path}")
@@ -478,9 +373,7 @@ async def async_main():
478
373
  logger.info("Starting prompt server")
479
374
  logger.info(f"Registered {len(prompt_registry)} prompts")
480
375
  logger.info(f"Registered {len(exposed_resources)} resources")
481
- logger.info(
482
- f"Using delimiters: {config.user_delimiter}, {config.assistant_delimiter}, {config.resource_delimiter}"
483
- )
376
+ logger.info(f"Using delimiters: {config.user_delimiter}, {config.assistant_delimiter}, {config.resource_delimiter}")
484
377
 
485
378
  # If a test prompt was specified, print it and exit
486
379
  if args.test:
@@ -7,11 +7,15 @@ Provides clean, testable classes for managing template substitution.
7
7
 
8
8
  import re
9
9
  from pathlib import Path
10
- from typing import Dict, List, Set, Any, Optional, Literal
10
+ from typing import Any, Dict, List, Literal, Optional, Set
11
11
 
12
+ from mcp.types import (
13
+ EmbeddedResource,
14
+ TextContent,
15
+ TextResourceContents,
16
+ )
12
17
  from pydantic import BaseModel, field_validator
13
18
 
14
- from mcp.types import TextContent, EmbeddedResource, TextResourceContents
15
19
  from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
16
20
  from mcp_agent.mcp.prompt_serialization import (
17
21
  multipart_messages_to_delimited_format,
@@ -67,9 +71,7 @@ class PromptContent(BaseModel):
67
71
  substituted = substituted.replace(make_placeholder(key), str(value))
68
72
  substituted_resources.append(substituted)
69
73
 
70
- return PromptContent(
71
- text=result, role=self.role, resources=substituted_resources
72
- )
74
+ return PromptContent(text=result, role=self.role, resources=substituted_resources)
73
75
 
74
76
 
75
77
  class PromptTemplate:
@@ -82,7 +84,7 @@ class PromptTemplate:
82
84
  template_text: str,
83
85
  delimiter_map: Optional[Dict[str, str]] = None,
84
86
  template_file_path: Optional[Path] = None,
85
- ):
87
+ ) -> None:
86
88
  """
87
89
  Initialize a prompt template.
88
90
 
@@ -127,9 +129,7 @@ class PromptTemplate:
127
129
  # Convert to delimited format
128
130
  delimited_content = multipart_messages_to_delimited_format(
129
131
  messages,
130
- user_delimiter=next(
131
- (k for k, v in delimiter_map.items() if v == "user"), "---USER"
132
- ),
132
+ user_delimiter=next((k for k, v in delimiter_map.items() if v == "user"), "---USER"),
133
133
  assistant_delimiter=next(
134
134
  (k for k, v in delimiter_map.items() if v == "assistant"),
135
135
  "---ASSISTANT",
@@ -167,9 +167,7 @@ class PromptTemplate:
167
167
  result.append(section.apply_substitutions(context))
168
168
  return result
169
169
 
170
- def apply_substitutions_to_multipart(
171
- self, context: Dict[str, Any]
172
- ) -> List[PromptMessageMultipart]:
170
+ def apply_substitutions_to_multipart(self, context: Dict[str, Any]) -> List[PromptMessageMultipart]:
173
171
  """
174
172
  Apply variable substitutions to the template and return PromptMessageMultipart objects.
175
173
 
@@ -201,9 +199,7 @@ class PromptTemplate:
201
199
  )
202
200
  )
203
201
 
204
- multiparts.append(
205
- PromptMessageMultipart(role=section.role, content=content_items)
206
- )
202
+ multiparts.append(PromptMessageMultipart(role=section.role, content=content_items))
207
203
 
208
204
  return multiparts
209
205
 
@@ -241,9 +237,7 @@ class PromptTemplate:
241
237
  )
242
238
  )
243
239
 
244
- multiparts.append(
245
- PromptMessageMultipart(role=section.role, content=content_items)
246
- )
240
+ multiparts.append(PromptMessageMultipart(role=section.role, content=content_items))
247
241
 
248
242
  return multiparts
249
243
 
@@ -260,9 +254,7 @@ class PromptTemplate:
260
254
  first_non_empty_line = next((line for line in lines if line.strip()), "")
261
255
  delimiter_values = set(self.delimiter_map.keys())
262
256
 
263
- is_simple_mode = (
264
- first_non_empty_line and first_non_empty_line not in delimiter_values
265
- )
257
+ is_simple_mode = first_non_empty_line and first_non_empty_line not in delimiter_values
266
258
 
267
259
  if is_simple_mode:
268
260
  # Simple mode: treat the entire content as a single user message
@@ -330,7 +322,7 @@ class PromptTemplateLoader:
330
322
  Loads and processes prompt templates from files.
331
323
  """
332
324
 
333
- def __init__(self, delimiter_map: Optional[Dict[str, str]] = None):
325
+ def __init__(self, delimiter_map: Optional[Dict[str, str]] = None) -> None:
334
326
  """
335
327
  Initialize the loader with optional custom delimiters.
336
328
 
@@ -358,9 +350,7 @@ class PromptTemplateLoader:
358
350
 
359
351
  return PromptTemplate(content, self.delimiter_map, template_file_path=file_path)
360
352
 
361
- def load_from_multipart(
362
- self, messages: List[PromptMessageMultipart]
363
- ) -> PromptTemplate:
353
+ def load_from_multipart(self, messages: List[PromptMessageMultipart]) -> PromptTemplate:
364
354
  """
365
355
  Create a PromptTemplate from a list of PromptMessageMultipart objects.
366
356
 
@@ -372,9 +362,7 @@ class PromptTemplateLoader:
372
362
  """
373
363
  delimited_content = multipart_messages_to_delimited_format(
374
364
  messages,
375
- user_delimiter=next(
376
- (k for k, v in self.delimiter_map.items() if v == "user"), "---USER"
377
- ),
365
+ user_delimiter=next((k for k, v in self.delimiter_map.items() if v == "user"), "---USER"),
378
366
  assistant_delimiter=next(
379
367
  (k for k, v in self.delimiter_map.items() if v == "assistant"),
380
368
  "---ASSISTANT",
@@ -401,18 +389,12 @@ class PromptTemplateLoader:
401
389
  first_non_empty_line = next((line for line in lines if line.strip()), "")
402
390
 
403
391
  # Check if we're in simple mode
404
- is_simple_mode = (
405
- first_non_empty_line and first_non_empty_line not in self.delimiter_map
406
- )
392
+ is_simple_mode = first_non_empty_line and first_non_empty_line not in self.delimiter_map
407
393
 
408
394
  if is_simple_mode:
409
395
  # In simple mode, use first line as description if it seems like one
410
396
  first_line = lines[0].strip() if lines else ""
411
- if (
412
- len(first_line) < 60
413
- and "{{" not in first_line
414
- and "}}" not in first_line
415
- ):
397
+ if len(first_line) < 60 and "{{" not in first_line and "}}" not in first_line:
416
398
  description = first_line
417
399
  else:
418
400
  description = f"Simple prompt: {file_path.stem}"
@@ -434,9 +416,7 @@ class PromptTemplateLoader:
434
416
  if first_role and first_content_index and first_content_index < len(lines):
435
417
  # Get up to 3 non-empty lines after the delimiter for a preview
436
418
  preview_lines = []
437
- for j in range(
438
- first_content_index, min(first_content_index + 10, len(lines))
439
- ):
419
+ for j in range(first_content_index, min(first_content_index + 10, len(lines))):
440
420
  stripped = lines[j].strip()
441
421
  if stripped and stripped not in self.delimiter_map:
442
422
  preview_lines.append(stripped)
@@ -452,9 +432,7 @@ class PromptTemplateLoader:
452
432
 
453
433
  # Extract resource paths from all sections that come after RESOURCE delimiters
454
434
  resource_paths = []
455
- resource_delimiter = next(
456
- (k for k, v in self.delimiter_map.items() if v == "resource"), "---RESOURCE"
457
- )
435
+ resource_delimiter = next((k for k, v in self.delimiter_map.items() if v == "resource"), "---RESOURCE")
458
436
  for i, line in enumerate(lines):
459
437
  if line.strip() == resource_delimiter:
460
438
  if i + 1 < len(lines) and lines[i + 1].strip():