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,1186 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Direct FastAgent implementation that uses the simplified Agent architecture.
|
|
3
|
+
This replaces the traditional FastAgent with a more streamlined approach that
|
|
4
|
+
directly creates Agent instances without proxies.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import asyncio
|
|
11
|
+
import inspect
|
|
12
|
+
import pathlib
|
|
13
|
+
import sys
|
|
14
|
+
from contextlib import asynccontextmanager
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from importlib.metadata import version as get_version
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import (
|
|
19
|
+
TYPE_CHECKING,
|
|
20
|
+
Any,
|
|
21
|
+
AsyncIterator,
|
|
22
|
+
Awaitable,
|
|
23
|
+
Callable,
|
|
24
|
+
Literal,
|
|
25
|
+
ParamSpec,
|
|
26
|
+
TypeVar,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
import yaml
|
|
30
|
+
from opentelemetry import trace
|
|
31
|
+
|
|
32
|
+
from fast_agent import config
|
|
33
|
+
from fast_agent.core import Core
|
|
34
|
+
from fast_agent.core.agent_app import AgentApp
|
|
35
|
+
from fast_agent.core.direct_decorators import (
|
|
36
|
+
agent as agent_decorator,
|
|
37
|
+
)
|
|
38
|
+
from fast_agent.core.direct_decorators import (
|
|
39
|
+
chain as chain_decorator,
|
|
40
|
+
)
|
|
41
|
+
from fast_agent.core.direct_decorators import (
|
|
42
|
+
custom as custom_decorator,
|
|
43
|
+
)
|
|
44
|
+
from fast_agent.core.direct_decorators import (
|
|
45
|
+
evaluator_optimizer as evaluator_optimizer_decorator,
|
|
46
|
+
)
|
|
47
|
+
from fast_agent.core.direct_decorators import (
|
|
48
|
+
iterative_planner as orchestrator2_decorator,
|
|
49
|
+
)
|
|
50
|
+
from fast_agent.core.direct_decorators import (
|
|
51
|
+
maker as maker_decorator,
|
|
52
|
+
)
|
|
53
|
+
from fast_agent.core.direct_decorators import (
|
|
54
|
+
orchestrator as orchestrator_decorator,
|
|
55
|
+
)
|
|
56
|
+
from fast_agent.core.direct_decorators import (
|
|
57
|
+
parallel as parallel_decorator,
|
|
58
|
+
)
|
|
59
|
+
from fast_agent.core.direct_decorators import (
|
|
60
|
+
router as router_decorator,
|
|
61
|
+
)
|
|
62
|
+
from fast_agent.core.direct_factory import (
|
|
63
|
+
create_agents_in_dependency_order,
|
|
64
|
+
get_default_model_source,
|
|
65
|
+
get_model_factory,
|
|
66
|
+
)
|
|
67
|
+
from fast_agent.core.error_handling import handle_error
|
|
68
|
+
from fast_agent.core.exceptions import (
|
|
69
|
+
AgentConfigError,
|
|
70
|
+
CircularDependencyError,
|
|
71
|
+
ModelConfigError,
|
|
72
|
+
PromptExitError,
|
|
73
|
+
ProviderKeyError,
|
|
74
|
+
ServerConfigError,
|
|
75
|
+
ServerInitializationError,
|
|
76
|
+
)
|
|
77
|
+
from fast_agent.core.logging.logger import get_logger
|
|
78
|
+
from fast_agent.core.prompt_templates import (
|
|
79
|
+
apply_template_variables,
|
|
80
|
+
enrich_with_environment_context,
|
|
81
|
+
)
|
|
82
|
+
from fast_agent.core.validation import (
|
|
83
|
+
validate_provider_keys_post_creation,
|
|
84
|
+
validate_server_references,
|
|
85
|
+
validate_workflow_references,
|
|
86
|
+
)
|
|
87
|
+
from fast_agent.mcp.prompts.prompt_load import load_prompt
|
|
88
|
+
from fast_agent.skills import SkillManifest, SkillRegistry
|
|
89
|
+
from fast_agent.ui.console import configure_console_stream
|
|
90
|
+
from fast_agent.ui.usage_display import display_usage_report
|
|
91
|
+
|
|
92
|
+
if TYPE_CHECKING:
|
|
93
|
+
from mcp.client.session import ElicitationFnT
|
|
94
|
+
from pydantic import AnyUrl
|
|
95
|
+
|
|
96
|
+
from fast_agent.constants import DEFAULT_AGENT_INSTRUCTION
|
|
97
|
+
from fast_agent.context import Context
|
|
98
|
+
from fast_agent.interfaces import AgentProtocol
|
|
99
|
+
from fast_agent.types import PromptMessageExtended
|
|
100
|
+
|
|
101
|
+
F = TypeVar("F", bound=Callable[..., Any]) # For decorated functions
|
|
102
|
+
logger = get_logger(__name__)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class FastAgent:
|
|
106
|
+
"""
|
|
107
|
+
A simplified FastAgent implementation that directly creates Agent instances
|
|
108
|
+
without using proxies.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
def __init__(
|
|
112
|
+
self,
|
|
113
|
+
name: str,
|
|
114
|
+
config_path: str | None = None,
|
|
115
|
+
ignore_unknown_args: bool = False,
|
|
116
|
+
parse_cli_args: bool = True,
|
|
117
|
+
quiet: bool = False, # Add quiet parameter
|
|
118
|
+
skills_directory: str | pathlib.Path | None = None,
|
|
119
|
+
**kwargs,
|
|
120
|
+
) -> None:
|
|
121
|
+
"""
|
|
122
|
+
Initialize the fast-agent application.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
name: Name of the application
|
|
126
|
+
config_path: Optional path to config file
|
|
127
|
+
ignore_unknown_args: Whether to ignore unknown command line arguments
|
|
128
|
+
when parse_cli_args is True.
|
|
129
|
+
parse_cli_args: If True, parse command line arguments using argparse.
|
|
130
|
+
Set to False when embedding FastAgent in another framework
|
|
131
|
+
(like FastAPI/Uvicorn) that handles its own arguments.
|
|
132
|
+
quiet: If True, disable progress display, tool and message logging for cleaner output
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
self.args = argparse.Namespace() # Initialize args always
|
|
136
|
+
self._programmatic_quiet = quiet # Store the programmatic quiet setting
|
|
137
|
+
self._skills_directory_override = (
|
|
138
|
+
Path(skills_directory).expanduser() if skills_directory else None
|
|
139
|
+
)
|
|
140
|
+
self._default_skill_manifests: list[SkillManifest] = []
|
|
141
|
+
self._server_instance_factory = None
|
|
142
|
+
self._server_instance_dispose = None
|
|
143
|
+
self._server_managed_instances: list[AgentInstance] = []
|
|
144
|
+
|
|
145
|
+
# --- Wrap argument parsing logic ---
|
|
146
|
+
if parse_cli_args:
|
|
147
|
+
# Setup command line argument parsing
|
|
148
|
+
parser = argparse.ArgumentParser(description="DirectFastAgent Application")
|
|
149
|
+
parser.add_argument(
|
|
150
|
+
"--model",
|
|
151
|
+
help="Override the default model for all agents",
|
|
152
|
+
)
|
|
153
|
+
parser.add_argument(
|
|
154
|
+
"--agent",
|
|
155
|
+
default="default",
|
|
156
|
+
help="Specify the agent to send a message to (used with --message)",
|
|
157
|
+
)
|
|
158
|
+
parser.add_argument(
|
|
159
|
+
"-m",
|
|
160
|
+
"--message",
|
|
161
|
+
help="Message to send to the specified agent",
|
|
162
|
+
)
|
|
163
|
+
parser.add_argument(
|
|
164
|
+
"-p", "--prompt-file", help="Path to a prompt file to use (either text or JSON)"
|
|
165
|
+
)
|
|
166
|
+
parser.add_argument(
|
|
167
|
+
"--quiet",
|
|
168
|
+
action="store_true",
|
|
169
|
+
help="Disable progress display, tool and message logging for cleaner output",
|
|
170
|
+
)
|
|
171
|
+
parser.add_argument(
|
|
172
|
+
"--version",
|
|
173
|
+
action="store_true",
|
|
174
|
+
help="Show version and exit",
|
|
175
|
+
)
|
|
176
|
+
parser.add_argument(
|
|
177
|
+
"--server",
|
|
178
|
+
action="store_true",
|
|
179
|
+
help="Run as an MCP server",
|
|
180
|
+
)
|
|
181
|
+
parser.add_argument(
|
|
182
|
+
"--transport",
|
|
183
|
+
choices=["sse", "http", "stdio", "acp"],
|
|
184
|
+
default=None,
|
|
185
|
+
help="Transport protocol to use when running as a server (sse, http, stdio, or acp)",
|
|
186
|
+
)
|
|
187
|
+
parser.add_argument(
|
|
188
|
+
"--port",
|
|
189
|
+
type=int,
|
|
190
|
+
default=8000,
|
|
191
|
+
help="Port to use when running as a server with SSE transport",
|
|
192
|
+
)
|
|
193
|
+
parser.add_argument(
|
|
194
|
+
"--host",
|
|
195
|
+
default="0.0.0.0",
|
|
196
|
+
help="Host address to bind to when running as a server with SSE transport",
|
|
197
|
+
)
|
|
198
|
+
parser.add_argument(
|
|
199
|
+
"--instance-scope",
|
|
200
|
+
choices=["shared", "connection", "request"],
|
|
201
|
+
default="shared",
|
|
202
|
+
help="Control MCP agent instancing behaviour (shared, connection, request)",
|
|
203
|
+
)
|
|
204
|
+
parser.add_argument(
|
|
205
|
+
"--skills",
|
|
206
|
+
help="Path to skills directory to use instead of default .claude/skills",
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if ignore_unknown_args:
|
|
210
|
+
known_args, _ = parser.parse_known_args()
|
|
211
|
+
self.args = known_args
|
|
212
|
+
else:
|
|
213
|
+
# Use parse_known_args here too, to avoid crashing on uvicorn args etc.
|
|
214
|
+
# even if ignore_unknown_args is False, we only care about *our* args.
|
|
215
|
+
known_args, unknown = parser.parse_known_args()
|
|
216
|
+
self.args = known_args
|
|
217
|
+
# Optionally, warn about unknown args if not ignoring?
|
|
218
|
+
# if unknown and not ignore_unknown_args:
|
|
219
|
+
# logger.warning(f"Ignoring unknown command line arguments: {unknown}")
|
|
220
|
+
|
|
221
|
+
# Track whether CLI flags were explicitly provided
|
|
222
|
+
cli_args = sys.argv[1:]
|
|
223
|
+
server_flag_used = "--server" in cli_args
|
|
224
|
+
transport_flag_used = any(
|
|
225
|
+
arg == "--transport" or arg.startswith("--transport=") for arg in cli_args
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# If a transport was provided, assume server mode even without --server
|
|
229
|
+
if transport_flag_used and not getattr(self.args, "server", False):
|
|
230
|
+
self.args.server = True
|
|
231
|
+
|
|
232
|
+
# Default the transport if still unset
|
|
233
|
+
if getattr(self.args, "transport", None) is None:
|
|
234
|
+
self.args.transport = "http"
|
|
235
|
+
|
|
236
|
+
# Warn that --server is deprecated when the user supplied it explicitly
|
|
237
|
+
if server_flag_used:
|
|
238
|
+
print(
|
|
239
|
+
"--server is deprecated; server mode is implied when --transport is provided. "
|
|
240
|
+
"This flag will be removed in a future release.",
|
|
241
|
+
file=sys.stderr,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Handle version flag
|
|
245
|
+
if self.args.version:
|
|
246
|
+
try:
|
|
247
|
+
app_version = get_version("fast-agent-mcp")
|
|
248
|
+
except: # noqa: E722
|
|
249
|
+
app_version = "unknown"
|
|
250
|
+
print(f"fast-agent-mcp v{app_version}")
|
|
251
|
+
sys.exit(0)
|
|
252
|
+
# --- End of wrapped logic ---
|
|
253
|
+
|
|
254
|
+
# Force quiet mode automatically when running ACP transport
|
|
255
|
+
transport = getattr(self.args, "transport", None)
|
|
256
|
+
if transport == "acp":
|
|
257
|
+
self._programmatic_quiet = True
|
|
258
|
+
setattr(self.args, "quiet", True)
|
|
259
|
+
|
|
260
|
+
# Apply programmatic quiet setting (overrides CLI if both are set)
|
|
261
|
+
if self._programmatic_quiet:
|
|
262
|
+
self.args.quiet = True
|
|
263
|
+
|
|
264
|
+
# Apply CLI skills directory if not already set programmatically
|
|
265
|
+
if (
|
|
266
|
+
self._skills_directory_override is None
|
|
267
|
+
and hasattr(self.args, "skills")
|
|
268
|
+
and self.args.skills
|
|
269
|
+
):
|
|
270
|
+
self._skills_directory_override = Path(self.args.skills).expanduser()
|
|
271
|
+
|
|
272
|
+
self.name = name
|
|
273
|
+
self.config_path = config_path
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
# Load configuration directly for this instance
|
|
277
|
+
self._load_config()
|
|
278
|
+
|
|
279
|
+
# Apply programmatic quiet mode to config before creating app
|
|
280
|
+
if self._programmatic_quiet and hasattr(self, "config"):
|
|
281
|
+
if "logger" not in self.config:
|
|
282
|
+
self.config["logger"] = {}
|
|
283
|
+
self.config["logger"]["progress_display"] = False
|
|
284
|
+
self.config["logger"]["show_chat"] = False
|
|
285
|
+
self.config["logger"]["show_tools"] = False
|
|
286
|
+
|
|
287
|
+
# Create the app with our local settings
|
|
288
|
+
self.app = Core(
|
|
289
|
+
name=name,
|
|
290
|
+
settings=config.Settings(**self.config) if hasattr(self, "config") else None,
|
|
291
|
+
**kwargs,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Stop progress display immediately if quiet mode is requested
|
|
295
|
+
if self._programmatic_quiet:
|
|
296
|
+
from fast_agent.ui.progress_display import progress_display
|
|
297
|
+
|
|
298
|
+
progress_display.stop()
|
|
299
|
+
|
|
300
|
+
except yaml.parser.ParserError as e:
|
|
301
|
+
handle_error(
|
|
302
|
+
e,
|
|
303
|
+
"YAML Parsing Error",
|
|
304
|
+
"There was an error parsing the config or secrets YAML configuration file.",
|
|
305
|
+
)
|
|
306
|
+
raise SystemExit(1)
|
|
307
|
+
|
|
308
|
+
# Dictionary to store agent configurations from decorators
|
|
309
|
+
self.agents: dict[str, dict[str, Any]] = {}
|
|
310
|
+
|
|
311
|
+
def _load_config(self) -> None:
|
|
312
|
+
"""Load configuration from YAML file including secrets using get_settings
|
|
313
|
+
but without relying on the global cache."""
|
|
314
|
+
|
|
315
|
+
# Import but make a local copy to avoid affecting the global state
|
|
316
|
+
from fast_agent.config import _settings, get_settings
|
|
317
|
+
|
|
318
|
+
# Temporarily clear the global settings to ensure a fresh load
|
|
319
|
+
old_settings = _settings
|
|
320
|
+
_settings = None
|
|
321
|
+
|
|
322
|
+
try:
|
|
323
|
+
# Use get_settings to load config - this handles all paths and secrets merging
|
|
324
|
+
settings = get_settings(self.config_path)
|
|
325
|
+
|
|
326
|
+
# Convert to dict for backward compatibility
|
|
327
|
+
self.config = settings.model_dump() if settings else {}
|
|
328
|
+
finally:
|
|
329
|
+
# Restore the original global settings
|
|
330
|
+
_settings = old_settings
|
|
331
|
+
|
|
332
|
+
@property
|
|
333
|
+
def context(self) -> Context:
|
|
334
|
+
"""Access the application context"""
|
|
335
|
+
return self.app.context
|
|
336
|
+
|
|
337
|
+
# Decorator methods with precise signatures for IDE completion
|
|
338
|
+
# Provide annotations so IDEs can discover these attributes on instances
|
|
339
|
+
if TYPE_CHECKING: # pragma: no cover - typing aid only
|
|
340
|
+
from collections.abc import Coroutine
|
|
341
|
+
from pathlib import Path
|
|
342
|
+
|
|
343
|
+
from fast_agent.skills import SkillManifest, SkillRegistry
|
|
344
|
+
from fast_agent.types import RequestParams
|
|
345
|
+
|
|
346
|
+
P = ParamSpec("P")
|
|
347
|
+
R = TypeVar("R")
|
|
348
|
+
|
|
349
|
+
def agent(
|
|
350
|
+
self,
|
|
351
|
+
name: str = "default",
|
|
352
|
+
instruction_or_kwarg: str | Path | AnyUrl | None = None,
|
|
353
|
+
*,
|
|
354
|
+
instruction: str | Path | AnyUrl = DEFAULT_AGENT_INSTRUCTION,
|
|
355
|
+
agents: list[str] | None = None,
|
|
356
|
+
servers: list[str] = [],
|
|
357
|
+
tools: dict[str, list[str]] | None = None,
|
|
358
|
+
resources: dict[str, list[str]] | None = None,
|
|
359
|
+
prompts: dict[str, list[str]] | None = None,
|
|
360
|
+
skills: list[SkillManifest | SkillRegistry | Path | str | None] | None = None,
|
|
361
|
+
model: str | None = None,
|
|
362
|
+
use_history: bool = True,
|
|
363
|
+
request_params: RequestParams | None = None,
|
|
364
|
+
human_input: bool = False,
|
|
365
|
+
default: bool = False,
|
|
366
|
+
elicitation_handler: ElicitationFnT | None = None,
|
|
367
|
+
api_key: str | None = None,
|
|
368
|
+
history_mode: Any | None = None,
|
|
369
|
+
max_parallel: int | None = None,
|
|
370
|
+
child_timeout_sec: int | None = None,
|
|
371
|
+
max_display_instances: int | None = None,
|
|
372
|
+
) -> Callable[
|
|
373
|
+
[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]
|
|
374
|
+
]: ...
|
|
375
|
+
|
|
376
|
+
def custom(
|
|
377
|
+
self,
|
|
378
|
+
cls,
|
|
379
|
+
name: str = "default",
|
|
380
|
+
instruction_or_kwarg: str | Path | AnyUrl | None = None,
|
|
381
|
+
*,
|
|
382
|
+
instruction: str | Path | AnyUrl = DEFAULT_AGENT_INSTRUCTION,
|
|
383
|
+
servers: list[str] = [],
|
|
384
|
+
tools: dict[str, list[str]] | None = None,
|
|
385
|
+
resources: dict[str, list[str]] | None = None,
|
|
386
|
+
prompts: dict[str, list[str]] | None = None,
|
|
387
|
+
model: str | None = None,
|
|
388
|
+
use_history: bool = True,
|
|
389
|
+
request_params: RequestParams | None = None,
|
|
390
|
+
human_input: bool = False,
|
|
391
|
+
default: bool = False,
|
|
392
|
+
elicitation_handler: ElicitationFnT | None = None,
|
|
393
|
+
api_key: str | None = None,
|
|
394
|
+
) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]: ...
|
|
395
|
+
|
|
396
|
+
def orchestrator(
|
|
397
|
+
self,
|
|
398
|
+
name: str,
|
|
399
|
+
*,
|
|
400
|
+
agents: list[str],
|
|
401
|
+
instruction: str
|
|
402
|
+
| Path
|
|
403
|
+
| AnyUrl = "You are an expert planner. Given an objective task and a list of Agents\n(which are collections of capabilities), your job is to break down the objective\ninto a series of steps, which can be performed by these agents.\n",
|
|
404
|
+
model: str | None = None,
|
|
405
|
+
request_params: RequestParams | None = None,
|
|
406
|
+
use_history: bool = False,
|
|
407
|
+
human_input: bool = False,
|
|
408
|
+
plan_type: Literal["full", "iterative"] = "full",
|
|
409
|
+
plan_iterations: int = 5,
|
|
410
|
+
default: bool = False,
|
|
411
|
+
api_key: str | None = None,
|
|
412
|
+
) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]: ...
|
|
413
|
+
|
|
414
|
+
def iterative_planner(
|
|
415
|
+
self,
|
|
416
|
+
name: str,
|
|
417
|
+
*,
|
|
418
|
+
agents: list[str],
|
|
419
|
+
instruction: str | Path | AnyUrl = "You are an expert planner. Plan iteratively.",
|
|
420
|
+
model: str | None = None,
|
|
421
|
+
request_params: RequestParams | None = None,
|
|
422
|
+
plan_iterations: int = -1,
|
|
423
|
+
default: bool = False,
|
|
424
|
+
api_key: str | None = None,
|
|
425
|
+
) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]: ...
|
|
426
|
+
|
|
427
|
+
def router(
|
|
428
|
+
self,
|
|
429
|
+
name: str,
|
|
430
|
+
*,
|
|
431
|
+
agents: list[str],
|
|
432
|
+
instruction: str | Path | AnyUrl | None = None,
|
|
433
|
+
servers: list[str] = [],
|
|
434
|
+
tools: dict[str, list[str]] | None = None,
|
|
435
|
+
resources: dict[str, list[str]] | None = None,
|
|
436
|
+
prompts: dict[str, list[str]] | None = None,
|
|
437
|
+
model: str | None = None,
|
|
438
|
+
use_history: bool = False,
|
|
439
|
+
request_params: RequestParams | None = None,
|
|
440
|
+
human_input: bool = False,
|
|
441
|
+
default: bool = False,
|
|
442
|
+
elicitation_handler: ElicitationFnT | None = None,
|
|
443
|
+
api_key: str | None = None,
|
|
444
|
+
) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]: ...
|
|
445
|
+
|
|
446
|
+
def chain(
|
|
447
|
+
self,
|
|
448
|
+
name: str,
|
|
449
|
+
*,
|
|
450
|
+
sequence: list[str],
|
|
451
|
+
instruction: str | Path | AnyUrl | None = None,
|
|
452
|
+
cumulative: bool = False,
|
|
453
|
+
default: bool = False,
|
|
454
|
+
) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]: ...
|
|
455
|
+
|
|
456
|
+
def parallel(
|
|
457
|
+
self,
|
|
458
|
+
name: str,
|
|
459
|
+
*,
|
|
460
|
+
fan_out: list[str],
|
|
461
|
+
fan_in: str | None = None,
|
|
462
|
+
instruction: str | Path | AnyUrl | None = None,
|
|
463
|
+
include_request: bool = True,
|
|
464
|
+
default: bool = False,
|
|
465
|
+
) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]: ...
|
|
466
|
+
|
|
467
|
+
def evaluator_optimizer(
|
|
468
|
+
self,
|
|
469
|
+
name: str,
|
|
470
|
+
*,
|
|
471
|
+
generator: str,
|
|
472
|
+
evaluator: str,
|
|
473
|
+
instruction: str | Path | AnyUrl | None = None,
|
|
474
|
+
min_rating: str = "GOOD",
|
|
475
|
+
max_refinements: int = 3,
|
|
476
|
+
refinement_instruction: str | None = None,
|
|
477
|
+
default: bool = False,
|
|
478
|
+
) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]: ...
|
|
479
|
+
|
|
480
|
+
def maker(
|
|
481
|
+
self,
|
|
482
|
+
name: str,
|
|
483
|
+
*,
|
|
484
|
+
worker: str,
|
|
485
|
+
k: int = 3,
|
|
486
|
+
max_samples: int = 50,
|
|
487
|
+
match_strategy: str = "exact",
|
|
488
|
+
red_flag_max_length: int | None = None,
|
|
489
|
+
instruction: str | Path | AnyUrl | None = None,
|
|
490
|
+
default: bool = False,
|
|
491
|
+
) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]: ...
|
|
492
|
+
|
|
493
|
+
# Runtime bindings (actual implementations)
|
|
494
|
+
agent = agent_decorator
|
|
495
|
+
custom = custom_decorator
|
|
496
|
+
orchestrator = orchestrator_decorator
|
|
497
|
+
iterative_planner = orchestrator2_decorator
|
|
498
|
+
router = router_decorator
|
|
499
|
+
chain = chain_decorator
|
|
500
|
+
parallel = parallel_decorator
|
|
501
|
+
evaluator_optimizer = evaluator_optimizer_decorator
|
|
502
|
+
maker = maker_decorator
|
|
503
|
+
|
|
504
|
+
def _get_acp_server_class(self):
|
|
505
|
+
"""Import and return the ACP server class with helpful error handling."""
|
|
506
|
+
try:
|
|
507
|
+
from fast_agent.acp.server import AgentACPServer
|
|
508
|
+
|
|
509
|
+
return AgentACPServer
|
|
510
|
+
except ModuleNotFoundError as exc:
|
|
511
|
+
if exc.name == "acp":
|
|
512
|
+
raise ServerInitializationError(
|
|
513
|
+
"ACP transport requires the 'agent-client-protocol' package. "
|
|
514
|
+
"Install it via `pip install fast-agent-mcp[acp]` or "
|
|
515
|
+
"`pip install agent-client-protocol`."
|
|
516
|
+
) from exc
|
|
517
|
+
raise
|
|
518
|
+
|
|
519
|
+
@asynccontextmanager
|
|
520
|
+
async def run(self) -> AsyncIterator["AgentApp"]:
|
|
521
|
+
"""
|
|
522
|
+
Context manager for running the application.
|
|
523
|
+
Initializes all registered agents.
|
|
524
|
+
"""
|
|
525
|
+
active_agents: dict[str, AgentProtocol] = {}
|
|
526
|
+
had_error = False
|
|
527
|
+
await self.app.initialize()
|
|
528
|
+
|
|
529
|
+
# Handle quiet mode and CLI model override safely
|
|
530
|
+
# Define these *before* they are used, checking if self.args exists and has the attributes
|
|
531
|
+
# Force quiet mode for stdio/acp transports to avoid polluting the protocol stream
|
|
532
|
+
quiet_mode = getattr(self.args, "quiet", False)
|
|
533
|
+
if getattr(self.args, "transport", None) in ["stdio", "acp"] and getattr(
|
|
534
|
+
self.args, "server", False
|
|
535
|
+
):
|
|
536
|
+
quiet_mode = True
|
|
537
|
+
cli_model_override = getattr(self.args, "model", None)
|
|
538
|
+
|
|
539
|
+
# Store the model source for UI display
|
|
540
|
+
model_source = get_default_model_source(
|
|
541
|
+
config_default_model=self.context.config.default_model,
|
|
542
|
+
cli_model=cli_model_override,
|
|
543
|
+
)
|
|
544
|
+
if self.context.config:
|
|
545
|
+
self.context.config.model_source = model_source # type: ignore[attr-defined]
|
|
546
|
+
|
|
547
|
+
tracer = trace.get_tracer(__name__)
|
|
548
|
+
with tracer.start_as_current_span(self.name):
|
|
549
|
+
try:
|
|
550
|
+
async with self.app.run():
|
|
551
|
+
registry = getattr(self.context, "skill_registry", None)
|
|
552
|
+
if self._skills_directory_override is not None:
|
|
553
|
+
override_registry = SkillRegistry(
|
|
554
|
+
base_dir=Path.cwd(),
|
|
555
|
+
override_directory=self._skills_directory_override,
|
|
556
|
+
)
|
|
557
|
+
self.context.skill_registry = override_registry
|
|
558
|
+
registry = override_registry
|
|
559
|
+
|
|
560
|
+
default_skills: list[SkillManifest] = []
|
|
561
|
+
if registry:
|
|
562
|
+
default_skills = registry.load_manifests()
|
|
563
|
+
|
|
564
|
+
self._apply_skills_to_agent_configs(default_skills)
|
|
565
|
+
|
|
566
|
+
# Apply quiet mode if requested
|
|
567
|
+
if quiet_mode:
|
|
568
|
+
cfg = self.app.context.config
|
|
569
|
+
if cfg is not None and cfg.logger is not None:
|
|
570
|
+
# Update our app's config directly
|
|
571
|
+
cfg_logger = cfg.logger
|
|
572
|
+
cfg_logger.progress_display = False
|
|
573
|
+
cfg_logger.show_chat = False
|
|
574
|
+
cfg_logger.show_tools = False
|
|
575
|
+
|
|
576
|
+
# Directly disable the progress display singleton
|
|
577
|
+
from fast_agent.ui.progress_display import progress_display
|
|
578
|
+
|
|
579
|
+
progress_display.stop()
|
|
580
|
+
|
|
581
|
+
# Pre-flight validation
|
|
582
|
+
if 0 == len(self.agents):
|
|
583
|
+
raise AgentConfigError(
|
|
584
|
+
"No agents defined. Please define at least one agent."
|
|
585
|
+
)
|
|
586
|
+
validate_server_references(self.context, self.agents)
|
|
587
|
+
validate_workflow_references(self.agents)
|
|
588
|
+
|
|
589
|
+
# Get a model factory function
|
|
590
|
+
# Now cli_model_override is guaranteed to be defined
|
|
591
|
+
def model_factory_func(model=None, request_params=None):
|
|
592
|
+
return get_model_factory(
|
|
593
|
+
self.context,
|
|
594
|
+
model=model,
|
|
595
|
+
request_params=request_params,
|
|
596
|
+
cli_model=cli_model_override, # Use the variable defined above
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
managed_instances: list[AgentInstance] = []
|
|
600
|
+
instance_lock = asyncio.Lock()
|
|
601
|
+
|
|
602
|
+
# Determine whether to apply global environment template variables.
|
|
603
|
+
apply_global_prompt_context = not (
|
|
604
|
+
getattr(self.args, "server", False)
|
|
605
|
+
and getattr(self.args, "transport", None) == "acp"
|
|
606
|
+
)
|
|
607
|
+
global_prompt_context: dict[str, str] | None = None
|
|
608
|
+
if apply_global_prompt_context:
|
|
609
|
+
context_variables: dict[str, str] = {}
|
|
610
|
+
client_info: dict[str, str] = {"name": self.name}
|
|
611
|
+
cli_name = getattr(self.args, "name", None)
|
|
612
|
+
if cli_name:
|
|
613
|
+
client_info["title"] = cli_name
|
|
614
|
+
|
|
615
|
+
# Pass skills directory override if configured
|
|
616
|
+
skills_override = (
|
|
617
|
+
str(self._skills_directory_override)
|
|
618
|
+
if self._skills_directory_override
|
|
619
|
+
else None
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
enrich_with_environment_context(
|
|
623
|
+
context_variables, str(Path.cwd()), client_info, skills_override
|
|
624
|
+
)
|
|
625
|
+
if context_variables:
|
|
626
|
+
global_prompt_context = context_variables
|
|
627
|
+
|
|
628
|
+
async def instantiate_agent_instance() -> AgentInstance:
|
|
629
|
+
async with instance_lock:
|
|
630
|
+
agents_map = await create_agents_in_dependency_order(
|
|
631
|
+
self.app,
|
|
632
|
+
self.agents,
|
|
633
|
+
model_factory_func,
|
|
634
|
+
)
|
|
635
|
+
validate_provider_keys_post_creation(agents_map)
|
|
636
|
+
instance = AgentInstance(AgentApp(agents_map), agents_map)
|
|
637
|
+
managed_instances.append(instance)
|
|
638
|
+
if global_prompt_context:
|
|
639
|
+
self._apply_instruction_context(instance, global_prompt_context)
|
|
640
|
+
return instance
|
|
641
|
+
|
|
642
|
+
async def dispose_agent_instance(instance: AgentInstance) -> None:
|
|
643
|
+
async with instance_lock:
|
|
644
|
+
if instance in managed_instances:
|
|
645
|
+
managed_instances.remove(instance)
|
|
646
|
+
await instance.shutdown()
|
|
647
|
+
|
|
648
|
+
primary_instance = await instantiate_agent_instance()
|
|
649
|
+
wrapper = primary_instance.app
|
|
650
|
+
active_agents = primary_instance.agents
|
|
651
|
+
|
|
652
|
+
self._server_instance_factory = instantiate_agent_instance
|
|
653
|
+
self._server_instance_dispose = dispose_agent_instance
|
|
654
|
+
self._server_managed_instances = managed_instances
|
|
655
|
+
|
|
656
|
+
# Disable streaming if parallel agents are present
|
|
657
|
+
from fast_agent.agents.agent_types import AgentType
|
|
658
|
+
|
|
659
|
+
has_parallel = any(
|
|
660
|
+
agent.agent_type == AgentType.PARALLEL for agent in active_agents.values()
|
|
661
|
+
)
|
|
662
|
+
if has_parallel:
|
|
663
|
+
cfg = self.app.context.config
|
|
664
|
+
if cfg is not None and cfg.logger is not None:
|
|
665
|
+
cfg.logger.streaming = "none"
|
|
666
|
+
|
|
667
|
+
# Handle command line options that should be processed after agent initialization
|
|
668
|
+
|
|
669
|
+
# Handle --server option
|
|
670
|
+
# Check if parse_cli_args was True before checking self.args.server
|
|
671
|
+
if hasattr(self.args, "server") and self.args.server:
|
|
672
|
+
# For stdio/acp transports, use stderr to avoid interfering with JSON-RPC
|
|
673
|
+
import sys
|
|
674
|
+
|
|
675
|
+
is_stdio_transport = self.args.transport in ["stdio", "acp"]
|
|
676
|
+
configure_console_stream("stderr" if is_stdio_transport else "stdout")
|
|
677
|
+
output_stream = sys.stderr if is_stdio_transport else sys.stdout
|
|
678
|
+
|
|
679
|
+
try:
|
|
680
|
+
# Print info message if not in quiet mode
|
|
681
|
+
if not quiet_mode:
|
|
682
|
+
print(
|
|
683
|
+
f"Starting fast-agent '{self.name}' in server mode",
|
|
684
|
+
file=output_stream,
|
|
685
|
+
)
|
|
686
|
+
print(f"Transport: {self.args.transport}", file=output_stream)
|
|
687
|
+
if self.args.transport == "sse":
|
|
688
|
+
print(
|
|
689
|
+
f"Listening on {self.args.host}:{self.args.port}",
|
|
690
|
+
file=output_stream,
|
|
691
|
+
)
|
|
692
|
+
print("Press Ctrl+C to stop", file=output_stream)
|
|
693
|
+
|
|
694
|
+
# Check which server type to use based on transport
|
|
695
|
+
if self.args.transport == "acp":
|
|
696
|
+
# Create and run ACP server
|
|
697
|
+
AgentACPServer = self._get_acp_server_class()
|
|
698
|
+
|
|
699
|
+
server_name = getattr(self.args, "server_name", None)
|
|
700
|
+
instance_scope = getattr(self.args, "instance_scope", "shared")
|
|
701
|
+
permissions_enabled = getattr(
|
|
702
|
+
self.args, "permissions_enabled", True
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
# Pass skills directory override if configured
|
|
706
|
+
skills_override = (
|
|
707
|
+
str(self._skills_directory_override)
|
|
708
|
+
if self._skills_directory_override
|
|
709
|
+
else None
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
acp_server = AgentACPServer(
|
|
713
|
+
primary_instance=primary_instance,
|
|
714
|
+
create_instance=self._server_instance_factory,
|
|
715
|
+
dispose_instance=self._server_instance_dispose,
|
|
716
|
+
instance_scope=instance_scope,
|
|
717
|
+
server_name=server_name or f"{self.name}",
|
|
718
|
+
skills_directory_override=skills_override,
|
|
719
|
+
permissions_enabled=permissions_enabled,
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
# Run the ACP server (this is a blocking call)
|
|
723
|
+
await acp_server.run_async()
|
|
724
|
+
else:
|
|
725
|
+
# Create the MCP server
|
|
726
|
+
from fast_agent.mcp.server import AgentMCPServer
|
|
727
|
+
|
|
728
|
+
tool_description = getattr(self.args, "tool_description", None)
|
|
729
|
+
server_description = getattr(self.args, "server_description", None)
|
|
730
|
+
server_name = getattr(self.args, "server_name", None)
|
|
731
|
+
instance_scope = getattr(self.args, "instance_scope", "shared")
|
|
732
|
+
mcp_server = AgentMCPServer(
|
|
733
|
+
primary_instance=primary_instance,
|
|
734
|
+
create_instance=self._server_instance_factory,
|
|
735
|
+
dispose_instance=self._server_instance_dispose,
|
|
736
|
+
instance_scope=instance_scope,
|
|
737
|
+
server_name=server_name or f"{self.name}-MCP-Server",
|
|
738
|
+
server_description=server_description,
|
|
739
|
+
tool_description=tool_description,
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
# Run the server directly (this is a blocking call)
|
|
743
|
+
await mcp_server.run_async(
|
|
744
|
+
transport=self.args.transport,
|
|
745
|
+
host=self.args.host,
|
|
746
|
+
port=self.args.port,
|
|
747
|
+
)
|
|
748
|
+
except KeyboardInterrupt:
|
|
749
|
+
if not quiet_mode:
|
|
750
|
+
print("\nServer stopped by user (Ctrl+C)", file=output_stream)
|
|
751
|
+
raise SystemExit(0)
|
|
752
|
+
except Exception as e:
|
|
753
|
+
if not quiet_mode:
|
|
754
|
+
import traceback
|
|
755
|
+
|
|
756
|
+
traceback.print_exc()
|
|
757
|
+
print(f"\nServer stopped with error: {e}", file=output_stream)
|
|
758
|
+
else:
|
|
759
|
+
print(f"\nServer stopped with error: {e}", file=output_stream)
|
|
760
|
+
raise SystemExit(1)
|
|
761
|
+
|
|
762
|
+
# Exit after server shutdown
|
|
763
|
+
raise SystemExit(0)
|
|
764
|
+
|
|
765
|
+
# Handle direct message sending if --message is provided
|
|
766
|
+
if hasattr(self.args, "message") and self.args.message:
|
|
767
|
+
agent_name = self.args.agent
|
|
768
|
+
message = self.args.message
|
|
769
|
+
|
|
770
|
+
if agent_name not in active_agents:
|
|
771
|
+
available_agents = ", ".join(active_agents.keys())
|
|
772
|
+
print(
|
|
773
|
+
f"\n\nError: Agent '{agent_name}' not found. Available agents: {available_agents}"
|
|
774
|
+
)
|
|
775
|
+
raise SystemExit(1)
|
|
776
|
+
|
|
777
|
+
try:
|
|
778
|
+
# Get response from the agent
|
|
779
|
+
agent = active_agents[agent_name]
|
|
780
|
+
response = await agent.send(message)
|
|
781
|
+
|
|
782
|
+
# In quiet mode, just print the raw response
|
|
783
|
+
# The chat display should already be turned off by the configuration
|
|
784
|
+
if self.args.quiet:
|
|
785
|
+
print(f"{response}")
|
|
786
|
+
|
|
787
|
+
raise SystemExit(0)
|
|
788
|
+
except Exception as e:
|
|
789
|
+
print(f"\n\nError sending message to agent '{agent_name}': {str(e)}")
|
|
790
|
+
raise SystemExit(1)
|
|
791
|
+
|
|
792
|
+
if hasattr(self.args, "prompt_file") and self.args.prompt_file:
|
|
793
|
+
agent_name = self.args.agent
|
|
794
|
+
prompt: list[PromptMessageExtended] = load_prompt(
|
|
795
|
+
Path(self.args.prompt_file)
|
|
796
|
+
)
|
|
797
|
+
if agent_name not in active_agents:
|
|
798
|
+
available_agents = ", ".join(active_agents.keys())
|
|
799
|
+
print(
|
|
800
|
+
f"\n\nError: Agent '{agent_name}' not found. Available agents: {available_agents}"
|
|
801
|
+
)
|
|
802
|
+
raise SystemExit(1)
|
|
803
|
+
|
|
804
|
+
try:
|
|
805
|
+
# Get response from the agent
|
|
806
|
+
agent = active_agents[agent_name]
|
|
807
|
+
prompt_result = await agent.generate(prompt)
|
|
808
|
+
|
|
809
|
+
# In quiet mode, just print the raw response
|
|
810
|
+
# The chat display should already be turned off by the configuration
|
|
811
|
+
if self.args.quiet:
|
|
812
|
+
print(f"{prompt_result.last_text()}")
|
|
813
|
+
|
|
814
|
+
raise SystemExit(0)
|
|
815
|
+
except Exception as e:
|
|
816
|
+
print(f"\n\nError sending message to agent '{agent_name}': {str(e)}")
|
|
817
|
+
raise SystemExit(1)
|
|
818
|
+
|
|
819
|
+
yield wrapper
|
|
820
|
+
|
|
821
|
+
except PromptExitError as e:
|
|
822
|
+
# User requested exit - not an error, show usage report
|
|
823
|
+
self._handle_error(e)
|
|
824
|
+
raise SystemExit(0)
|
|
825
|
+
except (
|
|
826
|
+
ServerConfigError,
|
|
827
|
+
ProviderKeyError,
|
|
828
|
+
AgentConfigError,
|
|
829
|
+
ServerInitializationError,
|
|
830
|
+
ModelConfigError,
|
|
831
|
+
CircularDependencyError,
|
|
832
|
+
) as e:
|
|
833
|
+
had_error = True
|
|
834
|
+
self._handle_error(e)
|
|
835
|
+
raise SystemExit(1)
|
|
836
|
+
|
|
837
|
+
finally:
|
|
838
|
+
# Ensure progress display is stopped before showing usage summary
|
|
839
|
+
try:
|
|
840
|
+
from fast_agent.ui.progress_display import progress_display
|
|
841
|
+
|
|
842
|
+
progress_display.stop()
|
|
843
|
+
except: # noqa: E722
|
|
844
|
+
pass
|
|
845
|
+
|
|
846
|
+
# Print usage report before cleanup (show for user exits too)
|
|
847
|
+
if (
|
|
848
|
+
getattr(self, "_server_managed_instances", None)
|
|
849
|
+
and not had_error
|
|
850
|
+
and not quiet_mode
|
|
851
|
+
and getattr(self.args, "server", False) is False
|
|
852
|
+
):
|
|
853
|
+
# Only show usage report for non-server interactive runs
|
|
854
|
+
if managed_instances:
|
|
855
|
+
instance = managed_instances[0]
|
|
856
|
+
self._print_usage_report(instance.agents)
|
|
857
|
+
elif active_agents and not had_error and not quiet_mode:
|
|
858
|
+
self._print_usage_report(active_agents)
|
|
859
|
+
|
|
860
|
+
# Clean up any active agents (always cleanup, even on errors)
|
|
861
|
+
if getattr(self, "_server_managed_instances", None) and getattr(
|
|
862
|
+
self, "_server_instance_dispose", None
|
|
863
|
+
):
|
|
864
|
+
# Dispose any remaining instances
|
|
865
|
+
remaining_instances = list(self._server_managed_instances)
|
|
866
|
+
for instance in remaining_instances:
|
|
867
|
+
try:
|
|
868
|
+
await self._server_instance_dispose(instance)
|
|
869
|
+
except Exception:
|
|
870
|
+
pass
|
|
871
|
+
self._server_managed_instances.clear()
|
|
872
|
+
elif active_agents:
|
|
873
|
+
for agent in active_agents.values():
|
|
874
|
+
try:
|
|
875
|
+
await agent.shutdown()
|
|
876
|
+
except Exception:
|
|
877
|
+
pass
|
|
878
|
+
|
|
879
|
+
def _apply_instruction_context(
|
|
880
|
+
self, instance: AgentInstance, context_vars: dict[str, str]
|
|
881
|
+
) -> None:
|
|
882
|
+
"""Resolve late-binding placeholders for all agents in the provided instance."""
|
|
883
|
+
if not context_vars:
|
|
884
|
+
return
|
|
885
|
+
|
|
886
|
+
for agent in instance.agents.values():
|
|
887
|
+
template = getattr(agent, "instruction", None)
|
|
888
|
+
if not template:
|
|
889
|
+
continue
|
|
890
|
+
|
|
891
|
+
resolved = apply_template_variables(template, context_vars)
|
|
892
|
+
if resolved == template:
|
|
893
|
+
continue
|
|
894
|
+
|
|
895
|
+
agent.instruction = resolved
|
|
896
|
+
|
|
897
|
+
# Note: We intentionally do NOT modify config.instruction here.
|
|
898
|
+
# The config should preserve the original template so that
|
|
899
|
+
# downstream logic (like MCP display) can check for template
|
|
900
|
+
# variables like {{serverInstructions}}.
|
|
901
|
+
|
|
902
|
+
request_params = getattr(agent, "_default_request_params", None)
|
|
903
|
+
if request_params is not None:
|
|
904
|
+
request_params.systemPrompt = resolved
|
|
905
|
+
|
|
906
|
+
# TODO -- find a cleaner way of doing this
|
|
907
|
+
# Keep any attached LLM in sync so the provider sees the resolved prompt
|
|
908
|
+
llm = getattr(agent, "_llm", None)
|
|
909
|
+
if llm is not None:
|
|
910
|
+
if getattr(llm, "default_request_params", None) is not None:
|
|
911
|
+
llm.default_request_params.systemPrompt = resolved
|
|
912
|
+
if hasattr(llm, "instruction"):
|
|
913
|
+
llm.instruction = resolved
|
|
914
|
+
|
|
915
|
+
def _apply_skills_to_agent_configs(self, default_skills: list[SkillManifest]) -> None:
|
|
916
|
+
self._default_skill_manifests = list(default_skills)
|
|
917
|
+
|
|
918
|
+
for agent_data in self.agents.values():
|
|
919
|
+
config_obj = agent_data.get("config")
|
|
920
|
+
if not config_obj:
|
|
921
|
+
continue
|
|
922
|
+
|
|
923
|
+
resolved = self._resolve_skills(config_obj.skills)
|
|
924
|
+
if not resolved:
|
|
925
|
+
resolved = list(default_skills)
|
|
926
|
+
else:
|
|
927
|
+
resolved = self._deduplicate_skills(resolved)
|
|
928
|
+
|
|
929
|
+
config_obj.skill_manifests = resolved
|
|
930
|
+
|
|
931
|
+
def _resolve_skills(
|
|
932
|
+
self,
|
|
933
|
+
entry: SkillManifest
|
|
934
|
+
| SkillRegistry
|
|
935
|
+
| Path
|
|
936
|
+
| str
|
|
937
|
+
| list[SkillManifest | SkillRegistry | Path | str | None]
|
|
938
|
+
| None,
|
|
939
|
+
) -> list[SkillManifest]:
|
|
940
|
+
if entry is None:
|
|
941
|
+
return []
|
|
942
|
+
if isinstance(entry, list):
|
|
943
|
+
manifests: list[SkillManifest] = []
|
|
944
|
+
for item in entry:
|
|
945
|
+
manifests.extend(self._resolve_skills(item))
|
|
946
|
+
return manifests
|
|
947
|
+
if isinstance(entry, SkillManifest):
|
|
948
|
+
return [entry]
|
|
949
|
+
if isinstance(entry, SkillRegistry):
|
|
950
|
+
try:
|
|
951
|
+
return entry.load_manifests()
|
|
952
|
+
except Exception:
|
|
953
|
+
logger.debug(
|
|
954
|
+
"Failed to load skills from registry",
|
|
955
|
+
data={"registry": type(entry).__name__},
|
|
956
|
+
)
|
|
957
|
+
return []
|
|
958
|
+
if isinstance(entry, (Path, str)):
|
|
959
|
+
# Use instance method to preserve original path for relative path computation
|
|
960
|
+
path = Path(entry) if isinstance(entry, str) else entry
|
|
961
|
+
registry = SkillRegistry(base_dir=Path.cwd(), override_directory=path)
|
|
962
|
+
return registry.load_manifests()
|
|
963
|
+
|
|
964
|
+
logger.debug(
|
|
965
|
+
"Unsupported skill entry type",
|
|
966
|
+
data={"type": type(entry).__name__},
|
|
967
|
+
)
|
|
968
|
+
return []
|
|
969
|
+
|
|
970
|
+
@staticmethod
|
|
971
|
+
def _deduplicate_skills(manifests: list[SkillManifest]) -> list[SkillManifest]:
|
|
972
|
+
unique: dict[str, SkillManifest] = {}
|
|
973
|
+
for manifest in manifests:
|
|
974
|
+
key = manifest.name.lower()
|
|
975
|
+
if key not in unique:
|
|
976
|
+
unique[key] = manifest
|
|
977
|
+
return list(unique.values())
|
|
978
|
+
|
|
979
|
+
def _handle_error(self, e: Exception, error_type: str | None = None) -> None:
|
|
980
|
+
"""
|
|
981
|
+
Handle errors with consistent formatting and messaging.
|
|
982
|
+
|
|
983
|
+
Args:
|
|
984
|
+
e: The exception that was raised
|
|
985
|
+
error_type: Optional explicit error type
|
|
986
|
+
"""
|
|
987
|
+
if isinstance(e, ServerConfigError):
|
|
988
|
+
handle_error(
|
|
989
|
+
e,
|
|
990
|
+
"Server Configuration Error",
|
|
991
|
+
"Please check your 'fastagent.config.yaml' configuration file and add the missing server definitions.",
|
|
992
|
+
)
|
|
993
|
+
elif isinstance(e, ProviderKeyError):
|
|
994
|
+
handle_error(
|
|
995
|
+
e,
|
|
996
|
+
"Provider Configuration Error",
|
|
997
|
+
"Please check your 'fastagent.secrets.yaml' configuration file and ensure all required API keys are set.",
|
|
998
|
+
)
|
|
999
|
+
elif isinstance(e, AgentConfigError):
|
|
1000
|
+
handle_error(
|
|
1001
|
+
e,
|
|
1002
|
+
"Workflow or Agent Configuration Error",
|
|
1003
|
+
"Please check your agent definition and ensure names and references are correct.",
|
|
1004
|
+
)
|
|
1005
|
+
elif isinstance(e, ServerInitializationError):
|
|
1006
|
+
handle_error(
|
|
1007
|
+
e,
|
|
1008
|
+
"MCP Server Startup Error",
|
|
1009
|
+
"There was an error starting up the MCP Server.",
|
|
1010
|
+
)
|
|
1011
|
+
elif isinstance(e, ModelConfigError):
|
|
1012
|
+
handle_error(
|
|
1013
|
+
e,
|
|
1014
|
+
"Model Configuration Error",
|
|
1015
|
+
"Common models: gpt-5.1, kimi, sonnet, haiku. Set reasoning effort on supported models with gpt-5-mini.high",
|
|
1016
|
+
)
|
|
1017
|
+
elif isinstance(e, CircularDependencyError):
|
|
1018
|
+
handle_error(
|
|
1019
|
+
e,
|
|
1020
|
+
"Circular Dependency Detected",
|
|
1021
|
+
"Check your agent configuration for circular dependencies.",
|
|
1022
|
+
)
|
|
1023
|
+
elif isinstance(e, PromptExitError):
|
|
1024
|
+
handle_error(
|
|
1025
|
+
e,
|
|
1026
|
+
"User requested exit",
|
|
1027
|
+
)
|
|
1028
|
+
elif isinstance(e, asyncio.CancelledError):
|
|
1029
|
+
handle_error(
|
|
1030
|
+
e,
|
|
1031
|
+
"Cancelled",
|
|
1032
|
+
"The operation was cancelled.",
|
|
1033
|
+
)
|
|
1034
|
+
else:
|
|
1035
|
+
handle_error(e, error_type or "Error", "An unexpected error occurred.")
|
|
1036
|
+
|
|
1037
|
+
def _print_usage_report(self, active_agents: dict) -> None:
|
|
1038
|
+
"""Print a formatted table of token usage for all agents."""
|
|
1039
|
+
display_usage_report(active_agents, show_if_progress_disabled=False, subdued_colors=True)
|
|
1040
|
+
|
|
1041
|
+
async def start_server(
|
|
1042
|
+
self,
|
|
1043
|
+
transport: str = "http",
|
|
1044
|
+
host: str = "0.0.0.0",
|
|
1045
|
+
port: int = 8000,
|
|
1046
|
+
server_name: str | None = None,
|
|
1047
|
+
server_description: str | None = None,
|
|
1048
|
+
tool_description: str | None = None,
|
|
1049
|
+
instance_scope: str = "shared",
|
|
1050
|
+
permissions_enabled: bool = True,
|
|
1051
|
+
) -> None:
|
|
1052
|
+
"""
|
|
1053
|
+
Start the application as an MCP server.
|
|
1054
|
+
This method initializes agents and exposes them through an MCP server.
|
|
1055
|
+
It is a blocking method that runs until the server is stopped.
|
|
1056
|
+
|
|
1057
|
+
Args:
|
|
1058
|
+
transport: Transport protocol to use ("stdio" or "sse")
|
|
1059
|
+
host: Host address for the server when using SSE
|
|
1060
|
+
port: Port for the server when using SSE
|
|
1061
|
+
server_name: Optional custom name for the MCP server
|
|
1062
|
+
server_description: Optional description/instructions for the MCP server
|
|
1063
|
+
tool_description: Optional description template for the exposed send tool.
|
|
1064
|
+
Use {agent} to reference the agent name.
|
|
1065
|
+
permissions_enabled: Whether to request tool permissions from ACP clients (default: True)
|
|
1066
|
+
"""
|
|
1067
|
+
# This method simply updates the command line arguments and uses run()
|
|
1068
|
+
# to ensure we follow the same initialization path for all operations
|
|
1069
|
+
|
|
1070
|
+
# Store original args
|
|
1071
|
+
original_args = None
|
|
1072
|
+
if hasattr(self, "args"):
|
|
1073
|
+
original_args = self.args
|
|
1074
|
+
|
|
1075
|
+
# Create our own args object with server settings
|
|
1076
|
+
from argparse import Namespace
|
|
1077
|
+
|
|
1078
|
+
self.args = Namespace()
|
|
1079
|
+
self.args.server = True
|
|
1080
|
+
self.args.transport = transport
|
|
1081
|
+
self.args.host = host
|
|
1082
|
+
self.args.port = port
|
|
1083
|
+
self.args.tool_description = tool_description
|
|
1084
|
+
self.args.server_description = server_description
|
|
1085
|
+
self.args.server_name = server_name
|
|
1086
|
+
self.args.instance_scope = instance_scope
|
|
1087
|
+
self.args.permissions_enabled = permissions_enabled
|
|
1088
|
+
# Force quiet mode for stdio/acp transports to avoid polluting the protocol stream
|
|
1089
|
+
self.args.quiet = (
|
|
1090
|
+
original_args.quiet if original_args and hasattr(original_args, "quiet") else False
|
|
1091
|
+
)
|
|
1092
|
+
if transport in ["stdio", "acp"]:
|
|
1093
|
+
self.args.quiet = True
|
|
1094
|
+
self.args.model = None
|
|
1095
|
+
if original_args is not None and hasattr(original_args, "model"):
|
|
1096
|
+
self.args.model = original_args.model
|
|
1097
|
+
|
|
1098
|
+
# Run the application, which will detect the server flag and start server mode
|
|
1099
|
+
async with self.run():
|
|
1100
|
+
pass # This won't be reached due to SystemExit in run()
|
|
1101
|
+
|
|
1102
|
+
# Restore original args (if we get here)
|
|
1103
|
+
if original_args:
|
|
1104
|
+
self.args = original_args
|
|
1105
|
+
|
|
1106
|
+
# Keep run_with_mcp_server for backward compatibility
|
|
1107
|
+
async def run_with_mcp_server(
|
|
1108
|
+
self,
|
|
1109
|
+
transport: str = "sse",
|
|
1110
|
+
host: str = "0.0.0.0",
|
|
1111
|
+
port: int = 8000,
|
|
1112
|
+
server_name: str | None = None,
|
|
1113
|
+
server_description: str | None = None,
|
|
1114
|
+
tool_description: str | None = None,
|
|
1115
|
+
instance_scope: str = "shared",
|
|
1116
|
+
) -> None:
|
|
1117
|
+
"""
|
|
1118
|
+
Run the application and expose agents through an MCP server.
|
|
1119
|
+
This method is kept for backward compatibility.
|
|
1120
|
+
For new code, use start_server() instead.
|
|
1121
|
+
|
|
1122
|
+
Args:
|
|
1123
|
+
transport: Transport protocol to use ("stdio" or "sse")
|
|
1124
|
+
host: Host address for the server when using SSE
|
|
1125
|
+
port: Port for the server when using SSE
|
|
1126
|
+
server_name: Optional custom name for the MCP server
|
|
1127
|
+
server_description: Optional description/instructions for the MCP server
|
|
1128
|
+
tool_description: Optional description template for the exposed send tool.
|
|
1129
|
+
"""
|
|
1130
|
+
await self.start_server(
|
|
1131
|
+
transport=transport,
|
|
1132
|
+
host=host,
|
|
1133
|
+
port=port,
|
|
1134
|
+
server_name=server_name,
|
|
1135
|
+
server_description=server_description,
|
|
1136
|
+
tool_description=tool_description,
|
|
1137
|
+
instance_scope=instance_scope,
|
|
1138
|
+
)
|
|
1139
|
+
|
|
1140
|
+
async def main(self):
|
|
1141
|
+
"""
|
|
1142
|
+
Helper method for checking if server mode was requested.
|
|
1143
|
+
|
|
1144
|
+
Usage:
|
|
1145
|
+
```python
|
|
1146
|
+
fast = FastAgent("My App")
|
|
1147
|
+
|
|
1148
|
+
@fast.agent(...)
|
|
1149
|
+
async def app_main():
|
|
1150
|
+
# Check if server mode was requested
|
|
1151
|
+
# This doesn't actually do anything - the check happens in run()
|
|
1152
|
+
# But it provides a way for application code to know if server mode
|
|
1153
|
+
# was requested for conditionals
|
|
1154
|
+
is_server_mode = hasattr(self, "args") and self.args.server
|
|
1155
|
+
|
|
1156
|
+
# Normal run - this will handle server mode automatically if requested
|
|
1157
|
+
async with fast.run() as agent:
|
|
1158
|
+
# This code only executes for normal mode
|
|
1159
|
+
# Server mode will exit before reaching here
|
|
1160
|
+
await agent.send("Hello")
|
|
1161
|
+
```
|
|
1162
|
+
|
|
1163
|
+
Returns:
|
|
1164
|
+
bool: True if --server flag is set, False otherwise
|
|
1165
|
+
"""
|
|
1166
|
+
# Just check if the flag is set, no action here
|
|
1167
|
+
# The actual server code will be handled by run()
|
|
1168
|
+
return hasattr(self, "args") and self.args.server
|
|
1169
|
+
|
|
1170
|
+
|
|
1171
|
+
@dataclass
|
|
1172
|
+
class AgentInstance:
|
|
1173
|
+
app: AgentApp
|
|
1174
|
+
agents: dict[str, "AgentProtocol"]
|
|
1175
|
+
|
|
1176
|
+
async def shutdown(self) -> None:
|
|
1177
|
+
for agent in self.agents.values():
|
|
1178
|
+
try:
|
|
1179
|
+
shutdown = getattr(agent, "shutdown", None)
|
|
1180
|
+
if shutdown is None:
|
|
1181
|
+
continue
|
|
1182
|
+
result = shutdown()
|
|
1183
|
+
if inspect.isawaitable(result):
|
|
1184
|
+
await result
|
|
1185
|
+
except Exception:
|
|
1186
|
+
pass
|