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.
Files changed (261) hide show
  1. fast_agent/__init__.py +183 -0
  2. fast_agent/acp/__init__.py +19 -0
  3. fast_agent/acp/acp_aware_mixin.py +304 -0
  4. fast_agent/acp/acp_context.py +437 -0
  5. fast_agent/acp/content_conversion.py +136 -0
  6. fast_agent/acp/filesystem_runtime.py +427 -0
  7. fast_agent/acp/permission_store.py +269 -0
  8. fast_agent/acp/server/__init__.py +5 -0
  9. fast_agent/acp/server/agent_acp_server.py +1472 -0
  10. fast_agent/acp/slash_commands.py +1050 -0
  11. fast_agent/acp/terminal_runtime.py +408 -0
  12. fast_agent/acp/tool_permission_adapter.py +125 -0
  13. fast_agent/acp/tool_permissions.py +474 -0
  14. fast_agent/acp/tool_progress.py +814 -0
  15. fast_agent/agents/__init__.py +85 -0
  16. fast_agent/agents/agent_types.py +64 -0
  17. fast_agent/agents/llm_agent.py +350 -0
  18. fast_agent/agents/llm_decorator.py +1139 -0
  19. fast_agent/agents/mcp_agent.py +1337 -0
  20. fast_agent/agents/tool_agent.py +271 -0
  21. fast_agent/agents/workflow/agents_as_tools_agent.py +849 -0
  22. fast_agent/agents/workflow/chain_agent.py +212 -0
  23. fast_agent/agents/workflow/evaluator_optimizer.py +380 -0
  24. fast_agent/agents/workflow/iterative_planner.py +652 -0
  25. fast_agent/agents/workflow/maker_agent.py +379 -0
  26. fast_agent/agents/workflow/orchestrator_models.py +218 -0
  27. fast_agent/agents/workflow/orchestrator_prompts.py +248 -0
  28. fast_agent/agents/workflow/parallel_agent.py +250 -0
  29. fast_agent/agents/workflow/router_agent.py +353 -0
  30. fast_agent/cli/__init__.py +0 -0
  31. fast_agent/cli/__main__.py +73 -0
  32. fast_agent/cli/commands/acp.py +159 -0
  33. fast_agent/cli/commands/auth.py +404 -0
  34. fast_agent/cli/commands/check_config.py +783 -0
  35. fast_agent/cli/commands/go.py +514 -0
  36. fast_agent/cli/commands/quickstart.py +557 -0
  37. fast_agent/cli/commands/serve.py +143 -0
  38. fast_agent/cli/commands/server_helpers.py +114 -0
  39. fast_agent/cli/commands/setup.py +174 -0
  40. fast_agent/cli/commands/url_parser.py +190 -0
  41. fast_agent/cli/constants.py +40 -0
  42. fast_agent/cli/main.py +115 -0
  43. fast_agent/cli/terminal.py +24 -0
  44. fast_agent/config.py +798 -0
  45. fast_agent/constants.py +41 -0
  46. fast_agent/context.py +279 -0
  47. fast_agent/context_dependent.py +50 -0
  48. fast_agent/core/__init__.py +92 -0
  49. fast_agent/core/agent_app.py +448 -0
  50. fast_agent/core/core_app.py +137 -0
  51. fast_agent/core/direct_decorators.py +784 -0
  52. fast_agent/core/direct_factory.py +620 -0
  53. fast_agent/core/error_handling.py +27 -0
  54. fast_agent/core/exceptions.py +90 -0
  55. fast_agent/core/executor/__init__.py +0 -0
  56. fast_agent/core/executor/executor.py +280 -0
  57. fast_agent/core/executor/task_registry.py +32 -0
  58. fast_agent/core/executor/workflow_signal.py +324 -0
  59. fast_agent/core/fastagent.py +1186 -0
  60. fast_agent/core/logging/__init__.py +5 -0
  61. fast_agent/core/logging/events.py +138 -0
  62. fast_agent/core/logging/json_serializer.py +164 -0
  63. fast_agent/core/logging/listeners.py +309 -0
  64. fast_agent/core/logging/logger.py +278 -0
  65. fast_agent/core/logging/transport.py +481 -0
  66. fast_agent/core/prompt.py +9 -0
  67. fast_agent/core/prompt_templates.py +183 -0
  68. fast_agent/core/validation.py +326 -0
  69. fast_agent/event_progress.py +62 -0
  70. fast_agent/history/history_exporter.py +49 -0
  71. fast_agent/human_input/__init__.py +47 -0
  72. fast_agent/human_input/elicitation_handler.py +123 -0
  73. fast_agent/human_input/elicitation_state.py +33 -0
  74. fast_agent/human_input/form_elements.py +59 -0
  75. fast_agent/human_input/form_fields.py +256 -0
  76. fast_agent/human_input/simple_form.py +113 -0
  77. fast_agent/human_input/types.py +40 -0
  78. fast_agent/interfaces.py +310 -0
  79. fast_agent/llm/__init__.py +9 -0
  80. fast_agent/llm/cancellation.py +22 -0
  81. fast_agent/llm/fastagent_llm.py +931 -0
  82. fast_agent/llm/internal/passthrough.py +161 -0
  83. fast_agent/llm/internal/playback.py +129 -0
  84. fast_agent/llm/internal/silent.py +41 -0
  85. fast_agent/llm/internal/slow.py +38 -0
  86. fast_agent/llm/memory.py +275 -0
  87. fast_agent/llm/model_database.py +490 -0
  88. fast_agent/llm/model_factory.py +388 -0
  89. fast_agent/llm/model_info.py +102 -0
  90. fast_agent/llm/prompt_utils.py +155 -0
  91. fast_agent/llm/provider/anthropic/anthropic_utils.py +84 -0
  92. fast_agent/llm/provider/anthropic/cache_planner.py +56 -0
  93. fast_agent/llm/provider/anthropic/llm_anthropic.py +796 -0
  94. fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +462 -0
  95. fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
  96. fast_agent/llm/provider/bedrock/llm_bedrock.py +2207 -0
  97. fast_agent/llm/provider/bedrock/multipart_converter_bedrock.py +84 -0
  98. fast_agent/llm/provider/google/google_converter.py +466 -0
  99. fast_agent/llm/provider/google/llm_google_native.py +681 -0
  100. fast_agent/llm/provider/openai/llm_aliyun.py +31 -0
  101. fast_agent/llm/provider/openai/llm_azure.py +143 -0
  102. fast_agent/llm/provider/openai/llm_deepseek.py +76 -0
  103. fast_agent/llm/provider/openai/llm_generic.py +35 -0
  104. fast_agent/llm/provider/openai/llm_google_oai.py +32 -0
  105. fast_agent/llm/provider/openai/llm_groq.py +42 -0
  106. fast_agent/llm/provider/openai/llm_huggingface.py +85 -0
  107. fast_agent/llm/provider/openai/llm_openai.py +1195 -0
  108. fast_agent/llm/provider/openai/llm_openai_compatible.py +138 -0
  109. fast_agent/llm/provider/openai/llm_openrouter.py +45 -0
  110. fast_agent/llm/provider/openai/llm_tensorzero_openai.py +128 -0
  111. fast_agent/llm/provider/openai/llm_xai.py +38 -0
  112. fast_agent/llm/provider/openai/multipart_converter_openai.py +561 -0
  113. fast_agent/llm/provider/openai/openai_multipart.py +169 -0
  114. fast_agent/llm/provider/openai/openai_utils.py +67 -0
  115. fast_agent/llm/provider/openai/responses.py +133 -0
  116. fast_agent/llm/provider_key_manager.py +139 -0
  117. fast_agent/llm/provider_types.py +34 -0
  118. fast_agent/llm/request_params.py +61 -0
  119. fast_agent/llm/sampling_converter.py +98 -0
  120. fast_agent/llm/stream_types.py +9 -0
  121. fast_agent/llm/usage_tracking.py +445 -0
  122. fast_agent/mcp/__init__.py +56 -0
  123. fast_agent/mcp/common.py +26 -0
  124. fast_agent/mcp/elicitation_factory.py +84 -0
  125. fast_agent/mcp/elicitation_handlers.py +164 -0
  126. fast_agent/mcp/gen_client.py +83 -0
  127. fast_agent/mcp/helpers/__init__.py +36 -0
  128. fast_agent/mcp/helpers/content_helpers.py +352 -0
  129. fast_agent/mcp/helpers/server_config_helpers.py +25 -0
  130. fast_agent/mcp/hf_auth.py +147 -0
  131. fast_agent/mcp/interfaces.py +92 -0
  132. fast_agent/mcp/logger_textio.py +108 -0
  133. fast_agent/mcp/mcp_agent_client_session.py +411 -0
  134. fast_agent/mcp/mcp_aggregator.py +2175 -0
  135. fast_agent/mcp/mcp_connection_manager.py +723 -0
  136. fast_agent/mcp/mcp_content.py +262 -0
  137. fast_agent/mcp/mime_utils.py +108 -0
  138. fast_agent/mcp/oauth_client.py +509 -0
  139. fast_agent/mcp/prompt.py +159 -0
  140. fast_agent/mcp/prompt_message_extended.py +155 -0
  141. fast_agent/mcp/prompt_render.py +84 -0
  142. fast_agent/mcp/prompt_serialization.py +580 -0
  143. fast_agent/mcp/prompts/__init__.py +0 -0
  144. fast_agent/mcp/prompts/__main__.py +7 -0
  145. fast_agent/mcp/prompts/prompt_constants.py +18 -0
  146. fast_agent/mcp/prompts/prompt_helpers.py +238 -0
  147. fast_agent/mcp/prompts/prompt_load.py +186 -0
  148. fast_agent/mcp/prompts/prompt_server.py +552 -0
  149. fast_agent/mcp/prompts/prompt_template.py +438 -0
  150. fast_agent/mcp/resource_utils.py +215 -0
  151. fast_agent/mcp/sampling.py +200 -0
  152. fast_agent/mcp/server/__init__.py +4 -0
  153. fast_agent/mcp/server/agent_server.py +613 -0
  154. fast_agent/mcp/skybridge.py +44 -0
  155. fast_agent/mcp/sse_tracking.py +287 -0
  156. fast_agent/mcp/stdio_tracking_simple.py +59 -0
  157. fast_agent/mcp/streamable_http_tracking.py +309 -0
  158. fast_agent/mcp/tool_execution_handler.py +137 -0
  159. fast_agent/mcp/tool_permission_handler.py +88 -0
  160. fast_agent/mcp/transport_tracking.py +634 -0
  161. fast_agent/mcp/types.py +24 -0
  162. fast_agent/mcp/ui_agent.py +48 -0
  163. fast_agent/mcp/ui_mixin.py +209 -0
  164. fast_agent/mcp_server_registry.py +89 -0
  165. fast_agent/py.typed +0 -0
  166. fast_agent/resources/examples/data-analysis/analysis-campaign.py +189 -0
  167. fast_agent/resources/examples/data-analysis/analysis.py +68 -0
  168. fast_agent/resources/examples/data-analysis/fastagent.config.yaml +41 -0
  169. fast_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +1471 -0
  170. fast_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
  171. fast_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +297 -0
  172. fast_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
  173. fast_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
  174. fast_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
  175. fast_agent/resources/examples/mcp/elicitations/forms_demo.py +107 -0
  176. fast_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
  177. fast_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
  178. fast_agent/resources/examples/mcp/elicitations/tool_call.py +21 -0
  179. fast_agent/resources/examples/mcp/state-transfer/agent_one.py +18 -0
  180. fast_agent/resources/examples/mcp/state-transfer/agent_two.py +18 -0
  181. fast_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +27 -0
  182. fast_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +15 -0
  183. fast_agent/resources/examples/researcher/fastagent.config.yaml +61 -0
  184. fast_agent/resources/examples/researcher/researcher-eval.py +53 -0
  185. fast_agent/resources/examples/researcher/researcher-imp.py +189 -0
  186. fast_agent/resources/examples/researcher/researcher.py +36 -0
  187. fast_agent/resources/examples/tensorzero/.env.sample +2 -0
  188. fast_agent/resources/examples/tensorzero/Makefile +31 -0
  189. fast_agent/resources/examples/tensorzero/README.md +56 -0
  190. fast_agent/resources/examples/tensorzero/agent.py +35 -0
  191. fast_agent/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
  192. fast_agent/resources/examples/tensorzero/demo_images/crab.png +0 -0
  193. fast_agent/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
  194. fast_agent/resources/examples/tensorzero/docker-compose.yml +105 -0
  195. fast_agent/resources/examples/tensorzero/fastagent.config.yaml +19 -0
  196. fast_agent/resources/examples/tensorzero/image_demo.py +67 -0
  197. fast_agent/resources/examples/tensorzero/mcp_server/Dockerfile +25 -0
  198. fast_agent/resources/examples/tensorzero/mcp_server/entrypoint.sh +35 -0
  199. fast_agent/resources/examples/tensorzero/mcp_server/mcp_server.py +31 -0
  200. fast_agent/resources/examples/tensorzero/mcp_server/pyproject.toml +11 -0
  201. fast_agent/resources/examples/tensorzero/simple_agent.py +25 -0
  202. fast_agent/resources/examples/tensorzero/tensorzero_config/system_schema.json +29 -0
  203. fast_agent/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +11 -0
  204. fast_agent/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +35 -0
  205. fast_agent/resources/examples/workflows/agents_as_tools_extended.py +73 -0
  206. fast_agent/resources/examples/workflows/agents_as_tools_simple.py +50 -0
  207. fast_agent/resources/examples/workflows/chaining.py +37 -0
  208. fast_agent/resources/examples/workflows/evaluator.py +77 -0
  209. fast_agent/resources/examples/workflows/fastagent.config.yaml +26 -0
  210. fast_agent/resources/examples/workflows/graded_report.md +89 -0
  211. fast_agent/resources/examples/workflows/human_input.py +28 -0
  212. fast_agent/resources/examples/workflows/maker.py +156 -0
  213. fast_agent/resources/examples/workflows/orchestrator.py +70 -0
  214. fast_agent/resources/examples/workflows/parallel.py +56 -0
  215. fast_agent/resources/examples/workflows/router.py +69 -0
  216. fast_agent/resources/examples/workflows/short_story.md +13 -0
  217. fast_agent/resources/examples/workflows/short_story.txt +19 -0
  218. fast_agent/resources/setup/.gitignore +30 -0
  219. fast_agent/resources/setup/agent.py +28 -0
  220. fast_agent/resources/setup/fastagent.config.yaml +65 -0
  221. fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
  222. fast_agent/resources/setup/pyproject.toml.tmpl +23 -0
  223. fast_agent/skills/__init__.py +9 -0
  224. fast_agent/skills/registry.py +235 -0
  225. fast_agent/tools/elicitation.py +369 -0
  226. fast_agent/tools/shell_runtime.py +402 -0
  227. fast_agent/types/__init__.py +59 -0
  228. fast_agent/types/conversation_summary.py +294 -0
  229. fast_agent/types/llm_stop_reason.py +78 -0
  230. fast_agent/types/message_search.py +249 -0
  231. fast_agent/ui/__init__.py +38 -0
  232. fast_agent/ui/console.py +59 -0
  233. fast_agent/ui/console_display.py +1080 -0
  234. fast_agent/ui/elicitation_form.py +946 -0
  235. fast_agent/ui/elicitation_style.py +59 -0
  236. fast_agent/ui/enhanced_prompt.py +1400 -0
  237. fast_agent/ui/history_display.py +734 -0
  238. fast_agent/ui/interactive_prompt.py +1199 -0
  239. fast_agent/ui/markdown_helpers.py +104 -0
  240. fast_agent/ui/markdown_truncator.py +1004 -0
  241. fast_agent/ui/mcp_display.py +857 -0
  242. fast_agent/ui/mcp_ui_utils.py +235 -0
  243. fast_agent/ui/mermaid_utils.py +169 -0
  244. fast_agent/ui/message_primitives.py +50 -0
  245. fast_agent/ui/notification_tracker.py +205 -0
  246. fast_agent/ui/plain_text_truncator.py +68 -0
  247. fast_agent/ui/progress_display.py +10 -0
  248. fast_agent/ui/rich_progress.py +195 -0
  249. fast_agent/ui/streaming.py +774 -0
  250. fast_agent/ui/streaming_buffer.py +449 -0
  251. fast_agent/ui/tool_display.py +422 -0
  252. fast_agent/ui/usage_display.py +204 -0
  253. fast_agent/utils/__init__.py +5 -0
  254. fast_agent/utils/reasoning_stream_parser.py +77 -0
  255. fast_agent/utils/time.py +22 -0
  256. fast_agent/workflow_telemetry.py +261 -0
  257. fast_agent_mcp-0.4.7.dist-info/METADATA +788 -0
  258. fast_agent_mcp-0.4.7.dist-info/RECORD +261 -0
  259. fast_agent_mcp-0.4.7.dist-info/WHEEL +4 -0
  260. fast_agent_mcp-0.4.7.dist-info/entry_points.txt +7 -0
  261. fast_agent_mcp-0.4.7.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,143 @@
