fast-agent-mcp 0.1.12__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.
- {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/METADATA +3 -4
- fast_agent_mcp-0.2.0.dist-info/RECORD +123 -0
- mcp_agent/__init__.py +75 -0
- mcp_agent/agents/agent.py +61 -415
- mcp_agent/agents/base_agent.py +522 -0
- mcp_agent/agents/workflow/__init__.py +1 -0
- mcp_agent/agents/workflow/chain_agent.py +173 -0
- mcp_agent/agents/workflow/evaluator_optimizer.py +362 -0
- mcp_agent/agents/workflow/orchestrator_agent.py +591 -0
- mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_models.py +11 -21
- mcp_agent/agents/workflow/parallel_agent.py +182 -0
- mcp_agent/agents/workflow/router_agent.py +307 -0
- mcp_agent/app.py +15 -19
- mcp_agent/cli/commands/bootstrap.py +19 -38
- mcp_agent/cli/commands/config.py +4 -4
- mcp_agent/cli/commands/setup.py +7 -14
- mcp_agent/cli/main.py +7 -10
- mcp_agent/cli/terminal.py +3 -3
- mcp_agent/config.py +25 -40
- mcp_agent/context.py +12 -21
- mcp_agent/context_dependent.py +3 -5
- mcp_agent/core/agent_types.py +10 -7
- mcp_agent/core/direct_agent_app.py +179 -0
- mcp_agent/core/direct_decorators.py +443 -0
- mcp_agent/core/direct_factory.py +476 -0
- mcp_agent/core/enhanced_prompt.py +23 -55
- mcp_agent/core/exceptions.py +8 -8
- mcp_agent/core/fastagent.py +145 -371
- mcp_agent/core/interactive_prompt.py +424 -0
- mcp_agent/core/mcp_content.py +17 -17
- mcp_agent/core/prompt.py +6 -9
- mcp_agent/core/request_params.py +6 -3
- mcp_agent/core/validation.py +92 -18
- mcp_agent/executor/decorator_registry.py +9 -17
- mcp_agent/executor/executor.py +8 -17
- mcp_agent/executor/task_registry.py +2 -4
- mcp_agent/executor/temporal.py +19 -41
- mcp_agent/executor/workflow.py +3 -5
- mcp_agent/executor/workflow_signal.py +15 -21
- mcp_agent/human_input/handler.py +4 -7
- mcp_agent/human_input/types.py +2 -3
- mcp_agent/llm/__init__.py +2 -0
- mcp_agent/llm/augmented_llm.py +450 -0
- mcp_agent/llm/augmented_llm_passthrough.py +162 -0
- mcp_agent/llm/augmented_llm_playback.py +83 -0
- mcp_agent/llm/memory.py +103 -0
- mcp_agent/{workflows/llm → llm}/model_factory.py +22 -16
- mcp_agent/{workflows/llm → llm}/prompt_utils.py +1 -3
- mcp_agent/llm/providers/__init__.py +8 -0
- mcp_agent/{workflows/llm → llm/providers}/anthropic_utils.py +8 -25
- mcp_agent/{workflows/llm → llm/providers}/augmented_llm_anthropic.py +56 -194
- mcp_agent/llm/providers/augmented_llm_deepseek.py +53 -0
- mcp_agent/{workflows/llm → llm/providers}/augmented_llm_openai.py +99 -190
- mcp_agent/{workflows/llm → llm}/providers/multipart_converter_anthropic.py +72 -71
- mcp_agent/{workflows/llm → llm}/providers/multipart_converter_openai.py +65 -71
- mcp_agent/{workflows/llm → llm}/providers/openai_multipart.py +16 -44
- mcp_agent/{workflows/llm → llm/providers}/openai_utils.py +4 -4
- mcp_agent/{workflows/llm → llm}/providers/sampling_converter_anthropic.py +9 -11
- mcp_agent/{workflows/llm → llm}/providers/sampling_converter_openai.py +8 -12
- mcp_agent/{workflows/llm → llm}/sampling_converter.py +3 -31
- mcp_agent/llm/sampling_format_converter.py +37 -0
- mcp_agent/logging/events.py +1 -5
- mcp_agent/logging/json_serializer.py +7 -6
- mcp_agent/logging/listeners.py +20 -23
- mcp_agent/logging/logger.py +17 -19
- mcp_agent/logging/rich_progress.py +10 -8
- mcp_agent/logging/tracing.py +4 -6
- mcp_agent/logging/transport.py +22 -22
- mcp_agent/mcp/gen_client.py +1 -3
- mcp_agent/mcp/interfaces.py +117 -110
- mcp_agent/mcp/logger_textio.py +97 -0
- mcp_agent/mcp/mcp_agent_client_session.py +7 -7
- mcp_agent/mcp/mcp_agent_server.py +8 -8
- mcp_agent/mcp/mcp_aggregator.py +102 -143
- mcp_agent/mcp/mcp_connection_manager.py +20 -27
- mcp_agent/mcp/prompt_message_multipart.py +68 -16
- mcp_agent/mcp/prompt_render.py +77 -0
- mcp_agent/mcp/prompt_serialization.py +30 -48
- mcp_agent/mcp/prompts/prompt_constants.py +18 -0
- mcp_agent/mcp/prompts/prompt_helpers.py +327 -0
- mcp_agent/mcp/prompts/prompt_load.py +109 -0
- mcp_agent/mcp/prompts/prompt_server.py +155 -195
- mcp_agent/mcp/prompts/prompt_template.py +35 -66
- mcp_agent/mcp/resource_utils.py +7 -14
- mcp_agent/mcp/sampling.py +17 -17
- mcp_agent/mcp_server/agent_server.py +13 -17
- mcp_agent/mcp_server_registry.py +13 -22
- mcp_agent/resources/examples/{workflows → in_dev}/agent_build.py +3 -2
- mcp_agent/resources/examples/in_dev/slides.py +110 -0
- mcp_agent/resources/examples/internal/agent.py +6 -3
- mcp_agent/resources/examples/internal/fastagent.config.yaml +8 -2
- mcp_agent/resources/examples/internal/job.py +2 -1
- mcp_agent/resources/examples/internal/prompt_category.py +1 -1
- mcp_agent/resources/examples/internal/prompt_sizing.py +3 -5
- mcp_agent/resources/examples/internal/sizer.py +2 -1
- mcp_agent/resources/examples/internal/social.py +2 -1
- mcp_agent/resources/examples/prompting/agent.py +2 -1
- mcp_agent/resources/examples/prompting/image_server.py +4 -8
- mcp_agent/resources/examples/prompting/work_with_image.py +19 -0
- mcp_agent/ui/console_display.py +16 -20
- fast_agent_mcp-0.1.12.dist-info/RECORD +0 -161
- mcp_agent/core/agent_app.py +0 -646
- mcp_agent/core/agent_utils.py +0 -71
- mcp_agent/core/decorators.py +0 -455
- mcp_agent/core/factory.py +0 -463
- mcp_agent/core/proxies.py +0 -269
- mcp_agent/core/types.py +0 -24
- mcp_agent/eval/__init__.py +0 -0
- mcp_agent/mcp/stdio.py +0 -111
- mcp_agent/resources/examples/data-analysis/analysis-campaign.py +0 -188
- mcp_agent/resources/examples/data-analysis/analysis.py +0 -65
- mcp_agent/resources/examples/data-analysis/fastagent.config.yaml +0 -41
- mcp_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -1471
- mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +0 -53
- mcp_agent/resources/examples/researcher/fastagent.config.yaml +0 -66
- mcp_agent/resources/examples/researcher/researcher-eval.py +0 -53
- mcp_agent/resources/examples/researcher/researcher-imp.py +0 -190
- mcp_agent/resources/examples/researcher/researcher.py +0 -38
- mcp_agent/resources/examples/workflows/chaining.py +0 -44
- mcp_agent/resources/examples/workflows/evaluator.py +0 -78
- mcp_agent/resources/examples/workflows/fastagent.config.yaml +0 -24
- mcp_agent/resources/examples/workflows/human_input.py +0 -25
- mcp_agent/resources/examples/workflows/orchestrator.py +0 -73
- mcp_agent/resources/examples/workflows/parallel.py +0 -78
- mcp_agent/resources/examples/workflows/router.py +0 -53
- mcp_agent/resources/examples/workflows/sse.py +0 -23
- mcp_agent/telemetry/__init__.py +0 -0
- mcp_agent/telemetry/usage_tracking.py +0 -18
- mcp_agent/workflows/__init__.py +0 -0
- mcp_agent/workflows/embedding/__init__.py +0 -0
- mcp_agent/workflows/embedding/embedding_base.py +0 -61
- mcp_agent/workflows/embedding/embedding_cohere.py +0 -49
- mcp_agent/workflows/embedding/embedding_openai.py +0 -46
- mcp_agent/workflows/evaluator_optimizer/__init__.py +0 -0
- mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +0 -481
- mcp_agent/workflows/intent_classifier/__init__.py +0 -0
- mcp_agent/workflows/intent_classifier/intent_classifier_base.py +0 -120
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +0 -134
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +0 -45
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +0 -45
- mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +0 -161
- mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +0 -60
- mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +0 -60
- mcp_agent/workflows/llm/__init__.py +0 -0
- mcp_agent/workflows/llm/augmented_llm.py +0 -753
- mcp_agent/workflows/llm/augmented_llm_passthrough.py +0 -241
- mcp_agent/workflows/llm/augmented_llm_playback.py +0 -109
- mcp_agent/workflows/llm/providers/__init__.py +0 -8
- mcp_agent/workflows/llm/sampling_format_converter.py +0 -22
- mcp_agent/workflows/orchestrator/__init__.py +0 -0
- mcp_agent/workflows/orchestrator/orchestrator.py +0 -578
- mcp_agent/workflows/parallel/__init__.py +0 -0
- mcp_agent/workflows/parallel/fan_in.py +0 -350
- mcp_agent/workflows/parallel/fan_out.py +0 -187
- mcp_agent/workflows/parallel/parallel_llm.py +0 -166
- mcp_agent/workflows/router/__init__.py +0 -0
- mcp_agent/workflows/router/router_base.py +0 -368
- mcp_agent/workflows/router/router_embedding.py +0 -240
- mcp_agent/workflows/router/router_embedding_cohere.py +0 -59
- mcp_agent/workflows/router/router_embedding_openai.py +0 -59
- mcp_agent/workflows/router/router_llm.py +0 -320
- mcp_agent/workflows/swarm/__init__.py +0 -0
- mcp_agent/workflows/swarm/swarm.py +0 -320
- mcp_agent/workflows/swarm/swarm_anthropic.py +0 -42
- mcp_agent/workflows/swarm/swarm_openai.py +0 -41
- {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
- /mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_prompts.py +0 -0
@@ -5,187 +5,94 @@ 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
|
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
18
|
AssistantMessage,
|
24
19
|
Message,
|
20
|
+
UserMessage,
|
25
21
|
)
|
26
|
-
from mcp.
|
27
|
-
|
28
|
-
|
22
|
+
from mcp.server.fastmcp.resources import FileResource
|
23
|
+
from mcp.types import PromptMessage
|
24
|
+
from pydantic import AnyUrl
|
29
25
|
|
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
|
+
)
|
36
|
+
from mcp_agent.mcp.prompts.prompt_load import create_messages_with_resources
|
30
37
|
from mcp_agent.mcp.prompts.prompt_template import (
|
31
|
-
PromptTemplateLoader,
|
32
38
|
PromptMetadata,
|
33
|
-
|
34
|
-
PromptTemplate,
|
39
|
+
PromptTemplateLoader,
|
35
40
|
)
|
36
41
|
|
37
42
|
# Configure logging
|
38
|
-
logging.basicConfig(level=logging.
|
43
|
+
logging.basicConfig(level=logging.ERROR)
|
39
44
|
logger = logging.getLogger("prompt_server")
|
40
45
|
|
41
46
|
# Create FastMCP server
|
42
47
|
mcp = FastMCP("Prompt Server")
|
43
48
|
|
44
49
|
|
45
|
-
|
46
|
-
"""Configuration for the prompt server"""
|
47
|
-
|
48
|
-
prompt_files: List[Path] = []
|
49
|
-
user_delimiter: str = "---USER"
|
50
|
-
assistant_delimiter: str = "---ASSISTANT"
|
51
|
-
resource_delimiter: str = "---RESOURCE"
|
52
|
-
http_timeout: float = 10.0
|
53
|
-
transport: str = "stdio"
|
54
|
-
port: int = 8000
|
55
|
-
|
56
|
-
|
57
|
-
# Will be initialized with command line args
|
58
|
-
config = None
|
59
|
-
|
60
|
-
# We'll maintain registries of all exposed resources and prompts
|
61
|
-
exposed_resources: Dict[str, Path] = {}
|
62
|
-
prompt_registry: Dict[str, PromptMetadata] = {}
|
63
|
-
|
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]:
|
50
|
+
def convert_to_fastmcp_messages(prompt_messages: List[PromptMessage]) -> List[Message]:
|
97
51
|
"""
|
98
|
-
|
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).
|
52
|
+
Convert PromptMessage objects from prompt_load to FastMCP Message objects.
|
53
|
+
This adapter prevents double-wrapping of messages.
|
103
54
|
|
104
55
|
Args:
|
105
|
-
|
106
|
-
prompt_files: List of prompt files (to help locate resource files)
|
56
|
+
prompt_messages: List of PromptMessage objects from prompt_load
|
107
57
|
|
108
58
|
Returns:
|
109
|
-
List of Message objects
|
59
|
+
List of FastMCP Message objects
|
110
60
|
"""
|
111
|
-
|
61
|
+
result = []
|
112
62
|
|
113
|
-
for
|
114
|
-
|
115
|
-
|
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))
|
116
71
|
|
117
|
-
|
118
|
-
messages.append(create_content_message(section.text, role))
|
72
|
+
return result
|
119
73
|
|
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
74
|
|
128
|
-
|
129
|
-
|
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}")
|
75
|
+
class PromptConfig(PromptMetadata):
|
76
|
+
"""Configuration for the prompt server"""
|
135
77
|
|
136
|
-
|
78
|
+
prompt_files: List[Path] = []
|
79
|
+
user_delimiter: str = DEFAULT_USER_DELIMITER
|
80
|
+
assistant_delimiter: str = DEFAULT_ASSISTANT_DELIMITER
|
81
|
+
resource_delimiter: str = DEFAULT_RESOURCE_DELIMITER
|
82
|
+
http_timeout: float = 10.0
|
83
|
+
transport: str = "stdio"
|
84
|
+
port: int = 8000
|
137
85
|
|
138
86
|
|
139
|
-
|
140
|
-
|
141
|
-
|
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"
|
87
|
+
# We'll maintain registries of all exposed resources and prompts
|
88
|
+
exposed_resources: Dict[str, Path] = {}
|
89
|
+
prompt_registry: Dict[str, PromptMetadata] = {}
|
146
90
|
|
147
91
|
|
148
92
|
# Define a single type for prompt handlers to avoid mypy issues
|
149
93
|
PromptHandler = Callable[..., Awaitable[List[Message]]]
|
150
94
|
|
151
95
|
|
152
|
-
def create_prompt_handler(
|
153
|
-
template: "PromptTemplate", template_vars: List[str], prompt_files: List[Path]
|
154
|
-
) -> PromptHandler:
|
155
|
-
"""Create a prompt handler function for the given template"""
|
156
|
-
if template_vars:
|
157
|
-
# With template variables
|
158
|
-
docstring = f"Prompt with template variables: {', '.join(template_vars)}"
|
159
|
-
|
160
|
-
async def prompt_handler(**kwargs: Any) -> List[Message]:
|
161
|
-
# 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
|
-
}
|
167
|
-
|
168
|
-
# Apply substitutions to the template
|
169
|
-
content_sections = template.apply_substitutions(context)
|
170
|
-
|
171
|
-
# Convert to MCP Message objects, handling resources properly
|
172
|
-
return create_messages_with_resources(content_sections, prompt_files)
|
173
|
-
else:
|
174
|
-
# No template variables
|
175
|
-
docstring = "Get a prompt with no variable substitution"
|
176
|
-
|
177
|
-
async def prompt_handler(**kwargs: Any) -> List[Message]:
|
178
|
-
# Get the content sections
|
179
|
-
content_sections = template.content_sections
|
180
|
-
|
181
|
-
# Convert to MCP Message objects, handling resources properly
|
182
|
-
return create_messages_with_resources(content_sections, prompt_files)
|
183
|
-
|
184
|
-
# Set the docstring
|
185
|
-
prompt_handler.__doc__ = docstring
|
186
|
-
return prompt_handler
|
187
|
-
|
188
|
-
|
189
96
|
# Type for resource handler
|
190
97
|
ResourceHandler = Callable[[], Awaitable[str | bytes]]
|
191
98
|
|
@@ -208,13 +115,9 @@ def create_resource_handler(resource_path: Path, mime_type: str) -> ResourceHand
|
|
208
115
|
return get_resource
|
209
116
|
|
210
117
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
DEFAULT_RESOURCE_DELIMITER = "---RESOURCE"
|
215
|
-
|
216
|
-
|
217
|
-
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]:
|
218
121
|
"""Get delimiter configuration, falling back to defaults if config is None"""
|
219
122
|
# Set defaults
|
220
123
|
config_values = {
|
@@ -234,11 +137,11 @@ 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, config: Optional[PromptConfig] = None) -> None:
|
238
141
|
"""Register a prompt file"""
|
239
142
|
try:
|
240
143
|
# Get delimiter configuration
|
241
|
-
config_values = get_delimiter_config(file_path)
|
144
|
+
config_values = get_delimiter_config(config, file_path)
|
242
145
|
|
243
146
|
# Use our prompt template loader to analyze the file
|
244
147
|
loader = PromptTemplateLoader(
|
@@ -268,10 +171,61 @@ def register_prompt(file_path: Path):
|
|
268
171
|
|
269
172
|
# Create and register prompt handler
|
270
173
|
template_vars = list(metadata.template_variables)
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
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)
|
275
229
|
|
276
230
|
# Register any referenced resources in the prompt
|
277
231
|
for resource_path in metadata.resource_paths:
|
@@ -295,9 +249,7 @@ def register_prompt(file_path: Path):
|
|
295
249
|
)
|
296
250
|
)
|
297
251
|
|
298
|
-
logger.info(
|
299
|
-
f"Registered resource: {resource_id} ({resource_file})"
|
300
|
-
)
|
252
|
+
logger.info(f"Registered resource: {resource_id} ({resource_file})")
|
301
253
|
except Exception as e:
|
302
254
|
logger.error(f"Error registering prompt {file_path}: {e}", exc_info=True)
|
303
255
|
|
@@ -305,9 +257,7 @@ def register_prompt(file_path: Path):
|
|
305
257
|
def parse_args():
|
306
258
|
"""Parse command line arguments"""
|
307
259
|
parser = argparse.ArgumentParser(description="FastMCP Prompt Server")
|
308
|
-
parser.add_argument(
|
309
|
-
"prompt_files", nargs="+", type=str, help="Prompt files to serve"
|
310
|
-
)
|
260
|
+
parser.add_argument("prompt_files", nargs="+", type=str, help="Prompt files to serve")
|
311
261
|
parser.add_argument(
|
312
262
|
"--user-delimiter",
|
313
263
|
type=str,
|
@@ -352,7 +302,39 @@ def parse_args():
|
|
352
302
|
return parser.parse_args()
|
353
303
|
|
354
304
|
|
355
|
-
|
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:
|
356
338
|
"""Register the general file resource handler"""
|
357
339
|
|
358
340
|
@mcp.resource("file://{path}")
|
@@ -384,14 +366,14 @@ async def register_file_resource_handler():
|
|
384
366
|
raise
|
385
367
|
|
386
368
|
|
387
|
-
async def test_prompt(prompt_name: str) -> int:
|
369
|
+
async def test_prompt(prompt_name: str, config: PromptConfig) -> int:
|
388
370
|
"""Test a prompt and print its details"""
|
389
371
|
if prompt_name not in prompt_registry:
|
390
372
|
logger.error(f"Test prompt not found: {prompt_name}")
|
391
373
|
return 1
|
392
374
|
|
393
375
|
# Get delimiter configuration with reasonable defaults
|
394
|
-
config_values = get_delimiter_config()
|
376
|
+
config_values = get_delimiter_config(config)
|
395
377
|
|
396
378
|
metadata = prompt_registry[prompt_name]
|
397
379
|
print(f"\nTesting prompt: {prompt_name}")
|
@@ -431,48 +413,24 @@ async def test_prompt(prompt_name: str) -> int:
|
|
431
413
|
return 0
|
432
414
|
|
433
415
|
|
434
|
-
async def async_main():
|
416
|
+
async def async_main() -> int:
|
435
417
|
"""Run the FastMCP server (async version)"""
|
436
|
-
global config
|
437
|
-
|
438
418
|
# Parse command line arguments
|
439
419
|
args = parse_args()
|
440
420
|
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
logger.warning(f"File not found: {path}")
|
447
|
-
continue
|
448
|
-
prompt_files.append(path.resolve())
|
449
|
-
|
450
|
-
if not prompt_files:
|
451
|
-
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))
|
452
426
|
return 1
|
453
427
|
|
454
|
-
# Initialize configuration
|
455
|
-
config = PromptConfig(
|
456
|
-
name="prompt_server",
|
457
|
-
description="FastMCP Prompt Server",
|
458
|
-
template_variables=set(),
|
459
|
-
resource_paths=[],
|
460
|
-
file_path=Path(__file__),
|
461
|
-
prompt_files=prompt_files,
|
462
|
-
user_delimiter=args.user_delimiter,
|
463
|
-
assistant_delimiter=args.assistant_delimiter,
|
464
|
-
resource_delimiter=args.resource_delimiter,
|
465
|
-
http_timeout=args.http_timeout,
|
466
|
-
transport=args.transport,
|
467
|
-
port=args.port,
|
468
|
-
)
|
469
|
-
|
470
428
|
# Register resource handlers
|
471
|
-
await register_file_resource_handler()
|
429
|
+
await register_file_resource_handler(config)
|
472
430
|
|
473
431
|
# Register all prompts
|
474
432
|
for file_path in config.prompt_files:
|
475
|
-
register_prompt(file_path)
|
433
|
+
register_prompt(file_path, config)
|
476
434
|
|
477
435
|
# Print startup info
|
478
436
|
logger.info("Starting prompt server")
|
@@ -484,13 +442,15 @@ async def async_main():
|
|
484
442
|
|
485
443
|
# If a test prompt was specified, print it and exit
|
486
444
|
if args.test:
|
487
|
-
return await test_prompt(args.test)
|
445
|
+
return await test_prompt(args.test, config)
|
488
446
|
|
489
447
|
# Start the server with the specified transport
|
490
448
|
if config.transport == "stdio":
|
491
449
|
await mcp.run_stdio_async()
|
492
450
|
else: # sse
|
493
|
-
|
451
|
+
# TODO update to 2025-03-26 specification and test config.
|
452
|
+
await mcp.run_sse_async()
|
453
|
+
return 0
|
494
454
|
|
495
455
|
|
496
456
|
def main() -> int:
|