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,309 @@
|
|
|
1
|
+
"""Tool proxy 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 MCPError
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from gobby.mcp_proxy.services.fallback import ToolFallbackResolver
|
|
11
|
+
from gobby.mcp_proxy.services.tool_filter import ToolFilterService
|
|
12
|
+
from gobby.mcp_proxy.tools.internal import InternalRegistryManager
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger("gobby.mcp.server")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def safe_truncate(text: str | bytes | None, length: int = 100) -> str:
|
|
18
|
+
"""Safely truncate text to length by unicode code points."""
|
|
19
|
+
if text is None:
|
|
20
|
+
return ""
|
|
21
|
+
if isinstance(text, bytes):
|
|
22
|
+
text = text.decode("utf-8", errors="replace")
|
|
23
|
+
if len(text) <= length:
|
|
24
|
+
return text
|
|
25
|
+
return text[:length] + "..."
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ToolProxyService:
|
|
29
|
+
"""Service for proxying tool calls and resource reads to underlying MCP servers."""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
mcp_manager: MCPClientManager,
|
|
34
|
+
internal_manager: "InternalRegistryManager | None" = None,
|
|
35
|
+
tool_filter: "ToolFilterService | None" = None,
|
|
36
|
+
fallback_resolver: "ToolFallbackResolver | None" = None,
|
|
37
|
+
validate_arguments: bool = True,
|
|
38
|
+
):
|
|
39
|
+
self._mcp_manager = mcp_manager
|
|
40
|
+
self._internal_manager = internal_manager
|
|
41
|
+
self._tool_filter = tool_filter
|
|
42
|
+
self._fallback_resolver = fallback_resolver
|
|
43
|
+
self._validate_arguments = validate_arguments
|
|
44
|
+
|
|
45
|
+
def _check_arguments(
|
|
46
|
+
self,
|
|
47
|
+
arguments: dict[str, Any],
|
|
48
|
+
schema: dict[str, Any],
|
|
49
|
+
) -> list[str]:
|
|
50
|
+
"""
|
|
51
|
+
Validate arguments against JSON schema.
|
|
52
|
+
|
|
53
|
+
Returns list of validation errors, empty if valid.
|
|
54
|
+
"""
|
|
55
|
+
errors = []
|
|
56
|
+
properties = schema.get("properties", {})
|
|
57
|
+
required = schema.get("required", [])
|
|
58
|
+
|
|
59
|
+
# Check for unknown parameters (likely typos like workflow_name vs name)
|
|
60
|
+
for key in arguments:
|
|
61
|
+
if key not in properties:
|
|
62
|
+
# Find similar parameter names for better error message
|
|
63
|
+
similar = [p for p in properties if p in key or key in p]
|
|
64
|
+
if similar:
|
|
65
|
+
errors.append(f"Unknown parameter '{key}'. Did you mean '{similar[0]}'?")
|
|
66
|
+
else:
|
|
67
|
+
valid_params = list(properties.keys())
|
|
68
|
+
errors.append(f"Unknown parameter '{key}'. Valid parameters: {valid_params}")
|
|
69
|
+
|
|
70
|
+
# Check for missing required parameters
|
|
71
|
+
for req in required:
|
|
72
|
+
if req not in arguments:
|
|
73
|
+
errors.append(f"Missing required parameter '{req}'")
|
|
74
|
+
|
|
75
|
+
return errors
|
|
76
|
+
|
|
77
|
+
async def list_tools(
|
|
78
|
+
self,
|
|
79
|
+
server_name: str,
|
|
80
|
+
session_id: str | None = None,
|
|
81
|
+
) -> dict[str, Any]:
|
|
82
|
+
"""
|
|
83
|
+
List tools for a specific server with progressive disclosure format.
|
|
84
|
+
|
|
85
|
+
When session_id is provided and a workflow is active, tools are filtered
|
|
86
|
+
based on the current phase's allowed_tools and blocked_tools settings.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
server_name: Server name (e.g., "gobby-tasks", "context7")
|
|
90
|
+
session_id: Optional session ID to apply workflow phase filtering
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Dict with tool metadata: {"success": true, "tools": [...], "tool_count": N}
|
|
94
|
+
"""
|
|
95
|
+
# Check internal servers first (gobby-tasks, gobby-memory, etc.)
|
|
96
|
+
if self._internal_manager and self._internal_manager.is_internal(server_name):
|
|
97
|
+
registry = self._internal_manager.get_registry(server_name)
|
|
98
|
+
if registry:
|
|
99
|
+
tools = registry.list_tools()
|
|
100
|
+
# Apply phase filtering if session_id provided
|
|
101
|
+
if session_id and self._tool_filter:
|
|
102
|
+
tools = self._tool_filter.filter_tools(tools, session_id)
|
|
103
|
+
return {"success": True, "tools": tools, "tool_count": len(tools)}
|
|
104
|
+
return {
|
|
105
|
+
"success": False,
|
|
106
|
+
"tools": [],
|
|
107
|
+
"error": f"Internal server '{server_name}' not found",
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Check external servers
|
|
111
|
+
if self._mcp_manager.has_server(server_name):
|
|
112
|
+
tools_map = await self._mcp_manager.list_tools(server_name)
|
|
113
|
+
tools_list = tools_map.get(server_name, [])
|
|
114
|
+
# Convert to lightweight format
|
|
115
|
+
brief_tools = []
|
|
116
|
+
for tool in tools_list:
|
|
117
|
+
if isinstance(tool, dict):
|
|
118
|
+
brief_tools.append(
|
|
119
|
+
{
|
|
120
|
+
"name": tool.get("name", "unknown"),
|
|
121
|
+
"brief": safe_truncate(tool.get("description", "")),
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
else:
|
|
125
|
+
brief_tools.append(
|
|
126
|
+
{
|
|
127
|
+
"name": tool.name,
|
|
128
|
+
"brief": safe_truncate(tool.description),
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
# Apply phase filtering if session_id provided
|
|
132
|
+
if session_id and self._tool_filter:
|
|
133
|
+
brief_tools = self._tool_filter.filter_tools(brief_tools, session_id)
|
|
134
|
+
return {"success": True, "tools": brief_tools, "tool_count": len(brief_tools)}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
"success": False,
|
|
138
|
+
"tools": [],
|
|
139
|
+
"error": f"Server '{server_name}' not found",
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async def call_tool(
|
|
143
|
+
self,
|
|
144
|
+
server_name: str,
|
|
145
|
+
tool_name: str,
|
|
146
|
+
arguments: dict[str, Any] | None = None,
|
|
147
|
+
) -> Any:
|
|
148
|
+
"""Execute a tool with optional pre-validation.
|
|
149
|
+
|
|
150
|
+
Pre-validates arguments against the tool's schema before execution.
|
|
151
|
+
On validation error, returns the schema in the error response so
|
|
152
|
+
the caller can self-correct in one round-trip.
|
|
153
|
+
|
|
154
|
+
On execution error, includes fallback_suggestions if a fallback resolver
|
|
155
|
+
is configured.
|
|
156
|
+
|
|
157
|
+
"""
|
|
158
|
+
args = arguments or {}
|
|
159
|
+
|
|
160
|
+
# Pre-validate arguments if enabled
|
|
161
|
+
if self._validate_arguments and args:
|
|
162
|
+
schema_result = await self.get_tool_schema(server_name, tool_name)
|
|
163
|
+
if schema_result.get("success"):
|
|
164
|
+
input_schema = schema_result.get("tool", {}).get("inputSchema", {})
|
|
165
|
+
if input_schema:
|
|
166
|
+
validation_errors = self._check_arguments(args, input_schema)
|
|
167
|
+
if validation_errors:
|
|
168
|
+
return {
|
|
169
|
+
"success": False,
|
|
170
|
+
"error": f"Invalid arguments: {validation_errors}",
|
|
171
|
+
"hint": "Review the schema below and retry with correct parameters",
|
|
172
|
+
"schema": input_schema,
|
|
173
|
+
"server_name": server_name,
|
|
174
|
+
"tool_name": tool_name,
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
# Check internal tools first
|
|
179
|
+
if self._internal_manager and self._internal_manager.is_internal(server_name):
|
|
180
|
+
registry = self._internal_manager.get_registry(server_name)
|
|
181
|
+
if registry:
|
|
182
|
+
return await registry.call(tool_name, args)
|
|
183
|
+
raise MCPError(f"Internal server '{server_name}' not found")
|
|
184
|
+
|
|
185
|
+
# Use MCP manager for external servers
|
|
186
|
+
return await self._mcp_manager.call_tool(server_name, tool_name, arguments)
|
|
187
|
+
|
|
188
|
+
except Exception as e:
|
|
189
|
+
error_message = str(e)
|
|
190
|
+
logger.warning(f"Tool call failed: {server_name}/{tool_name}: {error_message}")
|
|
191
|
+
|
|
192
|
+
# Build error response with fallback suggestions
|
|
193
|
+
response: dict[str, Any] = {
|
|
194
|
+
"success": False,
|
|
195
|
+
"error": error_message,
|
|
196
|
+
"server_name": server_name,
|
|
197
|
+
"tool_name": tool_name,
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
# Get fallback suggestions if resolver is available
|
|
201
|
+
if self._fallback_resolver:
|
|
202
|
+
try:
|
|
203
|
+
project_id = self._mcp_manager.project_id
|
|
204
|
+
if project_id:
|
|
205
|
+
suggestions = await self._fallback_resolver.find_alternatives_for_error(
|
|
206
|
+
server_name=server_name,
|
|
207
|
+
tool_name=tool_name,
|
|
208
|
+
error_message=error_message,
|
|
209
|
+
project_id=project_id,
|
|
210
|
+
)
|
|
211
|
+
response["fallback_suggestions"] = suggestions
|
|
212
|
+
else:
|
|
213
|
+
response["fallback_suggestions"] = []
|
|
214
|
+
except Exception as fallback_error:
|
|
215
|
+
logger.debug(f"Fallback resolver failed: {fallback_error}")
|
|
216
|
+
response["fallback_suggestions"] = []
|
|
217
|
+
else:
|
|
218
|
+
response["fallback_suggestions"] = []
|
|
219
|
+
|
|
220
|
+
return response
|
|
221
|
+
|
|
222
|
+
async def read_resource(self, server_name: str, uri: str) -> Any:
|
|
223
|
+
"""Read a resource."""
|
|
224
|
+
return await self._mcp_manager.read_resource(server_name, uri)
|
|
225
|
+
|
|
226
|
+
async def get_tool_schema(self, server_name: str, tool_name: str) -> dict[str, Any]:
|
|
227
|
+
"""Get full schema for a specific tool."""
|
|
228
|
+
# Check internal tools first
|
|
229
|
+
if self._internal_manager and self._internal_manager.is_internal(server_name):
|
|
230
|
+
registry = self._internal_manager.get_registry(server_name)
|
|
231
|
+
if registry:
|
|
232
|
+
schema = registry.get_schema(tool_name)
|
|
233
|
+
if schema:
|
|
234
|
+
return {"success": True, "tool": schema}
|
|
235
|
+
return {
|
|
236
|
+
"success": False,
|
|
237
|
+
"error": f"Tool '{tool_name}' not found on '{server_name}'",
|
|
238
|
+
}
|
|
239
|
+
return {"success": False, "error": f"Internal server '{server_name}' not found"}
|
|
240
|
+
|
|
241
|
+
if not self._mcp_manager.has_server(server_name):
|
|
242
|
+
return {"success": False, "error": f"Server '{server_name}' not found"}
|
|
243
|
+
|
|
244
|
+
# Use MCP manager for external servers
|
|
245
|
+
try:
|
|
246
|
+
return await self._mcp_manager.get_tool_input_schema(server_name, tool_name)
|
|
247
|
+
except Exception as e:
|
|
248
|
+
raise MCPError(f"Failed to get schema for {tool_name} on {server_name}: {e}") from e
|
|
249
|
+
|
|
250
|
+
def find_tool_server(self, tool_name: str) -> str | None:
|
|
251
|
+
"""
|
|
252
|
+
Find which server owns a tool by searching all available servers.
|
|
253
|
+
|
|
254
|
+
Searches internal registries first (faster), then external server configs.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
tool_name: Name of the tool to find
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Server name if found, None otherwise
|
|
261
|
+
"""
|
|
262
|
+
# Search internal registries first (fast, in-memory lookup)
|
|
263
|
+
if self._internal_manager:
|
|
264
|
+
server = self._internal_manager.find_tool_server(tool_name)
|
|
265
|
+
if server:
|
|
266
|
+
return server
|
|
267
|
+
|
|
268
|
+
# Search external server configs (cached tool metadata)
|
|
269
|
+
for server_name, config in self._mcp_manager._configs.items():
|
|
270
|
+
if config.tools:
|
|
271
|
+
for tool in config.tools:
|
|
272
|
+
tool_name_in_config = (
|
|
273
|
+
tool.get("name") if isinstance(tool, dict) else getattr(tool, "name", None)
|
|
274
|
+
)
|
|
275
|
+
if tool_name_in_config == tool_name:
|
|
276
|
+
return server_name
|
|
277
|
+
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
async def call_tool_by_name(
|
|
281
|
+
self,
|
|
282
|
+
tool_name: str,
|
|
283
|
+
arguments: dict[str, Any] | None = None,
|
|
284
|
+
) -> Any:
|
|
285
|
+
"""
|
|
286
|
+
Call a tool by name, automatically resolving the server.
|
|
287
|
+
|
|
288
|
+
Searches all available servers to find which one owns the tool,
|
|
289
|
+
then routes the call appropriately.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
tool_name: Name of the tool to call
|
|
293
|
+
arguments: Tool arguments
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Tool execution result, or error dict if tool not found
|
|
297
|
+
"""
|
|
298
|
+
server_name = self.find_tool_server(tool_name)
|
|
299
|
+
|
|
300
|
+
if server_name is None:
|
|
301
|
+
logger.warning(f"Tool '{tool_name}' not found on any server")
|
|
302
|
+
return {
|
|
303
|
+
"success": False,
|
|
304
|
+
"error": f"Tool '{tool_name}' not found on any available server",
|
|
305
|
+
"tool_name": tool_name,
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
logger.debug(f"Routing tool '{tool_name}' to server '{server_name}'")
|
|
309
|
+
return await self.call_tool(server_name, tool_name, arguments)
|