fast-agent-mcp 0.2.3__py3-none-any.whl → 0.2.5__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.2.3.dist-info → fast_agent_mcp-0.2.5.dist-info}/METADATA +13 -9
- {fast_agent_mcp-0.2.3.dist-info → fast_agent_mcp-0.2.5.dist-info}/RECORD +42 -40
- mcp_agent/__init__.py +2 -2
- mcp_agent/agents/agent.py +5 -0
- mcp_agent/agents/base_agent.py +152 -36
- mcp_agent/agents/workflow/chain_agent.py +9 -13
- mcp_agent/agents/workflow/evaluator_optimizer.py +3 -3
- mcp_agent/agents/workflow/orchestrator_agent.py +9 -7
- mcp_agent/agents/workflow/parallel_agent.py +2 -2
- mcp_agent/agents/workflow/router_agent.py +7 -5
- mcp_agent/cli/main.py +11 -0
- mcp_agent/config.py +29 -7
- mcp_agent/context.py +2 -0
- mcp_agent/core/{direct_agent_app.py → agent_app.py} +115 -15
- mcp_agent/core/direct_factory.py +9 -18
- mcp_agent/core/enhanced_prompt.py +3 -3
- mcp_agent/core/fastagent.py +218 -49
- mcp_agent/core/mcp_content.py +38 -5
- mcp_agent/core/prompt.py +70 -8
- mcp_agent/core/validation.py +1 -1
- mcp_agent/llm/augmented_llm.py +44 -16
- mcp_agent/llm/augmented_llm_passthrough.py +3 -1
- mcp_agent/llm/model_factory.py +16 -28
- mcp_agent/llm/providers/augmented_llm_openai.py +3 -3
- mcp_agent/llm/providers/multipart_converter_anthropic.py +8 -8
- mcp_agent/llm/providers/multipart_converter_openai.py +9 -9
- mcp_agent/mcp/helpers/__init__.py +3 -0
- mcp_agent/mcp/helpers/content_helpers.py +116 -0
- mcp_agent/mcp/interfaces.py +39 -16
- mcp_agent/mcp/mcp_aggregator.py +117 -13
- mcp_agent/mcp/prompt_message_multipart.py +29 -22
- mcp_agent/mcp/prompt_render.py +18 -15
- mcp_agent/mcp/prompt_serialization.py +42 -0
- mcp_agent/mcp/prompts/prompt_helpers.py +22 -112
- mcp_agent/mcp/prompts/prompt_load.py +51 -3
- mcp_agent/mcp_server/agent_server.py +62 -13
- mcp_agent/resources/examples/internal/agent.py +2 -2
- mcp_agent/resources/examples/internal/fastagent.config.yaml +5 -0
- mcp_agent/resources/examples/internal/history_transfer.py +35 -0
- mcp_agent/mcp/mcp_agent_server.py +0 -56
- {fast_agent_mcp-0.2.3.dist-info → fast_agent_mcp-0.2.5.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.3.dist-info → fast_agent_mcp-0.2.5.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.2.3.dist-info → fast_agent_mcp-0.2.5.dist-info}/licenses/LICENSE +0 -0
mcp_agent/mcp/mcp_aggregator.py
CHANGED
@@ -5,6 +5,7 @@ from typing import (
|
|
5
5
|
Callable,
|
6
6
|
Dict,
|
7
7
|
List,
|
8
|
+
Mapping,
|
8
9
|
Optional,
|
9
10
|
TypeVar,
|
10
11
|
)
|
@@ -373,7 +374,11 @@ class MCPAggregator(ContextDependent):
|
|
373
374
|
f"Failed to {method_name} '{operation_name}' on server '{server_name}': {e}"
|
374
375
|
)
|
375
376
|
logger.error(error_msg)
|
376
|
-
|
377
|
+
if error_factory:
|
378
|
+
return error_factory(error_msg)
|
379
|
+
else:
|
380
|
+
# Re-raise the original exception to propagate it
|
381
|
+
raise e
|
377
382
|
|
378
383
|
if self.connection_persistence:
|
379
384
|
server_connection = await self._persistent_connection_manager.get_server(
|
@@ -476,7 +481,10 @@ class MCPAggregator(ContextDependent):
|
|
476
481
|
)
|
477
482
|
|
478
483
|
async def get_prompt(
|
479
|
-
self,
|
484
|
+
self,
|
485
|
+
prompt_name: str | None,
|
486
|
+
arguments: dict[str, str] | None = None,
|
487
|
+
server_name: str | None = None,
|
480
488
|
) -> GetPromptResult:
|
481
489
|
"""
|
482
490
|
Get a prompt from a server.
|
@@ -485,6 +493,8 @@ class MCPAggregator(ContextDependent):
|
|
485
493
|
using the format 'server_name-prompt_name'
|
486
494
|
:param arguments: Optional dictionary of string arguments to pass to the prompt template
|
487
495
|
for templating
|
496
|
+
:param server_name: Optional name of the server to get the prompt from. If not provided
|
497
|
+
and prompt_name is not namespaced, will search all servers.
|
488
498
|
:return: GetPromptResult containing the prompt description and messages
|
489
499
|
with a namespaced_name property for display purposes
|
490
500
|
"""
|
@@ -493,17 +503,17 @@ class MCPAggregator(ContextDependent):
|
|
493
503
|
|
494
504
|
# Handle the case where prompt_name is None
|
495
505
|
if not prompt_name:
|
496
|
-
|
506
|
+
if server_name is None:
|
507
|
+
server_name = self.server_names[0] if self.server_names else None
|
497
508
|
local_prompt_name = None
|
498
509
|
namespaced_name = None
|
499
510
|
# Handle namespaced prompt name
|
500
|
-
elif SEP in prompt_name:
|
511
|
+
elif SEP in prompt_name and server_name is None:
|
501
512
|
server_name, local_prompt_name = prompt_name.split(SEP, 1)
|
502
513
|
namespaced_name = prompt_name # Already namespaced
|
503
|
-
# Plain prompt name -
|
514
|
+
# Plain prompt name - use provided server or search
|
504
515
|
else:
|
505
516
|
local_prompt_name = prompt_name
|
506
|
-
server_name = None
|
507
517
|
namespaced_name = None # Will be set when server is found
|
508
518
|
|
509
519
|
# If we have a specific server to check
|
@@ -700,7 +710,7 @@ class MCPAggregator(ContextDependent):
|
|
700
710
|
messages=[],
|
701
711
|
)
|
702
712
|
|
703
|
-
async def list_prompts(self, server_name: str = None) ->
|
713
|
+
async def list_prompts(self, server_name: str | None = None) -> Mapping[str, List[Prompt]]:
|
704
714
|
"""
|
705
715
|
List available prompts from one or all servers.
|
706
716
|
|
@@ -795,13 +805,16 @@ class MCPAggregator(ContextDependent):
|
|
795
805
|
logger.debug(f"Available prompts across servers: {results}")
|
796
806
|
return results
|
797
807
|
|
798
|
-
async def get_resource(
|
808
|
+
async def get_resource(
|
809
|
+
self, resource_uri: str, server_name: str | None = None
|
810
|
+
) -> ReadResourceResult:
|
799
811
|
"""
|
800
812
|
Get a resource directly from an MCP server by URI.
|
813
|
+
If server_name is None, will search all available servers.
|
801
814
|
|
802
815
|
Args:
|
803
|
-
server_name: Name of the MCP server to retrieve the resource from
|
804
816
|
resource_uri: URI of the resource to retrieve
|
817
|
+
server_name: Optional name of the MCP server to retrieve the resource from
|
805
818
|
|
806
819
|
Returns:
|
807
820
|
ReadResourceResult object containing the resource content
|
@@ -812,9 +825,45 @@ class MCPAggregator(ContextDependent):
|
|
812
825
|
if not self.initialized:
|
813
826
|
await self.load_servers()
|
814
827
|
|
815
|
-
|
816
|
-
|
828
|
+
# If specific server requested, use only that server
|
829
|
+
if server_name is not None:
|
830
|
+
if server_name not in self.server_names:
|
831
|
+
raise ValueError(f"Server '{server_name}' not found")
|
832
|
+
|
833
|
+
# Get the resource from the specified server
|
834
|
+
return await self._get_resource_from_server(server_name, resource_uri)
|
835
|
+
|
836
|
+
# If no server specified, search all servers
|
837
|
+
if not self.server_names:
|
838
|
+
raise ValueError("No servers available to get resource from")
|
839
|
+
|
840
|
+
# Try each server in order - simply attempt to get the resource
|
841
|
+
for s_name in self.server_names:
|
842
|
+
try:
|
843
|
+
return await self._get_resource_from_server(s_name, resource_uri)
|
844
|
+
except Exception:
|
845
|
+
# Continue to next server if not found
|
846
|
+
continue
|
847
|
+
|
848
|
+
# If we reach here, we couldn't find the resource on any server
|
849
|
+
raise ValueError(f"Resource '{resource_uri}' not found on any server")
|
850
|
+
|
851
|
+
async def _get_resource_from_server(
|
852
|
+
self, server_name: str, resource_uri: str
|
853
|
+
) -> ReadResourceResult:
|
854
|
+
"""
|
855
|
+
Internal helper method to get a resource from a specific server.
|
856
|
+
|
857
|
+
Args:
|
858
|
+
server_name: Name of the server to get the resource from
|
859
|
+
resource_uri: URI of the resource to retrieve
|
860
|
+
|
861
|
+
Returns:
|
862
|
+
ReadResourceResult containing the resource
|
817
863
|
|
864
|
+
Raises:
|
865
|
+
Exception: If the resource couldn't be found or other error occurs
|
866
|
+
"""
|
818
867
|
logger.info(
|
819
868
|
"Requesting resource",
|
820
869
|
data={
|
@@ -831,15 +880,70 @@ class MCPAggregator(ContextDependent):
|
|
831
880
|
raise ValueError(f"Invalid resource URI: {resource_uri}. Error: {e}")
|
832
881
|
|
833
882
|
# Use the _execute_on_server method to call read_resource on the server
|
834
|
-
|
883
|
+
result = await self._execute_on_server(
|
835
884
|
server_name=server_name,
|
836
885
|
operation_type="resource",
|
837
886
|
operation_name=resource_uri,
|
838
887
|
method_name="read_resource",
|
839
888
|
method_args={"uri": uri},
|
840
|
-
|
889
|
+
# Don't create ValueError, just return None on error so we can catch it
|
890
|
+
# error_factory=lambda _: None,
|
841
891
|
)
|
842
892
|
|
893
|
+
# If result is None, the resource was not found
|
894
|
+
if result is None:
|
895
|
+
raise ValueError(f"Resource '{resource_uri}' not found on server '{server_name}'")
|
896
|
+
|
897
|
+
return result
|
898
|
+
|
899
|
+
async def list_resources(self, server_name: str | None = None) -> Dict[str, List[str]]:
|
900
|
+
"""
|
901
|
+
List available resources from one or all servers.
|
902
|
+
|
903
|
+
Args:
|
904
|
+
server_name: Optional server name to list resources from. If not provided,
|
905
|
+
lists resources from all servers.
|
906
|
+
|
907
|
+
Returns:
|
908
|
+
Dictionary mapping server names to lists of resource URIs
|
909
|
+
"""
|
910
|
+
if not self.initialized:
|
911
|
+
await self.load_servers()
|
912
|
+
|
913
|
+
results: Dict[str, List[str]] = {}
|
914
|
+
|
915
|
+
# Get the list of servers to check
|
916
|
+
servers_to_check = [server_name] if server_name else self.server_names
|
917
|
+
|
918
|
+
# For each server, try to list its resources
|
919
|
+
for s_name in servers_to_check:
|
920
|
+
if s_name not in self.server_names:
|
921
|
+
logger.error(f"Server '{s_name}' not found")
|
922
|
+
continue
|
923
|
+
|
924
|
+
# Initialize empty list for this server
|
925
|
+
results[s_name] = []
|
926
|
+
|
927
|
+
try:
|
928
|
+
# Use the _execute_on_server method to call list_resources on the server
|
929
|
+
result = await self._execute_on_server(
|
930
|
+
server_name=s_name,
|
931
|
+
operation_type="resources-list",
|
932
|
+
operation_name="",
|
933
|
+
method_name="list_resources",
|
934
|
+
method_args={}, # Empty dictionary instead of None
|
935
|
+
# No error_factory to allow exceptions to propagate
|
936
|
+
)
|
937
|
+
|
938
|
+
# Get resources from result
|
939
|
+
resources = getattr(result, "resources", [])
|
940
|
+
results[s_name] = [str(r.uri) for r in resources]
|
941
|
+
|
942
|
+
except Exception as e:
|
943
|
+
logger.error(f"Error fetching resources from {s_name}: {e}")
|
944
|
+
|
945
|
+
return results
|
946
|
+
|
843
947
|
|
844
948
|
class MCPCompoundServer(Server):
|
845
949
|
"""
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import List, Union
|
1
|
+
from typing import List, Optional, Union
|
2
2
|
|
3
3
|
from mcp.types import (
|
4
4
|
EmbeddedResource,
|
@@ -7,29 +7,10 @@ from mcp.types import (
|
|
7
7
|
PromptMessage,
|
8
8
|
Role,
|
9
9
|
TextContent,
|
10
|
-
TextResourceContents,
|
11
10
|
)
|
12
11
|
from pydantic import BaseModel
|
13
12
|
|
14
|
-
|
15
|
-
def get_text(content: Union[TextContent, ImageContent, EmbeddedResource]) -> str | None:
|
16
|
-
"""
|
17
|
-
Extract text content from a content object if available.
|
18
|
-
|
19
|
-
Args:
|
20
|
-
content: A content object (TextContent, ImageContent, or EmbeddedResource)
|
21
|
-
|
22
|
-
Returns:
|
23
|
-
The text content as a string or None if not a text content
|
24
|
-
"""
|
25
|
-
if isinstance(content, TextContent):
|
26
|
-
return content.text
|
27
|
-
|
28
|
-
if isinstance(content, EmbeddedResource):
|
29
|
-
if isinstance(content.resource, TextResourceContents):
|
30
|
-
return content.resource.text
|
31
|
-
|
32
|
-
return None
|
13
|
+
from mcp_agent.mcp.helpers.content_helpers import get_text
|
33
14
|
|
34
15
|
|
35
16
|
class PromptMessageMultipart(BaseModel):
|
@@ -112,5 +93,31 @@ class PromptMessageMultipart(BaseModel):
|
|
112
93
|
|
113
94
|
@classmethod
|
114
95
|
def parse_get_prompt_result(cls, result: GetPromptResult) -> List["PromptMessageMultipart"]:
|
115
|
-
"""
|
96
|
+
"""
|
97
|
+
Parse a GetPromptResult into PromptMessageMultipart objects.
|
98
|
+
|
99
|
+
Args:
|
100
|
+
result: GetPromptResult from MCP server
|
101
|
+
|
102
|
+
Returns:
|
103
|
+
List of PromptMessageMultipart objects
|
104
|
+
"""
|
116
105
|
return cls.to_multipart(result.messages)
|
106
|
+
|
107
|
+
@classmethod
|
108
|
+
def from_get_prompt_result(
|
109
|
+
cls, result: Optional[GetPromptResult]
|
110
|
+
) -> List["PromptMessageMultipart"]:
|
111
|
+
"""
|
112
|
+
Convert a GetPromptResult to PromptMessageMultipart objects with error handling.
|
113
|
+
This method safely handles None values and empty results.
|
114
|
+
|
115
|
+
Args:
|
116
|
+
result: GetPromptResult from MCP server or None
|
117
|
+
|
118
|
+
Returns:
|
119
|
+
List of PromptMessageMultipart objects or empty list if result is None/empty
|
120
|
+
"""
|
121
|
+
if not result or not result.messages:
|
122
|
+
return []
|
123
|
+
return cls.to_multipart(result.messages)
|
mcp_agent/mcp/prompt_render.py
CHANGED
@@ -6,14 +6,14 @@ from typing import List
|
|
6
6
|
|
7
7
|
from mcp.types import BlobResourceContents, TextResourceContents
|
8
8
|
|
9
|
-
from mcp_agent.mcp.
|
10
|
-
from mcp_agent.mcp.prompts.prompt_helpers import (
|
9
|
+
from mcp_agent.mcp.helpers.content_helpers import (
|
11
10
|
get_resource_uri,
|
12
11
|
get_text,
|
13
12
|
is_image_content,
|
14
13
|
is_resource_content,
|
15
14
|
is_text_content,
|
16
15
|
)
|
16
|
+
from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
|
17
17
|
|
18
18
|
|
19
19
|
def render_multipart_message(message: PromptMessageMultipart) -> str:
|
@@ -34,36 +34,39 @@ def render_multipart_message(message: PromptMessageMultipart) -> str:
|
|
34
34
|
for content in message.content:
|
35
35
|
if is_text_content(content):
|
36
36
|
# Handle text content
|
37
|
-
|
38
|
-
|
37
|
+
text = get_text(content)
|
38
|
+
if text:
|
39
|
+
rendered_parts.append(text)
|
39
40
|
|
40
41
|
elif is_image_content(content):
|
41
42
|
# Format details about the image
|
42
|
-
|
43
|
-
data_size = len(
|
44
|
-
|
43
|
+
image_data = getattr(content, "data", "")
|
44
|
+
data_size = len(image_data) if image_data else 0
|
45
|
+
mime_type = getattr(content, "mimeType", "unknown")
|
46
|
+
image_info = f"[IMAGE: {mime_type}, {data_size} bytes]"
|
45
47
|
rendered_parts.append(image_info)
|
46
48
|
|
47
49
|
elif is_resource_content(content):
|
48
50
|
# Handle embedded resources
|
49
|
-
|
50
|
-
|
51
|
+
uri = get_resource_uri(content)
|
52
|
+
resource = getattr(content, "resource", None)
|
51
53
|
|
52
|
-
if isinstance(resource
|
54
|
+
if resource and isinstance(resource, TextResourceContents):
|
53
55
|
# Handle text resources
|
54
|
-
text = resource.
|
56
|
+
text = resource.text
|
55
57
|
text_length = len(text)
|
56
|
-
mime_type = resource
|
58
|
+
mime_type = getattr(resource, "mimeType", "text/plain")
|
57
59
|
|
58
60
|
# Preview with truncation for long content
|
59
61
|
preview = text[:300] + ("..." if text_length > 300 else "")
|
60
62
|
resource_info = f"[EMBEDDED TEXT RESOURCE: {mime_type}, {uri}, {text_length} chars]\n{preview}"
|
61
63
|
rendered_parts.append(resource_info)
|
62
64
|
|
63
|
-
elif isinstance(resource
|
65
|
+
elif resource and isinstance(resource, BlobResourceContents):
|
64
66
|
# Handle blob resources (binary data)
|
65
|
-
|
66
|
-
|
67
|
+
blob = getattr(resource, "blob", "")
|
68
|
+
blob_length = len(blob) if blob else 0
|
69
|
+
mime_type = getattr(resource, "mimeType", "application/octet-stream")
|
67
70
|
|
68
71
|
resource_info = f"[EMBEDDED BLOB RESOURCE: {mime_type}, {uri}, {blob_length} bytes]"
|
69
72
|
rendered_parts.append(resource_info)
|
@@ -110,6 +110,48 @@ def load_messages_from_json_file(file_path: str) -> List[PromptMessageMultipart]
|
|
110
110
|
return json_to_multipart_messages(json_str)
|
111
111
|
|
112
112
|
|
113
|
+
def save_messages_to_file(messages: List[PromptMessageMultipart], file_path: str) -> None:
|
114
|
+
"""
|
115
|
+
Save PromptMessageMultipart objects to a file, with format determined by file extension.
|
116
|
+
|
117
|
+
Uses JSON format for .json files and delimited text format for other extensions.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
messages: List of PromptMessageMultipart objects
|
121
|
+
file_path: Path to save the file
|
122
|
+
"""
|
123
|
+
path_str = str(file_path).lower()
|
124
|
+
|
125
|
+
if path_str.endswith(".json"):
|
126
|
+
# Use JSON format for .json files (MCP SDK compatible format)
|
127
|
+
save_messages_to_json_file(messages, file_path)
|
128
|
+
else:
|
129
|
+
# Use delimited text format for other extensions
|
130
|
+
save_messages_to_delimited_file(messages, file_path)
|
131
|
+
|
132
|
+
|
133
|
+
def load_messages_from_file(file_path: str) -> List[PromptMessageMultipart]:
|
134
|
+
"""
|
135
|
+
Load PromptMessageMultipart objects from a file, with format determined by file extension.
|
136
|
+
|
137
|
+
Uses JSON format for .json files and delimited text format for other extensions.
|
138
|
+
|
139
|
+
Args:
|
140
|
+
file_path: Path to the file
|
141
|
+
|
142
|
+
Returns:
|
143
|
+
List of PromptMessageMultipart objects
|
144
|
+
"""
|
145
|
+
path_str = str(file_path).lower()
|
146
|
+
|
147
|
+
if path_str.endswith(".json"):
|
148
|
+
# Use JSON format for .json files (MCP SDK compatible format)
|
149
|
+
return load_messages_from_json_file(file_path)
|
150
|
+
else:
|
151
|
+
# Use delimited text format for other extensions
|
152
|
+
return load_messages_from_delimited_file(file_path)
|
153
|
+
|
154
|
+
|
113
155
|
# -------------------------------------------------------------------------
|
114
156
|
# Delimited Text Format Functions
|
115
157
|
# -------------------------------------------------------------------------
|
@@ -8,112 +8,22 @@ without repetitive type checking.
|
|
8
8
|
from typing import List, Optional, Union, cast
|
9
9
|
|
10
10
|
from mcp.types import (
|
11
|
-
BlobResourceContents,
|
12
11
|
EmbeddedResource,
|
13
|
-
ImageContent,
|
14
12
|
PromptMessage,
|
15
13
|
TextContent,
|
16
|
-
TextResourceContents,
|
17
14
|
)
|
18
15
|
|
19
|
-
from mcp_agent.mcp.
|
16
|
+
from mcp_agent.mcp.helpers.content_helpers import get_image_data, get_text
|
20
17
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
The text content as a string or None if not a text content
|
31
|
-
"""
|
32
|
-
if isinstance(content, TextContent):
|
33
|
-
return content.text
|
34
|
-
|
35
|
-
if isinstance(content, EmbeddedResource):
|
36
|
-
if isinstance(content.resource, TextResourceContents):
|
37
|
-
return content.resource.text
|
38
|
-
|
39
|
-
return None
|
40
|
-
|
41
|
-
|
42
|
-
def get_image_data(content: Union[TextContent, ImageContent, EmbeddedResource]) -> Optional[str]:
|
43
|
-
"""
|
44
|
-
Extract image data from a content object if available.
|
45
|
-
|
46
|
-
Args:
|
47
|
-
content: A content object (TextContent, ImageContent, or EmbeddedResource)
|
48
|
-
|
49
|
-
Returns:
|
50
|
-
The image data as a base64 string or None if not an image content
|
51
|
-
"""
|
52
|
-
if isinstance(content, ImageContent):
|
53
|
-
return content.data
|
54
|
-
|
55
|
-
if isinstance(content, EmbeddedResource):
|
56
|
-
if isinstance(content.resource, BlobResourceContents):
|
57
|
-
# This assumes the blob might be an image, which isn't always true
|
58
|
-
# Consider checking the mimeType if needed
|
59
|
-
return content.resource.blob
|
60
|
-
|
61
|
-
return None
|
62
|
-
|
63
|
-
|
64
|
-
def get_resource_uri(content: Union[TextContent, ImageContent, EmbeddedResource]) -> Optional[str]:
|
65
|
-
"""
|
66
|
-
Extract resource URI from an EmbeddedResource if available.
|
67
|
-
|
68
|
-
Args:
|
69
|
-
content: A content object (TextContent, ImageContent, or EmbeddedResource)
|
70
|
-
|
71
|
-
Returns:
|
72
|
-
The resource URI as a string or None if not an embedded resource
|
73
|
-
"""
|
74
|
-
if isinstance(content, EmbeddedResource):
|
75
|
-
return str(content.resource.uri)
|
76
|
-
|
77
|
-
return None
|
78
|
-
|
79
|
-
|
80
|
-
def is_text_content(content: Union[TextContent, ImageContent, EmbeddedResource]) -> bool:
|
81
|
-
"""
|
82
|
-
Check if the content is text content.
|
83
|
-
|
84
|
-
Args:
|
85
|
-
content: A content object (TextContent, ImageContent, or EmbeddedResource)
|
86
|
-
|
87
|
-
Returns:
|
88
|
-
True if the content is TextContent, False otherwise
|
89
|
-
"""
|
90
|
-
return isinstance(content, TextContent)
|
91
|
-
|
92
|
-
|
93
|
-
def is_image_content(content: Union[TextContent, ImageContent, EmbeddedResource]) -> bool:
|
94
|
-
"""
|
95
|
-
Check if the content is image content.
|
96
|
-
|
97
|
-
Args:
|
98
|
-
content: A content object (TextContent, ImageContent, or EmbeddedResource)
|
99
|
-
|
100
|
-
Returns:
|
101
|
-
True if the content is ImageContent, False otherwise
|
102
|
-
"""
|
103
|
-
return isinstance(content, ImageContent)
|
104
|
-
|
105
|
-
|
106
|
-
def is_resource_content(content: Union[TextContent, ImageContent, EmbeddedResource]) -> bool:
|
107
|
-
"""
|
108
|
-
Check if the content is an embedded resource.
|
109
|
-
|
110
|
-
Args:
|
111
|
-
content: A content object (TextContent, ImageContent, or EmbeddedResource)
|
112
|
-
|
113
|
-
Returns:
|
114
|
-
True if the content is EmbeddedResource, False otherwise
|
115
|
-
"""
|
116
|
-
return isinstance(content, EmbeddedResource)
|
18
|
+
# Forward reference for PromptMessageMultipart, actual import happens at runtime
|
19
|
+
PromptMessageMultipartType = Union[object] # Will be replaced with actual type
|
20
|
+
try:
|
21
|
+
from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
|
22
|
+
PromptMessageMultipartType = PromptMessageMultipart
|
23
|
+
except ImportError:
|
24
|
+
# During initialization, there might be a circular import.
|
25
|
+
# We'll handle this gracefully.
|
26
|
+
pass
|
117
27
|
|
118
28
|
|
119
29
|
class MessageContent:
|
@@ -123,7 +33,7 @@ class MessageContent:
|
|
123
33
|
"""
|
124
34
|
|
125
35
|
@staticmethod
|
126
|
-
def get_all_text(message: Union[PromptMessage, PromptMessageMultipart]) -> List[str]:
|
36
|
+
def get_all_text(message: Union[PromptMessage, "PromptMessageMultipart"]) -> List[str]:
|
127
37
|
"""
|
128
38
|
Extract all text content from a message.
|
129
39
|
|
@@ -147,7 +57,7 @@ class MessageContent:
|
|
147
57
|
|
148
58
|
@staticmethod
|
149
59
|
def join_text(
|
150
|
-
message: Union[PromptMessage, PromptMessageMultipart], separator: str = "\n\n"
|
60
|
+
message: Union[PromptMessage, "PromptMessageMultipart"], separator: str = "\n\n"
|
151
61
|
) -> str:
|
152
62
|
"""
|
153
63
|
Join all text content in a message with a separator.
|
@@ -162,7 +72,7 @@ class MessageContent:
|
|
162
72
|
return separator.join(MessageContent.get_all_text(message))
|
163
73
|
|
164
74
|
@staticmethod
|
165
|
-
def get_first_text(message: Union[PromptMessage, PromptMessageMultipart]) -> Optional[str]:
|
75
|
+
def get_first_text(message: Union[PromptMessage, "PromptMessageMultipart"]) -> Optional[str]:
|
166
76
|
"""
|
167
77
|
Get the first available text content from a message.
|
168
78
|
|
@@ -183,7 +93,7 @@ class MessageContent:
|
|
183
93
|
return None
|
184
94
|
|
185
95
|
@staticmethod
|
186
|
-
def has_text_at_first_position(message: Union[PromptMessage, PromptMessageMultipart]) -> bool:
|
96
|
+
def has_text_at_first_position(message: Union[PromptMessage, "PromptMessageMultipart"]) -> bool:
|
187
97
|
"""
|
188
98
|
Check if a message has a TextContent at the first position.
|
189
99
|
This is a common case when dealing with messages that start with text.
|
@@ -202,7 +112,7 @@ class MessageContent:
|
|
202
112
|
|
203
113
|
@staticmethod
|
204
114
|
def get_text_at_first_position(
|
205
|
-
message: Union[PromptMessage, PromptMessageMultipart],
|
115
|
+
message: Union[PromptMessage, "PromptMessageMultipart"],
|
206
116
|
) -> Optional[str]:
|
207
117
|
"""
|
208
118
|
Get the text from the first position of a message if it's TextContent.
|
@@ -224,7 +134,7 @@ class MessageContent:
|
|
224
134
|
return cast("TextContent", message.content[0]).text
|
225
135
|
|
226
136
|
@staticmethod
|
227
|
-
def get_all_images(message: Union[PromptMessage, PromptMessageMultipart]) -> List[str]:
|
137
|
+
def get_all_images(message: Union[PromptMessage, "PromptMessageMultipart"]) -> List[str]:
|
228
138
|
"""
|
229
139
|
Extract all image data from a message.
|
230
140
|
|
@@ -247,7 +157,7 @@ class MessageContent:
|
|
247
157
|
return result
|
248
158
|
|
249
159
|
@staticmethod
|
250
|
-
def get_first_image(message: Union[PromptMessage, PromptMessageMultipart]) -> Optional[str]:
|
160
|
+
def get_first_image(message: Union[PromptMessage, "PromptMessageMultipart"]) -> Optional[str]:
|
251
161
|
"""
|
252
162
|
Get the first available image data from a message.
|
253
163
|
|
@@ -269,7 +179,7 @@ class MessageContent:
|
|
269
179
|
|
270
180
|
@staticmethod
|
271
181
|
def get_all_resources(
|
272
|
-
message: Union[PromptMessage, PromptMessageMultipart],
|
182
|
+
message: Union[PromptMessage, "PromptMessageMultipart"],
|
273
183
|
) -> List[EmbeddedResource]:
|
274
184
|
"""
|
275
185
|
Extract all embedded resources from a message.
|
@@ -288,7 +198,7 @@ class MessageContent:
|
|
288
198
|
return [content for content in message.content if isinstance(content, EmbeddedResource)]
|
289
199
|
|
290
200
|
@staticmethod
|
291
|
-
def has_text(message: Union[PromptMessage, PromptMessageMultipart]) -> bool:
|
201
|
+
def has_text(message: Union[PromptMessage, "PromptMessageMultipart"]) -> bool:
|
292
202
|
"""
|
293
203
|
Check if the message has any text content.
|
294
204
|
|
@@ -301,7 +211,7 @@ class MessageContent:
|
|
301
211
|
return len(MessageContent.get_all_text(message)) > 0
|
302
212
|
|
303
213
|
@staticmethod
|
304
|
-
def has_images(message: Union[PromptMessage, PromptMessageMultipart]) -> bool:
|
214
|
+
def has_images(message: Union[PromptMessage, "PromptMessageMultipart"]) -> bool:
|
305
215
|
"""
|
306
216
|
Check if the message has any image content.
|
307
217
|
|
@@ -314,7 +224,7 @@ class MessageContent:
|
|
314
224
|
return len(MessageContent.get_all_images(message)) > 0
|
315
225
|
|
316
226
|
@staticmethod
|
317
|
-
def has_resources(message: Union[PromptMessage, PromptMessageMultipart]) -> bool:
|
227
|
+
def has_resources(message: Union[PromptMessage, "PromptMessageMultipart"]) -> bool:
|
318
228
|
"""
|
319
229
|
Check if the message has any embedded resources.
|
320
230
|
|
@@ -324,4 +234,4 @@ class MessageContent:
|
|
324
234
|
Returns:
|
325
235
|
True if the message has embedded resources, False otherwise
|
326
236
|
"""
|
327
|
-
return len(MessageContent.get_all_resources(message)) > 0
|
237
|
+
return len(MessageContent.get_all_resources(message)) > 0
|
@@ -101,9 +101,57 @@ def create_resource_message(
|
|
101
101
|
|
102
102
|
|
103
103
|
def load_prompt(file: Path) -> List[PromptMessage]:
|
104
|
-
|
105
|
-
return
|
104
|
+
"""
|
105
|
+
Load a prompt from a file and return as PromptMessage objects.
|
106
|
+
|
107
|
+
The loader uses file extension to determine the format:
|
108
|
+
- .json files are loaded as MCP SDK compatible JSON format
|
109
|
+
- All other files are loaded using the template-based delimited format
|
110
|
+
|
111
|
+
Args:
|
112
|
+
file: Path to the prompt file
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
List of PromptMessage objects
|
116
|
+
"""
|
117
|
+
file_str = str(file).lower()
|
118
|
+
|
119
|
+
if file_str.endswith(".json"):
|
120
|
+
# JSON format (MCP SDK compatible)
|
121
|
+
from mcp_agent.mcp.prompt_serialization import load_messages_from_json_file
|
122
|
+
|
123
|
+
# Load multipart messages and convert to flat messages
|
124
|
+
multipart_messages = load_messages_from_json_file(str(file))
|
125
|
+
messages = []
|
126
|
+
for mp in multipart_messages:
|
127
|
+
messages.extend(mp.from_multipart())
|
128
|
+
return messages
|
129
|
+
else:
|
130
|
+
# Template-based format (delimited text)
|
131
|
+
template: PromptTemplate = PromptTemplateLoader().load_from_file(file)
|
132
|
+
return create_messages_with_resources(template.content_sections, [file])
|
106
133
|
|
107
134
|
|
108
135
|
def load_prompt_multipart(file: Path) -> List[PromptMessageMultipart]:
|
109
|
-
|
136
|
+
"""
|
137
|
+
Load a prompt from a file and return as PromptMessageMultipart objects.
|
138
|
+
|
139
|
+
The loader uses file extension to determine the format:
|
140
|
+
- .json files are loaded as MCP SDK compatible JSON format
|
141
|
+
- All other files are loaded using the template-based delimited format
|
142
|
+
|
143
|
+
Args:
|
144
|
+
file: Path to the prompt file
|
145
|
+
|
146
|
+
Returns:
|
147
|
+
List of PromptMessageMultipart objects
|
148
|
+
"""
|
149
|
+
file_str = str(file).lower()
|
150
|
+
|
151
|
+
if file_str.endswith(".json"):
|
152
|
+
# JSON format (MCP SDK compatible)
|
153
|
+
from mcp_agent.mcp.prompt_serialization import load_messages_from_json_file
|
154
|
+
return load_messages_from_json_file(str(file))
|
155
|
+
else:
|
156
|
+
# Template-based format (delimited text)
|
157
|
+
return PromptMessageMultipart.to_multipart(load_prompt(file))
|