fast-agent-mcp 0.4.7__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/__init__.py +183 -0
- fast_agent/acp/__init__.py +19 -0
- fast_agent/acp/acp_aware_mixin.py +304 -0
- fast_agent/acp/acp_context.py +437 -0
- fast_agent/acp/content_conversion.py +136 -0
- fast_agent/acp/filesystem_runtime.py +427 -0
- fast_agent/acp/permission_store.py +269 -0
- fast_agent/acp/server/__init__.py +5 -0
- fast_agent/acp/server/agent_acp_server.py +1472 -0
- fast_agent/acp/slash_commands.py +1050 -0
- fast_agent/acp/terminal_runtime.py +408 -0
- fast_agent/acp/tool_permission_adapter.py +125 -0
- fast_agent/acp/tool_permissions.py +474 -0
- fast_agent/acp/tool_progress.py +814 -0
- fast_agent/agents/__init__.py +85 -0
- fast_agent/agents/agent_types.py +64 -0
- fast_agent/agents/llm_agent.py +350 -0
- fast_agent/agents/llm_decorator.py +1139 -0
- fast_agent/agents/mcp_agent.py +1337 -0
- fast_agent/agents/tool_agent.py +271 -0
- fast_agent/agents/workflow/agents_as_tools_agent.py +849 -0
- fast_agent/agents/workflow/chain_agent.py +212 -0
- fast_agent/agents/workflow/evaluator_optimizer.py +380 -0
- fast_agent/agents/workflow/iterative_planner.py +652 -0
- fast_agent/agents/workflow/maker_agent.py +379 -0
- fast_agent/agents/workflow/orchestrator_models.py +218 -0
- fast_agent/agents/workflow/orchestrator_prompts.py +248 -0
- fast_agent/agents/workflow/parallel_agent.py +250 -0
- fast_agent/agents/workflow/router_agent.py +353 -0
- fast_agent/cli/__init__.py +0 -0
- fast_agent/cli/__main__.py +73 -0
- fast_agent/cli/commands/acp.py +159 -0
- fast_agent/cli/commands/auth.py +404 -0
- fast_agent/cli/commands/check_config.py +783 -0
- fast_agent/cli/commands/go.py +514 -0
- fast_agent/cli/commands/quickstart.py +557 -0
- fast_agent/cli/commands/serve.py +143 -0
- fast_agent/cli/commands/server_helpers.py +114 -0
- fast_agent/cli/commands/setup.py +174 -0
- fast_agent/cli/commands/url_parser.py +190 -0
- fast_agent/cli/constants.py +40 -0
- fast_agent/cli/main.py +115 -0
- fast_agent/cli/terminal.py +24 -0
- fast_agent/config.py +798 -0
- fast_agent/constants.py +41 -0
- fast_agent/context.py +279 -0
- fast_agent/context_dependent.py +50 -0
- fast_agent/core/__init__.py +92 -0
- fast_agent/core/agent_app.py +448 -0
- fast_agent/core/core_app.py +137 -0
- fast_agent/core/direct_decorators.py +784 -0
- fast_agent/core/direct_factory.py +620 -0
- fast_agent/core/error_handling.py +27 -0
- fast_agent/core/exceptions.py +90 -0
- fast_agent/core/executor/__init__.py +0 -0
- fast_agent/core/executor/executor.py +280 -0
- fast_agent/core/executor/task_registry.py +32 -0
- fast_agent/core/executor/workflow_signal.py +324 -0
- fast_agent/core/fastagent.py +1186 -0
- fast_agent/core/logging/__init__.py +5 -0
- fast_agent/core/logging/events.py +138 -0
- fast_agent/core/logging/json_serializer.py +164 -0
- fast_agent/core/logging/listeners.py +309 -0
- fast_agent/core/logging/logger.py +278 -0
- fast_agent/core/logging/transport.py +481 -0
- fast_agent/core/prompt.py +9 -0
- fast_agent/core/prompt_templates.py +183 -0
- fast_agent/core/validation.py +326 -0
- fast_agent/event_progress.py +62 -0
- fast_agent/history/history_exporter.py +49 -0
- fast_agent/human_input/__init__.py +47 -0
- fast_agent/human_input/elicitation_handler.py +123 -0
- fast_agent/human_input/elicitation_state.py +33 -0
- fast_agent/human_input/form_elements.py +59 -0
- fast_agent/human_input/form_fields.py +256 -0
- fast_agent/human_input/simple_form.py +113 -0
- fast_agent/human_input/types.py +40 -0
- fast_agent/interfaces.py +310 -0
- fast_agent/llm/__init__.py +9 -0
- fast_agent/llm/cancellation.py +22 -0
- fast_agent/llm/fastagent_llm.py +931 -0
- fast_agent/llm/internal/passthrough.py +161 -0
- fast_agent/llm/internal/playback.py +129 -0
- fast_agent/llm/internal/silent.py +41 -0
- fast_agent/llm/internal/slow.py +38 -0
- fast_agent/llm/memory.py +275 -0
- fast_agent/llm/model_database.py +490 -0
- fast_agent/llm/model_factory.py +388 -0
- fast_agent/llm/model_info.py +102 -0
- fast_agent/llm/prompt_utils.py +155 -0
- fast_agent/llm/provider/anthropic/anthropic_utils.py +84 -0
- fast_agent/llm/provider/anthropic/cache_planner.py +56 -0
- fast_agent/llm/provider/anthropic/llm_anthropic.py +796 -0
- fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +462 -0
- fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
- fast_agent/llm/provider/bedrock/llm_bedrock.py +2207 -0
- fast_agent/llm/provider/bedrock/multipart_converter_bedrock.py +84 -0
- fast_agent/llm/provider/google/google_converter.py +466 -0
- fast_agent/llm/provider/google/llm_google_native.py +681 -0
- fast_agent/llm/provider/openai/llm_aliyun.py +31 -0
- fast_agent/llm/provider/openai/llm_azure.py +143 -0
- fast_agent/llm/provider/openai/llm_deepseek.py +76 -0
- fast_agent/llm/provider/openai/llm_generic.py +35 -0
- fast_agent/llm/provider/openai/llm_google_oai.py +32 -0
- fast_agent/llm/provider/openai/llm_groq.py +42 -0
- fast_agent/llm/provider/openai/llm_huggingface.py +85 -0
- fast_agent/llm/provider/openai/llm_openai.py +1195 -0
- fast_agent/llm/provider/openai/llm_openai_compatible.py +138 -0
- fast_agent/llm/provider/openai/llm_openrouter.py +45 -0
- fast_agent/llm/provider/openai/llm_tensorzero_openai.py +128 -0
- fast_agent/llm/provider/openai/llm_xai.py +38 -0
- fast_agent/llm/provider/openai/multipart_converter_openai.py +561 -0
- fast_agent/llm/provider/openai/openai_multipart.py +169 -0
- fast_agent/llm/provider/openai/openai_utils.py +67 -0
- fast_agent/llm/provider/openai/responses.py +133 -0
- fast_agent/llm/provider_key_manager.py +139 -0
- fast_agent/llm/provider_types.py +34 -0
- fast_agent/llm/request_params.py +61 -0
- fast_agent/llm/sampling_converter.py +98 -0
- fast_agent/llm/stream_types.py +9 -0
- fast_agent/llm/usage_tracking.py +445 -0
- fast_agent/mcp/__init__.py +56 -0
- fast_agent/mcp/common.py +26 -0
- fast_agent/mcp/elicitation_factory.py +84 -0
- fast_agent/mcp/elicitation_handlers.py +164 -0
- fast_agent/mcp/gen_client.py +83 -0
- fast_agent/mcp/helpers/__init__.py +36 -0
- fast_agent/mcp/helpers/content_helpers.py +352 -0
- fast_agent/mcp/helpers/server_config_helpers.py +25 -0
- fast_agent/mcp/hf_auth.py +147 -0
- fast_agent/mcp/interfaces.py +92 -0
- fast_agent/mcp/logger_textio.py +108 -0
- fast_agent/mcp/mcp_agent_client_session.py +411 -0
- fast_agent/mcp/mcp_aggregator.py +2175 -0
- fast_agent/mcp/mcp_connection_manager.py +723 -0
- fast_agent/mcp/mcp_content.py +262 -0
- fast_agent/mcp/mime_utils.py +108 -0
- fast_agent/mcp/oauth_client.py +509 -0
- fast_agent/mcp/prompt.py +159 -0
- fast_agent/mcp/prompt_message_extended.py +155 -0
- fast_agent/mcp/prompt_render.py +84 -0
- fast_agent/mcp/prompt_serialization.py +580 -0
- fast_agent/mcp/prompts/__init__.py +0 -0
- fast_agent/mcp/prompts/__main__.py +7 -0
- fast_agent/mcp/prompts/prompt_constants.py +18 -0
- fast_agent/mcp/prompts/prompt_helpers.py +238 -0
- fast_agent/mcp/prompts/prompt_load.py +186 -0
- fast_agent/mcp/prompts/prompt_server.py +552 -0
- fast_agent/mcp/prompts/prompt_template.py +438 -0
- fast_agent/mcp/resource_utils.py +215 -0
- fast_agent/mcp/sampling.py +200 -0
- fast_agent/mcp/server/__init__.py +4 -0
- fast_agent/mcp/server/agent_server.py +613 -0
- fast_agent/mcp/skybridge.py +44 -0
- fast_agent/mcp/sse_tracking.py +287 -0
- fast_agent/mcp/stdio_tracking_simple.py +59 -0
- fast_agent/mcp/streamable_http_tracking.py +309 -0
- fast_agent/mcp/tool_execution_handler.py +137 -0
- fast_agent/mcp/tool_permission_handler.py +88 -0
- fast_agent/mcp/transport_tracking.py +634 -0
- fast_agent/mcp/types.py +24 -0
- fast_agent/mcp/ui_agent.py +48 -0
- fast_agent/mcp/ui_mixin.py +209 -0
- fast_agent/mcp_server_registry.py +89 -0
- fast_agent/py.typed +0 -0
- fast_agent/resources/examples/data-analysis/analysis-campaign.py +189 -0
- fast_agent/resources/examples/data-analysis/analysis.py +68 -0
- fast_agent/resources/examples/data-analysis/fastagent.config.yaml +41 -0
- fast_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +1471 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +297 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
- fast_agent/resources/examples/mcp/elicitations/forms_demo.py +107 -0
- fast_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
- fast_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
- fast_agent/resources/examples/mcp/elicitations/tool_call.py +21 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_one.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_two.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +27 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +15 -0
- fast_agent/resources/examples/researcher/fastagent.config.yaml +61 -0
- fast_agent/resources/examples/researcher/researcher-eval.py +53 -0
- fast_agent/resources/examples/researcher/researcher-imp.py +189 -0
- fast_agent/resources/examples/researcher/researcher.py +36 -0
- fast_agent/resources/examples/tensorzero/.env.sample +2 -0
- fast_agent/resources/examples/tensorzero/Makefile +31 -0
- fast_agent/resources/examples/tensorzero/README.md +56 -0
- fast_agent/resources/examples/tensorzero/agent.py +35 -0
- fast_agent/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/crab.png +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
- fast_agent/resources/examples/tensorzero/docker-compose.yml +105 -0
- fast_agent/resources/examples/tensorzero/fastagent.config.yaml +19 -0
- fast_agent/resources/examples/tensorzero/image_demo.py +67 -0
- fast_agent/resources/examples/tensorzero/mcp_server/Dockerfile +25 -0
- fast_agent/resources/examples/tensorzero/mcp_server/entrypoint.sh +35 -0
- fast_agent/resources/examples/tensorzero/mcp_server/mcp_server.py +31 -0
- fast_agent/resources/examples/tensorzero/mcp_server/pyproject.toml +11 -0
- fast_agent/resources/examples/tensorzero/simple_agent.py +25 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_schema.json +29 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +11 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +35 -0
- fast_agent/resources/examples/workflows/agents_as_tools_extended.py +73 -0
- fast_agent/resources/examples/workflows/agents_as_tools_simple.py +50 -0
- fast_agent/resources/examples/workflows/chaining.py +37 -0
- fast_agent/resources/examples/workflows/evaluator.py +77 -0
- fast_agent/resources/examples/workflows/fastagent.config.yaml +26 -0
- fast_agent/resources/examples/workflows/graded_report.md +89 -0
- fast_agent/resources/examples/workflows/human_input.py +28 -0
- fast_agent/resources/examples/workflows/maker.py +156 -0
- fast_agent/resources/examples/workflows/orchestrator.py +70 -0
- fast_agent/resources/examples/workflows/parallel.py +56 -0
- fast_agent/resources/examples/workflows/router.py +69 -0
- fast_agent/resources/examples/workflows/short_story.md +13 -0
- fast_agent/resources/examples/workflows/short_story.txt +19 -0
- fast_agent/resources/setup/.gitignore +30 -0
- fast_agent/resources/setup/agent.py +28 -0
- fast_agent/resources/setup/fastagent.config.yaml +65 -0
- fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
- fast_agent/resources/setup/pyproject.toml.tmpl +23 -0
- fast_agent/skills/__init__.py +9 -0
- fast_agent/skills/registry.py +235 -0
- fast_agent/tools/elicitation.py +369 -0
- fast_agent/tools/shell_runtime.py +402 -0
- fast_agent/types/__init__.py +59 -0
- fast_agent/types/conversation_summary.py +294 -0
- fast_agent/types/llm_stop_reason.py +78 -0
- fast_agent/types/message_search.py +249 -0
- fast_agent/ui/__init__.py +38 -0
- fast_agent/ui/console.py +59 -0
- fast_agent/ui/console_display.py +1080 -0
- fast_agent/ui/elicitation_form.py +946 -0
- fast_agent/ui/elicitation_style.py +59 -0
- fast_agent/ui/enhanced_prompt.py +1400 -0
- fast_agent/ui/history_display.py +734 -0
- fast_agent/ui/interactive_prompt.py +1199 -0
- fast_agent/ui/markdown_helpers.py +104 -0
- fast_agent/ui/markdown_truncator.py +1004 -0
- fast_agent/ui/mcp_display.py +857 -0
- fast_agent/ui/mcp_ui_utils.py +235 -0
- fast_agent/ui/mermaid_utils.py +169 -0
- fast_agent/ui/message_primitives.py +50 -0
- fast_agent/ui/notification_tracker.py +205 -0
- fast_agent/ui/plain_text_truncator.py +68 -0
- fast_agent/ui/progress_display.py +10 -0
- fast_agent/ui/rich_progress.py +195 -0
- fast_agent/ui/streaming.py +774 -0
- fast_agent/ui/streaming_buffer.py +449 -0
- fast_agent/ui/tool_display.py +422 -0
- fast_agent/ui/usage_display.py +204 -0
- fast_agent/utils/__init__.py +5 -0
- fast_agent/utils/reasoning_stream_parser.py +77 -0
- fast_agent/utils/time.py +22 -0
- fast_agent/workflow_telemetry.py +261 -0
- fast_agent_mcp-0.4.7.dist-info/METADATA +788 -0
- fast_agent_mcp-0.4.7.dist-info/RECORD +261 -0
- fast_agent_mcp-0.4.7.dist-info/WHEEL +4 -0
- fast_agent_mcp-0.4.7.dist-info/entry_points.txt +7 -0
- fast_agent_mcp-0.4.7.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helper functions for creating MCP content types with minimal code.
|
|
3
|
+
|
|
4
|
+
This module provides simple functions to create TextContent, ImageContent,
|
|
5
|
+
EmbeddedResource, and other MCP content types with minimal boilerplate.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import base64
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Literal, Union
|
|
11
|
+
|
|
12
|
+
from mcp.types import (
|
|
13
|
+
Annotations,
|
|
14
|
+
BlobResourceContents,
|
|
15
|
+
ContentBlock,
|
|
16
|
+
EmbeddedResource,
|
|
17
|
+
ImageContent,
|
|
18
|
+
ReadResourceResult,
|
|
19
|
+
ResourceContents,
|
|
20
|
+
TextContent,
|
|
21
|
+
TextResourceContents,
|
|
22
|
+
)
|
|
23
|
+
from pydantic import AnyUrl
|
|
24
|
+
|
|
25
|
+
from fast_agent.mcp.mime_utils import (
|
|
26
|
+
guess_mime_type,
|
|
27
|
+
is_binary_content,
|
|
28
|
+
is_image_mime_type,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def MCPText(
|
|
33
|
+
text: str,
|
|
34
|
+
role: Literal["user", "assistant"] = "user",
|
|
35
|
+
annotations: Annotations | None = None,
|
|
36
|
+
) -> dict:
|
|
37
|
+
"""
|
|
38
|
+
Create a message with text content.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
text: The text content
|
|
42
|
+
role: Role of the message, defaults to "user"
|
|
43
|
+
annotations: Optional annotations
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
A dictionary with role and content that can be used in a prompt
|
|
47
|
+
"""
|
|
48
|
+
return {
|
|
49
|
+
"role": role,
|
|
50
|
+
"content": TextContent(type="text", text=text, annotations=annotations),
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def MCPImage(
|
|
55
|
+
path: str | Path | None = None,
|
|
56
|
+
data: bytes | None = None,
|
|
57
|
+
mime_type: str | None = None,
|
|
58
|
+
role: Literal["user", "assistant"] = "user",
|
|
59
|
+
annotations: Annotations | None = None,
|
|
60
|
+
) -> dict:
|
|
61
|
+
"""
|
|
62
|
+
Create a message with image content.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
path: Path to the image file
|
|
66
|
+
data: Raw image data bytes (alternative to path)
|
|
67
|
+
mime_type: Optional mime type, will be guessed from path if not provided
|
|
68
|
+
role: Role of the message, defaults to "user"
|
|
69
|
+
annotations: Optional annotations
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
A dictionary with role and content that can be used in a prompt
|
|
73
|
+
"""
|
|
74
|
+
if path is None and data is None:
|
|
75
|
+
raise ValueError("Either path or data must be provided")
|
|
76
|
+
|
|
77
|
+
if path is not None and data is not None:
|
|
78
|
+
raise ValueError("Only one of path or data can be provided")
|
|
79
|
+
|
|
80
|
+
if path is not None:
|
|
81
|
+
path = Path(path)
|
|
82
|
+
if not mime_type:
|
|
83
|
+
mime_type = guess_mime_type(str(path))
|
|
84
|
+
with open(path, "rb") as f:
|
|
85
|
+
data = f.read()
|
|
86
|
+
|
|
87
|
+
if not mime_type:
|
|
88
|
+
mime_type = "image/png" # Default
|
|
89
|
+
|
|
90
|
+
b64_data = base64.b64encode(data).decode("ascii")
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
"role": role,
|
|
94
|
+
"content": ImageContent(
|
|
95
|
+
type="image", data=b64_data, mimeType=mime_type, annotations=annotations
|
|
96
|
+
),
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def MCPFile(
|
|
101
|
+
path: Union[str, Path],
|
|
102
|
+
mime_type: str | None = None,
|
|
103
|
+
role: Literal["user", "assistant"] = "user",
|
|
104
|
+
annotations: Annotations | None = None,
|
|
105
|
+
) -> dict:
|
|
106
|
+
"""
|
|
107
|
+
Create a message with an embedded resource from a file.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
path: Path to the resource file
|
|
111
|
+
mime_type: Optional mime type, will be guessed from path if not provided
|
|
112
|
+
role: Role of the message, defaults to "user"
|
|
113
|
+
annotations: Optional annotations
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
A dictionary with role and content that can be used in a prompt
|
|
117
|
+
"""
|
|
118
|
+
path = Path(path)
|
|
119
|
+
uri = f"file://{path.absolute()}"
|
|
120
|
+
|
|
121
|
+
if not mime_type:
|
|
122
|
+
mime_type = guess_mime_type(str(path))
|
|
123
|
+
|
|
124
|
+
# Determine if this is text or binary content
|
|
125
|
+
is_binary = is_binary_content(mime_type)
|
|
126
|
+
|
|
127
|
+
if is_binary:
|
|
128
|
+
# Read as binary
|
|
129
|
+
binary_data = path.read_bytes()
|
|
130
|
+
b64_data = base64.b64encode(binary_data).decode("ascii")
|
|
131
|
+
|
|
132
|
+
resource = BlobResourceContents(uri=AnyUrl(uri), blob=b64_data, mimeType=mime_type)
|
|
133
|
+
else:
|
|
134
|
+
# Read as text
|
|
135
|
+
try:
|
|
136
|
+
text_data = path.read_text(encoding="utf-8")
|
|
137
|
+
resource = TextResourceContents(uri=AnyUrl(uri), text=text_data, mimeType=mime_type)
|
|
138
|
+
except UnicodeDecodeError:
|
|
139
|
+
# Fallback to binary if text read fails
|
|
140
|
+
binary_data = path.read_bytes()
|
|
141
|
+
b64_data = base64.b64encode(binary_data).decode("ascii")
|
|
142
|
+
resource = BlobResourceContents(
|
|
143
|
+
uri=AnyUrl(uri), blob=b64_data, mimeType=mime_type or "application/octet-stream"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
"role": role,
|
|
148
|
+
"content": EmbeddedResource(type="resource", resource=resource, annotations=annotations),
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def MCPPrompt(
|
|
153
|
+
*content_items: Union[dict, str, Path, bytes, ContentBlock, ReadResourceResult],
|
|
154
|
+
role: Literal["user", "assistant"] = "user",
|
|
155
|
+
) -> list[dict]:
|
|
156
|
+
"""
|
|
157
|
+
Create one or more prompt messages with various content types.
|
|
158
|
+
|
|
159
|
+
This function intelligently creates different content types:
|
|
160
|
+
- Strings become TextContent
|
|
161
|
+
- File paths with image mime types become ImageContent
|
|
162
|
+
- File paths with text mime types or other mime types become EmbeddedResource
|
|
163
|
+
- Dicts with role and content are passed through unchanged
|
|
164
|
+
- Raw bytes become ImageContent
|
|
165
|
+
- TextContent objects are used directly
|
|
166
|
+
- ImageContent objects are used directly
|
|
167
|
+
- EmbeddedResource objects are used directly
|
|
168
|
+
- ResourceContent objects are wrapped in EmbeddedResource
|
|
169
|
+
- ReadResourceResult objects are expanded into multiple messages
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
*content_items: Content items of various types
|
|
173
|
+
role: Role for all items (user or assistant)
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
List of messages that can be used in a prompt
|
|
177
|
+
"""
|
|
178
|
+
result = []
|
|
179
|
+
|
|
180
|
+
for item in content_items:
|
|
181
|
+
if isinstance(item, dict) and "role" in item and "content" in item:
|
|
182
|
+
# Already a fully formed message
|
|
183
|
+
result.append(item)
|
|
184
|
+
elif isinstance(item, str):
|
|
185
|
+
# Simple text content
|
|
186
|
+
result.append(MCPText(item, role=role))
|
|
187
|
+
elif isinstance(item, Path):
|
|
188
|
+
# File path - determine the content type based on mime type
|
|
189
|
+
path_str = str(item)
|
|
190
|
+
mime_type = guess_mime_type(path_str)
|
|
191
|
+
|
|
192
|
+
if is_image_mime_type(mime_type):
|
|
193
|
+
# Image files (except SVG which is handled as text)
|
|
194
|
+
result.append(MCPImage(path=item, role=role))
|
|
195
|
+
else:
|
|
196
|
+
# All other file types (text documents, PDFs, SVGs, etc.)
|
|
197
|
+
result.append(MCPFile(path=item, role=role))
|
|
198
|
+
elif isinstance(item, bytes):
|
|
199
|
+
# Raw binary data, assume image
|
|
200
|
+
result.append(MCPImage(data=item, role=role))
|
|
201
|
+
elif isinstance(item, TextContent):
|
|
202
|
+
# Already a TextContent, wrap in a message
|
|
203
|
+
result.append({"role": role, "content": item})
|
|
204
|
+
elif isinstance(item, ImageContent):
|
|
205
|
+
# Already an ImageContent, wrap in a message
|
|
206
|
+
result.append({"role": role, "content": item})
|
|
207
|
+
elif isinstance(item, EmbeddedResource):
|
|
208
|
+
# Already an EmbeddedResource, wrap in a message
|
|
209
|
+
result.append({"role": role, "content": item})
|
|
210
|
+
elif hasattr(item, "type") and item.type == "resource" and hasattr(item, "resource"):
|
|
211
|
+
# Looks like an EmbeddedResource but may not be the exact class
|
|
212
|
+
result.append(
|
|
213
|
+
{"role": role, "content": EmbeddedResource(type="resource", resource=item.resource)}
|
|
214
|
+
)
|
|
215
|
+
elif isinstance(item, ResourceContents):
|
|
216
|
+
# It's a ResourceContents, wrap it in an EmbeddedResource
|
|
217
|
+
result.append(
|
|
218
|
+
{"role": role, "content": EmbeddedResource(type="resource", resource=item)}
|
|
219
|
+
)
|
|
220
|
+
elif isinstance(item, ReadResourceResult):
|
|
221
|
+
# It's a ReadResourceResult, convert each resource content
|
|
222
|
+
for resource_content in item.contents:
|
|
223
|
+
result.append(
|
|
224
|
+
{
|
|
225
|
+
"role": role,
|
|
226
|
+
"content": EmbeddedResource(type="resource", resource=resource_content),
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
else:
|
|
230
|
+
# Try to convert to string
|
|
231
|
+
result.append(MCPText(str(item), role=role))
|
|
232
|
+
|
|
233
|
+
return result
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def User(
|
|
237
|
+
*content_items: Union[dict, str, Path, bytes, ContentBlock, ReadResourceResult],
|
|
238
|
+
) -> list[dict]:
|
|
239
|
+
"""Create user message(s) with various content types."""
|
|
240
|
+
return MCPPrompt(*content_items, role="user")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def Assistant(
|
|
244
|
+
*content_items: Union[dict, str, Path, bytes, ContentBlock, ReadResourceResult],
|
|
245
|
+
) -> list[dict]:
|
|
246
|
+
"""Create assistant message(s) with various content types."""
|
|
247
|
+
return MCPPrompt(*content_items, role="assistant")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def create_message(content: Any, role: Literal["user", "assistant"] = "user") -> dict:
|
|
251
|
+
"""
|
|
252
|
+
Create a single prompt message from content of various types.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
content: Content of various types (str, Path, bytes, etc.)
|
|
256
|
+
role: Role of the message
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
A dictionary with role and content that can be used in a prompt
|
|
260
|
+
"""
|
|
261
|
+
messages = MCPPrompt(content, role=role)
|
|
262
|
+
return messages[0] if messages else {}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# mime_utils.py
|
|
2
|
+
|
|
3
|
+
import mimetypes
|
|
4
|
+
|
|
5
|
+
# Initialize mimetypes database
|
|
6
|
+
mimetypes.init()
|
|
7
|
+
|
|
8
|
+
# Extend with additional types that might be missing
|
|
9
|
+
mimetypes.add_type("text/x-python", ".py")
|
|
10
|
+
mimetypes.add_type("image/webp", ".webp")
|
|
11
|
+
|
|
12
|
+
# Known text-based MIME types not starting with "text/"
|
|
13
|
+
TEXT_MIME_TYPES = {
|
|
14
|
+
"application/json",
|
|
15
|
+
"application/javascript",
|
|
16
|
+
"application/xml",
|
|
17
|
+
"application/ld+json",
|
|
18
|
+
"application/xhtml+xml",
|
|
19
|
+
"application/x-httpd-php",
|
|
20
|
+
"application/x-sh",
|
|
21
|
+
"application/ecmascript",
|
|
22
|
+
"application/graphql",
|
|
23
|
+
"application/x-www-form-urlencoded",
|
|
24
|
+
"application/yaml",
|
|
25
|
+
"application/toml",
|
|
26
|
+
"application/x-python-code",
|
|
27
|
+
"application/vnd.api+json",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Common text-based MIME type patterns
|
|
31
|
+
TEXT_MIME_PATTERNS = ("+xml", "+json", "+yaml", "+text")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def guess_mime_type(file_path: str) -> str:
|
|
35
|
+
"""
|
|
36
|
+
Guess the MIME type of a file based on its extension.
|
|
37
|
+
"""
|
|
38
|
+
mime_type, _ = mimetypes.guess_type(file_path)
|
|
39
|
+
return mime_type or "application/octet-stream"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_text_mime_type(mime_type: str) -> bool:
|
|
43
|
+
"""Determine if a MIME type represents text content."""
|
|
44
|
+
if not mime_type:
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
# Standard text types
|
|
48
|
+
if mime_type.startswith("text/"):
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
# Known text types
|
|
52
|
+
if mime_type in TEXT_MIME_TYPES:
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
# Common text patterns
|
|
56
|
+
if any(mime_type.endswith(pattern) for pattern in TEXT_MIME_PATTERNS):
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def is_binary_content(mime_type: str) -> bool:
|
|
63
|
+
"""Check if content should be treated as binary."""
|
|
64
|
+
return not is_text_mime_type(mime_type)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def is_image_mime_type(mime_type: str) -> bool:
|
|
68
|
+
"""Check if a MIME type represents an image."""
|
|
69
|
+
return mime_type.startswith("image/") and mime_type != "image/svg+xml"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Common alias mapping and normalization helpers
|
|
73
|
+
_MIME_ALIASES = {
|
|
74
|
+
# Friendly or non-standard labels
|
|
75
|
+
"document/pdf": "application/pdf",
|
|
76
|
+
"image/jpg": "image/jpeg",
|
|
77
|
+
# Some providers sometimes return these variants
|
|
78
|
+
"application/x-pdf": "application/pdf",
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def normalize_mime_type(mime: str | None) -> str | None:
|
|
83
|
+
"""
|
|
84
|
+
Normalize a MIME-like string to a canonical MIME type.
|
|
85
|
+
|
|
86
|
+
- Lowercases and trims
|
|
87
|
+
- Resolves common aliases (e.g. image/jpg -> image/jpeg, document/pdf -> application/pdf)
|
|
88
|
+
- If input looks like a bare extension (e.g. "pdf", "png"), map via mimetypes
|
|
89
|
+
- Returns None for falsy inputs
|
|
90
|
+
"""
|
|
91
|
+
if not mime:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
m = mime.strip().lower()
|
|
95
|
+
|
|
96
|
+
# If it's an alias we know about
|
|
97
|
+
if m in _MIME_ALIASES:
|
|
98
|
+
return _MIME_ALIASES[m]
|
|
99
|
+
|
|
100
|
+
# If it already looks like a full MIME type
|
|
101
|
+
if "/" in m:
|
|
102
|
+
# image/jpg -> image/jpeg etc.
|
|
103
|
+
return _MIME_ALIASES.get(m, m)
|
|
104
|
+
|
|
105
|
+
# Treat as a bare file extension (e.g. "pdf", "png")
|
|
106
|
+
if not m.startswith("."):
|
|
107
|
+
m = "." + m
|
|
108
|
+
return mimetypes.types_map.get(m, None)
|