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.
- {fast_agent_mcp-0.1.13.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 +59 -371
- 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 +27 -11
- mcp_agent/agents/workflow/parallel_agent.py +182 -0
- mcp_agent/agents/workflow/router_agent.py +307 -0
- mcp_agent/app.py +3 -1
- mcp_agent/cli/commands/bootstrap.py +18 -7
- mcp_agent/cli/commands/setup.py +12 -4
- mcp_agent/cli/main.py +1 -1
- mcp_agent/cli/terminal.py +1 -1
- mcp_agent/config.py +24 -35
- mcp_agent/context.py +3 -1
- mcp_agent/context_dependent.py +3 -1
- 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 +15 -20
- mcp_agent/core/fastagent.py +151 -337
- mcp_agent/core/interactive_prompt.py +424 -0
- mcp_agent/core/mcp_content.py +19 -11
- mcp_agent/core/prompt.py +6 -2
- mcp_agent/core/validation.py +89 -16
- mcp_agent/executor/decorator_registry.py +6 -2
- mcp_agent/executor/temporal.py +35 -11
- mcp_agent/executor/workflow_signal.py +8 -2
- mcp_agent/human_input/handler.py +3 -1
- mcp_agent/llm/__init__.py +2 -0
- mcp_agent/{workflows/llm → llm}/augmented_llm.py +131 -256
- mcp_agent/{workflows/llm → llm}/augmented_llm_passthrough.py +35 -107
- mcp_agent/llm/augmented_llm_playback.py +83 -0
- mcp_agent/{workflows/llm → llm}/model_factory.py +26 -8
- mcp_agent/llm/providers/__init__.py +8 -0
- mcp_agent/{workflows/llm → llm/providers}/anthropic_utils.py +5 -1
- mcp_agent/{workflows/llm → llm/providers}/augmented_llm_anthropic.py +37 -141
- mcp_agent/llm/providers/augmented_llm_deepseek.py +53 -0
- mcp_agent/{workflows/llm → llm/providers}/augmented_llm_openai.py +112 -148
- mcp_agent/{workflows/llm → llm}/providers/multipart_converter_anthropic.py +78 -35
- mcp_agent/{workflows/llm → llm}/providers/multipart_converter_openai.py +73 -44
- mcp_agent/{workflows/llm → llm}/providers/openai_multipart.py +18 -4
- mcp_agent/{workflows/llm → llm/providers}/openai_utils.py +3 -3
- mcp_agent/{workflows/llm → llm}/providers/sampling_converter_anthropic.py +3 -3
- mcp_agent/{workflows/llm → llm}/providers/sampling_converter_openai.py +3 -3
- mcp_agent/{workflows/llm → llm}/sampling_converter.py +0 -21
- mcp_agent/{workflows/llm → llm}/sampling_format_converter.py +16 -1
- mcp_agent/logging/logger.py +2 -2
- mcp_agent/mcp/gen_client.py +9 -3
- mcp_agent/mcp/interfaces.py +67 -45
- mcp_agent/mcp/logger_textio.py +97 -0
- mcp_agent/mcp/mcp_agent_client_session.py +12 -4
- mcp_agent/mcp/mcp_agent_server.py +3 -1
- mcp_agent/mcp/mcp_aggregator.py +124 -93
- mcp_agent/mcp/mcp_connection_manager.py +21 -7
- mcp_agent/mcp/prompt_message_multipart.py +59 -1
- mcp_agent/mcp/prompt_render.py +77 -0
- mcp_agent/mcp/prompt_serialization.py +20 -13
- 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 +15 -5
- mcp_agent/mcp/prompts/prompt_server.py +154 -87
- mcp_agent/mcp/prompts/prompt_template.py +26 -35
- mcp_agent/mcp/resource_utils.py +3 -1
- mcp_agent/mcp/sampling.py +24 -15
- mcp_agent/mcp_server/agent_server.py +8 -5
- mcp_agent/mcp_server_registry.py +22 -9
- mcp_agent/resources/examples/{workflows → in_dev}/agent_build.py +1 -1
- mcp_agent/resources/examples/{data-analysis → in_dev}/slides.py +1 -1
- mcp_agent/resources/examples/internal/agent.py +4 -2
- mcp_agent/resources/examples/internal/fastagent.config.yaml +8 -2
- mcp_agent/resources/examples/prompting/image_server.py +3 -1
- mcp_agent/resources/examples/prompting/work_with_image.py +19 -0
- mcp_agent/ui/console_display.py +27 -7
- fast_agent_mcp-0.1.13.dist-info/RECORD +0 -164
- mcp_agent/core/agent_app.py +0 -570
- mcp_agent/core/agent_utils.py +0 -69
- mcp_agent/core/decorators.py +0 -448
- mcp_agent/core/factory.py +0 -422
- mcp_agent/core/proxies.py +0 -278
- mcp_agent/core/types.py +0 -22
- mcp_agent/eval/__init__.py +0 -0
- mcp_agent/mcp/stdio.py +0 -114
- 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 -189
- mcp_agent/resources/examples/researcher/researcher.py +0 -39
- mcp_agent/resources/examples/workflows/chaining.py +0 -45
- mcp_agent/resources/examples/workflows/evaluator.py +0 -79
- mcp_agent/resources/examples/workflows/fastagent.config.yaml +0 -24
- mcp_agent/resources/examples/workflows/human_input.py +0 -26
- mcp_agent/resources/examples/workflows/orchestrator.py +0 -74
- mcp_agent/resources/examples/workflows/parallel.py +0 -79
- mcp_agent/resources/examples/workflows/router.py +0 -54
- mcp_agent/resources/examples/workflows/sse.py +0 -23
- mcp_agent/telemetry/__init__.py +0 -0
- mcp_agent/telemetry/usage_tracking.py +0 -19
- mcp_agent/workflows/__init__.py +0 -0
- mcp_agent/workflows/embedding/__init__.py +0 -0
- mcp_agent/workflows/embedding/embedding_base.py +0 -58
- mcp_agent/workflows/embedding/embedding_cohere.py +0 -49
- mcp_agent/workflows/embedding/embedding_openai.py +0 -37
- mcp_agent/workflows/evaluator_optimizer/__init__.py +0 -0
- mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +0 -447
- mcp_agent/workflows/intent_classifier/__init__.py +0 -0
- mcp_agent/workflows/intent_classifier/intent_classifier_base.py +0 -117
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +0 -130
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +0 -41
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +0 -41
- mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +0 -150
- mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +0 -60
- mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +0 -58
- mcp_agent/workflows/llm/__init__.py +0 -0
- mcp_agent/workflows/llm/augmented_llm_playback.py +0 -111
- mcp_agent/workflows/llm/providers/__init__.py +0 -8
- mcp_agent/workflows/orchestrator/__init__.py +0 -0
- mcp_agent/workflows/orchestrator/orchestrator.py +0 -535
- mcp_agent/workflows/parallel/__init__.py +0 -0
- mcp_agent/workflows/parallel/fan_in.py +0 -320
- mcp_agent/workflows/parallel/fan_out.py +0 -181
- mcp_agent/workflows/parallel/parallel_llm.py +0 -149
- mcp_agent/workflows/router/__init__.py +0 -0
- mcp_agent/workflows/router/router_base.py +0 -338
- mcp_agent/workflows/router/router_embedding.py +0 -226
- 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 -304
- mcp_agent/workflows/swarm/__init__.py +0 -0
- mcp_agent/workflows/swarm/swarm.py +0 -292
- mcp_agent/workflows/swarm/swarm_anthropic.py +0 -42
- mcp_agent/workflows/swarm/swarm_openai.py +0 -41
- {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.1.13.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
- /mcp_agent/{workflows/llm → llm}/memory.py +0 -0
- /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.
|
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 =
|
44
|
-
assistant_delimiter: str =
|
45
|
-
resource_delimiter: str =
|
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(
|
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
|
-
|
175
|
-
mcp.
|
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(
|
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
|
-
|
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
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
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(
|
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
|
-
|
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(
|
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
|
-
|
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
|
-
|
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(
|
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
|
-
|
364
|
-
|
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(
|
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():
|
mcp_agent/mcp/resource_utils.py
CHANGED
@@ -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(
|
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
|
-
|
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(
|
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.
|
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
|
-
|
104
|
-
|
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
|
-
|
108
|
-
|
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(
|
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
|
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
|
-
#
|
7
|
-
from mcp_agent.core.
|
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:
|
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
|
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(
|
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
|