foundry-mcp 0.3.3__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 (135) hide show
  1. foundry_mcp/__init__.py +7 -0
  2. foundry_mcp/cli/__init__.py +80 -0
  3. foundry_mcp/cli/__main__.py +9 -0
  4. foundry_mcp/cli/agent.py +96 -0
  5. foundry_mcp/cli/commands/__init__.py +37 -0
  6. foundry_mcp/cli/commands/cache.py +137 -0
  7. foundry_mcp/cli/commands/dashboard.py +148 -0
  8. foundry_mcp/cli/commands/dev.py +446 -0
  9. foundry_mcp/cli/commands/journal.py +377 -0
  10. foundry_mcp/cli/commands/lifecycle.py +274 -0
  11. foundry_mcp/cli/commands/modify.py +824 -0
  12. foundry_mcp/cli/commands/plan.py +633 -0
  13. foundry_mcp/cli/commands/pr.py +393 -0
  14. foundry_mcp/cli/commands/review.py +652 -0
  15. foundry_mcp/cli/commands/session.py +479 -0
  16. foundry_mcp/cli/commands/specs.py +856 -0
  17. foundry_mcp/cli/commands/tasks.py +807 -0
  18. foundry_mcp/cli/commands/testing.py +676 -0
  19. foundry_mcp/cli/commands/validate.py +982 -0
  20. foundry_mcp/cli/config.py +98 -0
  21. foundry_mcp/cli/context.py +259 -0
  22. foundry_mcp/cli/flags.py +266 -0
  23. foundry_mcp/cli/logging.py +212 -0
  24. foundry_mcp/cli/main.py +44 -0
  25. foundry_mcp/cli/output.py +122 -0
  26. foundry_mcp/cli/registry.py +110 -0
  27. foundry_mcp/cli/resilience.py +178 -0
  28. foundry_mcp/cli/transcript.py +217 -0
  29. foundry_mcp/config.py +850 -0
  30. foundry_mcp/core/__init__.py +144 -0
  31. foundry_mcp/core/ai_consultation.py +1636 -0
  32. foundry_mcp/core/cache.py +195 -0
  33. foundry_mcp/core/capabilities.py +446 -0
  34. foundry_mcp/core/concurrency.py +898 -0
  35. foundry_mcp/core/context.py +540 -0
  36. foundry_mcp/core/discovery.py +1603 -0
  37. foundry_mcp/core/error_collection.py +728 -0
  38. foundry_mcp/core/error_store.py +592 -0
  39. foundry_mcp/core/feature_flags.py +592 -0
  40. foundry_mcp/core/health.py +749 -0
  41. foundry_mcp/core/journal.py +694 -0
  42. foundry_mcp/core/lifecycle.py +412 -0
  43. foundry_mcp/core/llm_config.py +1350 -0
  44. foundry_mcp/core/llm_patterns.py +510 -0
  45. foundry_mcp/core/llm_provider.py +1569 -0
  46. foundry_mcp/core/logging_config.py +374 -0
  47. foundry_mcp/core/metrics_persistence.py +584 -0
  48. foundry_mcp/core/metrics_registry.py +327 -0
  49. foundry_mcp/core/metrics_store.py +641 -0
  50. foundry_mcp/core/modifications.py +224 -0
  51. foundry_mcp/core/naming.py +123 -0
  52. foundry_mcp/core/observability.py +1216 -0
  53. foundry_mcp/core/otel.py +452 -0
  54. foundry_mcp/core/otel_stubs.py +264 -0
  55. foundry_mcp/core/pagination.py +255 -0
  56. foundry_mcp/core/progress.py +317 -0
  57. foundry_mcp/core/prometheus.py +577 -0
  58. foundry_mcp/core/prompts/__init__.py +464 -0
  59. foundry_mcp/core/prompts/fidelity_review.py +546 -0
  60. foundry_mcp/core/prompts/markdown_plan_review.py +511 -0
  61. foundry_mcp/core/prompts/plan_review.py +623 -0
  62. foundry_mcp/core/providers/__init__.py +225 -0
  63. foundry_mcp/core/providers/base.py +476 -0
  64. foundry_mcp/core/providers/claude.py +460 -0
  65. foundry_mcp/core/providers/codex.py +619 -0
  66. foundry_mcp/core/providers/cursor_agent.py +642 -0
  67. foundry_mcp/core/providers/detectors.py +488 -0
  68. foundry_mcp/core/providers/gemini.py +405 -0
  69. foundry_mcp/core/providers/opencode.py +616 -0
  70. foundry_mcp/core/providers/opencode_wrapper.js +302 -0
  71. foundry_mcp/core/providers/package-lock.json +24 -0
  72. foundry_mcp/core/providers/package.json +25 -0
  73. foundry_mcp/core/providers/registry.py +607 -0
  74. foundry_mcp/core/providers/test_provider.py +171 -0
  75. foundry_mcp/core/providers/validation.py +729 -0
  76. foundry_mcp/core/rate_limit.py +427 -0
  77. foundry_mcp/core/resilience.py +600 -0
  78. foundry_mcp/core/responses.py +934 -0
  79. foundry_mcp/core/review.py +366 -0
  80. foundry_mcp/core/security.py +438 -0
  81. foundry_mcp/core/spec.py +1650 -0
  82. foundry_mcp/core/task.py +1289 -0
  83. foundry_mcp/core/testing.py +450 -0
  84. foundry_mcp/core/validation.py +2081 -0
  85. foundry_mcp/dashboard/__init__.py +32 -0
  86. foundry_mcp/dashboard/app.py +119 -0
  87. foundry_mcp/dashboard/components/__init__.py +17 -0
  88. foundry_mcp/dashboard/components/cards.py +88 -0
  89. foundry_mcp/dashboard/components/charts.py +234 -0
  90. foundry_mcp/dashboard/components/filters.py +136 -0
  91. foundry_mcp/dashboard/components/tables.py +195 -0
  92. foundry_mcp/dashboard/data/__init__.py +11 -0
  93. foundry_mcp/dashboard/data/stores.py +433 -0
  94. foundry_mcp/dashboard/launcher.py +289 -0
  95. foundry_mcp/dashboard/views/__init__.py +12 -0
  96. foundry_mcp/dashboard/views/errors.py +217 -0
  97. foundry_mcp/dashboard/views/metrics.py +174 -0
  98. foundry_mcp/dashboard/views/overview.py +160 -0
  99. foundry_mcp/dashboard/views/providers.py +83 -0
  100. foundry_mcp/dashboard/views/sdd_workflow.py +255 -0
  101. foundry_mcp/dashboard/views/tool_usage.py +139 -0
  102. foundry_mcp/prompts/__init__.py +9 -0
  103. foundry_mcp/prompts/workflows.py +525 -0
  104. foundry_mcp/resources/__init__.py +9 -0
  105. foundry_mcp/resources/specs.py +591 -0
  106. foundry_mcp/schemas/__init__.py +38 -0
  107. foundry_mcp/schemas/sdd-spec-schema.json +386 -0
  108. foundry_mcp/server.py +164 -0
  109. foundry_mcp/tools/__init__.py +10 -0
  110. foundry_mcp/tools/unified/__init__.py +71 -0
  111. foundry_mcp/tools/unified/authoring.py +1487 -0
  112. foundry_mcp/tools/unified/context_helpers.py +98 -0
  113. foundry_mcp/tools/unified/documentation_helpers.py +198 -0
  114. foundry_mcp/tools/unified/environment.py +939 -0
  115. foundry_mcp/tools/unified/error.py +462 -0
  116. foundry_mcp/tools/unified/health.py +225 -0
  117. foundry_mcp/tools/unified/journal.py +841 -0
  118. foundry_mcp/tools/unified/lifecycle.py +632 -0
  119. foundry_mcp/tools/unified/metrics.py +777 -0
  120. foundry_mcp/tools/unified/plan.py +745 -0
  121. foundry_mcp/tools/unified/pr.py +294 -0
  122. foundry_mcp/tools/unified/provider.py +629 -0
  123. foundry_mcp/tools/unified/review.py +685 -0
  124. foundry_mcp/tools/unified/review_helpers.py +299 -0
  125. foundry_mcp/tools/unified/router.py +102 -0
  126. foundry_mcp/tools/unified/server.py +580 -0
  127. foundry_mcp/tools/unified/spec.py +808 -0
  128. foundry_mcp/tools/unified/task.py +2202 -0
  129. foundry_mcp/tools/unified/test.py +370 -0
  130. foundry_mcp/tools/unified/verification.py +520 -0
  131. foundry_mcp-0.3.3.dist-info/METADATA +337 -0
  132. foundry_mcp-0.3.3.dist-info/RECORD +135 -0
  133. foundry_mcp-0.3.3.dist-info/WHEEL +4 -0
  134. foundry_mcp-0.3.3.dist-info/entry_points.txt +3 -0
  135. foundry_mcp-0.3.3.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,7 @@
