fast-agent-mcp 0.2.57__py3-none-any.whl → 0.3.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.
Potentially problematic release.
This version of fast-agent-mcp might be problematic. Click here for more details.
- fast_agent/__init__.py +127 -0
- fast_agent/agents/__init__.py +36 -0
- {mcp_agent/core → fast_agent/agents}/agent_types.py +2 -1
- fast_agent/agents/llm_agent.py +217 -0
- fast_agent/agents/llm_decorator.py +486 -0
- mcp_agent/agents/base_agent.py → fast_agent/agents/mcp_agent.py +377 -385
- fast_agent/agents/tool_agent.py +168 -0
- {mcp_agent → fast_agent}/agents/workflow/chain_agent.py +43 -33
- {mcp_agent → fast_agent}/agents/workflow/evaluator_optimizer.py +31 -35
- {mcp_agent → fast_agent}/agents/workflow/iterative_planner.py +56 -47
- {mcp_agent → fast_agent}/agents/workflow/orchestrator_models.py +4 -4
- {mcp_agent → fast_agent}/agents/workflow/parallel_agent.py +34 -41
- {mcp_agent → fast_agent}/agents/workflow/router_agent.py +54 -39
- {mcp_agent → fast_agent}/cli/__main__.py +5 -3
- {mcp_agent → fast_agent}/cli/commands/check_config.py +95 -66
- {mcp_agent → fast_agent}/cli/commands/go.py +20 -11
- {mcp_agent → fast_agent}/cli/commands/quickstart.py +4 -4
- {mcp_agent → fast_agent}/cli/commands/server_helpers.py +1 -1
- {mcp_agent → fast_agent}/cli/commands/setup.py +64 -134
- {mcp_agent → fast_agent}/cli/commands/url_parser.py +9 -8
- {mcp_agent → fast_agent}/cli/main.py +36 -16
- {mcp_agent → fast_agent}/cli/terminal.py +2 -2
- {mcp_agent → fast_agent}/config.py +13 -2
- fast_agent/constants.py +8 -0
- {mcp_agent → fast_agent}/context.py +24 -19
- {mcp_agent → fast_agent}/context_dependent.py +9 -5
- fast_agent/core/__init__.py +17 -0
- {mcp_agent → fast_agent}/core/agent_app.py +39 -36
- fast_agent/core/core_app.py +135 -0
- {mcp_agent → fast_agent}/core/direct_decorators.py +12 -26
- {mcp_agent → fast_agent}/core/direct_factory.py +95 -73
- {mcp_agent → fast_agent/core}/executor/executor.py +4 -5
- {mcp_agent → fast_agent}/core/fastagent.py +32 -32
- fast_agent/core/logging/__init__.py +5 -0
- {mcp_agent → fast_agent/core}/logging/events.py +3 -3
- {mcp_agent → fast_agent/core}/logging/json_serializer.py +1 -1
- {mcp_agent → fast_agent/core}/logging/listeners.py +85 -7
- {mcp_agent → fast_agent/core}/logging/logger.py +7 -7
- {mcp_agent → fast_agent/core}/logging/transport.py +10 -11
- fast_agent/core/prompt.py +9 -0
- {mcp_agent → fast_agent}/core/validation.py +4 -4
- fast_agent/event_progress.py +61 -0
- fast_agent/history/history_exporter.py +44 -0
- {mcp_agent → fast_agent}/human_input/__init__.py +9 -12
- {mcp_agent → fast_agent}/human_input/elicitation_handler.py +26 -8
- {mcp_agent → fast_agent}/human_input/elicitation_state.py +7 -7
- {mcp_agent → fast_agent}/human_input/simple_form.py +6 -4
- {mcp_agent → fast_agent}/human_input/types.py +1 -18
- fast_agent/interfaces.py +228 -0
- fast_agent/llm/__init__.py +9 -0
- mcp_agent/llm/augmented_llm.py → fast_agent/llm/fastagent_llm.py +128 -218
- fast_agent/llm/internal/passthrough.py +137 -0
- mcp_agent/llm/augmented_llm_playback.py → fast_agent/llm/internal/playback.py +29 -25
- mcp_agent/llm/augmented_llm_silent.py → fast_agent/llm/internal/silent.py +10 -17
- fast_agent/llm/internal/slow.py +38 -0
- {mcp_agent → fast_agent}/llm/memory.py +40 -30
- {mcp_agent → fast_agent}/llm/model_database.py +35 -2
- {mcp_agent → fast_agent}/llm/model_factory.py +103 -77
- fast_agent/llm/model_info.py +126 -0
- {mcp_agent/llm/providers → fast_agent/llm/provider/anthropic}/anthropic_utils.py +7 -7
- fast_agent/llm/provider/anthropic/llm_anthropic.py +603 -0
- {mcp_agent/llm/providers → fast_agent/llm/provider/anthropic}/multipart_converter_anthropic.py +79 -86
- fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
- fast_agent/llm/provider/bedrock/llm_bedrock.py +2192 -0
- {mcp_agent/llm/providers → fast_agent/llm/provider/google}/google_converter.py +66 -14
- fast_agent/llm/provider/google/llm_google_native.py +431 -0
- mcp_agent/llm/providers/augmented_llm_aliyun.py → fast_agent/llm/provider/openai/llm_aliyun.py +6 -7
- mcp_agent/llm/providers/augmented_llm_azure.py → fast_agent/llm/provider/openai/llm_azure.py +4 -4
- mcp_agent/llm/providers/augmented_llm_deepseek.py → fast_agent/llm/provider/openai/llm_deepseek.py +10 -11
- mcp_agent/llm/providers/augmented_llm_generic.py → fast_agent/llm/provider/openai/llm_generic.py +4 -4
- mcp_agent/llm/providers/augmented_llm_google_oai.py → fast_agent/llm/provider/openai/llm_google_oai.py +4 -4
- mcp_agent/llm/providers/augmented_llm_groq.py → fast_agent/llm/provider/openai/llm_groq.py +14 -16
- mcp_agent/llm/providers/augmented_llm_openai.py → fast_agent/llm/provider/openai/llm_openai.py +133 -206
- mcp_agent/llm/providers/augmented_llm_openrouter.py → fast_agent/llm/provider/openai/llm_openrouter.py +6 -6
- mcp_agent/llm/providers/augmented_llm_tensorzero_openai.py → fast_agent/llm/provider/openai/llm_tensorzero_openai.py +17 -16
- mcp_agent/llm/providers/augmented_llm_xai.py → fast_agent/llm/provider/openai/llm_xai.py +6 -6
- {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/multipart_converter_openai.py +125 -63
- {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/openai_multipart.py +12 -12
- {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/openai_utils.py +18 -16
- {mcp_agent → fast_agent}/llm/provider_key_manager.py +2 -2
- {mcp_agent → fast_agent}/llm/provider_types.py +2 -0
- {mcp_agent → fast_agent}/llm/sampling_converter.py +15 -12
- {mcp_agent → fast_agent}/llm/usage_tracking.py +23 -5
- fast_agent/mcp/__init__.py +43 -0
- {mcp_agent → fast_agent}/mcp/elicitation_factory.py +3 -3
- {mcp_agent → fast_agent}/mcp/elicitation_handlers.py +19 -10
- {mcp_agent → fast_agent}/mcp/gen_client.py +3 -3
- fast_agent/mcp/helpers/__init__.py +36 -0
- fast_agent/mcp/helpers/content_helpers.py +183 -0
- {mcp_agent → fast_agent}/mcp/helpers/server_config_helpers.py +8 -8
- {mcp_agent → fast_agent}/mcp/hf_auth.py +25 -23
- fast_agent/mcp/interfaces.py +93 -0
- {mcp_agent → fast_agent}/mcp/logger_textio.py +4 -4
- {mcp_agent → fast_agent}/mcp/mcp_agent_client_session.py +49 -44
- {mcp_agent → fast_agent}/mcp/mcp_aggregator.py +66 -115
- {mcp_agent → fast_agent}/mcp/mcp_connection_manager.py +16 -23
- {mcp_agent/core → fast_agent/mcp}/mcp_content.py +23 -15
- {mcp_agent → fast_agent}/mcp/mime_utils.py +39 -0
- fast_agent/mcp/prompt.py +159 -0
- mcp_agent/mcp/prompt_message_multipart.py → fast_agent/mcp/prompt_message_extended.py +27 -20
- {mcp_agent → fast_agent}/mcp/prompt_render.py +21 -19
- {mcp_agent → fast_agent}/mcp/prompt_serialization.py +46 -46
- fast_agent/mcp/prompts/__main__.py +7 -0
- {mcp_agent → fast_agent}/mcp/prompts/prompt_helpers.py +31 -30
- {mcp_agent → fast_agent}/mcp/prompts/prompt_load.py +8 -8
- {mcp_agent → fast_agent}/mcp/prompts/prompt_server.py +11 -19
- {mcp_agent → fast_agent}/mcp/prompts/prompt_template.py +18 -18
- {mcp_agent → fast_agent}/mcp/resource_utils.py +1 -1
- {mcp_agent → fast_agent}/mcp/sampling.py +31 -26
- {mcp_agent/mcp_server → fast_agent/mcp/server}/__init__.py +1 -1
- {mcp_agent/mcp_server → fast_agent/mcp/server}/agent_server.py +5 -6
- fast_agent/mcp/ui_agent.py +48 -0
- fast_agent/mcp/ui_mixin.py +209 -0
- fast_agent/mcp_server_registry.py +90 -0
- {mcp_agent → fast_agent}/resources/examples/data-analysis/analysis-campaign.py +5 -4
- {mcp_agent → fast_agent}/resources/examples/data-analysis/analysis.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_forms_server.py +25 -3
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/forms_demo.py +3 -3
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/game_character.py +2 -2
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/game_character_handler.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/tool_call.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/agent_one.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/agent_two.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/researcher/researcher-eval.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/researcher/researcher-imp.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/researcher/researcher.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/tensorzero/agent.py +2 -2
- {mcp_agent → fast_agent}/resources/examples/tensorzero/image_demo.py +3 -3
- {mcp_agent → fast_agent}/resources/examples/tensorzero/simple_agent.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/workflows/chaining.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/workflows/evaluator.py +3 -3
- {mcp_agent → fast_agent}/resources/examples/workflows/human_input.py +5 -3
- {mcp_agent → fast_agent}/resources/examples/workflows/orchestrator.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/workflows/parallel.py +2 -2
- {mcp_agent → fast_agent}/resources/examples/workflows/router.py +5 -2
- fast_agent/resources/setup/.gitignore +24 -0
- fast_agent/resources/setup/agent.py +18 -0
- fast_agent/resources/setup/fastagent.config.yaml +44 -0
- fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
- fast_agent/tools/elicitation.py +369 -0
- fast_agent/types/__init__.py +32 -0
- fast_agent/types/llm_stop_reason.py +77 -0
- fast_agent/ui/__init__.py +38 -0
- fast_agent/ui/console_display.py +1005 -0
- {mcp_agent/human_input → fast_agent/ui}/elicitation_form.py +56 -39
- mcp_agent/human_input/elicitation_forms.py → fast_agent/ui/elicitation_style.py +1 -1
- {mcp_agent/core → fast_agent/ui}/enhanced_prompt.py +96 -25
- {mcp_agent/core → fast_agent/ui}/interactive_prompt.py +330 -125
- fast_agent/ui/mcp_ui_utils.py +224 -0
- {mcp_agent → fast_agent/ui}/progress_display.py +2 -2
- {mcp_agent/logging → fast_agent/ui}/rich_progress.py +4 -4
- {mcp_agent/core → fast_agent/ui}/usage_display.py +3 -8
- {fast_agent_mcp-0.2.57.dist-info → fast_agent_mcp-0.3.0.dist-info}/METADATA +7 -7
- fast_agent_mcp-0.3.0.dist-info/RECORD +202 -0
- fast_agent_mcp-0.3.0.dist-info/entry_points.txt +5 -0
- fast_agent_mcp-0.2.57.dist-info/RECORD +0 -192
- fast_agent_mcp-0.2.57.dist-info/entry_points.txt +0 -6
- mcp_agent/__init__.py +0 -114
- mcp_agent/agents/agent.py +0 -92
- mcp_agent/agents/workflow/__init__.py +0 -1
- mcp_agent/agents/workflow/orchestrator_agent.py +0 -597
- mcp_agent/app.py +0 -175
- mcp_agent/core/__init__.py +0 -26
- mcp_agent/core/prompt.py +0 -191
- mcp_agent/event_progress.py +0 -134
- mcp_agent/human_input/handler.py +0 -81
- mcp_agent/llm/__init__.py +0 -2
- mcp_agent/llm/augmented_llm_passthrough.py +0 -232
- mcp_agent/llm/augmented_llm_slow.py +0 -53
- mcp_agent/llm/providers/__init__.py +0 -8
- mcp_agent/llm/providers/augmented_llm_anthropic.py +0 -717
- mcp_agent/llm/providers/augmented_llm_bedrock.py +0 -1788
- mcp_agent/llm/providers/augmented_llm_google_native.py +0 -495
- mcp_agent/llm/providers/sampling_converter_anthropic.py +0 -57
- mcp_agent/llm/providers/sampling_converter_openai.py +0 -26
- mcp_agent/llm/sampling_format_converter.py +0 -37
- mcp_agent/logging/__init__.py +0 -0
- mcp_agent/mcp/__init__.py +0 -50
- mcp_agent/mcp/helpers/__init__.py +0 -25
- mcp_agent/mcp/helpers/content_helpers.py +0 -187
- mcp_agent/mcp/interfaces.py +0 -266
- mcp_agent/mcp/prompts/__init__.py +0 -0
- mcp_agent/mcp/prompts/__main__.py +0 -10
- mcp_agent/mcp_server_registry.py +0 -343
- mcp_agent/tools/tool_definition.py +0 -14
- mcp_agent/ui/console_display.py +0 -790
- mcp_agent/ui/console_display_legacy.py +0 -401
- {mcp_agent → fast_agent}/agents/workflow/orchestrator_prompts.py +0 -0
- {mcp_agent/agents → fast_agent/cli}/__init__.py +0 -0
- {mcp_agent → fast_agent}/cli/constants.py +0 -0
- {mcp_agent → fast_agent}/core/error_handling.py +0 -0
- {mcp_agent → fast_agent}/core/exceptions.py +0 -0
- {mcp_agent/cli → fast_agent/core/executor}/__init__.py +0 -0
- {mcp_agent → fast_agent/core}/executor/task_registry.py +0 -0
- {mcp_agent → fast_agent/core}/executor/workflow_signal.py +0 -0
- {mcp_agent → fast_agent}/human_input/form_fields.py +0 -0
- {mcp_agent → fast_agent}/llm/prompt_utils.py +0 -0
- {mcp_agent/core → fast_agent/llm}/request_params.py +0 -0
- {mcp_agent → fast_agent}/mcp/common.py +0 -0
- {mcp_agent/executor → fast_agent/mcp/prompts}/__init__.py +0 -0
- {mcp_agent → fast_agent}/mcp/prompts/prompt_constants.py +0 -0
- {mcp_agent → fast_agent}/py.typed +0 -0
- {mcp_agent → fast_agent}/resources/examples/data-analysis/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_account_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_game_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +0 -0
- {mcp_agent → fast_agent}/resources/examples/researcher/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/.env.sample +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/Makefile +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/README.md +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/crab.png +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/docker-compose.yml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/Dockerfile +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/entrypoint.sh +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/mcp_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/pyproject.toml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/system_schema.json +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/graded_report.md +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/short_story.md +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/short_story.txt +0 -0
- {mcp_agent → fast_agent/ui}/console.py +0 -0
- {mcp_agent/core → fast_agent/ui}/mermaid_utils.py +0 -0
- {fast_agent_mcp-0.2.57.dist-info → fast_agent_mcp-0.3.0.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.57.dist-info → fast_agent_mcp-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -9,13 +9,24 @@ import time
|
|
|
9
9
|
from typing import List, Optional, Union
|
|
10
10
|
|
|
11
11
|
# Proper type imports for each provider
|
|
12
|
-
|
|
13
|
-
from
|
|
14
|
-
|
|
12
|
+
try:
|
|
13
|
+
from anthropic.types import Usage as AnthropicUsage
|
|
14
|
+
except Exception: # pragma: no cover - optional dependency
|
|
15
|
+
AnthropicUsage = object # type: ignore
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from google.genai.types import GenerateContentResponseUsageMetadata as GoogleUsage
|
|
19
|
+
except Exception: # pragma: no cover - optional dependency
|
|
20
|
+
GoogleUsage = object # type: ignore
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from openai.types.completion_usage import CompletionUsage as OpenAIUsage
|
|
24
|
+
except Exception: # pragma: no cover - optional dependency
|
|
25
|
+
OpenAIUsage = object # type: ignore
|
|
15
26
|
from pydantic import BaseModel, Field, computed_field
|
|
16
27
|
|
|
17
|
-
from
|
|
18
|
-
from
|
|
28
|
+
from fast_agent.llm.model_database import ModelDatabase
|
|
29
|
+
from fast_agent.llm.provider_types import Provider
|
|
19
30
|
|
|
20
31
|
|
|
21
32
|
# Fast-agent specific usage type for synthetic providers
|
|
@@ -230,6 +241,13 @@ class UsageAccumulator(BaseModel):
|
|
|
230
241
|
if self.model is None:
|
|
231
242
|
self.model = turn.model
|
|
232
243
|
|
|
244
|
+
# add tool call count to the last turn (if present)
|
|
245
|
+
# not ideal way to do it, but works well enough. full history would be available through the
|
|
246
|
+
# message_history; maybe we consolidate there and put turn_usage on the turn.
|
|
247
|
+
def count_tools(self, tool_calls: int) -> None:
|
|
248
|
+
if self.turns and self.turns[-1]:
|
|
249
|
+
self.turns[-1].tool_calls = tool_calls
|
|
250
|
+
|
|
233
251
|
@computed_field
|
|
234
252
|
@property
|
|
235
253
|
def cumulative_input_tokens(self) -> int:
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP utilities and types for Fast Agent.
|
|
3
|
+
|
|
4
|
+
Public API:
|
|
5
|
+
- `PromptMessageExtended`: canonical message container used internally by providers.
|
|
6
|
+
- Helpers from `fast_agent.mcp.helpers` (re-exported for convenience).
|
|
7
|
+
|
|
8
|
+
Note: Backward compatibility for legacy `PromptMessageMultipart` imports is handled
|
|
9
|
+
via `fast_agent.mcp.prompt_message_multipart`, which subclasses `PromptMessageExtended`.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .helpers import (
|
|
13
|
+
ensure_multipart_messages,
|
|
14
|
+
get_image_data,
|
|
15
|
+
get_resource_text,
|
|
16
|
+
get_resource_uri,
|
|
17
|
+
get_text,
|
|
18
|
+
is_image_content,
|
|
19
|
+
is_resource_content,
|
|
20
|
+
is_resource_link,
|
|
21
|
+
is_text_content,
|
|
22
|
+
normalize_to_extended_list,
|
|
23
|
+
split_thinking_content,
|
|
24
|
+
text_content,
|
|
25
|
+
)
|
|
26
|
+
from .prompt_message_extended import PromptMessageExtended
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"PromptMessageExtended",
|
|
30
|
+
# Helpers
|
|
31
|
+
"get_text",
|
|
32
|
+
"get_image_data",
|
|
33
|
+
"get_resource_uri",
|
|
34
|
+
"is_text_content",
|
|
35
|
+
"is_image_content",
|
|
36
|
+
"is_resource_content",
|
|
37
|
+
"is_resource_link",
|
|
38
|
+
"get_resource_text",
|
|
39
|
+
"ensure_multipart_messages",
|
|
40
|
+
"normalize_to_extended_list",
|
|
41
|
+
"split_thinking_content",
|
|
42
|
+
"text_content",
|
|
43
|
+
]
|
|
@@ -6,9 +6,9 @@ from typing import Any, Optional
|
|
|
6
6
|
|
|
7
7
|
from mcp.client.session import ElicitationFnT
|
|
8
8
|
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
from
|
|
9
|
+
from fast_agent.agents.agent_types import AgentConfig
|
|
10
|
+
from fast_agent.core.logging.logger import get_logger
|
|
11
|
+
from fast_agent.mcp.elicitation_handlers import (
|
|
12
12
|
auto_cancel_elicitation_handler,
|
|
13
13
|
forms_elicitation_handler,
|
|
14
14
|
)
|
|
@@ -8,10 +8,10 @@ from typing import TYPE_CHECKING, Any
|
|
|
8
8
|
from mcp.shared.context import RequestContext
|
|
9
9
|
from mcp.types import ElicitRequestParams, ElicitResult, ErrorData
|
|
10
10
|
|
|
11
|
-
from
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
from
|
|
11
|
+
from fast_agent.core.logging.logger import get_logger
|
|
12
|
+
from fast_agent.human_input.elicitation_handler import elicitation_input_callback
|
|
13
|
+
from fast_agent.human_input.types import HumanInputRequest
|
|
14
|
+
from fast_agent.mcp.helpers.server_config_helpers import get_server_config
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
17
|
from mcp import ClientSession
|
|
@@ -24,7 +24,7 @@ async def auto_cancel_elicitation_handler(
|
|
|
24
24
|
params: ElicitRequestParams,
|
|
25
25
|
) -> ElicitResult | ErrorData:
|
|
26
26
|
"""Handler that automatically cancels all elicitation requests.
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
Useful for production deployments where you want to advertise elicitation
|
|
29
29
|
capability but automatically decline all requests.
|
|
30
30
|
"""
|
|
@@ -51,7 +51,8 @@ async def forms_elicitation_handler(
|
|
|
51
51
|
agent_name: str | None = None
|
|
52
52
|
|
|
53
53
|
# 1. Check if we have an MCPAgentClientSession in the context
|
|
54
|
-
from
|
|
54
|
+
from fast_agent.mcp.mcp_agent_client_session import MCPAgentClientSession
|
|
55
|
+
|
|
55
56
|
if hasattr(context, "session") and isinstance(context.session, MCPAgentClientSession):
|
|
56
57
|
agent_name = context.session.agent_name
|
|
57
58
|
|
|
@@ -90,9 +91,17 @@ async def forms_elicitation_handler(
|
|
|
90
91
|
elif response_data == "__CANCELLED__":
|
|
91
92
|
return ElicitResult(action="cancel")
|
|
92
93
|
elif response_data == "__DISABLE_SERVER__":
|
|
93
|
-
#
|
|
94
|
-
logger.warning(
|
|
95
|
-
|
|
94
|
+
# Respect user's request: disable elicitation for this server for this session
|
|
95
|
+
logger.warning(
|
|
96
|
+
f"User requested to disable elicitation for server: {server_name} — disabling for session"
|
|
97
|
+
)
|
|
98
|
+
try:
|
|
99
|
+
from fast_agent.human_input.elicitation_state import elicitation_state
|
|
100
|
+
|
|
101
|
+
elicitation_state.disable_server(server_name)
|
|
102
|
+
except Exception:
|
|
103
|
+
# Do not fail the flow if state update fails
|
|
104
|
+
pass
|
|
96
105
|
return ElicitResult(action="cancel")
|
|
97
106
|
|
|
98
107
|
# Parse response based on schema if provided
|
|
@@ -152,4 +161,4 @@ async def forms_elicitation_handler(
|
|
|
152
161
|
return ElicitResult(action="accept", content=content)
|
|
153
162
|
except (KeyboardInterrupt, EOFError, TimeoutError):
|
|
154
163
|
# User cancelled or timeout
|
|
155
|
-
return ElicitResult(action="cancel")
|
|
164
|
+
return ElicitResult(action="cancel")
|
|
@@ -5,9 +5,9 @@ from typing import AsyncGenerator, Callable
|
|
|
5
5
|
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
|
6
6
|
from mcp import ClientSession
|
|
7
7
|
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
from
|
|
8
|
+
from fast_agent.core.logging.logger import get_logger
|
|
9
|
+
from fast_agent.mcp.interfaces import ServerRegistryProtocol
|
|
10
|
+
from fast_agent.mcp.mcp_agent_client_session import MCPAgentClientSession
|
|
11
11
|
|
|
12
12
|
logger = get_logger(__name__)
|
|
13
13
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helper modules for working with MCP content (Fast Agent namespace).
|
|
3
|
+
|
|
4
|
+
This mirrors the legacy fast_agent.mcp.helpers API to provide a stable,
|
|
5
|
+
cycle-free import path now that PromptMessageExtended lives in fast_agent.mcp.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .content_helpers import (
|
|
9
|
+
ensure_multipart_messages,
|
|
10
|
+
get_image_data,
|
|
11
|
+
get_resource_text,
|
|
12
|
+
get_resource_uri,
|
|
13
|
+
get_text,
|
|
14
|
+
is_image_content,
|
|
15
|
+
is_resource_content,
|
|
16
|
+
is_resource_link,
|
|
17
|
+
is_text_content,
|
|
18
|
+
normalize_to_extended_list,
|
|
19
|
+
split_thinking_content,
|
|
20
|
+
text_content,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"get_text",
|
|
25
|
+
"get_image_data",
|
|
26
|
+
"get_resource_uri",
|
|
27
|
+
"is_text_content",
|
|
28
|
+
"is_image_content",
|
|
29
|
+
"is_resource_content",
|
|
30
|
+
"is_resource_link",
|
|
31
|
+
"get_resource_text",
|
|
32
|
+
"ensure_multipart_messages",
|
|
33
|
+
"normalize_to_extended_list",
|
|
34
|
+
"split_thinking_content",
|
|
35
|
+
"text_content",
|
|
36
|
+
]
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helper functions for working with content objects (Fast Agent namespace).
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import TYPE_CHECKING, List, Optional, Sequence, Union
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from fast_agent.mcp.prompt_message_extended import PromptMessageExtended
|
|
10
|
+
|
|
11
|
+
from mcp.types import (
|
|
12
|
+
BlobResourceContents,
|
|
13
|
+
ContentBlock,
|
|
14
|
+
EmbeddedResource,
|
|
15
|
+
ImageContent,
|
|
16
|
+
PromptMessage,
|
|
17
|
+
ReadResourceResult,
|
|
18
|
+
ResourceLink,
|
|
19
|
+
TextContent,
|
|
20
|
+
TextResourceContents,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_text(content: ContentBlock) -> Optional[str]:
|
|
25
|
+
"""Extract text content from a content object if available."""
|
|
26
|
+
if isinstance(content, TextContent):
|
|
27
|
+
return content.text
|
|
28
|
+
|
|
29
|
+
if isinstance(content, TextResourceContents):
|
|
30
|
+
return content.text
|
|
31
|
+
|
|
32
|
+
if isinstance(content, EmbeddedResource):
|
|
33
|
+
if isinstance(content.resource, TextResourceContents):
|
|
34
|
+
return content.resource.text
|
|
35
|
+
|
|
36
|
+
if isinstance(content, ResourceLink):
|
|
37
|
+
name = content.name or "unknown"
|
|
38
|
+
uri_str = str(content.uri)
|
|
39
|
+
mime_type = content.mimeType or "unknown"
|
|
40
|
+
description = content.description or "No description"
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
f"Linked Resource ${name} MIME type {mime_type}>\n"
|
|
44
|
+
f"Resource Link: {uri_str}\n"
|
|
45
|
+
f"${description}\n"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_image_data(content: ContentBlock) -> Optional[str]:
|
|
52
|
+
"""Extract image data from a content object if available."""
|
|
53
|
+
if isinstance(content, ImageContent):
|
|
54
|
+
return content.data
|
|
55
|
+
|
|
56
|
+
if isinstance(content, EmbeddedResource):
|
|
57
|
+
if isinstance(content.resource, BlobResourceContents):
|
|
58
|
+
return content.resource.blob
|
|
59
|
+
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_resource_uri(content: ContentBlock) -> Optional[str]:
|
|
64
|
+
"""Extract resource URI from an EmbeddedResource if available."""
|
|
65
|
+
if isinstance(content, EmbeddedResource):
|
|
66
|
+
return str(content.resource.uri)
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def is_text_content(content: ContentBlock) -> bool:
|
|
71
|
+
"""Check if the content is text content."""
|
|
72
|
+
return isinstance(content, TextContent) or isinstance(content, TextResourceContents)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def is_image_content(content: Union[TextContent, ImageContent, EmbeddedResource]) -> bool:
|
|
76
|
+
"""Check if the content is image content."""
|
|
77
|
+
return isinstance(content, ImageContent)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def is_resource_content(content: ContentBlock) -> bool:
|
|
81
|
+
"""Check if the content is an embedded resource."""
|
|
82
|
+
return isinstance(content, EmbeddedResource)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def is_resource_link(content: ContentBlock) -> bool:
|
|
86
|
+
"""Check if the content is a resource link."""
|
|
87
|
+
return isinstance(content, ResourceLink)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_resource_text(result: ReadResourceResult, index: int = 0) -> Optional[str]:
|
|
91
|
+
"""Extract text content from a ReadResourceResult at the specified index."""
|
|
92
|
+
if index >= len(result.contents):
|
|
93
|
+
raise IndexError(
|
|
94
|
+
f"Index {index} out of bounds for contents list of length {len(result.contents)}"
|
|
95
|
+
)
|
|
96
|
+
content = result.contents[index]
|
|
97
|
+
if isinstance(content, TextResourceContents):
|
|
98
|
+
return content.text
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def split_thinking_content(message: str) -> tuple[Optional[str], str]:
|
|
103
|
+
"""Split a message into thinking and content parts."""
|
|
104
|
+
import re
|
|
105
|
+
|
|
106
|
+
pattern = r"^<think>(.*?)</think>\s*(.*)$"
|
|
107
|
+
match = re.match(pattern, message, re.DOTALL)
|
|
108
|
+
|
|
109
|
+
if match:
|
|
110
|
+
thinking_content = match.group(1).strip()
|
|
111
|
+
main_content = match.group(2).strip()
|
|
112
|
+
return (thinking_content, main_content)
|
|
113
|
+
else:
|
|
114
|
+
return (None, message)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def text_content(text: str) -> TextContent:
|
|
118
|
+
"""Convenience to create a TextContent block from a string."""
|
|
119
|
+
return TextContent(type="text", text=text)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def ensure_multipart_messages(
|
|
123
|
+
messages: List[Union["PromptMessageExtended", PromptMessage]],
|
|
124
|
+
) -> List["PromptMessageExtended"]:
|
|
125
|
+
"""Ensure all messages in a list are PromptMessageExtended objects."""
|
|
126
|
+
# Import here to avoid circular dependency
|
|
127
|
+
from fast_agent.mcp.prompt_message_extended import PromptMessageExtended
|
|
128
|
+
|
|
129
|
+
if not messages:
|
|
130
|
+
return []
|
|
131
|
+
|
|
132
|
+
result = []
|
|
133
|
+
for message in messages:
|
|
134
|
+
if isinstance(message, PromptMessage):
|
|
135
|
+
result.append(PromptMessageExtended(role=message.role, content=[message.content]))
|
|
136
|
+
else:
|
|
137
|
+
result.append(message)
|
|
138
|
+
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def normalize_to_extended_list(
|
|
143
|
+
messages: Union[
|
|
144
|
+
str,
|
|
145
|
+
PromptMessage,
|
|
146
|
+
"PromptMessageExtended",
|
|
147
|
+
Sequence[Union[str, PromptMessage, "PromptMessageExtended"]],
|
|
148
|
+
],
|
|
149
|
+
) -> List["PromptMessageExtended"]:
|
|
150
|
+
"""Normalize various input types to a list of PromptMessageExtended objects."""
|
|
151
|
+
# Import here to avoid circular dependency
|
|
152
|
+
from fast_agent.mcp.prompt_message_extended import PromptMessageExtended
|
|
153
|
+
|
|
154
|
+
if messages is None:
|
|
155
|
+
return []
|
|
156
|
+
|
|
157
|
+
# Single string → convert to user PromptMessageExtended
|
|
158
|
+
if isinstance(messages, str):
|
|
159
|
+
return [
|
|
160
|
+
PromptMessageExtended(role="user", content=[TextContent(type="text", text=messages)])
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
# Single PromptMessage → convert to PromptMessageExtended
|
|
164
|
+
if isinstance(messages, PromptMessage):
|
|
165
|
+
return [PromptMessageExtended(role=messages.role, content=[messages.content])]
|
|
166
|
+
|
|
167
|
+
# Single PromptMessageExtended → wrap in a list
|
|
168
|
+
if isinstance(messages, PromptMessageExtended):
|
|
169
|
+
return [messages]
|
|
170
|
+
|
|
171
|
+
# List of mixed types → convert each element
|
|
172
|
+
result: List[PromptMessageExtended] = []
|
|
173
|
+
for item in messages:
|
|
174
|
+
if isinstance(item, str):
|
|
175
|
+
result.append(
|
|
176
|
+
PromptMessageExtended(role="user", content=[TextContent(type="text", text=item)])
|
|
177
|
+
)
|
|
178
|
+
elif isinstance(item, PromptMessage):
|
|
179
|
+
result.append(PromptMessageExtended(role=item.role, content=[item.content]))
|
|
180
|
+
else:
|
|
181
|
+
result.append(item)
|
|
182
|
+
|
|
183
|
+
return result
|
|
@@ -3,23 +3,23 @@
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any, Optional
|
|
4
4
|
|
|
5
5
|
if TYPE_CHECKING:
|
|
6
|
-
from
|
|
6
|
+
from fast_agent.config import MCPServerSettings
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def get_server_config(ctx: Any) -> Optional["MCPServerSettings"]:
|
|
10
10
|
"""Extract server config from context if available.
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
Type guard helper that safely accesses server_config with proper type checking.
|
|
13
13
|
"""
|
|
14
14
|
# Import here to avoid circular import
|
|
15
|
-
from
|
|
16
|
-
|
|
15
|
+
from fast_agent.mcp.mcp_agent_client_session import MCPAgentClientSession
|
|
16
|
+
|
|
17
17
|
# Check if ctx has a session attribute (RequestContext case)
|
|
18
18
|
if hasattr(ctx, "session"):
|
|
19
|
-
if isinstance(ctx.session, MCPAgentClientSession) and hasattr(ctx.session,
|
|
19
|
+
if isinstance(ctx.session, MCPAgentClientSession) and hasattr(ctx.session, "server_config"):
|
|
20
20
|
return ctx.session.server_config
|
|
21
21
|
# Also check if ctx itself is MCPAgentClientSession (direct call case)
|
|
22
|
-
elif isinstance(ctx, MCPAgentClientSession) and hasattr(ctx,
|
|
22
|
+
elif isinstance(ctx, MCPAgentClientSession) and hasattr(ctx, "server_config"):
|
|
23
23
|
return ctx.server_config
|
|
24
|
-
|
|
25
|
-
return None
|
|
24
|
+
|
|
25
|
+
return None
|
|
@@ -8,10 +8,10 @@ from urllib.parse import urlparse
|
|
|
8
8
|
def is_huggingface_url(url: str) -> bool:
|
|
9
9
|
"""
|
|
10
10
|
Check if a URL is a HuggingFace URL that should receive HF_TOKEN authentication.
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
Args:
|
|
13
13
|
url: The URL to check
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
Returns:
|
|
16
16
|
True if the URL is a HuggingFace URL, False otherwise
|
|
17
17
|
"""
|
|
@@ -20,11 +20,11 @@ def is_huggingface_url(url: str) -> bool:
|
|
|
20
20
|
hostname = parsed.hostname
|
|
21
21
|
if hostname is None:
|
|
22
22
|
return False
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
# Check for HuggingFace domains
|
|
25
25
|
if hostname in {"hf.co", "huggingface.co"}:
|
|
26
26
|
return True
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
# Check for HuggingFace Spaces (*.hf.space)
|
|
29
29
|
# Use endswith to match subdomains like space-name.hf.space
|
|
30
30
|
# but ensure exact match to prevent spoofing like evil.hf.space.com
|
|
@@ -35,12 +35,14 @@ def is_huggingface_url(url: str) -> bool:
|
|
|
35
35
|
if len(parts) == 3 and parts[-2:] == ["hf", "space"]:
|
|
36
36
|
space_name = parts[0]
|
|
37
37
|
# Validate space name: not empty, not just hyphens/dots, no spaces
|
|
38
|
-
return (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
return (
|
|
39
|
+
len(space_name) > 0
|
|
40
|
+
and space_name != "-"
|
|
41
|
+
and not space_name.startswith(".")
|
|
42
|
+
and not space_name.endswith(".")
|
|
43
|
+
and " " not in space_name
|
|
44
|
+
)
|
|
45
|
+
|
|
44
46
|
return False
|
|
45
47
|
except Exception:
|
|
46
48
|
return False
|
|
@@ -49,7 +51,7 @@ def is_huggingface_url(url: str) -> bool:
|
|
|
49
51
|
def get_hf_token_from_env() -> Optional[str]:
|
|
50
52
|
"""
|
|
51
53
|
Get the HuggingFace token from the HF_TOKEN environment variable.
|
|
52
|
-
|
|
54
|
+
|
|
53
55
|
Returns:
|
|
54
56
|
The HF_TOKEN value if set, None otherwise
|
|
55
57
|
"""
|
|
@@ -59,11 +61,11 @@ def get_hf_token_from_env() -> Optional[str]:
|
|
|
59
61
|
def should_add_hf_auth(url: str, existing_headers: Optional[Dict[str, str]]) -> bool:
|
|
60
62
|
"""
|
|
61
63
|
Determine if HuggingFace authentication should be added to the headers.
|
|
62
|
-
|
|
64
|
+
|
|
63
65
|
Args:
|
|
64
66
|
url: The URL to check
|
|
65
67
|
existing_headers: Existing headers dictionary (may be None)
|
|
66
|
-
|
|
68
|
+
|
|
67
69
|
Returns:
|
|
68
70
|
True if HF auth should be added, False otherwise
|
|
69
71
|
"""
|
|
@@ -71,10 +73,10 @@ def should_add_hf_auth(url: str, existing_headers: Optional[Dict[str, str]]) ->
|
|
|
71
73
|
# 1. URL is a HuggingFace URL
|
|
72
74
|
# 2. No existing Authorization/X-HF-Authorization header is set
|
|
73
75
|
# 3. HF_TOKEN environment variable is available
|
|
74
|
-
|
|
76
|
+
|
|
75
77
|
if not is_huggingface_url(url):
|
|
76
78
|
return False
|
|
77
|
-
|
|
79
|
+
|
|
78
80
|
if existing_headers:
|
|
79
81
|
# Check if this is a .hf.space domain
|
|
80
82
|
try:
|
|
@@ -92,31 +94,31 @@ def should_add_hf_auth(url: str, existing_headers: Optional[Dict[str, str]]) ->
|
|
|
92
94
|
# Fallback to checking Authorization header
|
|
93
95
|
if "Authorization" in existing_headers:
|
|
94
96
|
return False
|
|
95
|
-
|
|
97
|
+
|
|
96
98
|
return get_hf_token_from_env() is not None
|
|
97
99
|
|
|
98
100
|
|
|
99
101
|
def add_hf_auth_header(url: str, headers: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]:
|
|
100
102
|
"""
|
|
101
103
|
Add HuggingFace authentication header if appropriate.
|
|
102
|
-
|
|
104
|
+
|
|
103
105
|
Args:
|
|
104
106
|
url: The URL to check
|
|
105
107
|
headers: Existing headers dictionary (may be None)
|
|
106
|
-
|
|
108
|
+
|
|
107
109
|
Returns:
|
|
108
110
|
Updated headers dictionary with HF auth if appropriate, or original headers
|
|
109
111
|
"""
|
|
110
112
|
if not should_add_hf_auth(url, headers):
|
|
111
113
|
return headers
|
|
112
|
-
|
|
114
|
+
|
|
113
115
|
hf_token = get_hf_token_from_env()
|
|
114
116
|
if hf_token is None:
|
|
115
117
|
return headers
|
|
116
|
-
|
|
118
|
+
|
|
117
119
|
# Create new headers dict or copy existing one
|
|
118
120
|
result_headers = dict(headers) if headers else {}
|
|
119
|
-
|
|
121
|
+
|
|
120
122
|
# Check if this is a .hf.space domain
|
|
121
123
|
try:
|
|
122
124
|
parsed = urlparse(url)
|
|
@@ -130,5 +132,5 @@ def add_hf_auth_header(url: str, headers: Optional[Dict[str, str]]) -> Optional[
|
|
|
130
132
|
except Exception:
|
|
131
133
|
# Fallback to standard Authorization header
|
|
132
134
|
result_headers["Authorization"] = f"Bearer {hf_token}"
|
|
133
|
-
|
|
134
|
-
return result_headers
|
|
135
|
+
|
|
136
|
+
return result_headers
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interface definitions to prevent circular imports.
|
|
3
|
+
This module defines protocols (interfaces) that can be used to break circular dependencies.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from datetime import timedelta
|
|
7
|
+
from typing import (
|
|
8
|
+
AsyncContextManager,
|
|
9
|
+
Callable,
|
|
10
|
+
Optional,
|
|
11
|
+
Protocol,
|
|
12
|
+
runtime_checkable,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
|
16
|
+
from mcp import ClientSession
|
|
17
|
+
|
|
18
|
+
from fast_agent.interfaces import (
|
|
19
|
+
AgentProtocol,
|
|
20
|
+
FastAgentLLMProtocol,
|
|
21
|
+
LlmAgentProtocol,
|
|
22
|
+
LLMFactoryProtocol,
|
|
23
|
+
ModelFactoryFunctionProtocol,
|
|
24
|
+
ModelT,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"MCPConnectionManagerProtocol",
|
|
29
|
+
"ServerRegistryProtocol",
|
|
30
|
+
"ServerConnection",
|
|
31
|
+
"FastAgentLLMProtocol",
|
|
32
|
+
"AgentProtocol",
|
|
33
|
+
"LlmAgentProtocol",
|
|
34
|
+
"LLMFactoryProtocol",
|
|
35
|
+
"ModelFactoryFunctionProtocol",
|
|
36
|
+
"ModelT",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@runtime_checkable
|
|
41
|
+
class MCPConnectionManagerProtocol(Protocol):
|
|
42
|
+
"""Protocol for MCPConnectionManager functionality needed by ServerRegistry."""
|
|
43
|
+
|
|
44
|
+
async def get_server(
|
|
45
|
+
self,
|
|
46
|
+
server_name: str,
|
|
47
|
+
client_session_factory: Optional[
|
|
48
|
+
Callable[
|
|
49
|
+
[
|
|
50
|
+
MemoryObjectReceiveStream,
|
|
51
|
+
MemoryObjectSendStream,
|
|
52
|
+
Optional[timedelta],
|
|
53
|
+
],
|
|
54
|
+
ClientSession,
|
|
55
|
+
]
|
|
56
|
+
] = None,
|
|
57
|
+
) -> "ServerConnection": ...
|
|
58
|
+
|
|
59
|
+
async def disconnect_server(self, server_name: str) -> None: ...
|
|
60
|
+
|
|
61
|
+
async def disconnect_all_servers(self) -> None: ...
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@runtime_checkable
|
|
65
|
+
class ServerRegistryProtocol(Protocol):
|
|
66
|
+
"""Protocol defining the minimal interface of ServerRegistry needed by gen_client."""
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def connection_manager(self) -> MCPConnectionManagerProtocol: ...
|
|
70
|
+
|
|
71
|
+
def initialize_server(
|
|
72
|
+
self,
|
|
73
|
+
server_name: str,
|
|
74
|
+
client_session_factory: Optional[
|
|
75
|
+
Callable[
|
|
76
|
+
[
|
|
77
|
+
MemoryObjectReceiveStream,
|
|
78
|
+
MemoryObjectSendStream,
|
|
79
|
+
Optional[timedelta],
|
|
80
|
+
],
|
|
81
|
+
ClientSession,
|
|
82
|
+
]
|
|
83
|
+
] = None,
|
|
84
|
+
) -> AsyncContextManager[ClientSession]:
|
|
85
|
+
"""Initialize a server and yield a client session."""
|
|
86
|
+
...
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ServerConnection(Protocol):
|
|
90
|
+
"""Protocol for server connection objects returned by MCPConnectionManager."""
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def session(self) -> ClientSession: ...
|
|
@@ -6,7 +6,7 @@ import io
|
|
|
6
6
|
import os
|
|
7
7
|
from typing import TextIO
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from fast_agent.core.logging.logger import get_logger
|
|
10
10
|
|
|
11
11
|
logger = get_logger(__name__)
|
|
12
12
|
|
|
@@ -82,13 +82,13 @@ class LoggerTextIO(TextIO):
|
|
|
82
82
|
This prevents output from showing on the terminal
|
|
83
83
|
while still allowing our write() method to capture it for logging.
|
|
84
84
|
"""
|
|
85
|
-
if not hasattr(self,
|
|
85
|
+
if not hasattr(self, "_devnull_fd"):
|
|
86
86
|
self._devnull_fd = os.open(os.devnull, os.O_WRONLY)
|
|
87
87
|
return self._devnull_fd
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
def __del__(self):
|
|
90
90
|
"""Clean up the devnull file descriptor."""
|
|
91
|
-
if hasattr(self,
|
|
91
|
+
if hasattr(self, "_devnull_fd"):
|
|
92
92
|
try:
|
|
93
93
|
os.close(self._devnull_fd)
|
|
94
94
|
except (OSError, AttributeError):
|