superqode 0.1.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.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- superqode-0.1.5.dist-info/top_level.txt +1 -0
superqode/mcp/config.py
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""MCP server configuration for SuperQode.
|
|
2
|
+
|
|
3
|
+
This module handles loading, saving, and managing MCP server configurations.
|
|
4
|
+
Supports stdio (local process), HTTP, and SSE transport types.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Literal
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
# Default MCP config file locations
|
|
17
|
+
MCP_CONFIG_FILENAME = "mcp.json"
|
|
18
|
+
MCP_CONFIG_DIRS = [
|
|
19
|
+
Path.cwd() / ".superqode",
|
|
20
|
+
Path.home() / ".superqode",
|
|
21
|
+
Path.home() / ".config" / "superqode",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class MCPStdioConfig:
|
|
27
|
+
"""Configuration for stdio-based MCP server (local process).
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
command: The executable command to run
|
|
31
|
+
args: Command line arguments
|
|
32
|
+
env: Environment variables to set
|
|
33
|
+
cwd: Working directory for the process
|
|
34
|
+
timeout: Connection timeout in seconds
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
transport: Literal["stdio"] = "stdio"
|
|
38
|
+
command: str = ""
|
|
39
|
+
args: list[str] = field(default_factory=list)
|
|
40
|
+
env: dict[str, str] = field(default_factory=dict)
|
|
41
|
+
cwd: str | None = None
|
|
42
|
+
timeout: float = 30.0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class MCPHttpConfig:
|
|
47
|
+
"""Configuration for HTTP-based MCP server (streamable HTTP).
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
url: The HTTP endpoint URL
|
|
51
|
+
headers: HTTP headers to include in requests
|
|
52
|
+
timeout: Request timeout in seconds
|
|
53
|
+
sse_read_timeout: SSE read timeout in seconds
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
transport: Literal["http"] = "http"
|
|
57
|
+
url: str = ""
|
|
58
|
+
headers: dict[str, str] = field(default_factory=dict)
|
|
59
|
+
timeout: float = 30.0
|
|
60
|
+
sse_read_timeout: float = 300.0
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class MCPSSEConfig:
|
|
65
|
+
"""Configuration for SSE-based MCP server.
|
|
66
|
+
|
|
67
|
+
Attributes:
|
|
68
|
+
url: The SSE endpoint URL
|
|
69
|
+
headers: HTTP headers to include in requests
|
|
70
|
+
timeout: Request timeout in seconds
|
|
71
|
+
sse_read_timeout: SSE read timeout in seconds
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
transport: Literal["sse"] = "sse"
|
|
75
|
+
url: str = ""
|
|
76
|
+
headers: dict[str, str] = field(default_factory=dict)
|
|
77
|
+
timeout: float = 5.0
|
|
78
|
+
sse_read_timeout: float = 300.0
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class MCPServerConfig:
|
|
83
|
+
"""Complete configuration for an MCP server.
|
|
84
|
+
|
|
85
|
+
Attributes:
|
|
86
|
+
id: Unique identifier for this server
|
|
87
|
+
name: Human-readable name
|
|
88
|
+
description: Optional description
|
|
89
|
+
enabled: Whether the server is enabled
|
|
90
|
+
auto_connect: Whether to connect automatically on startup
|
|
91
|
+
config: Transport-specific configuration
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
id: str
|
|
95
|
+
name: str
|
|
96
|
+
description: str = ""
|
|
97
|
+
enabled: bool = True
|
|
98
|
+
auto_connect: bool = True
|
|
99
|
+
config: MCPStdioConfig | MCPHttpConfig | MCPSSEConfig = field(default_factory=MCPStdioConfig)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def find_mcp_config_file() -> Path | None:
|
|
103
|
+
"""Find the MCP configuration file.
|
|
104
|
+
|
|
105
|
+
Searches in order:
|
|
106
|
+
1. .superqode/mcp.json in current directory
|
|
107
|
+
2. ~/.superqode/mcp.json
|
|
108
|
+
3. ~/.config/superqode/mcp.json
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Path to config file if found, None otherwise
|
|
112
|
+
"""
|
|
113
|
+
for config_dir in MCP_CONFIG_DIRS:
|
|
114
|
+
config_path = config_dir / MCP_CONFIG_FILENAME
|
|
115
|
+
if config_path.exists():
|
|
116
|
+
return config_path
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def load_mcp_config(config_path: Path | None = None) -> dict[str, MCPServerConfig]:
|
|
121
|
+
"""Load MCP server configurations from file.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
config_path: Optional explicit path to config file
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Dictionary mapping server IDs to their configurations
|
|
128
|
+
"""
|
|
129
|
+
if config_path is None:
|
|
130
|
+
config_path = find_mcp_config_file()
|
|
131
|
+
|
|
132
|
+
if config_path is None or not config_path.exists():
|
|
133
|
+
return {}
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
with open(config_path, encoding="utf-8") as f:
|
|
137
|
+
data = json.load(f)
|
|
138
|
+
except (json.JSONDecodeError, OSError) as e:
|
|
139
|
+
logger.error(f"Failed to load MCP config from {config_path}: {e}")
|
|
140
|
+
return {}
|
|
141
|
+
|
|
142
|
+
servers: dict[str, MCPServerConfig] = {}
|
|
143
|
+
|
|
144
|
+
# Handle both formats: {"mcpServers": {...}} and {"servers": {...}}
|
|
145
|
+
servers_data = data.get("mcpServers", data.get("servers", {}))
|
|
146
|
+
|
|
147
|
+
for server_id, server_data in servers_data.items():
|
|
148
|
+
try:
|
|
149
|
+
server_config = _parse_server_config(server_id, server_data)
|
|
150
|
+
servers[server_id] = server_config
|
|
151
|
+
except Exception as e:
|
|
152
|
+
logger.warning(f"Failed to parse MCP server config '{server_id}': {e}")
|
|
153
|
+
|
|
154
|
+
return servers
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _parse_server_config(server_id: str, data: dict[str, Any]) -> MCPServerConfig:
|
|
158
|
+
"""Parse a single server configuration from JSON data."""
|
|
159
|
+
# Determine transport type
|
|
160
|
+
transport = data.get("transport", "stdio")
|
|
161
|
+
|
|
162
|
+
# Handle legacy format (command at top level = stdio)
|
|
163
|
+
if "command" in data and "transport" not in data:
|
|
164
|
+
transport = "stdio"
|
|
165
|
+
elif "url" in data and "transport" not in data:
|
|
166
|
+
# Determine if HTTP or SSE based on URL or other hints
|
|
167
|
+
transport = data.get("transport", "http")
|
|
168
|
+
|
|
169
|
+
# Parse transport-specific config
|
|
170
|
+
if transport == "stdio":
|
|
171
|
+
config = MCPStdioConfig(
|
|
172
|
+
command=data.get("command", ""),
|
|
173
|
+
args=data.get("args", []),
|
|
174
|
+
env=_resolve_env_vars(data.get("env", {})),
|
|
175
|
+
cwd=data.get("cwd"),
|
|
176
|
+
timeout=data.get("timeout", 30.0),
|
|
177
|
+
)
|
|
178
|
+
elif transport == "sse":
|
|
179
|
+
config = MCPSSEConfig(
|
|
180
|
+
url=data.get("url", ""),
|
|
181
|
+
headers=data.get("headers", {}),
|
|
182
|
+
timeout=data.get("timeout", 5.0),
|
|
183
|
+
sse_read_timeout=data.get("sse_read_timeout", 300.0),
|
|
184
|
+
)
|
|
185
|
+
else: # http (streamable)
|
|
186
|
+
config = MCPHttpConfig(
|
|
187
|
+
url=data.get("url", ""),
|
|
188
|
+
headers=data.get("headers", {}),
|
|
189
|
+
timeout=data.get("timeout", 30.0),
|
|
190
|
+
sse_read_timeout=data.get("sse_read_timeout", 300.0),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
return MCPServerConfig(
|
|
194
|
+
id=server_id,
|
|
195
|
+
name=data.get("name", server_id),
|
|
196
|
+
description=data.get("description", ""),
|
|
197
|
+
enabled=data.get("enabled", not data.get("disabled", False)),
|
|
198
|
+
auto_connect=data.get("autoConnect", data.get("auto_connect", True)),
|
|
199
|
+
config=config,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _resolve_env_vars(env: dict[str, str]) -> dict[str, str]:
|
|
204
|
+
"""Resolve environment variable references in env dict.
|
|
205
|
+
|
|
206
|
+
Supports ${VAR} syntax for referencing environment variables.
|
|
207
|
+
"""
|
|
208
|
+
resolved = {}
|
|
209
|
+
for key, value in env.items():
|
|
210
|
+
if isinstance(value, str) and value.startswith("${") and value.endswith("}"):
|
|
211
|
+
var_name = value[2:-1]
|
|
212
|
+
resolved[key] = os.environ.get(var_name, "")
|
|
213
|
+
else:
|
|
214
|
+
resolved[key] = value
|
|
215
|
+
return resolved
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def save_mcp_config(
|
|
219
|
+
servers: dict[str, MCPServerConfig],
|
|
220
|
+
config_path: Path | None = None,
|
|
221
|
+
) -> None:
|
|
222
|
+
"""Save MCP server configurations to file.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
servers: Dictionary mapping server IDs to configurations
|
|
226
|
+
config_path: Optional explicit path to config file
|
|
227
|
+
"""
|
|
228
|
+
if config_path is None:
|
|
229
|
+
# Default to .superqode/mcp.json in current directory
|
|
230
|
+
config_path = MCP_CONFIG_DIRS[0] / MCP_CONFIG_FILENAME
|
|
231
|
+
|
|
232
|
+
# Ensure directory exists
|
|
233
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
234
|
+
|
|
235
|
+
# Convert to JSON-serializable format
|
|
236
|
+
data = {"mcpServers": {}}
|
|
237
|
+
|
|
238
|
+
for server_id, server_config in servers.items():
|
|
239
|
+
server_data: dict[str, Any] = {
|
|
240
|
+
"name": server_config.name,
|
|
241
|
+
"enabled": server_config.enabled,
|
|
242
|
+
"autoConnect": server_config.auto_connect,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if server_config.description:
|
|
246
|
+
server_data["description"] = server_config.description
|
|
247
|
+
|
|
248
|
+
config = server_config.config
|
|
249
|
+
if isinstance(config, MCPStdioConfig):
|
|
250
|
+
server_data["transport"] = "stdio"
|
|
251
|
+
server_data["command"] = config.command
|
|
252
|
+
if config.args:
|
|
253
|
+
server_data["args"] = config.args
|
|
254
|
+
if config.env:
|
|
255
|
+
server_data["env"] = config.env
|
|
256
|
+
if config.cwd:
|
|
257
|
+
server_data["cwd"] = config.cwd
|
|
258
|
+
if config.timeout != 30.0:
|
|
259
|
+
server_data["timeout"] = config.timeout
|
|
260
|
+
elif isinstance(config, MCPSSEConfig):
|
|
261
|
+
server_data["transport"] = "sse"
|
|
262
|
+
server_data["url"] = config.url
|
|
263
|
+
if config.headers:
|
|
264
|
+
server_data["headers"] = config.headers
|
|
265
|
+
if config.timeout != 5.0:
|
|
266
|
+
server_data["timeout"] = config.timeout
|
|
267
|
+
if config.sse_read_timeout != 300.0:
|
|
268
|
+
server_data["sse_read_timeout"] = config.sse_read_timeout
|
|
269
|
+
else: # MCPHttpConfig
|
|
270
|
+
server_data["transport"] = "http"
|
|
271
|
+
server_data["url"] = config.url
|
|
272
|
+
if config.headers:
|
|
273
|
+
server_data["headers"] = config.headers
|
|
274
|
+
if config.timeout != 30.0:
|
|
275
|
+
server_data["timeout"] = config.timeout
|
|
276
|
+
if config.sse_read_timeout != 300.0:
|
|
277
|
+
server_data["sse_read_timeout"] = config.sse_read_timeout
|
|
278
|
+
|
|
279
|
+
data["mcpServers"][server_id] = server_data
|
|
280
|
+
|
|
281
|
+
try:
|
|
282
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
283
|
+
json.dump(data, f, indent=2)
|
|
284
|
+
logger.info(f"Saved MCP config to {config_path}")
|
|
285
|
+
except OSError as e:
|
|
286
|
+
logger.error(f"Failed to save MCP config to {config_path}: {e}")
|
|
287
|
+
raise
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def create_default_mcp_config() -> dict[str, MCPServerConfig]:
|
|
291
|
+
"""Create a default MCP configuration with example servers.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Dictionary with example MCP server configurations
|
|
295
|
+
"""
|
|
296
|
+
return {
|
|
297
|
+
"filesystem": MCPServerConfig(
|
|
298
|
+
id="filesystem",
|
|
299
|
+
name="Filesystem",
|
|
300
|
+
description="Access to local filesystem",
|
|
301
|
+
enabled=False, # Disabled by default for security
|
|
302
|
+
auto_connect=False,
|
|
303
|
+
config=MCPStdioConfig(
|
|
304
|
+
command="uvx",
|
|
305
|
+
args=["mcp-server-filesystem", "--root", "."],
|
|
306
|
+
),
|
|
307
|
+
),
|
|
308
|
+
"fetch": MCPServerConfig(
|
|
309
|
+
id="fetch",
|
|
310
|
+
name="Fetch",
|
|
311
|
+
description="HTTP fetch capabilities",
|
|
312
|
+
enabled=False,
|
|
313
|
+
auto_connect=False,
|
|
314
|
+
config=MCPStdioConfig(
|
|
315
|
+
command="uvx",
|
|
316
|
+
args=["mcp-server-fetch"],
|
|
317
|
+
),
|
|
318
|
+
),
|
|
319
|
+
}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""MCP integration utilities for SuperQode.
|
|
2
|
+
|
|
3
|
+
This module provides high-level integration functions for using MCP
|
|
4
|
+
within SuperQode' agent system.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from superqode.mcp.client import MCPClientManager, MCPConnectionState
|
|
12
|
+
from superqode.mcp.config import (
|
|
13
|
+
MCPServerConfig,
|
|
14
|
+
MCPStdioConfig,
|
|
15
|
+
MCPHttpConfig,
|
|
16
|
+
MCPSSEConfig,
|
|
17
|
+
load_mcp_config,
|
|
18
|
+
save_mcp_config,
|
|
19
|
+
create_default_mcp_config,
|
|
20
|
+
)
|
|
21
|
+
from superqode.mcp.types import (
|
|
22
|
+
MCPTool,
|
|
23
|
+
MCPToolResult,
|
|
24
|
+
MCPResource,
|
|
25
|
+
MCPPrompt,
|
|
26
|
+
ServerCapability,
|
|
27
|
+
LoggingLevel,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
# Global MCP client manager instance
|
|
33
|
+
_mcp_manager: MCPClientManager | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def get_mcp_manager() -> MCPClientManager:
|
|
37
|
+
"""Get or create the global MCP client manager."""
|
|
38
|
+
global _mcp_manager
|
|
39
|
+
if _mcp_manager is None:
|
|
40
|
+
_mcp_manager = MCPClientManager()
|
|
41
|
+
await _mcp_manager.__aenter__()
|
|
42
|
+
_mcp_manager.load_config()
|
|
43
|
+
return _mcp_manager
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def shutdown_mcp_manager() -> None:
|
|
47
|
+
"""Shutdown the global MCP client manager."""
|
|
48
|
+
global _mcp_manager
|
|
49
|
+
if _mcp_manager is not None:
|
|
50
|
+
await _mcp_manager.__aexit__(None, None, None)
|
|
51
|
+
_mcp_manager = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def initialize_mcp(auto_connect: bool = True) -> dict[str, bool]:
|
|
55
|
+
"""Initialize MCP support and optionally connect to servers."""
|
|
56
|
+
manager = await get_mcp_manager()
|
|
57
|
+
if auto_connect:
|
|
58
|
+
return await manager.connect_all()
|
|
59
|
+
return {}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_mcp_tools_for_agent() -> list[dict[str, Any]]:
|
|
63
|
+
"""Get MCP tools formatted for agent tool use.
|
|
64
|
+
|
|
65
|
+
Returns tools in a format suitable for LLM function calling.
|
|
66
|
+
"""
|
|
67
|
+
if _mcp_manager is None:
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
tools = _mcp_manager.list_all_tools()
|
|
71
|
+
return [
|
|
72
|
+
{
|
|
73
|
+
"type": "function",
|
|
74
|
+
"function": {
|
|
75
|
+
"name": f"mcp_{tool.server_id}_{tool.name}",
|
|
76
|
+
"description": f"[MCP:{tool.server_id}] {tool.description}",
|
|
77
|
+
"parameters": tool.input_schema,
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
for tool in tools
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async def execute_mcp_tool(
|
|
85
|
+
tool_name: str,
|
|
86
|
+
arguments: dict[str, Any],
|
|
87
|
+
) -> MCPToolResult:
|
|
88
|
+
"""Execute an MCP tool by name.
|
|
89
|
+
|
|
90
|
+
Handles the mcp_{server_id}_{tool_name} format used by agents.
|
|
91
|
+
"""
|
|
92
|
+
if _mcp_manager is None:
|
|
93
|
+
return MCPToolResult(
|
|
94
|
+
content=[],
|
|
95
|
+
is_error=True,
|
|
96
|
+
error_message="MCP not initialized",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Parse tool name: mcp_{server_id}_{tool_name}
|
|
100
|
+
if tool_name.startswith("mcp_"):
|
|
101
|
+
parts = tool_name[4:].split("_", 1)
|
|
102
|
+
if len(parts) == 2:
|
|
103
|
+
server_id, actual_tool_name = parts
|
|
104
|
+
return await _mcp_manager.call_tool(server_id, actual_tool_name, arguments)
|
|
105
|
+
|
|
106
|
+
# Try to find tool across all servers
|
|
107
|
+
result = _mcp_manager.find_tool(tool_name)
|
|
108
|
+
if result:
|
|
109
|
+
server_id, tool = result
|
|
110
|
+
return await _mcp_manager.call_tool(server_id, tool.name, arguments)
|
|
111
|
+
|
|
112
|
+
return MCPToolResult(
|
|
113
|
+
content=[],
|
|
114
|
+
is_error=True,
|
|
115
|
+
error_message=f"Tool not found: {tool_name}",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def format_tool_result_for_agent(result: MCPToolResult) -> str:
|
|
120
|
+
"""Format an MCP tool result for agent consumption."""
|
|
121
|
+
if result.is_error:
|
|
122
|
+
return f"Error: {result.error_message}"
|
|
123
|
+
|
|
124
|
+
output_parts = []
|
|
125
|
+
for item in result.content:
|
|
126
|
+
if item.get("type") == "text":
|
|
127
|
+
output_parts.append(item.get("text", ""))
|
|
128
|
+
elif item.get("type") == "image":
|
|
129
|
+
output_parts.append(f"[Image: {item.get('mimeType', 'image')}]")
|
|
130
|
+
elif item.get("type") == "audio":
|
|
131
|
+
output_parts.append(f"[Audio: {item.get('mimeType', 'audio')}]")
|
|
132
|
+
elif item.get("type") == "resource":
|
|
133
|
+
output_parts.append(f"[Resource: {item.get('uri', 'unknown')}]")
|
|
134
|
+
else:
|
|
135
|
+
output_parts.append(str(item))
|
|
136
|
+
|
|
137
|
+
return "\n".join(output_parts)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class MCPToolHandler:
|
|
141
|
+
"""Handler for MCP tools within SuperQode' agent system."""
|
|
142
|
+
|
|
143
|
+
def __init__(self, manager: MCPClientManager | None = None):
|
|
144
|
+
self._manager = manager
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def manager(self) -> MCPClientManager | None:
|
|
148
|
+
return self._manager or _mcp_manager
|
|
149
|
+
|
|
150
|
+
def is_mcp_tool(self, tool_name: str) -> bool:
|
|
151
|
+
"""Check if a tool name refers to an MCP tool."""
|
|
152
|
+
if tool_name.startswith("mcp_"):
|
|
153
|
+
return True
|
|
154
|
+
if self.manager:
|
|
155
|
+
return self.manager.find_tool(tool_name) is not None
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
async def execute(
|
|
159
|
+
self,
|
|
160
|
+
tool_name: str,
|
|
161
|
+
arguments: dict[str, Any],
|
|
162
|
+
) -> dict[str, Any]:
|
|
163
|
+
"""Execute an MCP tool."""
|
|
164
|
+
result = await execute_mcp_tool(tool_name, arguments)
|
|
165
|
+
return {
|
|
166
|
+
"success": not result.is_error,
|
|
167
|
+
"content": result.content,
|
|
168
|
+
"error": result.error_message,
|
|
169
|
+
"formatted": format_tool_result_for_agent(result),
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
def get_tool_definitions(self) -> list[dict[str, Any]]:
|
|
173
|
+
"""Get tool definitions for agent use."""
|
|
174
|
+
return get_mcp_tools_for_agent()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# Convenience functions for common operations
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
async def add_mcp_server(
|
|
181
|
+
server_id: str,
|
|
182
|
+
name: str,
|
|
183
|
+
command: str | None = None,
|
|
184
|
+
args: list[str] | None = None,
|
|
185
|
+
url: str | None = None,
|
|
186
|
+
transport: str = "stdio",
|
|
187
|
+
enabled: bool = True,
|
|
188
|
+
auto_connect: bool = True,
|
|
189
|
+
**kwargs: Any,
|
|
190
|
+
) -> MCPServerConfig:
|
|
191
|
+
"""Add a new MCP server configuration."""
|
|
192
|
+
if transport == "stdio":
|
|
193
|
+
config = MCPStdioConfig(
|
|
194
|
+
command=command or "",
|
|
195
|
+
args=args or [],
|
|
196
|
+
env=kwargs.get("env", {}),
|
|
197
|
+
cwd=kwargs.get("cwd"),
|
|
198
|
+
timeout=kwargs.get("timeout", 30.0),
|
|
199
|
+
)
|
|
200
|
+
elif transport == "sse":
|
|
201
|
+
config = MCPSSEConfig(
|
|
202
|
+
url=url or "",
|
|
203
|
+
headers=kwargs.get("headers", {}),
|
|
204
|
+
timeout=kwargs.get("timeout", 5.0),
|
|
205
|
+
sse_read_timeout=kwargs.get("sse_read_timeout", 300.0),
|
|
206
|
+
)
|
|
207
|
+
else: # http
|
|
208
|
+
config = MCPHttpConfig(
|
|
209
|
+
url=url or "",
|
|
210
|
+
headers=kwargs.get("headers", {}),
|
|
211
|
+
timeout=kwargs.get("timeout", 30.0),
|
|
212
|
+
sse_read_timeout=kwargs.get("sse_read_timeout", 300.0),
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
server_config = MCPServerConfig(
|
|
216
|
+
id=server_id,
|
|
217
|
+
name=name,
|
|
218
|
+
description=kwargs.get("description", ""),
|
|
219
|
+
enabled=enabled,
|
|
220
|
+
auto_connect=auto_connect,
|
|
221
|
+
config=config,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
manager = await get_mcp_manager()
|
|
225
|
+
manager.add_server(server_config)
|
|
226
|
+
|
|
227
|
+
return server_config
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
async def connect_mcp_server(server_id: str) -> bool:
|
|
231
|
+
"""Connect to a specific MCP server."""
|
|
232
|
+
manager = await get_mcp_manager()
|
|
233
|
+
return await manager.connect(server_id)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
async def disconnect_mcp_server(server_id: str) -> None:
|
|
237
|
+
"""Disconnect from a specific MCP server."""
|
|
238
|
+
manager = await get_mcp_manager()
|
|
239
|
+
await manager.disconnect(server_id)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
async def restart_mcp_server(server_id: str) -> bool:
|
|
243
|
+
"""Restart a specific MCP server."""
|
|
244
|
+
manager = await get_mcp_manager()
|
|
245
|
+
return await manager.restart_server(server_id)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def get_mcp_status() -> dict[str, Any]:
|
|
249
|
+
"""Get current MCP status summary."""
|
|
250
|
+
if _mcp_manager is None:
|
|
251
|
+
return {
|
|
252
|
+
"initialized": False,
|
|
253
|
+
"total_servers": 0,
|
|
254
|
+
"connected": 0,
|
|
255
|
+
"total_tools": 0,
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
"initialized": True,
|
|
260
|
+
**_mcp_manager.get_status_summary(),
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def has_mcp_capability(server_id: str, capability: ServerCapability) -> bool:
|
|
265
|
+
"""Check if a server has a specific capability."""
|
|
266
|
+
if _mcp_manager is None:
|
|
267
|
+
return False
|
|
268
|
+
return _mcp_manager.has_capability(server_id, capability)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
async def set_mcp_logging_level(server_id: str, level: LoggingLevel) -> bool:
|
|
272
|
+
"""Set logging level for an MCP server."""
|
|
273
|
+
if _mcp_manager is None:
|
|
274
|
+
return False
|
|
275
|
+
return await _mcp_manager.set_logging_level(server_id, level)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
async def read_mcp_resource(server_id: str, uri: str) -> dict[str, Any] | None:
|
|
279
|
+
"""Read a resource from an MCP server."""
|
|
280
|
+
if _mcp_manager is None:
|
|
281
|
+
return None
|
|
282
|
+
|
|
283
|
+
content = await _mcp_manager.read_resource(server_id, uri)
|
|
284
|
+
if content is None:
|
|
285
|
+
return None
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
"uri": content.uri,
|
|
289
|
+
"mime_type": content.mime_type,
|
|
290
|
+
"text": content.text,
|
|
291
|
+
"blob": content.blob,
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
async def get_mcp_prompt(
|
|
296
|
+
server_id: str,
|
|
297
|
+
prompt_name: str,
|
|
298
|
+
arguments: dict[str, str] | None = None,
|
|
299
|
+
) -> dict[str, Any] | None:
|
|
300
|
+
"""Get a prompt from an MCP server."""
|
|
301
|
+
if _mcp_manager is None:
|
|
302
|
+
return None
|
|
303
|
+
|
|
304
|
+
result = await _mcp_manager.get_prompt(server_id, prompt_name, arguments)
|
|
305
|
+
if result is None:
|
|
306
|
+
return None
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
"description": result.description,
|
|
310
|
+
"messages": [{"role": msg.role, "content": msg.content} for msg in result.messages],
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def list_mcp_servers() -> list[dict[str, Any]]:
|
|
315
|
+
"""List all configured MCP servers with their status."""
|
|
316
|
+
if _mcp_manager is None:
|
|
317
|
+
return []
|
|
318
|
+
|
|
319
|
+
servers = []
|
|
320
|
+
for server_id, config in _mcp_manager.get_server_configs().items():
|
|
321
|
+
conn = _mcp_manager.get_connection(server_id)
|
|
322
|
+
servers.append(
|
|
323
|
+
{
|
|
324
|
+
"id": server_id,
|
|
325
|
+
"name": config.name,
|
|
326
|
+
"description": config.description,
|
|
327
|
+
"enabled": config.enabled,
|
|
328
|
+
"auto_connect": config.auto_connect,
|
|
329
|
+
"transport": type(config.config)
|
|
330
|
+
.__name__.replace("MCP", "")
|
|
331
|
+
.replace("Config", "")
|
|
332
|
+
.lower(),
|
|
333
|
+
"state": conn.state.value if conn else "disconnected",
|
|
334
|
+
"error": conn.error_message if conn else None,
|
|
335
|
+
}
|
|
336
|
+
)
|
|
337
|
+
return servers
|