gobby 0.2.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.
- gobby/__init__.py +3 -0
- gobby/adapters/__init__.py +30 -0
- gobby/adapters/base.py +93 -0
- gobby/adapters/claude_code.py +276 -0
- gobby/adapters/codex.py +1292 -0
- gobby/adapters/gemini.py +343 -0
- gobby/agents/__init__.py +37 -0
- gobby/agents/codex_session.py +120 -0
- gobby/agents/constants.py +112 -0
- gobby/agents/context.py +362 -0
- gobby/agents/definitions.py +133 -0
- gobby/agents/gemini_session.py +111 -0
- gobby/agents/registry.py +618 -0
- gobby/agents/runner.py +968 -0
- gobby/agents/session.py +259 -0
- gobby/agents/spawn.py +916 -0
- gobby/agents/spawners/__init__.py +77 -0
- gobby/agents/spawners/base.py +142 -0
- gobby/agents/spawners/cross_platform.py +266 -0
- gobby/agents/spawners/embedded.py +225 -0
- gobby/agents/spawners/headless.py +226 -0
- gobby/agents/spawners/linux.py +125 -0
- gobby/agents/spawners/macos.py +277 -0
- gobby/agents/spawners/windows.py +308 -0
- gobby/agents/tty_config.py +319 -0
- gobby/autonomous/__init__.py +32 -0
- gobby/autonomous/progress_tracker.py +447 -0
- gobby/autonomous/stop_registry.py +269 -0
- gobby/autonomous/stuck_detector.py +383 -0
- gobby/cli/__init__.py +67 -0
- gobby/cli/__main__.py +8 -0
- gobby/cli/agents.py +529 -0
- gobby/cli/artifacts.py +266 -0
- gobby/cli/daemon.py +329 -0
- gobby/cli/extensions.py +526 -0
- gobby/cli/github.py +263 -0
- gobby/cli/init.py +53 -0
- gobby/cli/install.py +614 -0
- gobby/cli/installers/__init__.py +37 -0
- gobby/cli/installers/antigravity.py +65 -0
- gobby/cli/installers/claude.py +363 -0
- gobby/cli/installers/codex.py +192 -0
- gobby/cli/installers/gemini.py +294 -0
- gobby/cli/installers/git_hooks.py +377 -0
- gobby/cli/installers/shared.py +737 -0
- gobby/cli/linear.py +250 -0
- gobby/cli/mcp.py +30 -0
- gobby/cli/mcp_proxy.py +698 -0
- gobby/cli/memory.py +304 -0
- gobby/cli/merge.py +384 -0
- gobby/cli/projects.py +79 -0
- gobby/cli/sessions.py +622 -0
- gobby/cli/tasks/__init__.py +30 -0
- gobby/cli/tasks/_utils.py +658 -0
- gobby/cli/tasks/ai.py +1025 -0
- gobby/cli/tasks/commits.py +169 -0
- gobby/cli/tasks/crud.py +685 -0
- gobby/cli/tasks/deps.py +135 -0
- gobby/cli/tasks/labels.py +63 -0
- gobby/cli/tasks/main.py +273 -0
- gobby/cli/tasks/search.py +178 -0
- gobby/cli/tui.py +34 -0
- gobby/cli/utils.py +513 -0
- gobby/cli/workflows.py +927 -0
- gobby/cli/worktrees.py +481 -0
- gobby/config/__init__.py +129 -0
- gobby/config/app.py +551 -0
- gobby/config/extensions.py +167 -0
- gobby/config/features.py +472 -0
- gobby/config/llm_providers.py +98 -0
- gobby/config/logging.py +66 -0
- gobby/config/mcp.py +346 -0
- gobby/config/persistence.py +247 -0
- gobby/config/servers.py +141 -0
- gobby/config/sessions.py +250 -0
- gobby/config/tasks.py +784 -0
- gobby/hooks/__init__.py +104 -0
- gobby/hooks/artifact_capture.py +213 -0
- gobby/hooks/broadcaster.py +243 -0
- gobby/hooks/event_handlers.py +723 -0
- gobby/hooks/events.py +218 -0
- gobby/hooks/git.py +169 -0
- gobby/hooks/health_monitor.py +171 -0
- gobby/hooks/hook_manager.py +856 -0
- gobby/hooks/hook_types.py +575 -0
- gobby/hooks/plugins.py +813 -0
- gobby/hooks/session_coordinator.py +396 -0
- gobby/hooks/verification_runner.py +268 -0
- gobby/hooks/webhooks.py +339 -0
- gobby/install/claude/commands/gobby/bug.md +51 -0
- gobby/install/claude/commands/gobby/chore.md +51 -0
- gobby/install/claude/commands/gobby/epic.md +52 -0
- gobby/install/claude/commands/gobby/eval.md +235 -0
- gobby/install/claude/commands/gobby/feat.md +49 -0
- gobby/install/claude/commands/gobby/nit.md +52 -0
- gobby/install/claude/commands/gobby/ref.md +52 -0
- gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
- gobby/install/claude/hooks/hook_dispatcher.py +364 -0
- gobby/install/claude/hooks/validate_settings.py +102 -0
- gobby/install/claude/hooks-template.json +118 -0
- gobby/install/codex/hooks/hook_dispatcher.py +153 -0
- gobby/install/codex/prompts/forget.md +7 -0
- gobby/install/codex/prompts/memories.md +7 -0
- gobby/install/codex/prompts/recall.md +7 -0
- gobby/install/codex/prompts/remember.md +13 -0
- gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
- gobby/install/gemini/hooks-template.json +138 -0
- gobby/install/shared/plugins/code_guardian.py +456 -0
- gobby/install/shared/plugins/example_notify.py +331 -0
- gobby/integrations/__init__.py +10 -0
- gobby/integrations/github.py +145 -0
- gobby/integrations/linear.py +145 -0
- gobby/llm/__init__.py +40 -0
- gobby/llm/base.py +120 -0
- gobby/llm/claude.py +578 -0
- gobby/llm/claude_executor.py +503 -0
- gobby/llm/codex.py +322 -0
- gobby/llm/codex_executor.py +513 -0
- gobby/llm/executor.py +316 -0
- gobby/llm/factory.py +34 -0
- gobby/llm/gemini.py +258 -0
- gobby/llm/gemini_executor.py +339 -0
- gobby/llm/litellm.py +287 -0
- gobby/llm/litellm_executor.py +303 -0
- gobby/llm/resolver.py +499 -0
- gobby/llm/service.py +236 -0
- gobby/mcp_proxy/__init__.py +29 -0
- gobby/mcp_proxy/actions.py +175 -0
- gobby/mcp_proxy/daemon_control.py +198 -0
- gobby/mcp_proxy/importer.py +436 -0
- gobby/mcp_proxy/lazy.py +325 -0
- gobby/mcp_proxy/manager.py +798 -0
- gobby/mcp_proxy/metrics.py +609 -0
- gobby/mcp_proxy/models.py +139 -0
- gobby/mcp_proxy/registries.py +215 -0
- gobby/mcp_proxy/schema_hash.py +381 -0
- gobby/mcp_proxy/semantic_search.py +706 -0
- gobby/mcp_proxy/server.py +549 -0
- gobby/mcp_proxy/services/__init__.py +0 -0
- gobby/mcp_proxy/services/fallback.py +306 -0
- gobby/mcp_proxy/services/recommendation.py +224 -0
- gobby/mcp_proxy/services/server_mgmt.py +214 -0
- gobby/mcp_proxy/services/system.py +72 -0
- gobby/mcp_proxy/services/tool_filter.py +231 -0
- gobby/mcp_proxy/services/tool_proxy.py +309 -0
- gobby/mcp_proxy/stdio.py +565 -0
- gobby/mcp_proxy/tools/__init__.py +27 -0
- gobby/mcp_proxy/tools/agents.py +1103 -0
- gobby/mcp_proxy/tools/artifacts.py +207 -0
- gobby/mcp_proxy/tools/hub.py +335 -0
- gobby/mcp_proxy/tools/internal.py +337 -0
- gobby/mcp_proxy/tools/memory.py +543 -0
- gobby/mcp_proxy/tools/merge.py +422 -0
- gobby/mcp_proxy/tools/metrics.py +283 -0
- gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
- gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
- gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
- gobby/mcp_proxy/tools/orchestration/review.py +736 -0
- gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
- gobby/mcp_proxy/tools/session_messages.py +1056 -0
- gobby/mcp_proxy/tools/task_dependencies.py +219 -0
- gobby/mcp_proxy/tools/task_expansion.py +591 -0
- gobby/mcp_proxy/tools/task_github.py +393 -0
- gobby/mcp_proxy/tools/task_linear.py +379 -0
- gobby/mcp_proxy/tools/task_orchestration.py +77 -0
- gobby/mcp_proxy/tools/task_readiness.py +522 -0
- gobby/mcp_proxy/tools/task_sync.py +351 -0
- gobby/mcp_proxy/tools/task_validation.py +843 -0
- gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
- gobby/mcp_proxy/tools/tasks/_context.py +112 -0
- gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
- gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
- gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
- gobby/mcp_proxy/tools/tasks/_search.py +215 -0
- gobby/mcp_proxy/tools/tasks/_session.py +125 -0
- gobby/mcp_proxy/tools/workflows.py +973 -0
- gobby/mcp_proxy/tools/worktrees.py +1264 -0
- gobby/mcp_proxy/transports/__init__.py +0 -0
- gobby/mcp_proxy/transports/base.py +95 -0
- gobby/mcp_proxy/transports/factory.py +44 -0
- gobby/mcp_proxy/transports/http.py +139 -0
- gobby/mcp_proxy/transports/stdio.py +213 -0
- gobby/mcp_proxy/transports/websocket.py +136 -0
- gobby/memory/backends/__init__.py +116 -0
- gobby/memory/backends/mem0.py +408 -0
- gobby/memory/backends/memu.py +485 -0
- gobby/memory/backends/null.py +111 -0
- gobby/memory/backends/openmemory.py +537 -0
- gobby/memory/backends/sqlite.py +304 -0
- gobby/memory/context.py +87 -0
- gobby/memory/manager.py +1001 -0
- gobby/memory/protocol.py +451 -0
- gobby/memory/search/__init__.py +66 -0
- gobby/memory/search/text.py +127 -0
- gobby/memory/viz.py +258 -0
- gobby/prompts/__init__.py +13 -0
- gobby/prompts/defaults/expansion/system.md +119 -0
- gobby/prompts/defaults/expansion/user.md +48 -0
- gobby/prompts/defaults/external_validation/agent.md +72 -0
- gobby/prompts/defaults/external_validation/external.md +63 -0
- gobby/prompts/defaults/external_validation/spawn.md +83 -0
- gobby/prompts/defaults/external_validation/system.md +6 -0
- gobby/prompts/defaults/features/import_mcp.md +22 -0
- gobby/prompts/defaults/features/import_mcp_github.md +17 -0
- gobby/prompts/defaults/features/import_mcp_search.md +16 -0
- gobby/prompts/defaults/features/recommend_tools.md +32 -0
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
- gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
- gobby/prompts/defaults/features/server_description.md +20 -0
- gobby/prompts/defaults/features/server_description_system.md +6 -0
- gobby/prompts/defaults/features/task_description.md +31 -0
- gobby/prompts/defaults/features/task_description_system.md +6 -0
- gobby/prompts/defaults/features/tool_summary.md +17 -0
- gobby/prompts/defaults/features/tool_summary_system.md +6 -0
- gobby/prompts/defaults/research/step.md +58 -0
- gobby/prompts/defaults/validation/criteria.md +47 -0
- gobby/prompts/defaults/validation/validate.md +38 -0
- gobby/prompts/loader.py +346 -0
- gobby/prompts/models.py +113 -0
- gobby/py.typed +0 -0
- gobby/runner.py +488 -0
- gobby/search/__init__.py +23 -0
- gobby/search/protocol.py +104 -0
- gobby/search/tfidf.py +232 -0
- gobby/servers/__init__.py +7 -0
- gobby/servers/http.py +636 -0
- gobby/servers/models.py +31 -0
- gobby/servers/routes/__init__.py +23 -0
- gobby/servers/routes/admin.py +416 -0
- gobby/servers/routes/dependencies.py +118 -0
- gobby/servers/routes/mcp/__init__.py +24 -0
- gobby/servers/routes/mcp/hooks.py +135 -0
- gobby/servers/routes/mcp/plugins.py +121 -0
- gobby/servers/routes/mcp/tools.py +1337 -0
- gobby/servers/routes/mcp/webhooks.py +159 -0
- gobby/servers/routes/sessions.py +582 -0
- gobby/servers/websocket.py +766 -0
- gobby/sessions/__init__.py +13 -0
- gobby/sessions/analyzer.py +322 -0
- gobby/sessions/lifecycle.py +240 -0
- gobby/sessions/manager.py +563 -0
- gobby/sessions/processor.py +225 -0
- gobby/sessions/summary.py +532 -0
- gobby/sessions/transcripts/__init__.py +41 -0
- gobby/sessions/transcripts/base.py +125 -0
- gobby/sessions/transcripts/claude.py +386 -0
- gobby/sessions/transcripts/codex.py +143 -0
- gobby/sessions/transcripts/gemini.py +195 -0
- gobby/storage/__init__.py +21 -0
- gobby/storage/agents.py +409 -0
- gobby/storage/artifact_classifier.py +341 -0
- gobby/storage/artifacts.py +285 -0
- gobby/storage/compaction.py +67 -0
- gobby/storage/database.py +357 -0
- gobby/storage/inter_session_messages.py +194 -0
- gobby/storage/mcp.py +680 -0
- gobby/storage/memories.py +562 -0
- gobby/storage/merge_resolutions.py +550 -0
- gobby/storage/migrations.py +860 -0
- gobby/storage/migrations_legacy.py +1359 -0
- gobby/storage/projects.py +166 -0
- gobby/storage/session_messages.py +251 -0
- gobby/storage/session_tasks.py +97 -0
- gobby/storage/sessions.py +817 -0
- gobby/storage/task_dependencies.py +223 -0
- gobby/storage/tasks/__init__.py +42 -0
- gobby/storage/tasks/_aggregates.py +180 -0
- gobby/storage/tasks/_crud.py +449 -0
- gobby/storage/tasks/_id.py +104 -0
- gobby/storage/tasks/_lifecycle.py +311 -0
- gobby/storage/tasks/_manager.py +889 -0
- gobby/storage/tasks/_models.py +300 -0
- gobby/storage/tasks/_ordering.py +119 -0
- gobby/storage/tasks/_path_cache.py +110 -0
- gobby/storage/tasks/_queries.py +343 -0
- gobby/storage/tasks/_search.py +143 -0
- gobby/storage/workflow_audit.py +393 -0
- gobby/storage/worktrees.py +547 -0
- gobby/sync/__init__.py +29 -0
- gobby/sync/github.py +333 -0
- gobby/sync/linear.py +304 -0
- gobby/sync/memories.py +284 -0
- gobby/sync/tasks.py +641 -0
- gobby/tasks/__init__.py +8 -0
- gobby/tasks/build_verification.py +193 -0
- gobby/tasks/commits.py +633 -0
- gobby/tasks/context.py +747 -0
- gobby/tasks/criteria.py +342 -0
- gobby/tasks/enhanced_validator.py +226 -0
- gobby/tasks/escalation.py +263 -0
- gobby/tasks/expansion.py +626 -0
- gobby/tasks/external_validator.py +764 -0
- gobby/tasks/issue_extraction.py +171 -0
- gobby/tasks/prompts/expand.py +327 -0
- gobby/tasks/research.py +421 -0
- gobby/tasks/tdd.py +352 -0
- gobby/tasks/tree_builder.py +263 -0
- gobby/tasks/validation.py +712 -0
- gobby/tasks/validation_history.py +357 -0
- gobby/tasks/validation_models.py +89 -0
- gobby/tools/__init__.py +0 -0
- gobby/tools/summarizer.py +170 -0
- gobby/tui/__init__.py +5 -0
- gobby/tui/api_client.py +281 -0
- gobby/tui/app.py +327 -0
- gobby/tui/screens/__init__.py +25 -0
- gobby/tui/screens/agents.py +333 -0
- gobby/tui/screens/chat.py +450 -0
- gobby/tui/screens/dashboard.py +377 -0
- gobby/tui/screens/memory.py +305 -0
- gobby/tui/screens/metrics.py +231 -0
- gobby/tui/screens/orchestrator.py +904 -0
- gobby/tui/screens/sessions.py +412 -0
- gobby/tui/screens/tasks.py +442 -0
- gobby/tui/screens/workflows.py +289 -0
- gobby/tui/screens/worktrees.py +174 -0
- gobby/tui/widgets/__init__.py +21 -0
- gobby/tui/widgets/chat.py +210 -0
- gobby/tui/widgets/conductor.py +104 -0
- gobby/tui/widgets/menu.py +132 -0
- gobby/tui/widgets/message_panel.py +160 -0
- gobby/tui/widgets/review_gate.py +224 -0
- gobby/tui/widgets/task_tree.py +99 -0
- gobby/tui/widgets/token_budget.py +166 -0
- gobby/tui/ws_client.py +258 -0
- gobby/utils/__init__.py +3 -0
- gobby/utils/daemon_client.py +235 -0
- gobby/utils/git.py +222 -0
- gobby/utils/id.py +38 -0
- gobby/utils/json_helpers.py +161 -0
- gobby/utils/logging.py +376 -0
- gobby/utils/machine_id.py +135 -0
- gobby/utils/metrics.py +589 -0
- gobby/utils/project_context.py +182 -0
- gobby/utils/project_init.py +263 -0
- gobby/utils/status.py +256 -0
- gobby/utils/validation.py +80 -0
- gobby/utils/version.py +23 -0
- gobby/workflows/__init__.py +4 -0
- gobby/workflows/actions.py +1310 -0
- gobby/workflows/approval_flow.py +138 -0
- gobby/workflows/artifact_actions.py +103 -0
- gobby/workflows/audit_helpers.py +110 -0
- gobby/workflows/autonomous_actions.py +286 -0
- gobby/workflows/context_actions.py +394 -0
- gobby/workflows/definitions.py +130 -0
- gobby/workflows/detection_helpers.py +208 -0
- gobby/workflows/engine.py +485 -0
- gobby/workflows/evaluator.py +669 -0
- gobby/workflows/git_utils.py +96 -0
- gobby/workflows/hooks.py +169 -0
- gobby/workflows/lifecycle_evaluator.py +613 -0
- gobby/workflows/llm_actions.py +70 -0
- gobby/workflows/loader.py +333 -0
- gobby/workflows/mcp_actions.py +60 -0
- gobby/workflows/memory_actions.py +272 -0
- gobby/workflows/premature_stop.py +164 -0
- gobby/workflows/session_actions.py +139 -0
- gobby/workflows/state_actions.py +123 -0
- gobby/workflows/state_manager.py +104 -0
- gobby/workflows/stop_signal_actions.py +163 -0
- gobby/workflows/summary_actions.py +344 -0
- gobby/workflows/task_actions.py +249 -0
- gobby/workflows/task_enforcement_actions.py +901 -0
- gobby/workflows/templates.py +52 -0
- gobby/workflows/todo_actions.py +84 -0
- gobby/workflows/webhook.py +223 -0
- gobby/workflows/webhook_executor.py +399 -0
- gobby/worktrees/__init__.py +5 -0
- gobby/worktrees/git.py +690 -0
- gobby/worktrees/merge/__init__.py +20 -0
- gobby/worktrees/merge/conflict_parser.py +177 -0
- gobby/worktrees/merge/resolver.py +485 -0
- gobby-0.2.5.dist-info/METADATA +351 -0
- gobby-0.2.5.dist-info/RECORD +383 -0
- gobby-0.2.5.dist-info/WHEEL +5 -0
- gobby-0.2.5.dist-info/entry_points.txt +2 -0
- gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
- gobby-0.2.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Server management service."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
from gobby.mcp_proxy.manager import MCPClientManager
|
|
7
|
+
from gobby.mcp_proxy.models import MCPServerConfig
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from gobby.config.app import DaemonConfig
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("gobby.mcp.server")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ServerManagementService:
|
|
16
|
+
"""Service for managing MCP server configurations."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
mcp_manager: MCPClientManager,
|
|
21
|
+
config_manager: Any,
|
|
22
|
+
config: "DaemonConfig | None" = None,
|
|
23
|
+
):
|
|
24
|
+
"""
|
|
25
|
+
Args:
|
|
26
|
+
mcp_manager: MCP client manager
|
|
27
|
+
config_manager: Config manager (for saving changes)
|
|
28
|
+
config: Daemon configuration (for import operations)
|
|
29
|
+
"""
|
|
30
|
+
self._mcp_manager = mcp_manager
|
|
31
|
+
self._config_manager = config_manager
|
|
32
|
+
self._config = config
|
|
33
|
+
|
|
34
|
+
async def add_server(
|
|
35
|
+
self,
|
|
36
|
+
name: str,
|
|
37
|
+
transport: str,
|
|
38
|
+
url: str | None = None,
|
|
39
|
+
command: str | None = None,
|
|
40
|
+
args: list[str] | None = None,
|
|
41
|
+
env: dict[str, str] | None = None,
|
|
42
|
+
headers: dict[str, str] | None = None,
|
|
43
|
+
enabled: bool = True,
|
|
44
|
+
project_id: str | None = None,
|
|
45
|
+
) -> dict[str, Any]:
|
|
46
|
+
"""Add a new MCP server."""
|
|
47
|
+
try:
|
|
48
|
+
# Resolve project ID
|
|
49
|
+
if not project_id:
|
|
50
|
+
from gobby.utils.project_context import get_project_context
|
|
51
|
+
|
|
52
|
+
ctx = get_project_context()
|
|
53
|
+
if ctx and ctx.get("id"):
|
|
54
|
+
project_id = ctx["id"]
|
|
55
|
+
|
|
56
|
+
if not project_id:
|
|
57
|
+
return {
|
|
58
|
+
"success": False,
|
|
59
|
+
"error": "project_id is required. Run 'gobby init' or provide project_id.",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Create config object
|
|
63
|
+
server_config = MCPServerConfig(
|
|
64
|
+
name=name,
|
|
65
|
+
project_id=project_id,
|
|
66
|
+
transport=transport,
|
|
67
|
+
url=url,
|
|
68
|
+
command=command,
|
|
69
|
+
args=args,
|
|
70
|
+
env=env,
|
|
71
|
+
headers=headers,
|
|
72
|
+
enabled=enabled,
|
|
73
|
+
)
|
|
74
|
+
# Validate - catch validation errors separately for clear error messages
|
|
75
|
+
try:
|
|
76
|
+
server_config.validate()
|
|
77
|
+
except ValueError as e:
|
|
78
|
+
return {"success": False, "error": f"Validation error: {e}"}
|
|
79
|
+
|
|
80
|
+
# Add to manager (runtime)
|
|
81
|
+
self._mcp_manager.add_server_config(server_config)
|
|
82
|
+
|
|
83
|
+
# Persist to config
|
|
84
|
+
# self._config_manager.add_mcp_server(...) # Mocking this interaction
|
|
85
|
+
|
|
86
|
+
# Attempt connection
|
|
87
|
+
if enabled:
|
|
88
|
+
try:
|
|
89
|
+
await self._mcp_manager.connect_all([server_config])
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.warning(f"Added server {name} but connection failed: {e}")
|
|
92
|
+
return {
|
|
93
|
+
"success": True,
|
|
94
|
+
"message": f"Server added but connection failed: {str(e)}",
|
|
95
|
+
"connected": False,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
"success": True,
|
|
100
|
+
"message": f"Server {name} added successfully",
|
|
101
|
+
"connected": enabled,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.exception(f"Unexpected error adding server {name}")
|
|
106
|
+
return {"success": False, "error": str(e)}
|
|
107
|
+
|
|
108
|
+
async def remove_server(self, name: str) -> dict[str, Any]:
|
|
109
|
+
"""Remove an MCP server.
|
|
110
|
+
|
|
111
|
+
Disconnects the server first if connected, then removes the configuration.
|
|
112
|
+
"""
|
|
113
|
+
try:
|
|
114
|
+
# First disconnect if connected
|
|
115
|
+
if name in self._mcp_manager._connections:
|
|
116
|
+
try:
|
|
117
|
+
connection = self._mcp_manager._connections[name]
|
|
118
|
+
if connection.is_connected:
|
|
119
|
+
await connection.disconnect()
|
|
120
|
+
# Update health state
|
|
121
|
+
if name in self._mcp_manager.health:
|
|
122
|
+
from gobby.mcp_proxy.models import ConnectionState
|
|
123
|
+
|
|
124
|
+
self._mcp_manager.health[name].state = ConnectionState.DISCONNECTED
|
|
125
|
+
# Remove from connections
|
|
126
|
+
del self._mcp_manager._connections[name]
|
|
127
|
+
logger.info(f"Disconnected server {name} before removal")
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.warning(f"Error disconnecting server {name}: {e}")
|
|
130
|
+
# Continue with removal even if disconnect fails
|
|
131
|
+
|
|
132
|
+
# Remove from runtime config
|
|
133
|
+
self._mcp_manager.remove_server_config(name)
|
|
134
|
+
|
|
135
|
+
# Persist
|
|
136
|
+
# self._config_manager.remove_mcp_server(name)
|
|
137
|
+
|
|
138
|
+
return {"success": True, "message": f"Server {name} removed"}
|
|
139
|
+
except Exception as e:
|
|
140
|
+
logger.error(f"Failed to remove server {name}: {e}")
|
|
141
|
+
return {"success": False, "error": str(e)}
|
|
142
|
+
|
|
143
|
+
async def import_server(
|
|
144
|
+
self,
|
|
145
|
+
from_project: str | None = None,
|
|
146
|
+
github_url: str | None = None,
|
|
147
|
+
query: str | None = None,
|
|
148
|
+
servers: list[str] | None = None,
|
|
149
|
+
) -> dict[str, Any]:
|
|
150
|
+
"""Import MCP server(s) from various sources.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
from_project: Import from another Gobby project by name or ID
|
|
154
|
+
github_url: Import from a GitHub repository URL
|
|
155
|
+
query: Import by natural language search query
|
|
156
|
+
servers: Optional list of specific server names to import
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Result dict with imported servers or error
|
|
160
|
+
"""
|
|
161
|
+
# Validate at least one source is provided
|
|
162
|
+
if not from_project and not github_url and not query:
|
|
163
|
+
return {
|
|
164
|
+
"success": False,
|
|
165
|
+
"error": "Specify at least one: from_project, github_url, or query",
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# Get current project context
|
|
169
|
+
from gobby.utils.project_context import get_project_context
|
|
170
|
+
|
|
171
|
+
project_ctx = get_project_context()
|
|
172
|
+
if not project_ctx or not project_ctx.get("id"):
|
|
173
|
+
return {
|
|
174
|
+
"success": False,
|
|
175
|
+
"error": "No current project. Run 'gobby init' first.",
|
|
176
|
+
}
|
|
177
|
+
current_project_id = project_ctx["id"]
|
|
178
|
+
|
|
179
|
+
# Validate config is available
|
|
180
|
+
if not self._config:
|
|
181
|
+
return {
|
|
182
|
+
"success": False,
|
|
183
|
+
"error": "Daemon configuration not available for import operations",
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
# Create importer lazily with required dependencies
|
|
188
|
+
from gobby.mcp_proxy.importer import MCPServerImporter
|
|
189
|
+
from gobby.storage.database import LocalDatabase
|
|
190
|
+
|
|
191
|
+
db = LocalDatabase()
|
|
192
|
+
importer = MCPServerImporter(
|
|
193
|
+
config=self._config,
|
|
194
|
+
db=db,
|
|
195
|
+
current_project_id=current_project_id,
|
|
196
|
+
mcp_client_manager=self._mcp_manager,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Execute import based on source
|
|
200
|
+
if from_project:
|
|
201
|
+
return await importer.import_from_project(
|
|
202
|
+
source_project=from_project,
|
|
203
|
+
servers=servers,
|
|
204
|
+
)
|
|
205
|
+
elif github_url:
|
|
206
|
+
return await importer.import_from_github(github_url)
|
|
207
|
+
elif query:
|
|
208
|
+
return await importer.import_from_query(query)
|
|
209
|
+
else:
|
|
210
|
+
return {"success": False, "error": "No import source specified"}
|
|
211
|
+
|
|
212
|
+
except Exception as e:
|
|
213
|
+
logger.exception("Failed to import MCP server")
|
|
214
|
+
return {"success": False, "error": str(e)}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""System status service."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from gobby.mcp_proxy.manager import MCPClientManager
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("gobby.mcp.server")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SystemService:
|
|
13
|
+
"""Service for system status and information."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self, mcp_manager: MCPClientManager, port: int, websocket_port: int, start_time: float
|
|
17
|
+
):
|
|
18
|
+
self._mcp_manager = mcp_manager
|
|
19
|
+
self._port = port
|
|
20
|
+
self._websocket_port = websocket_port
|
|
21
|
+
self._start_time = start_time
|
|
22
|
+
|
|
23
|
+
def get_status(self) -> dict[str, Any]:
|
|
24
|
+
"""Get system status."""
|
|
25
|
+
health = self._mcp_manager.get_server_health()
|
|
26
|
+
lazy_states = self._mcp_manager.get_lazy_connection_states()
|
|
27
|
+
|
|
28
|
+
# Merge lazy connection info into health
|
|
29
|
+
for name, lazy_info in lazy_states.items():
|
|
30
|
+
if name in health:
|
|
31
|
+
health[name]["lazy_connection"] = lazy_info
|
|
32
|
+
else:
|
|
33
|
+
# Server registered but never connected (lazy mode)
|
|
34
|
+
health[name] = {
|
|
35
|
+
"state": "configured", # Not connected yet
|
|
36
|
+
"health": "unknown",
|
|
37
|
+
"last_check": None,
|
|
38
|
+
"failures": 0,
|
|
39
|
+
"response_time_ms": None,
|
|
40
|
+
"lazy_connection": lazy_info,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Aggregate health: system is healthy if connected servers are healthy
|
|
44
|
+
# In lazy mode, unconfigured servers don't count as unhealthy
|
|
45
|
+
all_healthy = (
|
|
46
|
+
all(
|
|
47
|
+
server_health.get("state") in ["connected", "healthy", "configured"]
|
|
48
|
+
for server_health in health.values()
|
|
49
|
+
)
|
|
50
|
+
if health
|
|
51
|
+
else True
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Count configured vs connected
|
|
55
|
+
configured_count = len(health)
|
|
56
|
+
connected_count = sum(
|
|
57
|
+
1
|
|
58
|
+
for h in health.values()
|
|
59
|
+
if h.get("state") == "connected" or h.get("lazy_connection", {}).get("is_connected")
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
"running": True,
|
|
64
|
+
"pid": os.getpid(),
|
|
65
|
+
"healthy": all_healthy,
|
|
66
|
+
"http_port": self._port,
|
|
67
|
+
"websocket_port": self._websocket_port,
|
|
68
|
+
"mcp_servers": health,
|
|
69
|
+
"lazy_mode": self._mcp_manager.lazy_connect,
|
|
70
|
+
"configured_servers": configured_count,
|
|
71
|
+
"connected_servers": connected_count,
|
|
72
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Tool filtering service based on workflow step restrictions."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from gobby.storage.database import LocalDatabase
|
|
9
|
+
from gobby.workflows.loader import WorkflowLoader
|
|
10
|
+
from gobby.workflows.state_manager import WorkflowStateManager
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("gobby.mcp.tool_filter")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ToolFilterService:
|
|
16
|
+
"""
|
|
17
|
+
Service to filter tools based on workflow step restrictions.
|
|
18
|
+
|
|
19
|
+
When a session has an active step-based workflow, this service
|
|
20
|
+
filters the tool list to only include tools allowed in the current step.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
db: "LocalDatabase | None" = None,
|
|
26
|
+
loader: "WorkflowLoader | None" = None,
|
|
27
|
+
state_manager: "WorkflowStateManager | None" = None,
|
|
28
|
+
):
|
|
29
|
+
self._db = db
|
|
30
|
+
self._loader = loader
|
|
31
|
+
self._state_manager = state_manager
|
|
32
|
+
|
|
33
|
+
def _get_state_manager(self) -> "WorkflowStateManager | None":
|
|
34
|
+
"""Lazy initialization of state manager."""
|
|
35
|
+
if self._state_manager:
|
|
36
|
+
return self._state_manager
|
|
37
|
+
|
|
38
|
+
if self._db:
|
|
39
|
+
from gobby.workflows.state_manager import WorkflowStateManager
|
|
40
|
+
|
|
41
|
+
self._state_manager = WorkflowStateManager(self._db)
|
|
42
|
+
return self._state_manager
|
|
43
|
+
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
def _get_loader(self) -> "WorkflowLoader | None":
|
|
47
|
+
"""Lazy initialization of workflow loader."""
|
|
48
|
+
if self._loader:
|
|
49
|
+
return self._loader
|
|
50
|
+
|
|
51
|
+
from gobby.workflows.loader import WorkflowLoader
|
|
52
|
+
|
|
53
|
+
self._loader = WorkflowLoader()
|
|
54
|
+
return self._loader
|
|
55
|
+
|
|
56
|
+
def get_step_restrictions(
|
|
57
|
+
self,
|
|
58
|
+
session_id: str,
|
|
59
|
+
project_path: str | Path | None = None,
|
|
60
|
+
) -> dict[str, Any] | None:
|
|
61
|
+
"""
|
|
62
|
+
Get tool restrictions for the current workflow step.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
session_id: Session ID to check
|
|
66
|
+
project_path: Optional project path for loading workflow
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Dict with allowed_tools and blocked_tools, or None if no workflow active
|
|
70
|
+
"""
|
|
71
|
+
state_manager = self._get_state_manager()
|
|
72
|
+
if not state_manager:
|
|
73
|
+
logger.debug("No state manager available for tool filtering")
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
state = state_manager.get_state(session_id)
|
|
77
|
+
if not state:
|
|
78
|
+
logger.debug(f"No workflow state for session {session_id}")
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
loader = self._get_loader()
|
|
82
|
+
if not loader:
|
|
83
|
+
logger.debug("No workflow loader available")
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
proj = Path(project_path) if project_path else None
|
|
87
|
+
definition = loader.load_workflow(state.workflow_name, proj)
|
|
88
|
+
if not definition:
|
|
89
|
+
logger.warning(f"Workflow '{state.workflow_name}' not found")
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
step = definition.get_step(state.step)
|
|
93
|
+
if not step:
|
|
94
|
+
logger.warning(f"Step '{state.step}' not found in workflow '{state.workflow_name}'")
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
"workflow_name": state.workflow_name,
|
|
99
|
+
"step": state.step,
|
|
100
|
+
"allowed_tools": step.allowed_tools,
|
|
101
|
+
"blocked_tools": step.blocked_tools,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
def is_tool_allowed(
|
|
105
|
+
self,
|
|
106
|
+
tool_name: str,
|
|
107
|
+
session_id: str,
|
|
108
|
+
project_path: str | Path | None = None,
|
|
109
|
+
) -> tuple[bool, str | None]:
|
|
110
|
+
"""
|
|
111
|
+
Check if a tool is allowed in the current workflow step.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
tool_name: Name of the tool to check
|
|
115
|
+
session_id: Session ID
|
|
116
|
+
project_path: Optional project path
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Tuple of (is_allowed, reason). If no workflow is active, returns (True, None).
|
|
120
|
+
"""
|
|
121
|
+
restrictions = self.get_step_restrictions(session_id, project_path)
|
|
122
|
+
if not restrictions:
|
|
123
|
+
return True, None
|
|
124
|
+
|
|
125
|
+
# Check blocked list first
|
|
126
|
+
if tool_name in restrictions["blocked_tools"]:
|
|
127
|
+
return False, f"Tool '{tool_name}' is blocked in step '{restrictions['step']}'"
|
|
128
|
+
|
|
129
|
+
# Check allowed list
|
|
130
|
+
allowed = restrictions["allowed_tools"]
|
|
131
|
+
if allowed == "all":
|
|
132
|
+
return True, None
|
|
133
|
+
|
|
134
|
+
if tool_name not in allowed:
|
|
135
|
+
return (
|
|
136
|
+
False,
|
|
137
|
+
f"Tool '{tool_name}' is not in allowed list for step '{restrictions['step']}'",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return True, None
|
|
141
|
+
|
|
142
|
+
def filter_tools(
|
|
143
|
+
self,
|
|
144
|
+
tools: list[dict[str, Any]],
|
|
145
|
+
session_id: str | None = None,
|
|
146
|
+
project_path: str | Path | None = None,
|
|
147
|
+
) -> list[dict[str, Any]]:
|
|
148
|
+
"""
|
|
149
|
+
Filter a list of tools based on workflow step restrictions.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
tools: List of tool dicts with at least a 'name' key
|
|
153
|
+
session_id: Session ID to check for workflow state
|
|
154
|
+
project_path: Optional project path
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Filtered list of tools. If no session_id or no active workflow,
|
|
158
|
+
returns the original list unchanged.
|
|
159
|
+
"""
|
|
160
|
+
if not session_id:
|
|
161
|
+
return tools
|
|
162
|
+
|
|
163
|
+
restrictions = self.get_step_restrictions(session_id, project_path)
|
|
164
|
+
if not restrictions:
|
|
165
|
+
return tools
|
|
166
|
+
|
|
167
|
+
allowed = restrictions["allowed_tools"]
|
|
168
|
+
blocked = restrictions["blocked_tools"]
|
|
169
|
+
|
|
170
|
+
filtered = []
|
|
171
|
+
for tool in tools:
|
|
172
|
+
name = tool.get("name", "")
|
|
173
|
+
|
|
174
|
+
# Skip blocked tools
|
|
175
|
+
if name in blocked:
|
|
176
|
+
logger.debug(f"Filtering out blocked tool: {name}")
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
# Check allowed list
|
|
180
|
+
if allowed != "all" and name not in allowed:
|
|
181
|
+
logger.debug(f"Filtering out non-allowed tool: {name}")
|
|
182
|
+
continue
|
|
183
|
+
|
|
184
|
+
filtered.append(tool)
|
|
185
|
+
|
|
186
|
+
if len(filtered) < len(tools):
|
|
187
|
+
logger.info(
|
|
188
|
+
f"Filtered {len(tools) - len(filtered)} tools based on step '{restrictions['step']}'"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return filtered
|
|
192
|
+
|
|
193
|
+
def filter_servers_tools(
|
|
194
|
+
self,
|
|
195
|
+
servers: list[dict[str, Any]],
|
|
196
|
+
session_id: str | None = None,
|
|
197
|
+
project_path: str | Path | None = None,
|
|
198
|
+
) -> list[dict[str, Any]]:
|
|
199
|
+
"""
|
|
200
|
+
Filter tools from multiple servers based on workflow step restrictions.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
servers: List of server dicts with 'name' and 'tools' keys
|
|
204
|
+
session_id: Session ID to check for workflow state
|
|
205
|
+
project_path: Optional project path
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Servers list with filtered tools. Empty servers are kept but with empty tool lists.
|
|
209
|
+
"""
|
|
210
|
+
if not session_id:
|
|
211
|
+
return servers
|
|
212
|
+
|
|
213
|
+
restrictions = self.get_step_restrictions(session_id, project_path)
|
|
214
|
+
if not restrictions:
|
|
215
|
+
return servers
|
|
216
|
+
|
|
217
|
+
result = []
|
|
218
|
+
for server in servers:
|
|
219
|
+
server_name = server.get("name", "")
|
|
220
|
+
tools = server.get("tools", [])
|
|
221
|
+
|
|
222
|
+
filtered_tools = self.filter_tools(tools, session_id, project_path)
|
|
223
|
+
|
|
224
|
+
result.append(
|
|
225
|
+
{
|
|
226
|
+
"name": server_name,
|
|
227
|
+
"tools": filtered_tools,
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
return result
|