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.
Files changed (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
@@ -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