1
+ """Run FastAgent as an MCP server from the command line."""
2
+
3
+ from enum import Enum
4
+ from pathlib import Path
5
+
6
+ import typer
7
+
8
+ from fast_agent.cli.commands.go import (
9
+ collect_stdio_commands,
10
+ resolve_instruction_option,
11
+ run_async_agent,
12
+ )
13
+
14
+
15
+ class ServeTransport(str, Enum):
16
+ HTTP = "http"
17
+ SSE = "sse"
18
+ STDIO = "stdio"
19
+ ACP = "acp"
20
+
21
+
22
+ class InstanceScope(str, Enum):
23
+ SHARED = "shared"
24
+ CONNECTION = "connection"
25
+ REQUEST = "request"
26
+
27
+
28
+ app = typer.Typer(
29
+ help="Run FastAgent as an MCP server without writing an agent.py file",
30
+ context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
31
+ )
32
+
33
+
34
+ @app.callback(invoke_without_command=True, no_args_is_help=False)
35
+ def serve(
36
+ ctx: typer.Context,
37
+ name: str = typer.Option("fast-agent", "--name", help="Name for the MCP server"),
38
+ instruction: str | None = typer.Option(
39
+ None, "--instruction", "-i", help="Path to file or URL containing instruction for the agent"
40
+ ),
41
+ config_path: str | None = typer.Option(None, "--config-path", "-c", help="Path to config file"),
42
+ servers: str | None = typer.Option(
43
+ None, "--servers", help="Comma-separated list of server names to enable from config"
44
+ ),
45
+ urls: str | None = typer.Option(
46
+ None, "--url", help="Comma-separated list of HTTP/SSE URLs to connect to"
47
+ ),
48
+ auth: str | None = typer.Option(
49
+ None, "--auth", help="Bearer token for authorization with URL-based servers"
50
+ ),
51
+ model: str | None = typer.Option(
52
+ None, "--model", "--models", help="Override the default model (e.g., haiku, sonnet, gpt-4)"
53
+ ),
54
+ skills_dir: Path | None = typer.Option(
55
+ None,
56
+ "--skills-dir",
57
+ "--skills",
58
+ help="Override the default skills directory",
59
+ ),
60
+ npx: str | None = typer.Option(
61
+ None, "--npx", help="NPX package and args to run as MCP server (quoted)"
62
+ ),
63
+ uvx: str | None = typer.Option(
64
+ None, "--uvx", help="UVX package and args to run as MCP server (quoted)"
65
+ ),
66
+ stdio: str | None = typer.Option(
67
+ None, "--stdio", help="Command to run as STDIO MCP server (quoted)"
68
+ ),
69
+ description: str | None = typer.Option(
70
+ None,
71
+ "--description",
72
+ "-d",
73
+ help="Description used for the exposed send tool (use {agent} to reference the agent name)",
74
+ ),
75
+ transport: ServeTransport = typer.Option(
76
+ ServeTransport.HTTP,
77
+ "--transport",
78
+ help="Transport protocol to expose (http, sse, stdio, acp)",
79
+ ),
80
+ host: str = typer.Option(
81
+ "0.0.0.0",
82
+ "--host",
83
+ help="Host address to bind when using HTTP or SSE transport",
84
+ ),
85
+ port: int = typer.Option(
86
+ 8000,
87
+ "--port",
88
+ help="Port to use when running as a server with HTTP or SSE transport",
89
+ ),
90
+ shell: bool = typer.Option(
91
+ False,
92
+ "--shell",
93
+ "-x",
94
+ help="Enable a local shell runtime and expose the execute tool (bash or pwsh).",
95
+ ),
96
+ instance_scope: InstanceScope = typer.Option(
97
+ InstanceScope.SHARED,
98
+ "--instance-scope",
99
+ help="Control how MCP clients receive isolated agent instances (shared, connection, request)",
100
+ ),
101
+ no_permissions: bool = typer.Option(
102
+ False,
103
+ "--no-permissions",
104
+ help="Disable tool permission requests (allow all tool executions without asking) - ACP only",
105
+ ),
106
+ ) -> None:
107
+ """
108
+ Run FastAgent as an MCP server.
109
+
110
+ Examples:
111
+ fast-agent serve --model=haiku --instruction=./instruction.md --transport=http --port=8000
112
+ fast-agent serve --url=http://localhost:8001/mcp --auth=YOUR_API_TOKEN
113
+ fast-agent serve --stdio "python my_server.py --debug"
114
+ fast-agent serve --npx "@modelcontextprotocol/server-filesystem /path/to/data"
115
+ fast-agent serve --description "Interact with the {agent} assistant"
116
+ """
117
+ stdio_commands = collect_stdio_commands(npx, uvx, stdio)
118
+ shell_enabled = shell
119
+
120
+ resolved_instruction, agent_name = resolve_instruction_option(instruction)
121
+
122
+ run_async_agent(
123
+ name=name,
124
+ instruction=resolved_instruction,
125
+ config_path=config_path,
126
+ servers=servers,
127
+ urls=urls,
128
+ auth=auth,
129
+ model=model,
130
+ message=None,
131
+ prompt_file=None,
132
+ stdio_commands=stdio_commands,
133
+ agent_name=agent_name,
134
+ skills_directory=skills_dir,
135
+ shell_enabled=shell_enabled,
136
+ mode="serve",
137
+ transport=transport.value,
138
+ host=host,
139
+ port=port,
140
+ tool_description=description,
141
+ instance_scope=instance_scope.value,
142
+ permissions_enabled=not no_permissions,
143
+ )
@@ -0,0 +1,114 @@
1
+ """Helper functions for server configuration and naming."""
2
+
3
+ from typing import Any
4
+
5
+
6
+ def generate_server_name(identifier: str) -> str:
7
+ """Generate a clean server name from various identifiers.
8
+
9
+ Args:
10
+ identifier: Package name, file path, or other identifier
11
+
12
+ Returns:
13
+ Clean server name with only alphanumeric and underscore characters
14
+
15
+ Examples:
16
+ >>> generate_server_name("@modelcontextprotocol/server-filesystem")
17
+ 'server_filesystem'
18
+ >>> generate_server_name("./src/my-server.py")
19
+ 'src_my_server'
20
+ >>> generate_server_name("my-mcp-server")
21
+ 'my_mcp_server'
22
+ """
23
+
24
+ # Remove leading ./ if present
25
+ if identifier.startswith("./"):
26
+ identifier = identifier[2:]
27
+
28
+ # Handle npm package names with org prefix (only if no file extension)
29
+ has_file_ext = any(identifier.endswith(ext) for ext in [".py", ".js", ".ts"])
30
+ if "/" in identifier and not has_file_ext:
31
+ # This is likely an npm package, take the part after the last slash
32
+ identifier = identifier.split("/")[-1]
33
+
34
+ # Remove file extension for common script files
35
+ for ext in [".py", ".js", ".ts"]:
36
+ if identifier.endswith(ext):
37
+ identifier = identifier[: -len(ext)]
38
+ break
39
+
40
+ # Replace special characters with underscores
41
+ # Remove @ prefix if present
42
+ identifier = identifier.lstrip("@")
43
+
44
+ # Replace non-alphanumeric characters with underscores
45
+ server_name = ""
46
+ for char in identifier:
47
+ if char.isalnum():
48
+ server_name += char
49
+ else:
50
+ server_name += "_"
51
+
52
+ # Clean up multiple underscores
53
+ while "__" in server_name:
54
+ server_name = server_name.replace("__", "_")
55
+
56
+ # Remove leading/trailing underscores
57
+ server_name = server_name.strip("_")
58
+
59
+ return server_name
60
+
61
+
62
+ async def add_servers_to_config(fast_app: Any, servers: dict[str, dict[str, Any]]) -> None:
63
+ """Add server configurations to the FastAgent app config.
64
+
65
+ This function handles the repetitive initialization and configuration
66
+ of MCP servers, ensuring the app is initialized and the config
67
+ structure exists before adding servers.
68
+
69
+ Args:
70
+ fast_app: The FastAgent instance
71
+ servers: Dictionary of server configurations
72
+ """
73
+ if not servers:
74
+ return
75
+
76
+ from fast_agent.config import MCPServerSettings, MCPSettings
77
+
78
+ # Initialize the app to ensure context is ready
79
+ await fast_app.app.initialize()
80
+
81
+ # Initialize mcp settings if needed
82
+ if not hasattr(fast_app.app.context.config, "mcp"):
83
+ fast_app.app.context.config.mcp = MCPSettings()
84
+
85
+ # Initialize servers dictionary if needed
86
+ if (
87
+ not hasattr(fast_app.app.context.config.mcp, "servers")
88
+ or fast_app.app.context.config.mcp.servers is None
89
+ ):
90
+ fast_app.app.context.config.mcp.servers = {}
91
+
92
+ # Add each server to the config (and keep the runtime registry in sync)
93
+ for server_name, server_config in servers.items():
94
+ # Build server settings based on transport type
95
+ server_settings = {"transport": server_config["transport"]}
96
+
97
+ # Add transport-specific settings
98
+ if server_config["transport"] == "stdio":
99
+ server_settings["command"] = server_config["command"]
100
+ server_settings["args"] = server_config["args"]
101
+ elif server_config["transport"] in ["http", "sse"]:
102
+ server_settings["url"] = server_config["url"]
103
+ if "headers" in server_config:
104
+ server_settings["headers"] = server_config["headers"]
105
+
106
+ mcp_server = MCPServerSettings(**server_settings)
107
+ # Update config model
108
+ fast_app.app.context.config.mcp.servers[server_name] = mcp_server
109
+ # Ensure ServerRegistry sees dynamic additions even when no config file exists
110
+ if (
111
+ hasattr(fast_app.app.context, "server_registry")
112
+ and fast_app.app.context.server_registry is not None
113
+ ):
114
+ fast_app.app.context.server_registry.registry[server_name] = mcp_server
@@ -0,0 +1,174 @@
1
+ from pathlib import Path
2
+
3
+ import typer
4
+ from rich.prompt import Confirm
5
+
6
+ from fast_agent.ui.console import console as shared_console
7
+
8
+ app = typer.Typer()
9
+ console = shared_console
10
+
11
+
12
+ def load_template_text(filename: str) -> str:
13
+ """Load template text from packaged resources only.
14
+
15
+ Special-case: when requesting 'fastagent.secrets.yaml', read the
16
+ 'fastagent.secrets.yaml.example' template from resources, but still
17
+ return its contents so we can write out the real secrets file name
18
+ in the destination project.
19
+ """
20
+ from importlib.resources import files
21
+
22
+ # Map requested filenames to resource templates
23
+ if filename == "fastagent.secrets.yaml":
24
+ res_name = "fastagent.secrets.yaml.example"
25
+ elif filename == "pyproject.toml":
26
+ res_name = "pyproject.toml.tmpl"
27
+ else:
28
+ res_name = filename
29
+ resource_path = files("fast_agent").joinpath("resources").joinpath("setup").joinpath(res_name)
30
+ if resource_path.is_file():
31
+ return resource_path.read_text()
32
+
33
+ raise RuntimeError(
34
+ f"Setup template missing: '{filename}'.\n"
35
+ f"Expected packaged resource at: {resource_path}.\n"
36
+ "This indicates a packaging issue. Please rebuild/reinstall fast-agent."
37
+ )
38
+
39
+
40
+ # (No embedded template defaults; templates are the single source of truth.)
41
+
42
+
43
+ def find_gitignore(path: Path) -> bool:
44
+ """Check if a .gitignore file exists in this directory or any parent."""
45
+ current = path
46
+ while current != current.parent: # Stop at root directory
47
+ if (current / ".gitignore").exists():
48
+ return True
49
+ current = current.parent
50
+ return False
51
+
52
+
53
+ def create_file(path: Path, content: str, force: bool = False) -> bool:
54
+ """Create a file with given content if it doesn't exist or force is True."""
55
+ if path.exists() and not force:
56
+ should_overwrite = Confirm.ask(
57
+ f"[yellow]Warning:[/yellow] {path} already exists. Overwrite?",
58
+ default=False,
59
+ )
60
+ if not should_overwrite:
61
+ console.print(f"Skipping {path}")
62
+ return False
63
+
64
+ path.write_text(content.strip() + "\n")
65
+ console.print(f"[green]Created[/green] {path}")
66
+ return True
67
+
68
+
69
+ @app.callback(invoke_without_command=True)
70
+ def init(
71
+ config_dir: str = typer.Option(
72
+ ".",
73
+ "--config-dir",
74
+ "-c",
75
+ help="Directory where configuration files will be created",
76
+ ),
77
+ force: bool = typer.Option(False, "--force", "-f", help="Force overwrite existing files"),
78
+ ) -> None:
79
+ """Initialize a new FastAgent project with configuration files and example agent."""
80
+
81
+ config_path = Path(config_dir).resolve()
82
+ if not config_path.exists():
83
+ should_create = Confirm.ask(
84
+ f"Directory {config_path} does not exist. Create it?", default=True
85
+ )
86
+ if should_create:
87
+ config_path.mkdir(parents=True)
88
+ else:
89
+ raise typer.Abort()
90
+
91
+ # Check for existing .gitignore
92
+ needs_gitignore = not find_gitignore(config_path)
93
+
94
+ console.print("\n[bold]fast-agent setup[/bold]\n")
95
+ console.print("This will create the following files:")
96
+ console.print(f" - {config_path}/fastagent.config.yaml")
97
+ console.print(f" - {config_path}/fastagent.secrets.yaml")
98
+ console.print(f" - {config_path}/agent.py")
99
+ console.print(f" - {config_path}/pyproject.toml")
100
+ if needs_gitignore:
101
+ console.print(f" - {config_path}/.gitignore")
102
+
103
+ if not Confirm.ask("\nContinue?", default=True):
104
+ raise typer.Abort()
105
+
106
+ # Create configuration files
107
+ created = []
108
+ if create_file(
109
+ config_path / "fastagent.config.yaml", load_template_text("fastagent.config.yaml"), force
110
+ ):
111
+ created.append("fastagent.yaml")
112
+
113
+ if create_file(
114
+ config_path / "fastagent.secrets.yaml", load_template_text("fastagent.secrets.yaml"), force
115
+ ):
116
+ created.append("fastagent.secrets.yaml")
117
+
118
+ if create_file(config_path / "agent.py", load_template_text("agent.py"), force):
119
+ created.append("agent.py")
120
+
121
+ # Create a minimal pyproject.toml so `uv run` installs dependencies
122
+ def _render_pyproject(template_text: str) -> str:
123
+ # Determine Python requirement from installed fast-agent-mcp metadata, fall back if missing
124
+ py_req = ">=3.13.7"
125
+ try:
126
+ from importlib.metadata import metadata
127
+
128
+ md = metadata("fast-agent-mcp")
129
+ req = md.get("Requires-Python")
130
+ if req:
131
+ py_req = req
132
+ except Exception:
133
+ pass
134
+
135
+ # Always use latest fast-agent-mcp (no version pin)
136
+ fast_agent_dep = '"fast-agent-mcp"'
137
+
138
+ return template_text.replace("{{python_requires}}", py_req).replace(
139
+ "{{fast_agent_dep}}", fast_agent_dep
140
+ )
141
+
142
+ pyproject_template = load_template_text("pyproject.toml")
143
+ pyproject_text = _render_pyproject(pyproject_template)
144
+ if create_file(config_path / "pyproject.toml", pyproject_text, force):
145
+ created.append("pyproject.toml")
146
+
147
+ # Only create .gitignore if none exists in parent directories
148
+ if needs_gitignore and create_file(
149
+ config_path / ".gitignore", load_template_text(".gitignore"), force
150
+ ):
151
+ created.append(".gitignore")
152
+
153
+ if created:
154
+ console.print("\n[green]Setup completed successfully![/green]")
155
+ if not needs_gitignore:
156
+ console.print(
157
+ "[yellow]Note:[/yellow] Found an existing .gitignore in this or a parent directory. "
158
+ "Ensure it ignores 'fastagent.secrets.yaml' to avoid committing secrets."
159
+ )
160
+ if "fastagent.secrets.yaml" in created:
161
+ console.print("\n[yellow]Important:[/yellow] Remember to:")
162
+ console.print(
163
+ "1. Add your API keys to fastagent.secrets.yaml, or set environment variables. Use [cyan]fast-agent check[/cyan] to verify."
164
+ )
165
+ console.print(
166
+ "2. Keep fastagent.secrets.yaml secure and never commit it to version control"
167
+ )
168
+ console.print(
169
+ "3. Update fastagent.config.yaml to set a default model (currently system default is 'gpt-5-mini.low')"
170
+ )
171
+ console.print("\nTo get started, run:")
172
+ console.print(" uv run agent.py")
173
+ else:
174
+ console.print("\n[yellow]No files were created or modified.[/yellow]")
@@ -0,0 +1,190 @@
1
+ """
2
+ URL parsing utility for the fast-agent CLI.
3
+ Provides functions to parse URLs and determine MCP server configurations.
4
+ """
5
+
6
+ import hashlib
7
+ import re
8
+ from typing import Literal
9
+ from urllib.parse import urlparse
10
+
11
+ from fast_agent.mcp.hf_auth import add_hf_auth_header
12
+
13
+
14
+ def parse_server_url(
15
+ url: str,
16
+ ) -> tuple[str, Literal["http", "sse"], str]:
17
+ """
18
+ Parse a server URL and determine the transport type and server name.
19
+
20
+ Args:
21
+ url: The URL to parse
22
+
23
+ Returns:
24
+ Tuple containing:
25
+ - server_name: A generated name for the server
26
+ - transport_type: Either "http" or "sse" based on URL
27
+ - url: The parsed and validated URL
28
+
29
+ Raises:
30
+ ValueError: If the URL is invalid or unsupported
31
+ """
32
+ # Basic URL validation
33
+ if not url:
34
+ raise ValueError("URL cannot be empty")
35
+
36
+ # Parse the URL
37
+ parsed_url = urlparse(url)
38
+
39
+ # Ensure scheme is present and is either http or https
40
+ if not parsed_url.scheme or parsed_url.scheme not in ("http", "https"):
41
+ raise ValueError(f"URL must have http or https scheme: {url}")
42
+
43
+ # Ensure netloc (hostname) is present
44
+ if not parsed_url.netloc:
45
+ raise ValueError(f"URL must include a hostname: {url}")
46
+
47
+ # Determine transport type based on URL path
48
+ transport_type: Literal["http", "sse"] = "http"
49
+ path = parsed_url.path or ""
50
+ normalized_path = path.rstrip("/")
51
+ if normalized_path.endswith("/sse"):
52
+ transport_type = "sse"
53
+ elif not normalized_path.endswith("/mcp"):
54
+ # If path doesn't end with /mcp or /sse (handling trailing slash), append /mcp once
55
+ url = f"{url.rstrip('/')}" + "/mcp"
56
+
57
+ # Generate a server name based on hostname and port
58
+ server_name = generate_server_name(url)
59
+
60
+ return server_name, transport_type, url
61
+
62
+
63
+ def generate_server_name(url: str) -> str:
64
+ """
65
+ Generate a unique and readable server name from a URL.
66
+
67
+ Args:
68
+ url: The URL to generate a name for
69
+
70
+ Returns:
71
+ A server name string
72
+ """
73
+ parsed_url = urlparse(url)
74
+
75
+ # Extract hostname and port
76
+ hostname, _, port_str = parsed_url.netloc.partition(":")
77
+
78
+ # Clean the hostname for use in a server name
79
+ # Replace non-alphanumeric characters with underscores
80
+ clean_hostname = re.sub(r"[^a-zA-Z0-9]", "_", hostname)
81
+
82
+ if len(clean_hostname) > 15:
83
+ clean_hostname = clean_hostname[:9] + clean_hostname[-5:]
84
+
85
+ # If it's localhost or an IP, add a more unique identifier
86
+ if clean_hostname in ("localhost", "127_0_0_1") or re.match(r"^(\d+_){3}\d+$", clean_hostname):
87
+ # Use the path as part of the name for uniqueness
88
+ path = parsed_url.path.strip("/")
89
+ path = re.sub(r"[^a-zA-Z0-9]", "_", path)
90
+
91
+ # Include port if specified
92
+ port = f"_{port_str}" if port_str else ""
93
+
94
+ if path:
95
+ return f"{clean_hostname}{port}_{path[:20]}" # Limit path length
96
+ else:
97
+ # Use a hash if no path for uniqueness
98
+ url_hash = hashlib.md5(url.encode()).hexdigest()[:8]
99
+ return f"{clean_hostname}{port}_{url_hash}"
100
+
101
+ return clean_hostname
102
+
103
+
104
+ def parse_server_urls(
105
+ urls_param: str, auth_token: str | None = None
106
+ ) -> list[tuple[str, Literal["http", "sse"], str, dict[str, str] | None]]:
107
+ """
108
+ Parse a comma-separated list of URLs into server configurations.
109
+
110
+ Args:
111
+ urls_param: Comma-separated list of URLs
112
+ auth_token: Optional bearer token for authorization
113
+
114
+ Returns:
115
+ List of tuples containing (server_name, transport_type, url, headers)
116
+
117
+ Raises:
118
+ ValueError: If any URL is invalid
119
+ """
120
+ if not urls_param:
121
+ return []
122
+
123
+ # Split by comma and strip whitespace
124
+ url_list = [url.strip() for url in urls_param.split(",")]
125
+
126
+ # Prepare headers if auth token is provided
127
+ headers = None
128
+ if auth_token:
129
+ headers = {"Authorization": f"Bearer {auth_token}"}
130
+
131
+ # Parse each URL
132
+ result = []
133
+ for url in url_list:
134
+ server_name, transport_type, parsed_url = parse_server_url(url)
135
+
136
+ # Apply HuggingFace authentication if appropriate
137
+ final_headers = add_hf_auth_header(parsed_url, headers)
138
+
139
+ result.append((server_name, transport_type, parsed_url, final_headers))
140
+
141
+ return result
142
+
143
+
144
+ def generate_server_configs(
145
+ parsed_urls: list[tuple[str, Literal["http", "sse"], str, dict[str, str] | None]],
146
+ ) -> dict[str, dict[str, str | dict[str, str]]]:
147
+ """
148
+ Generate server configurations from parsed URLs.
149
+
150
+ Args:
151
+ parsed_urls: List of tuples containing (server_name, transport_type, url, headers)
152
+
153
+ Returns:
154
+ Dictionary of server configurations
155
+ """
156
+ server_configs: dict[str, dict[str, str | dict[str, str]]] = {}
157
+ # Keep track of server name occurrences to handle collisions
158
+ name_counts = {}
159
+
160
+ for server_name, transport_type, url, headers in parsed_urls:
161
+ # Handle name collisions by adding a suffix
162
+ final_name = server_name
163
+ if server_name in server_configs:
164
+ # Initialize counter if we haven't seen this name yet
165
+ if server_name not in name_counts:
166
+ name_counts[server_name] = 1
167
+
168
+ # Generate a new name with suffix
169
+ suffix = name_counts[server_name]
170
+ final_name = f"{server_name}_{suffix}"
171
+ name_counts[server_name] += 1
172
+
173
+ # Ensure the new name is also unique
174
+ while final_name in server_configs:
175
+ suffix = name_counts[server_name]
176
+ final_name = f"{server_name}_{suffix}"
177
+ name_counts[server_name] += 1
178
+
179
+ config: dict[str, str | dict[str, str]] = {
180
+ "transport": transport_type,
181
+ "url": url,
182
+ }
183
+
184
+ # Add headers if provided
185
+ if headers:
186
+ config["headers"] = headers
187
+
188
+ server_configs[final_name] = config
189
+
190
+ return server_configs
@@ -0,0 +1,40 @@
1
+ """Shared constants for CLI routing and commands."""
2
+
3
+ # Options that should automatically route to the 'go' command
4
+ GO_SPECIFIC_OPTIONS = {
5
+ "--npx",
6
+ "--uvx",
7
+ "--stdio",
8
+ "--url",
9
+ "--model",
10
+ "--models",
11
+ "--instruction",
12
+ "-i",
13
+ "--message",
14
+ "-m",
15
+ "--prompt-file",
16
+ "-p",
17
+ "--servers",
18
+ "--auth",
19
+ "--name",
20
+ "--config-path",
21
+ "-c",
22
+ "--shell",
23
+ "-x",
24
+ "--skills",
25
+ "--skills-dir",
26
+ }
27
+
28
+ # Known subcommands that should not trigger auto-routing
29
+ KNOWN_SUBCOMMANDS = {
30
+ "go",
31
+ "serve",
32
+ "setup",
33
+ "check",
34
+ "auth",
35
+ "bootstrap",
36
+ "quickstart",
37
+ "--help",
38
+ "-h",
39
+ "--version",
40
+ }