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
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP OAuth Callback Server - Local HTTP Server for OAuth Redirects.
|
|
3
|
+
|
|
4
|
+
Implements a lightweight HTTP server to handle OAuth callbacks
|
|
5
|
+
during the authorization flow.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- Single-use callback handling
|
|
9
|
+
- Timeout support
|
|
10
|
+
- Success/error page display
|
|
11
|
+
- Thread-safe operation
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import logging
|
|
18
|
+
import urllib.parse
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
21
|
+
from typing import Dict, Optional
|
|
22
|
+
import threading
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# HTML templates for callback responses
|
|
28
|
+
SUCCESS_HTML = """<!DOCTYPE html>
|
|
29
|
+
<html>
|
|
30
|
+
<head>
|
|
31
|
+
<title>Authorization Successful</title>
|
|
32
|
+
<style>
|
|
33
|
+
body {
|
|
34
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
35
|
+
display: flex;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
align-items: center;
|
|
38
|
+
height: 100vh;
|
|
39
|
+
margin: 0;
|
|
40
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
41
|
+
}
|
|
42
|
+
.container {
|
|
43
|
+
text-align: center;
|
|
44
|
+
padding: 40px;
|
|
45
|
+
background: white;
|
|
46
|
+
border-radius: 12px;
|
|
47
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
|
48
|
+
max-width: 400px;
|
|
49
|
+
}
|
|
50
|
+
.icon {
|
|
51
|
+
font-size: 64px;
|
|
52
|
+
margin-bottom: 20px;
|
|
53
|
+
}
|
|
54
|
+
h1 {
|
|
55
|
+
color: #22c55e;
|
|
56
|
+
margin: 0 0 10px 0;
|
|
57
|
+
font-size: 24px;
|
|
58
|
+
}
|
|
59
|
+
p {
|
|
60
|
+
color: #666;
|
|
61
|
+
margin: 0;
|
|
62
|
+
font-size: 14px;
|
|
63
|
+
}
|
|
64
|
+
.close-msg {
|
|
65
|
+
margin-top: 20px;
|
|
66
|
+
color: #999;
|
|
67
|
+
font-size: 12px;
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
70
|
+
</head>
|
|
71
|
+
<body>
|
|
72
|
+
<div class="container">
|
|
73
|
+
<div class="icon">✓</div>
|
|
74
|
+
<h1>Authorization Successful</h1>
|
|
75
|
+
<p>You have been authenticated with the MCP server.</p>
|
|
76
|
+
<p class="close-msg">You can close this window and return to SuperQode.</p>
|
|
77
|
+
</div>
|
|
78
|
+
<script>
|
|
79
|
+
// Try to close the window after a delay
|
|
80
|
+
setTimeout(function() {
|
|
81
|
+
window.close();
|
|
82
|
+
}, 3000);
|
|
83
|
+
</script>
|
|
84
|
+
</body>
|
|
85
|
+
</html>"""
|
|
86
|
+
|
|
87
|
+
ERROR_HTML = """<!DOCTYPE html>
|
|
88
|
+
<html>
|
|
89
|
+
<head>
|
|
90
|
+
<title>Authorization Failed</title>
|
|
91
|
+
<style>
|
|
92
|
+
body {
|
|
93
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
94
|
+
display: flex;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
align-items: center;
|
|
97
|
+
height: 100vh;
|
|
98
|
+
margin: 0;
|
|
99
|
+
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
100
|
+
}
|
|
101
|
+
.container {
|
|
102
|
+
text-align: center;
|
|
103
|
+
padding: 40px;
|
|
104
|
+
background: white;
|
|
105
|
+
border-radius: 12px;
|
|
106
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
|
107
|
+
max-width: 400px;
|
|
108
|
+
}
|
|
109
|
+
.icon {
|
|
110
|
+
font-size: 64px;
|
|
111
|
+
margin-bottom: 20px;
|
|
112
|
+
}
|
|
113
|
+
h1 {
|
|
114
|
+
color: #ef4444;
|
|
115
|
+
margin: 0 0 10px 0;
|
|
116
|
+
font-size: 24px;
|
|
117
|
+
}
|
|
118
|
+
p {
|
|
119
|
+
color: #666;
|
|
120
|
+
margin: 0;
|
|
121
|
+
font-size: 14px;
|
|
122
|
+
}
|
|
123
|
+
.error-detail {
|
|
124
|
+
margin-top: 15px;
|
|
125
|
+
padding: 10px;
|
|
126
|
+
background: #fef2f2;
|
|
127
|
+
border-radius: 6px;
|
|
128
|
+
color: #b91c1c;
|
|
129
|
+
font-size: 12px;
|
|
130
|
+
font-family: monospace;
|
|
131
|
+
}
|
|
132
|
+
</style>
|
|
133
|
+
</head>
|
|
134
|
+
<body>
|
|
135
|
+
<div class="container">
|
|
136
|
+
<div class="icon">✕</div>
|
|
137
|
+
<h1>Authorization Failed</h1>
|
|
138
|
+
<p>There was a problem authenticating with the MCP server.</p>
|
|
139
|
+
<div class="error-detail">{error}</div>
|
|
140
|
+
</div>
|
|
141
|
+
</body>
|
|
142
|
+
</html>"""
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@dataclass
|
|
146
|
+
class CallbackResult:
|
|
147
|
+
"""Result from an OAuth callback."""
|
|
148
|
+
|
|
149
|
+
code: Optional[str] = None
|
|
150
|
+
state: Optional[str] = None
|
|
151
|
+
error: Optional[str] = None
|
|
152
|
+
error_description: Optional[str] = None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class OAuthCallbackHandler(BaseHTTPRequestHandler):
|
|
156
|
+
"""HTTP request handler for OAuth callbacks."""
|
|
157
|
+
|
|
158
|
+
# Class-level storage for callback results
|
|
159
|
+
callback_results: Dict[str, asyncio.Future] = {}
|
|
160
|
+
server_lock = threading.Lock()
|
|
161
|
+
|
|
162
|
+
def log_message(self, format: str, *args) -> None:
|
|
163
|
+
"""Suppress default logging."""
|
|
164
|
+
logger.debug(format % args)
|
|
165
|
+
|
|
166
|
+
def do_GET(self) -> None:
|
|
167
|
+
"""Handle GET request (OAuth callback)."""
|
|
168
|
+
# Parse the URL
|
|
169
|
+
parsed = urllib.parse.urlparse(self.path)
|
|
170
|
+
|
|
171
|
+
# Only handle the callback path
|
|
172
|
+
if parsed.path != "/mcp/oauth/callback":
|
|
173
|
+
self.send_error(404, "Not Found")
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
# Parse query parameters
|
|
177
|
+
params = urllib.parse.parse_qs(parsed.query)
|
|
178
|
+
|
|
179
|
+
# Extract OAuth parameters
|
|
180
|
+
code = params.get("code", [None])[0]
|
|
181
|
+
state = params.get("state", [None])[0]
|
|
182
|
+
error = params.get("error", [None])[0]
|
|
183
|
+
error_description = params.get("error_description", [None])[0]
|
|
184
|
+
|
|
185
|
+
# Create result
|
|
186
|
+
result = CallbackResult(
|
|
187
|
+
code=code,
|
|
188
|
+
state=state,
|
|
189
|
+
error=error,
|
|
190
|
+
error_description=error_description,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Store result and notify waiting future
|
|
194
|
+
with self.server_lock:
|
|
195
|
+
if state and state in self.callback_results:
|
|
196
|
+
future = self.callback_results[state]
|
|
197
|
+
if not future.done():
|
|
198
|
+
# Use call_soon_threadsafe to set result from HTTP thread
|
|
199
|
+
try:
|
|
200
|
+
loop = future.get_loop()
|
|
201
|
+
loop.call_soon_threadsafe(future.set_result, result)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.error(f"Error setting callback result: {e}")
|
|
204
|
+
|
|
205
|
+
# Send response
|
|
206
|
+
if error:
|
|
207
|
+
self._send_error_page(error, error_description)
|
|
208
|
+
else:
|
|
209
|
+
self._send_success_page()
|
|
210
|
+
|
|
211
|
+
def _send_success_page(self) -> None:
|
|
212
|
+
"""Send success HTML page."""
|
|
213
|
+
content = SUCCESS_HTML.encode("utf-8")
|
|
214
|
+
self.send_response(200)
|
|
215
|
+
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
216
|
+
self.send_header("Content-Length", str(len(content)))
|
|
217
|
+
self.end_headers()
|
|
218
|
+
self.wfile.write(content)
|
|
219
|
+
|
|
220
|
+
def _send_error_page(self, error: str, description: Optional[str]) -> None:
|
|
221
|
+
"""Send error HTML page."""
|
|
222
|
+
error_msg = error
|
|
223
|
+
if description:
|
|
224
|
+
error_msg = f"{error}: {description}"
|
|
225
|
+
|
|
226
|
+
content = ERROR_HTML.format(error=error_msg).encode("utf-8")
|
|
227
|
+
self.send_response(400)
|
|
228
|
+
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
229
|
+
self.send_header("Content-Length", str(len(content)))
|
|
230
|
+
self.end_headers()
|
|
231
|
+
self.wfile.write(content)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class OAuthCallbackServer:
|
|
235
|
+
"""
|
|
236
|
+
Local HTTP server for OAuth callbacks.
|
|
237
|
+
|
|
238
|
+
Runs a lightweight HTTP server on localhost to receive OAuth
|
|
239
|
+
authorization callbacks.
|
|
240
|
+
|
|
241
|
+
Usage:
|
|
242
|
+
server = OAuthCallbackServer()
|
|
243
|
+
await server.start()
|
|
244
|
+
|
|
245
|
+
# Start OAuth flow with state parameter
|
|
246
|
+
state = "random_state_value"
|
|
247
|
+
|
|
248
|
+
# Wait for callback
|
|
249
|
+
result = await server.wait_for_callback(state, timeout=300)
|
|
250
|
+
|
|
251
|
+
# result.code contains the authorization code
|
|
252
|
+
|
|
253
|
+
await server.stop()
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
DEFAULT_PORT = 19876
|
|
257
|
+
|
|
258
|
+
def __init__(self, port: int = DEFAULT_PORT):
|
|
259
|
+
self.port = port
|
|
260
|
+
self._server: Optional[HTTPServer] = None
|
|
261
|
+
self._server_thread: Optional[threading.Thread] = None
|
|
262
|
+
self._running = False
|
|
263
|
+
|
|
264
|
+
async def start(self) -> None:
|
|
265
|
+
"""Start the callback server."""
|
|
266
|
+
if self._running:
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
# Create HTTP server
|
|
270
|
+
try:
|
|
271
|
+
self._server = HTTPServer(
|
|
272
|
+
("localhost", self.port),
|
|
273
|
+
OAuthCallbackHandler,
|
|
274
|
+
)
|
|
275
|
+
self._server.timeout = 1 # Allow periodic checks
|
|
276
|
+
|
|
277
|
+
# Start server in background thread
|
|
278
|
+
self._running = True
|
|
279
|
+
self._server_thread = threading.Thread(
|
|
280
|
+
target=self._serve_forever,
|
|
281
|
+
daemon=True,
|
|
282
|
+
)
|
|
283
|
+
self._server_thread.start()
|
|
284
|
+
|
|
285
|
+
logger.info(f"OAuth callback server started on port {self.port}")
|
|
286
|
+
|
|
287
|
+
except OSError as e:
|
|
288
|
+
if "Address already in use" in str(e):
|
|
289
|
+
# Port in use, try next port
|
|
290
|
+
self.port += 1
|
|
291
|
+
await self.start()
|
|
292
|
+
else:
|
|
293
|
+
raise
|
|
294
|
+
|
|
295
|
+
def _serve_forever(self) -> None:
|
|
296
|
+
"""Server loop running in background thread."""
|
|
297
|
+
while self._running:
|
|
298
|
+
self._server.handle_request()
|
|
299
|
+
|
|
300
|
+
async def stop(self) -> None:
|
|
301
|
+
"""Stop the callback server."""
|
|
302
|
+
self._running = False
|
|
303
|
+
|
|
304
|
+
if self._server:
|
|
305
|
+
self._server.shutdown()
|
|
306
|
+
self._server = None
|
|
307
|
+
|
|
308
|
+
if self._server_thread:
|
|
309
|
+
self._server_thread.join(timeout=5)
|
|
310
|
+
self._server_thread = None
|
|
311
|
+
|
|
312
|
+
logger.info("OAuth callback server stopped")
|
|
313
|
+
|
|
314
|
+
async def wait_for_callback(
|
|
315
|
+
self,
|
|
316
|
+
state: str,
|
|
317
|
+
timeout: float = 300,
|
|
318
|
+
) -> CallbackResult:
|
|
319
|
+
"""
|
|
320
|
+
Wait for an OAuth callback with the given state.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
state: The state parameter to wait for
|
|
324
|
+
timeout: Maximum time to wait in seconds
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
CallbackResult with the authorization code or error
|
|
328
|
+
"""
|
|
329
|
+
if not self._running:
|
|
330
|
+
await self.start()
|
|
331
|
+
|
|
332
|
+
# Create future for this state
|
|
333
|
+
loop = asyncio.get_event_loop()
|
|
334
|
+
future: asyncio.Future[CallbackResult] = loop.create_future()
|
|
335
|
+
|
|
336
|
+
with OAuthCallbackHandler.server_lock:
|
|
337
|
+
OAuthCallbackHandler.callback_results[state] = future
|
|
338
|
+
|
|
339
|
+
try:
|
|
340
|
+
result = await asyncio.wait_for(future, timeout=timeout)
|
|
341
|
+
return result
|
|
342
|
+
except asyncio.TimeoutError:
|
|
343
|
+
return CallbackResult(
|
|
344
|
+
error="timeout",
|
|
345
|
+
error_description="OAuth callback timed out",
|
|
346
|
+
)
|
|
347
|
+
finally:
|
|
348
|
+
# Clean up
|
|
349
|
+
with OAuthCallbackHandler.server_lock:
|
|
350
|
+
OAuthCallbackHandler.callback_results.pop(state, None)
|
|
351
|
+
|
|
352
|
+
def get_redirect_uri(self) -> str:
|
|
353
|
+
"""Get the redirect URI for this server."""
|
|
354
|
+
return f"http://localhost:{self.port}/mcp/oauth/callback"
|
|
355
|
+
|
|
356
|
+
@property
|
|
357
|
+
def is_running(self) -> bool:
|
|
358
|
+
"""Check if the server is running."""
|
|
359
|
+
return self._running
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
# Global server instance for shared use
|
|
363
|
+
_global_server: Optional[OAuthCallbackServer] = None
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
async def get_callback_server() -> OAuthCallbackServer:
|
|
367
|
+
"""Get or create the global callback server."""
|
|
368
|
+
global _global_server
|
|
369
|
+
|
|
370
|
+
if _global_server is None:
|
|
371
|
+
_global_server = OAuthCallbackServer()
|
|
372
|
+
|
|
373
|
+
if not _global_server.is_running:
|
|
374
|
+
await _global_server.start()
|
|
375
|
+
|
|
376
|
+
return _global_server
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
async def shutdown_callback_server() -> None:
|
|
380
|
+
"""Shutdown the global callback server."""
|
|
381
|
+
global _global_server
|
|
382
|
+
|
|
383
|
+
if _global_server is not None:
|
|
384
|
+
await _global_server.stop()
|
|
385
|
+
_global_server = None
|
superqode/mcp/types.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"""MCP type definitions for SuperQode.
|
|
2
|
+
|
|
3
|
+
This module defines the data types used for MCP tool, resource, and prompt
|
|
4
|
+
representations within SuperQode. Aligned with MCP protocol version 2025-03-26.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import Any, Literal
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ServerCapability(Enum):
|
|
13
|
+
"""Server capabilities that can be checked."""
|
|
14
|
+
|
|
15
|
+
EXPERIMENTAL = "experimental"
|
|
16
|
+
LOGGING = "logging"
|
|
17
|
+
PROMPTS = "prompts"
|
|
18
|
+
RESOURCES = "resources"
|
|
19
|
+
TOOLS = "tools"
|
|
20
|
+
COMPLETIONS = "completions"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class LoggingLevel(Enum):
|
|
24
|
+
"""MCP logging levels."""
|
|
25
|
+
|
|
26
|
+
DEBUG = "debug"
|
|
27
|
+
INFO = "info"
|
|
28
|
+
NOTICE = "notice"
|
|
29
|
+
WARNING = "warning"
|
|
30
|
+
ERROR = "error"
|
|
31
|
+
CRITICAL = "critical"
|
|
32
|
+
ALERT = "alert"
|
|
33
|
+
EMERGENCY = "emergency"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ToolAnnotations:
|
|
38
|
+
"""Annotations providing hints about tool behavior.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
title: Human-readable title for the tool
|
|
42
|
+
read_only_hint: If true, the tool does not modify its environment
|
|
43
|
+
destructive_hint: If true, the tool may perform destructive updates
|
|
44
|
+
idempotent_hint: If true, calling repeatedly has no additional effect
|
|
45
|
+
open_world_hint: If true, tool may interact with external entities
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
title: str | None = None
|
|
49
|
+
read_only_hint: bool | None = None
|
|
50
|
+
destructive_hint: bool | None = None
|
|
51
|
+
idempotent_hint: bool | None = None
|
|
52
|
+
open_world_hint: bool | None = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class MCPTool:
|
|
57
|
+
"""Represents an MCP tool available from a server.
|
|
58
|
+
|
|
59
|
+
Attributes:
|
|
60
|
+
name: The tool's unique name
|
|
61
|
+
description: Human-readable description of what the tool does
|
|
62
|
+
input_schema: JSON Schema defining the tool's input parameters
|
|
63
|
+
output_schema: Optional JSON Schema for tool output
|
|
64
|
+
server_id: ID of the server providing this tool
|
|
65
|
+
annotations: Optional hints about tool behavior
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
name: str
|
|
69
|
+
description: str
|
|
70
|
+
input_schema: dict[str, Any]
|
|
71
|
+
server_id: str
|
|
72
|
+
output_schema: dict[str, Any] | None = None
|
|
73
|
+
annotations: ToolAnnotations | None = None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class MCPResource:
|
|
78
|
+
"""Represents an MCP resource available from a server.
|
|
79
|
+
|
|
80
|
+
Attributes:
|
|
81
|
+
uri: The resource's unique URI
|
|
82
|
+
name: Human-readable name for the resource
|
|
83
|
+
description: Optional description of the resource
|
|
84
|
+
mime_type: Optional MIME type of the resource content
|
|
85
|
+
server_id: ID of the server providing this resource
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
uri: str
|
|
89
|
+
name: str
|
|
90
|
+
description: str | None = None
|
|
91
|
+
mime_type: str | None = None
|
|
92
|
+
server_id: str = ""
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class MCPResourceTemplate:
|
|
97
|
+
"""Represents an MCP resource template for dynamic resources.
|
|
98
|
+
|
|
99
|
+
Attributes:
|
|
100
|
+
uri_template: URI template with placeholders
|
|
101
|
+
name: Human-readable name for the template
|
|
102
|
+
description: Optional description
|
|
103
|
+
mime_type: Optional MIME type of generated resources
|
|
104
|
+
server_id: ID of the server providing this template
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
uri_template: str
|
|
108
|
+
name: str
|
|
109
|
+
description: str | None = None
|
|
110
|
+
mime_type: str | None = None
|
|
111
|
+
server_id: str = ""
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass
|
|
115
|
+
class MCPPromptArgument:
|
|
116
|
+
"""Argument definition for an MCP prompt.
|
|
117
|
+
|
|
118
|
+
Attributes:
|
|
119
|
+
name: Argument name
|
|
120
|
+
description: Optional description
|
|
121
|
+
required: Whether the argument is required
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
name: str
|
|
125
|
+
description: str | None = None
|
|
126
|
+
required: bool = False
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@dataclass
|
|
130
|
+
class MCPPrompt:
|
|
131
|
+
"""Represents an MCP prompt template from a server.
|
|
132
|
+
|
|
133
|
+
Attributes:
|
|
134
|
+
name: The prompt's unique name
|
|
135
|
+
description: Human-readable description of the prompt
|
|
136
|
+
arguments: List of argument definitions for the prompt
|
|
137
|
+
server_id: ID of the server providing this prompt
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
name: str
|
|
141
|
+
description: str | None = None
|
|
142
|
+
arguments: list[MCPPromptArgument] = field(default_factory=list)
|
|
143
|
+
server_id: str = ""
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass
|
|
147
|
+
class MCPToolResult:
|
|
148
|
+
"""Result from executing an MCP tool.
|
|
149
|
+
|
|
150
|
+
Attributes:
|
|
151
|
+
content: The tool's output content (text, images, etc.)
|
|
152
|
+
is_error: Whether the tool execution resulted in an error
|
|
153
|
+
error_message: Error message if is_error is True
|
|
154
|
+
structured_content: Optional structured output data
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
content: list[dict[str, Any]]
|
|
158
|
+
is_error: bool = False
|
|
159
|
+
error_message: str | None = None
|
|
160
|
+
structured_content: dict[str, Any] | None = None
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@dataclass
|
|
164
|
+
class MCPResourceContent:
|
|
165
|
+
"""Content retrieved from an MCP resource.
|
|
166
|
+
|
|
167
|
+
Attributes:
|
|
168
|
+
uri: The resource URI
|
|
169
|
+
mime_type: MIME type of the content
|
|
170
|
+
text: Text content (if text-based)
|
|
171
|
+
blob: Binary content as base64 (if binary)
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
uri: str
|
|
175
|
+
mime_type: str | None = None
|
|
176
|
+
text: str | None = None
|
|
177
|
+
blob: str | None = None
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@dataclass
|
|
181
|
+
class MCPPromptMessage:
|
|
182
|
+
"""A message from an MCP prompt.
|
|
183
|
+
|
|
184
|
+
Attributes:
|
|
185
|
+
role: The message role (user, assistant)
|
|
186
|
+
content: The message content
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
role: Literal["user", "assistant"]
|
|
190
|
+
content: str | dict[str, Any]
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@dataclass
|
|
194
|
+
class MCPPromptResult:
|
|
195
|
+
"""Result from getting an MCP prompt.
|
|
196
|
+
|
|
197
|
+
Attributes:
|
|
198
|
+
description: Optional description of the prompt
|
|
199
|
+
messages: List of messages in the prompt
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
description: str | None = None
|
|
203
|
+
messages: list[MCPPromptMessage] = field(default_factory=list)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@dataclass
|
|
207
|
+
class MCPCompletionResult:
|
|
208
|
+
"""Result from a completion request.
|
|
209
|
+
|
|
210
|
+
Attributes:
|
|
211
|
+
values: List of completion values
|
|
212
|
+
total: Total number of completions (if known)
|
|
213
|
+
has_more: Whether more completions are available
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
values: list[str]
|
|
217
|
+
total: int | None = None
|
|
218
|
+
has_more: bool | None = None
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@dataclass
|
|
222
|
+
class MCPRoot:
|
|
223
|
+
"""Represents a root directory for the MCP client.
|
|
224
|
+
|
|
225
|
+
Attributes:
|
|
226
|
+
uri: The root URI
|
|
227
|
+
name: Optional human-readable name
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
uri: str
|
|
231
|
+
name: str | None = None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@dataclass
|
|
235
|
+
class MCPServerCapabilities:
|
|
236
|
+
"""Server capabilities reported during initialization.
|
|
237
|
+
|
|
238
|
+
Attributes:
|
|
239
|
+
experimental: Experimental capabilities
|
|
240
|
+
logging: Logging capability
|
|
241
|
+
prompts: Prompts capability with list_changed support
|
|
242
|
+
resources: Resources capability with subscribe and list_changed
|
|
243
|
+
tools: Tools capability with list_changed support
|
|
244
|
+
completions: Completions capability
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
experimental: dict[str, Any] | None = None
|
|
248
|
+
logging: bool = False
|
|
249
|
+
prompts: bool = False
|
|
250
|
+
prompts_list_changed: bool = False
|
|
251
|
+
resources: bool = False
|
|
252
|
+
resources_subscribe: bool = False
|
|
253
|
+
resources_list_changed: bool = False
|
|
254
|
+
tools: bool = False
|
|
255
|
+
tools_list_changed: bool = False
|
|
256
|
+
completions: bool = False
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@dataclass
|
|
260
|
+
class MCPServerInfo:
|
|
261
|
+
"""Information about an MCP server.
|
|
262
|
+
|
|
263
|
+
Attributes:
|
|
264
|
+
name: Server name
|
|
265
|
+
version: Server version
|
|
266
|
+
capabilities: Server capabilities
|
|
267
|
+
protocol_version: MCP protocol version
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
name: str
|
|
271
|
+
version: str
|
|
272
|
+
capabilities: MCPServerCapabilities
|
|
273
|
+
protocol_version: str = ""
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@dataclass
|
|
277
|
+
class MCPProgress:
|
|
278
|
+
"""Progress notification data.
|
|
279
|
+
|
|
280
|
+
Attributes:
|
|
281
|
+
progress_token: Token identifying the operation
|
|
282
|
+
progress: Current progress value (0.0 to 1.0 or absolute)
|
|
283
|
+
total: Optional total value for absolute progress
|
|
284
|
+
message: Optional progress message
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
progress_token: str | int
|
|
288
|
+
progress: float
|
|
289
|
+
total: float | None = None
|
|
290
|
+
message: str | None = None
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Memory Package - Persistent learning from QE sessions.
|
|
3
|
+
|
|
4
|
+
Provides:
|
|
5
|
+
- QEMemory store for project-specific learnings
|
|
6
|
+
- Issue pattern tracking
|
|
7
|
+
- False positive suppressions
|
|
8
|
+
- File risk scoring
|
|
9
|
+
- Role effectiveness metrics
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .store import (
|
|
13
|
+
QEMemory,
|
|
14
|
+
IssuePattern,
|
|
15
|
+
Suppression,
|
|
16
|
+
FixPattern,
|
|
17
|
+
RoleMetrics,
|
|
18
|
+
MemoryStore,
|
|
19
|
+
)
|
|
20
|
+
from .feedback import FeedbackCollector, FindingFeedback
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"QEMemory",
|
|
24
|
+
"IssuePattern",
|
|
25
|
+
"Suppression",
|
|
26
|
+
"FixPattern",
|
|
27
|
+
"RoleMetrics",
|
|
28
|
+
"MemoryStore",
|
|
29
|
+
"FeedbackCollector",
|
|
30
|
+
"FindingFeedback",
|
|
31
|
+
]
|