fast-agent-mcp 0.3.3__py3-none-any.whl → 0.3.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fast-agent-mcp might be problematic. Click here for more details.

Files changed (44) hide show
  1. fast_agent/__init__.py +6 -3
  2. fast_agent/agents/__init__.py +63 -14
  3. fast_agent/cli/__main__.py +8 -5
  4. fast_agent/cli/commands/auth.py +370 -0
  5. fast_agent/cli/commands/check_config.py +54 -3
  6. fast_agent/cli/commands/go.py +1 -1
  7. fast_agent/cli/commands/quickstart.py +3 -1
  8. fast_agent/cli/commands/server_helpers.py +10 -2
  9. fast_agent/cli/commands/setup.py +3 -2
  10. fast_agent/cli/constants.py +1 -1
  11. fast_agent/cli/main.py +3 -1
  12. fast_agent/config.py +63 -8
  13. fast_agent/core/__init__.py +38 -37
  14. fast_agent/core/direct_factory.py +1 -1
  15. fast_agent/mcp/mcp_connection_manager.py +21 -3
  16. fast_agent/mcp/oauth_client.py +481 -0
  17. fast_agent/mcp/ui_agent.py +1 -1
  18. fast_agent/resources/examples/data-analysis/analysis-campaign.py +1 -1
  19. fast_agent/resources/examples/data-analysis/analysis.py +1 -1
  20. fast_agent/resources/examples/mcp/elicitations/forms_demo.py +1 -1
  21. fast_agent/resources/examples/mcp/elicitations/game_character.py +1 -1
  22. fast_agent/resources/examples/mcp/elicitations/tool_call.py +1 -1
  23. fast_agent/resources/examples/mcp/state-transfer/agent_one.py +1 -1
  24. fast_agent/resources/examples/mcp/state-transfer/agent_two.py +1 -1
  25. fast_agent/resources/examples/researcher/researcher-eval.py +1 -1
  26. fast_agent/resources/examples/researcher/researcher-imp.py +1 -1
  27. fast_agent/resources/examples/researcher/researcher.py +1 -1
  28. fast_agent/resources/examples/tensorzero/agent.py +1 -1
  29. fast_agent/resources/examples/tensorzero/image_demo.py +1 -1
  30. fast_agent/resources/examples/tensorzero/simple_agent.py +1 -1
  31. fast_agent/resources/examples/workflows/chaining.py +1 -1
  32. fast_agent/resources/examples/workflows/evaluator.py +1 -1
  33. fast_agent/resources/examples/workflows/human_input.py +1 -1
  34. fast_agent/resources/examples/workflows/orchestrator.py +1 -1
  35. fast_agent/resources/examples/workflows/parallel.py +1 -1
  36. fast_agent/resources/examples/workflows/router.py +1 -1
  37. fast_agent/resources/setup/agent.py +1 -1
  38. fast_agent/resources/setup/fastagent.config.yaml +2 -2
  39. fast_agent/ui/mcp_ui_utils.py +12 -1
  40. {fast_agent_mcp-0.3.3.dist-info → fast_agent_mcp-0.3.5.dist-info}/METADATA +40 -3
  41. {fast_agent_mcp-0.3.3.dist-info → fast_agent_mcp-0.3.5.dist-info}/RECORD +44 -42
  42. {fast_agent_mcp-0.3.3.dist-info → fast_agent_mcp-0.3.5.dist-info}/WHEEL +0 -0
  43. {fast_agent_mcp-0.3.3.dist-info → fast_agent_mcp-0.3.5.dist-info}/entry_points.txt +0 -0
  44. {fast_agent_mcp-0.3.3.dist-info → fast_agent_mcp-0.3.5.dist-info}/licenses/LICENSE +0 -0
@@ -1,11 +1,12 @@
1
1
  from pathlib import Path
2
2
 
3
3
  import typer
4
- from rich.console import Console
5
4
  from rich.prompt import Confirm
6
5
 
6
+ from fast_agent.ui.console import console as shared_console
7
+
7
8
  app = typer.Typer()
8
- console = Console()
9
+ console = shared_console
9
10
 
10
11
 
11
12
  def load_template_text(filename: str) -> str:
@@ -22,4 +22,4 @@ GO_SPECIFIC_OPTIONS = {
22
22
  }
23
23
 
24
24
  # Known subcommands that should not trigger auto-routing
25
- KNOWN_SUBCOMMANDS = {"go", "setup", "check", "bootstrap", "quickstart", "--help", "-h", "--version"}
25
+ KNOWN_SUBCOMMANDS = {"go", "setup", "check", "auth", "bootstrap", "quickstart", "--help", "-h", "--version"}
fast_agent/cli/main.py CHANGED
@@ -3,7 +3,7 @@
3
3
  import typer