1
+ """Foundry MCP - MCP server for SDD toolkit spec management."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from foundry_mcp.server import create_server, main
6
+
7
+ __all__ = ["__version__", "create_server", "main"]
@@ -0,0 +1,80 @@
1
+ """SDD CLI - Native command-line interface for Spec-Driven Development.
2
+
3
+ This CLI provides JSON-only output designed for AI coding assistants.
4
+ All commands emit structured JSON to stdout for reliable parsing.
5
+ """
6
+
7
+ from foundry_mcp.cli.config import CLIContext, create_context
8
+ from foundry_mcp.cli.flags import (
9
+ CLIFlagRegistry,
10
+ apply_cli_flag_overrides,
11
+ flags_for_discovery,
12
+ get_cli_flags,
13
+ with_flag_options,
14
+ )
15
+ from foundry_mcp.cli.logging import (
16
+ CLILogContext,
17
+ cli_command,
18
+ get_cli_logger,
19
+ get_request_id,
20
+ set_request_id,
21
+ )
22
+ from foundry_mcp.cli.main import cli
23
+ from foundry_mcp.cli.output import emit, emit_error, emit_success
24
+ from foundry_mcp.cli.registry import get_context, set_context
25
+ from foundry_mcp.cli.resilience import (
26
+ FAST_TIMEOUT,
27
+ MEDIUM_TIMEOUT,
28
+ SLOW_TIMEOUT,
29
+ with_sync_timeout,
30
+ cli_retryable,
31
+ handle_keyboard_interrupt,
32
+ )
33
+ from foundry_mcp.cli.context import (
34
+ ContextSession,
35
+ ContextTracker,
36
+ get_context_tracker,
37
+ get_session_status,
38
+ record_consultation,
39
+ start_cli_session,
40
+ )
41
+
42
+ __all__ = [
43
+ # Entry point
44
+ "cli",
45
+ # Context
46
+ "CLIContext",
47
+ "create_context",
48
+ "get_context",
49
+ "set_context",
50
+ # Output
51
+ "emit",
52
+ "emit_error",
53
+ "emit_success",
54
+ # Feature flags
55
+ "CLIFlagRegistry",
56
+ "apply_cli_flag_overrides",
57
+ "flags_for_discovery",
58
+ "get_cli_flags",
59
+ "with_flag_options",
60
+ # Logging
61
+ "CLILogContext",
62
+ "cli_command",
63
+ "get_cli_logger",
64
+ "get_request_id",
65
+ "set_request_id",
66
+ # Resilience
67
+ "FAST_TIMEOUT",
68
+ "MEDIUM_TIMEOUT",
69
+ "SLOW_TIMEOUT",
70
+ "with_sync_timeout",
71
+ "cli_retryable",
72
+ "handle_keyboard_interrupt",
73
+ # Session/Context tracking
74
+ "ContextSession",
75
+ "ContextTracker",
76
+ "get_context_tracker",
77
+ "get_session_status",
78
+ "record_consultation",
79
+ "start_cli_session",
80
+ ]
@@ -0,0 +1,9 @@
1
+ """SDD CLI module entry point.
2
+
3
+ Enables running the CLI via: python -m foundry_mcp.cli
4
+ """
5
+
6
+ from foundry_mcp.cli.main import cli
7
+
8
+ if __name__ == "__main__":
9
+ cli()
@@ -0,0 +1,96 @@
1
+ """Agent detection and feature gating for CLI commands.
2
+
3
+ Provides mechanisms to detect which AI coding assistant is running the CLI
4
+ and gate features that are only available for specific agents.
5
+
6
+ Agent types:
7
+ - claude-code: Anthropic's Claude Code (has transcript access)
8
+ - cursor: Cursor IDE
9
+ - generic: Unknown/default agent
10
+
11
+ Configuration:
12
+ - Set FOUNDRY_MCP_AGENT_TYPE in MCP server config (env section)
13
+ - The CLI inherits this from the MCP server environment
14
+ """
15
+
16
+ import os
17
+ from functools import wraps
18
+ from typing import Callable, TypeVar
19
+
20
+ from foundry_mcp.cli.output import emit_success
21
+
22
+ # Valid agent types
23
+ AGENT_TYPES = frozenset({"claude-code", "cursor", "generic"})
24
+ DEFAULT_AGENT_TYPE = "generic"
25
+
26
+ F = TypeVar("F", bound=Callable)
27
+
28
+
29
+ def get_agent_type() -> str:
30
+ """Get the configured agent type from environment.
31
+
32
+ Set via MCP server config:
33
+ "env": {"FOUNDRY_MCP_AGENT_TYPE": "claude-code"}
34
+
35
+ Returns:
36
+ Agent type string (claude-code, cursor, generic).
37
+ """
38
+ env_agent = os.environ.get("FOUNDRY_MCP_AGENT_TYPE", "")
39
+ agent = env_agent.lower().strip()
40
+ return agent if agent in AGENT_TYPES else DEFAULT_AGENT_TYPE
41
+
42
+
43
+ def agent_gated(required_agent: str) -> Callable[[F], F]:
44
+ """Decorator for agent-specific commands.
45
+
46
+ When the current agent type doesn't match the required agent,
47
+ returns a success response with a warning indicating the feature
48
+ is unavailable, rather than failing.
49
+
50
+ Args:
51
+ required_agent: The agent type required for this command (e.g., "claude-code").
52
+
53
+ Returns:
54
+ Decorated function that gates execution by agent type.
55
+
56
+ Example:
57
+ @session.command("token-usage")
58
+ @agent_gated("claude-code")
59
+ def token_usage_cmd():
60
+ # Only runs when agent_type == "claude-code"
61
+ ...
62
+ """
63
+
64
+ def decorator(f: F) -> F:
65
+ @wraps(f)
66
+ def wrapper(*args, **kwargs):
67
+ current_agent = get_agent_type()
68
+
69
+ if current_agent != required_agent:
70
+ emit_success(
71
+ {
72
+ "available": False,
73
+ "reason": f"This feature requires agent_type='{required_agent}'",
74
+ "current_agent": current_agent,
75
+ "hint": f"Set FOUNDRY_MCP_AGENT_TYPE={required_agent} or configure in foundry-mcp.toml",
76
+ },
77
+ meta={
78
+ "warning": f"Feature unavailable for agent_type='{current_agent}'"
79
+ },
80
+ )
81
+ return
82
+
83
+ return f(*args, **kwargs)
84
+
85
+ return wrapper # type: ignore
86
+
87
+ return decorator
88
+
89
+
90
+ def is_claude_code() -> bool:
91
+ """Check if the current agent is Claude Code.
92
+
93
+ Returns:
94
+ True if agent_type is "claude-code".
95
+ """
96
+ return get_agent_type() == "claude-code"
@@ -0,0 +1,37 @@
1
+ """CLI command groups.
2
+
3
+ The CLI is organized into domain groups (e.g. `specs`, `tasks`, `test`).
4
+ Legacy top-level aliases are intentionally not exported.
5
+ """
6
+
7
+ from foundry_mcp.cli.commands.cache import cache
8
+ from foundry_mcp.cli.commands.dashboard import dashboard_group
9
+ from foundry_mcp.cli.commands.dev import dev_group
10
+ from foundry_mcp.cli.commands.journal import journal
11
+ from foundry_mcp.cli.commands.lifecycle import lifecycle
12
+ from foundry_mcp.cli.commands.modify import modify_group
13
+ from foundry_mcp.cli.commands.plan import plan_group
14
+ from foundry_mcp.cli.commands.pr import pr_group
15
+ from foundry_mcp.cli.commands.review import review_group
16
+ from foundry_mcp.cli.commands.session import session
17
+ from foundry_mcp.cli.commands.specs import specs
18
+ from foundry_mcp.cli.commands.tasks import tasks
19
+ from foundry_mcp.cli.commands.testing import test_group
20
+ from foundry_mcp.cli.commands.validate import validate_group
21
+
22
+ __all__ = [
23
+ "cache",
24
+ "dashboard_group",
25
+ "dev_group",
26
+ "journal",
27
+ "lifecycle",
28
+ "modify_group",
29
+ "plan_group",
30
+ "pr_group",
31
+ "review_group",
32
+ "session",
33
+ "specs",
34
+ "tasks",
35
+ "test_group",
36
+ "validate_group",
37
+ ]
@@ -0,0 +1,137 @@
1
+ """Cache management commands for SDD CLI.
2
+
3
+ Provides commands for inspecting and managing the AI consultation cache.
4
+ """
5
+
6
+ from typing import Optional
7
+
8
+ import click
9
+
10
+ from foundry_mcp.cli.logging import cli_command, get_cli_logger
11
+ from foundry_mcp.cli.output import emit_success
12
+ from foundry_mcp.cli.resilience import (
13
+ FAST_TIMEOUT,
14
+ handle_keyboard_interrupt,
15
+ with_sync_timeout,
16
+ )
17
+ from foundry_mcp.core.cache import CacheManager, is_cache_enabled
18
+
19
+ logger = get_cli_logger()
20
+
21
+
22
+ @click.group("cache")
23
+ def cache() -> None:
24
+ """AI consultation cache management."""
25
+ pass
26
+
27
+
28
+ @cache.command("info")
29
+ @click.pass_context
30
+ @cli_command("info")
31
+ @handle_keyboard_interrupt()
32
+ @with_sync_timeout(FAST_TIMEOUT, "Cache info lookup timed out")
33
+ def cache_info_cmd(ctx: click.Context) -> None:
34
+ """Show cache information and statistics.
35
+
36
+ Displays cache location, size, and entry counts.
37
+ """
38
+ if not is_cache_enabled():
39
+ emit_success(
40
+ {
41
+ "enabled": False,
42
+ "message": "Cache is disabled",
43
+ "hint": "Unset FOUNDRY_MCP_CACHE_DISABLED to enable caching",
44
+ }
45
+ )
46
+ return
47
+
48
+ manager = CacheManager()
49
+ stats = manager.get_stats()
50
+
51
+ emit_success(
52
+ {
53
+ "enabled": True,
54
+ **stats,
55
+ }
56
+ )
57
+
58
+
59
+ @cache.command("clear")
60
+ @click.option("--spec-id", help="Only clear entries for this spec ID.")
61
+ @click.option(
62
+ "--review-type",
63
+ type=click.Choice(["fidelity", "plan"]),
64
+ help="Only clear entries of this review type.",
65
+ )
66
+ @click.pass_context
67
+ @cli_command("clear")
68
+ @handle_keyboard_interrupt()
69
+ @with_sync_timeout(FAST_TIMEOUT, "Cache clear timed out")
70
+ def cache_clear_cmd(
71
+ ctx: click.Context,
72
+ spec_id: Optional[str],
73
+ review_type: Optional[str],
74
+ ) -> None:
75
+ """Clear cache entries with optional filters.
76
+
77
+ Without filters, clears all cache entries.
78
+ Use --spec-id and/or --review-type to filter.
79
+ """
80
+ if not is_cache_enabled():
81
+ emit_success(
82
+ {
83
+ "enabled": False,
84
+ "entries_deleted": 0,
85
+ "message": "Cache is disabled",
86
+ }
87
+ )
88
+ return
89
+
90
+ manager = CacheManager()
91
+ deleted = manager.clear(spec_id=spec_id, review_type=review_type)
92
+
93
+ filters = {}
94
+ if spec_id:
95
+ filters["spec_id"] = spec_id
96
+ if review_type:
97
+ filters["review_type"] = review_type
98
+
99
+ emit_success(
100
+ {
101
+ "entries_deleted": deleted,
102
+ "filters": filters if filters else None,
103
+ }
104
+ )
105
+
106
+
107
+ @cache.command("cleanup")
108
+ @click.pass_context
109
+ @cli_command("cleanup")
110
+ @handle_keyboard_interrupt()
111
+ @with_sync_timeout(FAST_TIMEOUT, "Cache cleanup timed out")
112
+ def cache_cleanup_cmd(ctx: click.Context) -> None:
113
+ """Remove expired cache entries.
114
+
115
+ Cleans up entries that have exceeded their TTL.
116
+ """
117
+ if not is_cache_enabled():
118
+ emit_success(
119
+ {
120
+ "enabled": False,
121
+ "entries_removed": 0,
122
+ "message": "Cache is disabled",
123
+ }
124
+ )
125
+ return
126
+
127
+ manager = CacheManager()
128
+ removed = manager.cleanup_expired()
129
+
130
+ emit_success(
131
+ {
132
+ "entries_removed": removed,
133
+ "message": f"Removed {removed} expired entries"
134
+ if removed
135
+ else "No expired entries found",
136
+ }
137
+ )
@@ -0,0 +1,148 @@
1
+ """Dashboard CLI commands.
2
+
3
+ Provides commands for starting, stopping, and managing the Streamlit dashboard.
4
+ """
5
+
6
+ import click
7
+
8
+ from foundry_mcp.cli.output import emit, emit_error
9
+
10
+
11
+ @click.group("dashboard")
12
+ def dashboard_group() -> None:
13
+ """Dashboard server management commands.
14
+
15
+ Start, stop, and manage the Streamlit-based observability dashboard.
16
+ """
17
+ pass
18
+
19
+
20
+ @dashboard_group.command("start")
21
+ @click.option(
22
+ "--port",
23
+ "-p",
24
+ default=8501,
25
+ type=int,
26
+ help="Port to run dashboard on (default: 8501)",
27
+ )
28
+ @click.option(
29
+ "--host",
30
+ "-H",
31
+ default="127.0.0.1",
32
+ help="Host to bind to (default: 127.0.0.1 for localhost only)",
33
+ )
34
+ @click.option(
35
+ "--no-browser",
36
+ is_flag=True,
37
+ default=False,
38
+ help="Don't automatically open browser",
39
+ )
40
+ @click.pass_context
41
+ def dashboard_start_cmd(
42
+ ctx: click.Context,
43
+ port: int,
44
+ host: str,
45
+ no_browser: bool,
46
+ ) -> None:
47
+ """Start the Streamlit dashboard server.
48
+
49
+ Launches the dashboard in a background process and optionally opens
50
+ your browser to view it.
51
+
52
+ Examples:
53
+ # Start with defaults (localhost:8501, opens browser)
54
+ foundry-cli dashboard start
55
+
56
+ # Start on custom port without browser
57
+ foundry-cli dashboard start --port 8080 --no-browser
58
+
59
+ # Expose to network (be careful with security)
60
+ foundry-cli dashboard start --host 0.0.0.0
61
+ """
62
+ try:
63
+ from foundry_mcp.dashboard import launch_dashboard
64
+
65
+ result = launch_dashboard(
66
+ host=host,
67
+ port=port,
68
+ open_browser=not no_browser,
69
+ )
70
+
71
+ if result.get("success"):
72
+ emit(
73
+ {
74
+ "success": True,
75
+ "message": result.get("message", "Dashboard started"),
76
+ "url": result.get("url"),
77
+ "pid": result.get("pid"),
78
+ }
79
+ )
80
+ else:
81
+ emit_error(result.get("message", "Failed to start dashboard"))
82
+
83
+ except ImportError:
84
+ emit_error(
85
+ "Dashboard dependencies not installed. "
86
+ "Install with: pip install foundry-mcp[dashboard]"
87
+ )
88
+ except Exception as e:
89
+ emit_error(f"Failed to start dashboard: {e}")
90
+
91
+
92
+ @dashboard_group.command("stop")
93
+ @click.pass_context
94
+ def dashboard_stop_cmd(ctx: click.Context) -> None:
95
+ """Stop the running dashboard server.
96
+
97
+ Terminates the dashboard process if one is running.
98
+ """
99
+ try:
100
+ from foundry_mcp.dashboard import stop_dashboard
101
+
102
+ result = stop_dashboard()
103
+
104
+ if result.get("success"):
105
+ emit(
106
+ {
107
+ "success": True,
108
+ "message": result.get("message", "Dashboard stopped"),
109
+ }
110
+ )
111
+ else:
112
+ emit(
113
+ {
114
+ "success": False,
115
+ "message": result.get("message", "No dashboard to stop"),
116
+ }
117
+ )
118
+
119
+ except ImportError:
120
+ emit_error("Dashboard module not available")
121
+ except Exception as e:
122
+ emit_error(f"Failed to stop dashboard: {e}")
123
+
124
+
125
+ @dashboard_group.command("status")
126
+ @click.pass_context
127
+ def dashboard_status_cmd(ctx: click.Context) -> None:
128
+ """Check if dashboard is running.
129
+
130
+ Shows the current status of the dashboard server process.
131
+ """
132
+ try:
133
+ from foundry_mcp.dashboard import get_dashboard_status
134
+
135
+ status = get_dashboard_status()
136
+
137
+ emit(
138
+ {
139
+ "running": status.get("running", False),
140
+ "pid": status.get("pid"),
141
+ "exit_code": status.get("exit_code"),
142
+ }
143
+ )
144
+
145
+ except ImportError:
146
+ emit({"running": False, "message": "Dashboard module not available"})
147
+ except Exception as e:
148
+ emit_error(f"Failed to get dashboard status: {e}")