4
4
  from rich.table import Table
5
5
 
6
- from fast_agent.cli.commands import check_config, go, quickstart, setup
6
+ from fast_agent.cli.commands import auth, check_config, go, quickstart, setup
7
7
  from fast_agent.cli.terminal import Application
8
8
  from fast_agent.ui.console import console as shared_console
9
9
 
@@ -16,6 +16,7 @@ app = typer.Typer(
16
16
  app.add_typer(go.app, name="go", help="Run an interactive agent directly from the command line")
17
17
  app.add_typer(setup.app, name="setup", help="Set up a new agent project")
18
18
  app.add_typer(check_config.app, name="check", help="Show or diagnose fast-agent configuration")
19
+ app.add_typer(auth.app, name="auth", help="Manage OAuth authentication for MCP servers")
19
20
  app.add_typer(quickstart.app, name="bootstrap", help="Create example applications")
20
21
  app.add_typer(quickstart.app, name="quickstart", help="Create example applications")
21
22
 
@@ -62,6 +63,7 @@ def show_welcome() -> None:
62
63
 
63
64
  table.add_row("[bold]go[/bold]", "Start an interactive session")
64
65
  table.add_row("check", "Show current configuration")
66
+ table.add_row("auth", "Manage OAuth tokens and keyring")
65
67
  table.add_row("setup", "Create agent template and configuration")
66
68
  table.add_row("quickstart", "Create example applications (workflow, researcher, etc.)")
67
69
 
fast_agent/config.py CHANGED
@@ -9,14 +9,29 @@ from pathlib import Path
9
9
  from typing import Any, Dict, List, Literal, Optional, Tuple
10
10
 
11
11
  from mcp import Implementation
12
- from pydantic import BaseModel, ConfigDict, field_validator
12
+ from pydantic import BaseModel, ConfigDict, field_validator, model_validator
13
13
  from pydantic_settings import BaseSettings, SettingsConfigDict
14
14
 
15
15
 
16
16
  class MCPServerAuthSettings(BaseModel):
17
- """Represents authentication configuration for a server."""
17
+ """Represents authentication configuration for a server.
18
18
 
19
- api_key: str | None = None
19
+ Minimal OAuth v2.1 support with sensible defaults.
20
+ """
21
+
22
+ # Enable OAuth for SSE/HTTP transports. If None is provided for the auth block,
23
+ # the system will assume OAuth is enabled by default.
24
+ oauth: bool = True
25
+
26
+ # Local callback server configuration
27
+ redirect_port: int = 3030
28
+ redirect_path: str = "/callback"
29
+
30
+ # Optional scope override. If set to a list, values are space-joined.
31
+ scope: str | list[str] | None = None
32
+
33
+ # Token persistence: use OS keychain via 'keyring' by default; fallback to 'memory'.
34
+ persist: Literal["keyring", "memory"] = "keyring"
20
35
 
21
36
  model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
22
37
 
@@ -109,6 +124,42 @@ class MCPServerSettings(BaseModel):
109
124
 
110
125
  implementation: Implementation | None = None
111
126
 
127
+ @model_validator(mode="before")
128
+ @classmethod
129
+ def validate_transport_inference(cls, values):
130
+ """Automatically infer transport type based on url/command presence."""
131
+ import warnings
132
+
133
+ if isinstance(values, dict):
134
+ # Check if transport was explicitly provided in the input
135
+ transport_explicit = "transport" in values
136
+ url = values.get("url")
137
+ command = values.get("command")
138
+
139
+ # Only infer if transport was not explicitly set
140
+ if not transport_explicit:
141
+ # Check if we have both url and command specified
142
+ has_url = url is not None and str(url).strip()
143
+ has_command = command is not None and str(command).strip()
144
+
145
+ if has_url and has_command:
146
+ warnings.warn(
147
+ f"MCP Server config has both 'url' ({url}) and 'command' ({command}) specified. "
148
+ "Preferring HTTP transport and ignoring command.",
149
+ UserWarning,
150
+ stacklevel=4,
151
+ )
152
+ values["transport"] = "http"
153
+ values["command"] = None # Clear command to avoid confusion
154
+ elif has_url and not has_command:
155
+ values["transport"] = "http"
156
+ elif has_command and not has_url:
157
+ # Keep default "stdio" for command-based servers
158
+ values["transport"] = "stdio"
159
+ # If neither url nor command is specified, keep default "stdio"
160
+
161
+ return values
162
+
112
163
 
113
164
  class MCPSettings(BaseModel):
114
165
  """Configuration for all MCP servers."""
@@ -260,8 +311,8 @@ class TensorZeroSettings(BaseModel):
260
311
  Settings for using TensorZero via its OpenAI-compatible API.
261
312
  """
262
313
 
263
- base_url: Optional[str] = None
264
- api_key: Optional[str] = None
314
+ base_url: str | None = None
315
+ api_key: str | None = None
265
316
  model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
266
317
 
267
318
 
@@ -287,7 +338,7 @@ class HuggingFaceSettings(BaseModel):
287
338
  Settings for HuggingFace authentication (used for MCP connections).
288
339
  """
289
340
 
290
- api_key: Optional[str] = None
341
+ api_key: str | None = None
291
342
  model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
292
343
 
293
344
 
@@ -408,7 +459,7 @@ class Settings(BaseSettings):
408
459
  execution_engine: Literal["asyncio"] = "asyncio"
409
460
  """Execution engine for the fast-agent application"""
410
461
 
411
- default_model: str | None = "haiku"
462
+ default_model: str | None = "gpt-5-mini.low"
412
463
  """
413
464
  Default model for agents. Format is provider.model_name.<reasoning_effort>, for example openai.o3-mini.low
414
465
  Aliases are provided for common models e.g. sonnet, haiku, gpt-4.1, o3-mini etc.
@@ -459,7 +510,7 @@ class Settings(BaseSettings):
459
510
  groq: GroqSettings | None = None
460
511
  """Settings for using the Groq provider in the fast-agent application"""
461
512
 
462
- logger: LoggerSettings | None = LoggerSettings()
513
+ logger: LoggerSettings = LoggerSettings()
463
514
  """Logger settings for the fast-agent application"""
464
515
 
465
516
  # MCP UI integration mode for handling ui:// embedded resources from MCP tool results
@@ -470,6 +521,10 @@ class Settings(BaseSettings):
470
521
  - "auto": Extract and automatically open ui:// resources.
471
522
  """
472
523
 
524
+ # Output directory for MCP-UI generated HTML files (relative to CWD if not absolute)
525
+ mcp_ui_output_dir: str = ".fast-agent/ui"
526
+ """Directory where MCP-UI HTML files are written. Relative paths are resolved from CWD."""
527
+
473
528
  @classmethod
474
529
  def find_config(cls) -> Path | None:
475
530
  """Find the config file in the current directory or parent directories."""
@@ -2,45 +2,32 @@
2
2
  Core interfaces and decorators for fast-agent.
3
3
 
4
4
  Public API:
5
- - `Core`: The core application container (eagerly exported)
6
- - `FastAgent`: High-level, decorator-driven application class (lazy-loaded)
5
+ - `Core`: The core application container
6
+ - `AgentApp`: Container for interacting with agents
7
+ - `FastAgent`: High-level, decorator-driven application class
7
8
  - Decorators: `agent`, `custom`, `orchestrator`, `iterative_planner`,
8
- `router`, `chain`, `parallel`, `evaluator_optimizer` (lazy-loaded)
9
- """
10
-
11
- from typing import TYPE_CHECKING as _TYPE_CHECKING
9
+ `router`, `chain`, `parallel`, `evaluator_optimizer`
12
10
 
13
- from .core_app import Core # Eager export for external applications
11
+ Exports are resolved lazily to avoid circular imports during package init.
12
+ """
14
13
 
15
- __all__ = [
16
- "Core",
17
- "AgentApp",
18
- "FastAgent",
19
- # Decorators
20
- "agent",
21
- "custom",
22
- "orchestrator",
23
- "iterative_planner",
24
- "router",
25
- "chain",
26
- "parallel",
27
- "evaluator_optimizer",
28
- ]
14
+ from typing import TYPE_CHECKING
29
15
 
30
16
 
31
17
  def __getattr__(name: str):
32
- # Lazy imports to avoid heavy dependencies and circular imports at init time
33
- if name == "FastAgent":
34
- from .fastagent import FastAgent
35
-
36
- return FastAgent
37
18
  if name == "AgentApp":
38
19
  from .agent_app import AgentApp
39
20
 
40
21
  return AgentApp
22
+ elif name == "Core":
23
+ from .core_app import Core
41
24
 
42
- # Decorators from direct_decorators
43
- if name in {
25
+ return Core
26
+ elif name == "FastAgent":
27
+ from .fastagent import FastAgent
28
+
29
+ return FastAgent
30
+ elif name in (
44
31
  "agent",
45
32
  "custom",
46
33
  "orchestrator",
@@ -49,20 +36,22 @@ def __getattr__(name: str):
49
36
  "chain",
50
37
  "parallel",
51
38
  "evaluator_optimizer",
52
- }:
39
+ ):
53
40
  from . import direct_decorators as _dd
54
41
 
55
- return getattr(_dd, name)
56
-
42
+ return getattr(
43
+ _dd,
44
+ name,
45
+ )
57
46
  raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
58
47
 
59
48
 
60
- # Help static analyzers/IDEs resolve symbols and signatures without importing at runtime.
61
- if _TYPE_CHECKING: # pragma: no cover - typing aid only
49
+ if TYPE_CHECKING: # pragma: no cover - typing aid only
62
50
  from .agent_app import AgentApp as AgentApp # noqa: F401
63
- from .direct_decorators import (
51
+ from .core_app import Core as Core # noqa: F401
52
+ from .direct_decorators import ( # noqa: F401
64
53
  agent as agent,
65
- ) # noqa: F401
54
+ )
66
55
  from .direct_decorators import (
67
56
  chain as chain,
68
57
  )
@@ -87,5 +76,17 @@ if _TYPE_CHECKING: # pragma: no cover - typing aid only
87
76
  from .fastagent import FastAgent as FastAgent # noqa: F401
88
77
 
89
78
 
90
- def __dir__(): # pragma: no cover - developer experience aid
91
- return sorted(__all__)
79
+ __all__ = [
80
+ "Core",
81
+ "AgentApp",
82
+ "FastAgent",
83
+ # Decorators
84
+ "agent",
85
+ "custom",
86
+ "orchestrator",
87
+ "iterative_planner",
88
+ "router",
89
+ "chain",
90
+ "parallel",
91
+ "evaluator_optimizer",
92
+ ]
@@ -5,9 +5,9 @@ Implements type-safe factories with improved error handling.
5
5
 
6
6
  from typing import Any, Dict, Optional, Protocol, TypeVar
7
7
 
8
+ from fast_agent.agents import McpAgent
8
9
  from fast_agent.agents.agent_types import AgentConfig, AgentType
9
10
  from fast_agent.agents.llm_agent import LlmAgent
10
- from fast_agent.agents.mcp_agent import McpAgent
11
11
  from fast_agent.agents.workflow.evaluator_optimizer import (
12
12
  EvaluatorOptimizerAgent,
13
13
  QualityRating,
@@ -33,6 +33,7 @@ from fast_agent.core.logging.logger import get_logger
33
33
  from fast_agent.event_progress import ProgressAction
34
34
  from fast_agent.mcp.logger_textio import get_stderr_handler
35
35
  from fast_agent.mcp.mcp_agent_client_session import MCPAgentClientSession
36
+ from fast_agent.mcp.oauth_client import build_oauth_provider
36
37
 
37
38
  if TYPE_CHECKING:
38
39
  from fast_agent.context import Context
@@ -341,6 +342,8 @@ class MCPConnectionManager(ContextDependent):
341
342
 
342
343
  def transport_context_factory():
343
344
  if config.transport == "stdio":
345
+ if not config.command:
346
+ raise ValueError(f"Server '{server_name}' uses stdio transport but no command is specified")
344
347
  server_params = StdioServerParameters(
345
348
  command=config.command,
346
349
  args=config.args if config.args is not None else [],
@@ -353,18 +356,33 @@ class MCPConnectionManager(ContextDependent):
353
356
  logger.debug(f"{server_name}: Creating stdio client with custom error handler")
354
357
  return _add_none_to_context(stdio_client(server_params, errlog=error_handler))
355
358
  elif config.transport == "sse":
359
+ if not config.url:
360
+ raise ValueError(f"Server '{server_name}' uses sse transport but no url is specified")
356
361
  # Suppress MCP library error spam
357
362
  self._suppress_mcp_sse_errors()
358
-
363
+ oauth_auth = build_oauth_provider(config)
364
+ # If using OAuth, strip any pre-existing Authorization headers to avoid conflicts
365
+ headers = dict(config.headers or {})
366
+ if oauth_auth is not None:
367
+ headers.pop("Authorization", None)
368
+ headers.pop("X-HF-Authorization", None)
359
369
  return _add_none_to_context(
360
370
  sse_client(
361
371
  config.url,
362
- config.headers,
372
+ headers,
363
373
  sse_read_timeout=config.read_transport_sse_timeout_seconds,
374
+ auth=oauth_auth,
364
375
  )
365
376
  )
366
377
  elif config.transport == "http":
367
- return streamablehttp_client(config.url, config.headers)
378
+ if not config.url:
379
+ raise ValueError(f"Server '{server_name}' uses http transport but no url is specified")
380
+ oauth_auth = build_oauth_provider(config)
381
+ headers = dict(config.headers or {})
382
+ if oauth_auth is not None:
383
+ headers.pop("Authorization", None)
384
+ headers.pop("X-HF-Authorization", None)
385
+ return streamablehttp_client(config.url, headers, auth=oauth_auth)
368
386
  else:
369
387
  raise ValueError(f"Unsupported transport: {config.transport}")
370
388