codevira 1.6.0__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.
- codevira-1.6.0.dist-info/LICENSE +21 -0
- codevira-1.6.0.dist-info/METADATA +477 -0
- codevira-1.6.0.dist-info/RECORD +58 -0
- codevira-1.6.0.dist-info/WHEEL +5 -0
- codevira-1.6.0.dist-info/entry_points.txt +2 -0
- codevira-1.6.0.dist-info/top_level.txt +2 -0
- indexer/__init__.py +1 -0
- indexer/chunker.py +428 -0
- indexer/global_db.py +197 -0
- indexer/graph_generator.py +380 -0
- indexer/index_codebase.py +588 -0
- indexer/outcome_tracker.py +172 -0
- indexer/rule_learner.py +186 -0
- indexer/sqlite_graph.py +640 -0
- indexer/treesitter_parser.py +423 -0
- mcp_server/__init__.py +1 -0
- mcp_server/__main__.py +20 -0
- mcp_server/auto_init.py +257 -0
- mcp_server/cli.py +622 -0
- mcp_server/crash_logger.py +236 -0
- mcp_server/data/__init__.py +1 -0
- mcp_server/data/agents/builder.md +84 -0
- mcp_server/data/agents/developer.md +111 -0
- mcp_server/data/agents/documenter.md +138 -0
- mcp_server/data/agents/orchestrator.md +96 -0
- mcp_server/data/agents/planner.md +106 -0
- mcp_server/data/agents/reviewer.md +82 -0
- mcp_server/data/agents/tester.md +83 -0
- mcp_server/data/config.example.yaml +33 -0
- mcp_server/data/rules/coding-standards.md +48 -0
- mcp_server/data/rules/engineering-excellence.md +28 -0
- mcp_server/data/rules/git-cicd-governance.md +32 -0
- mcp_server/data/rules/git_commits.md +130 -0
- mcp_server/data/rules/incremental-updates.md +5 -0
- mcp_server/data/rules/master_rule.md +187 -0
- mcp_server/data/rules/multi-language.md +19 -0
- mcp_server/data/rules/persistence.md +21 -0
- mcp_server/data/rules/resilience-observability.md +17 -0
- mcp_server/data/rules/smoke-testing.md +48 -0
- mcp_server/data/rules/testing-standards.md +23 -0
- mcp_server/detect.py +284 -0
- mcp_server/gitignore.py +284 -0
- mcp_server/global_sync.py +187 -0
- mcp_server/http_server.py +341 -0
- mcp_server/ide_inject.py +444 -0
- mcp_server/launchd.py +156 -0
- mcp_server/migrate.py +215 -0
- mcp_server/paths.py +256 -0
- mcp_server/prompts.py +136 -0
- mcp_server/server.py +1049 -0
- mcp_server/tools/__init__.py +0 -0
- mcp_server/tools/changesets.py +223 -0
- mcp_server/tools/code_reader.py +335 -0
- mcp_server/tools/graph.py +637 -0
- mcp_server/tools/learning.py +238 -0
- mcp_server/tools/playbook.py +89 -0
- mcp_server/tools/roadmap.py +599 -0
- mcp_server/tools/search.py +145 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"""
|
|
2
|
+
http_server.py — HTTP/Streamable transport for Codevira MCP server.
|
|
3
|
+
|
|
4
|
+
Runs the same 36 MCP tools as stdio mode but over HTTP, enabling:
|
|
5
|
+
- URL-based MCP registration in Claude Code, Cursor, Windsurf
|
|
6
|
+
- HTTPS via mkcert for locally-trusted certificates (required by Claude.ai)
|
|
7
|
+
- Parallel multi-client connections without spawning a process per client
|
|
8
|
+
|
|
9
|
+
Endpoint layout:
|
|
10
|
+
POST /mcp — Streamable HTTP (MCP 2025-03-26 spec, preferred)
|
|
11
|
+
GET / — Health check → {"status": "ok", "transport": "streamable-http"}
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
codevira serve # HTTP on 127.0.0.1:7007
|
|
15
|
+
codevira serve --port 7443 --https # HTTPS on 127.0.0.1:7443
|
|
16
|
+
codevira serve --host 0.0.0.0 # Expose on all interfaces (LAN)
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import contextlib
|
|
21
|
+
import logging
|
|
22
|
+
import secrets
|
|
23
|
+
import subprocess
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import AsyncIterator
|
|
26
|
+
|
|
27
|
+
from starlette.applications import Starlette
|
|
28
|
+
from starlette.middleware import Middleware
|
|
29
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
30
|
+
from starlette.requests import Request
|
|
31
|
+
from starlette.responses import JSONResponse
|
|
32
|
+
from starlette.routing import Mount, Route
|
|
33
|
+
|
|
34
|
+
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
|
|
35
|
+
|
|
36
|
+
# Import the Server instance — all tool handlers are registered at module level
|
|
37
|
+
# via decorators, so this is safe to import from both stdio and HTTP modes.
|
|
38
|
+
from mcp_server.server import server
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
# Bearer token file — auto-generated on first non-loopback serve
|
|
43
|
+
_TOKEN_FILE_NAME = "http_bearer_token"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _certs_dir() -> Path:
|
|
47
|
+
from mcp_server.paths import get_global_home
|
|
48
|
+
return get_global_home() / "certs"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _cert_file() -> Path:
|
|
52
|
+
return _certs_dir() / "localhost.pem"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _key_file() -> Path:
|
|
56
|
+
return _certs_dir() / "localhost-key.pem"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Bearer token auth (required when binding to non-loopback addresses)
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
def _get_or_create_token() -> str:
|
|
64
|
+
"""Return the bearer token, creating one if it doesn't exist.
|
|
65
|
+
|
|
66
|
+
Token is stored in ~/.codevira/http_bearer_token so it persists across
|
|
67
|
+
restarts but is NOT committed to any project repo.
|
|
68
|
+
"""
|
|
69
|
+
from mcp_server.paths import get_global_home
|
|
70
|
+
token_path = get_global_home() / _TOKEN_FILE_NAME
|
|
71
|
+
if token_path.exists():
|
|
72
|
+
token = token_path.read_text(encoding="utf-8").strip()
|
|
73
|
+
if token:
|
|
74
|
+
return token
|
|
75
|
+
token = secrets.token_urlsafe(32)
|
|
76
|
+
token_path.write_text(token + "\n", encoding="utf-8")
|
|
77
|
+
token_path.chmod(0o600)
|
|
78
|
+
return token
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class _BearerAuthMiddleware(BaseHTTPMiddleware):
|
|
82
|
+
"""Reject requests without a valid Bearer token.
|
|
83
|
+
|
|
84
|
+
Applied only when the server binds to a non-loopback address (0.0.0.0, LAN IP, etc.)
|
|
85
|
+
to prevent unauthenticated access from other machines on the network.
|
|
86
|
+
The health endpoint (GET /) is exempt so uptime monitors still work.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(self, app, token: str):
|
|
90
|
+
super().__init__(app)
|
|
91
|
+
self._token = token
|
|
92
|
+
|
|
93
|
+
async def dispatch(self, request: Request, call_next):
|
|
94
|
+
# Allow health check without auth
|
|
95
|
+
if request.url.path == "/" and request.method == "GET":
|
|
96
|
+
return await call_next(request)
|
|
97
|
+
|
|
98
|
+
auth = request.headers.get("Authorization", "")
|
|
99
|
+
if auth != f"Bearer {self._token}":
|
|
100
|
+
return JSONResponse(
|
|
101
|
+
{"error": "Unauthorized", "hint": "Set Authorization: Bearer <token> header"},
|
|
102
|
+
status_code=401,
|
|
103
|
+
)
|
|
104
|
+
return await call_next(request)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
# Certificate helpers
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
def _certs_exist() -> bool:
|
|
112
|
+
return _cert_file().exists() and _key_file().exists()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def generate_mkcert_certs() -> tuple[Path, Path]:
|
|
116
|
+
"""
|
|
117
|
+
Generate trusted localhost certs using mkcert.
|
|
118
|
+
Requires mkcert to be installed and its CA to be in the system trust store
|
|
119
|
+
(`mkcert -install` must have been run at least once).
|
|
120
|
+
Returns (cert_path, key_path).
|
|
121
|
+
Raises RuntimeError if mkcert is not found.
|
|
122
|
+
"""
|
|
123
|
+
import shutil
|
|
124
|
+
|
|
125
|
+
if not shutil.which("mkcert"):
|
|
126
|
+
raise RuntimeError(
|
|
127
|
+
"mkcert not found.\n"
|
|
128
|
+
" Install: brew install mkcert\n"
|
|
129
|
+
" Trust CA: mkcert -install\n"
|
|
130
|
+
"Then re-run: codevira serve --https"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
cert_f = _cert_file()
|
|
134
|
+
key_f = _key_file()
|
|
135
|
+
_certs_dir().mkdir(parents=True, exist_ok=True)
|
|
136
|
+
|
|
137
|
+
subprocess.run(
|
|
138
|
+
[
|
|
139
|
+
"mkcert",
|
|
140
|
+
"-cert-file", str(cert_f),
|
|
141
|
+
"-key-file", str(key_f),
|
|
142
|
+
"localhost",
|
|
143
|
+
"127.0.0.1",
|
|
144
|
+
"::1",
|
|
145
|
+
],
|
|
146
|
+
check=True,
|
|
147
|
+
)
|
|
148
|
+
return cert_f, key_f
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
# ASGI app factory
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
def create_app(bearer_token: str | None = None) -> Starlette:
|
|
156
|
+
"""
|
|
157
|
+
Build and return the Starlette ASGI application.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
bearer_token: If set, all requests (except GET /) must include
|
|
161
|
+
an ``Authorization: Bearer <token>`` header.
|
|
162
|
+
Used when binding to non-loopback addresses.
|
|
163
|
+
|
|
164
|
+
Routes:
|
|
165
|
+
GET / → health check (useful for uptime monitoring)
|
|
166
|
+
POST /mcp → MCP Streamable HTTP transport (MCP 2025-03-26)
|
|
167
|
+
GET /mcp → MCP SSE stream (some clients open a GET first)
|
|
168
|
+
DELETE /mcp → session teardown
|
|
169
|
+
"""
|
|
170
|
+
session_manager = StreamableHTTPSessionManager(
|
|
171
|
+
app=server,
|
|
172
|
+
stateless=True, # no resumability — simpler for local single-user use
|
|
173
|
+
json_response=False, # SSE streaming (not JSON batching)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
@contextlib.asynccontextmanager
|
|
177
|
+
async def lifespan(_app: Starlette) -> AsyncIterator[None]:
|
|
178
|
+
async with session_manager.run():
|
|
179
|
+
yield
|
|
180
|
+
|
|
181
|
+
async def health(_req: Request) -> JSONResponse:
|
|
182
|
+
return JSONResponse({"status": "ok", "transport": "streamable-http", "server": "codevira"})
|
|
183
|
+
|
|
184
|
+
middleware = []
|
|
185
|
+
if bearer_token:
|
|
186
|
+
middleware.append(Middleware(_BearerAuthMiddleware, token=bearer_token))
|
|
187
|
+
|
|
188
|
+
return Starlette(
|
|
189
|
+
routes=[
|
|
190
|
+
Route("/", endpoint=health, methods=["GET"]),
|
|
191
|
+
Mount("/mcp", app=session_manager.handle_request),
|
|
192
|
+
],
|
|
193
|
+
lifespan=lifespan,
|
|
194
|
+
middleware=middleware,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# ---------------------------------------------------------------------------
|
|
199
|
+
# Server runner
|
|
200
|
+
# ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
def run_http_server(
|
|
203
|
+
host: str = "127.0.0.1",
|
|
204
|
+
port: int = 7007,
|
|
205
|
+
use_https: bool = False,
|
|
206
|
+
project_dir: Path | None = None,
|
|
207
|
+
) -> None:
|
|
208
|
+
"""
|
|
209
|
+
Start the HTTP MCP server. Blocks until Ctrl+C.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
host: Bind address. Use "0.0.0.0" to expose on LAN.
|
|
213
|
+
port: TCP port to listen on.
|
|
214
|
+
use_https: If True, load (or auto-generate) mkcert certs and serve TLS.
|
|
215
|
+
project_dir: Optional project root override (sets paths context).
|
|
216
|
+
"""
|
|
217
|
+
import uvicorn
|
|
218
|
+
|
|
219
|
+
# If a project directory was passed explicitly, set it so all path
|
|
220
|
+
# resolution in this process uses it. The CLI also calls set_project_dir()
|
|
221
|
+
# globally, but a direct caller of run_http_server() may not have done so.
|
|
222
|
+
if project_dir is not None:
|
|
223
|
+
from mcp_server.paths import set_project_dir
|
|
224
|
+
set_project_dir(project_dir)
|
|
225
|
+
|
|
226
|
+
# ---- Startup side effects (mirror server.py main()) ----
|
|
227
|
+
try:
|
|
228
|
+
from mcp_server.crash_logger import install_global_handler
|
|
229
|
+
install_global_handler()
|
|
230
|
+
except Exception as e:
|
|
231
|
+
logger.warning("Could not install crash handler: %s", e)
|
|
232
|
+
|
|
233
|
+
# v1.6: Auto-migrate legacy .codevira/ → ~/.codevira/projects/<key>/
|
|
234
|
+
try:
|
|
235
|
+
from mcp_server.migrate import detect_migration_needed, migrate_to_centralized
|
|
236
|
+
from mcp_server.paths import get_project_root
|
|
237
|
+
_proj_root = get_project_root()
|
|
238
|
+
if detect_migration_needed(_proj_root):
|
|
239
|
+
logger.info("Migrating legacy .codevira/ to centralized storage...")
|
|
240
|
+
result = migrate_to_centralized(_proj_root)
|
|
241
|
+
if result.get("migrated"):
|
|
242
|
+
logger.info("Migration complete: %d files moved to %s",
|
|
243
|
+
result.get("files_copied", 0), result.get("new_path", ""))
|
|
244
|
+
except Exception as e:
|
|
245
|
+
logger.warning("Could not run storage migration: %s", e)
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
from indexer.index_codebase import start_background_watcher
|
|
249
|
+
start_background_watcher(quiet=True)
|
|
250
|
+
logger.info("Live file watcher active")
|
|
251
|
+
except Exception as e:
|
|
252
|
+
logger.warning("Could not start background watcher: %s", e)
|
|
253
|
+
try:
|
|
254
|
+
from mcp_server.crash_logger import log_crash
|
|
255
|
+
log_crash(e, context="http serve: background watcher")
|
|
256
|
+
except Exception:
|
|
257
|
+
pass
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
from indexer.outcome_tracker import analyze_session_outcomes
|
|
261
|
+
from indexer.rule_learner import run_rule_inference
|
|
262
|
+
analyze_session_outcomes()
|
|
263
|
+
run_rule_inference()
|
|
264
|
+
except Exception as e:
|
|
265
|
+
logger.warning("Could not run startup learning: %s", e)
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
from mcp_server.global_sync import import_global_to_project
|
|
269
|
+
import_global_to_project()
|
|
270
|
+
except Exception as e:
|
|
271
|
+
logger.warning("Could not sync global memory: %s", e)
|
|
272
|
+
|
|
273
|
+
# ---- TLS certificate setup ----
|
|
274
|
+
ssl_certfile: str | None = None
|
|
275
|
+
ssl_keyfile: str | None = None
|
|
276
|
+
|
|
277
|
+
if use_https:
|
|
278
|
+
if not _certs_exist():
|
|
279
|
+
print(" Generating localhost TLS certificate via mkcert ...")
|
|
280
|
+
try:
|
|
281
|
+
generate_mkcert_certs()
|
|
282
|
+
print(f" Certificate: {_cert_file()}")
|
|
283
|
+
print(f" Private key: {_key_file()}")
|
|
284
|
+
except RuntimeError as e:
|
|
285
|
+
print(f"\n ERROR: {e}\n")
|
|
286
|
+
return
|
|
287
|
+
ssl_certfile = str(_cert_file())
|
|
288
|
+
ssl_keyfile = str(_key_file())
|
|
289
|
+
|
|
290
|
+
# ---- Print registration instructions ----
|
|
291
|
+
scheme = "https" if use_https else "http"
|
|
292
|
+
url = f"{scheme}://{host}:{port}/mcp"
|
|
293
|
+
display_host = "localhost" if host in ("127.0.0.1", "::1") else host
|
|
294
|
+
display_url = f"{scheme}://{display_host}:{port}/mcp"
|
|
295
|
+
|
|
296
|
+
print()
|
|
297
|
+
print(" Codevira MCP — HTTP Server")
|
|
298
|
+
print(" " + "─" * 44)
|
|
299
|
+
print(f" Endpoint : {display_url}")
|
|
300
|
+
print(f" Transport: MCP Streamable HTTP (2025-03-26)")
|
|
301
|
+
print()
|
|
302
|
+
print(" ── Register in Claude Code ──────────────────")
|
|
303
|
+
print(" Add to ~/.claude/settings.json (global) or")
|
|
304
|
+
print(" .claude/settings.json (project):")
|
|
305
|
+
print()
|
|
306
|
+
print(' {')
|
|
307
|
+
print(' "mcpServers": {')
|
|
308
|
+
print(' "codevira": {')
|
|
309
|
+
print(f' "url": "{display_url}"')
|
|
310
|
+
print(' }')
|
|
311
|
+
print(' }')
|
|
312
|
+
print(' }')
|
|
313
|
+
print()
|
|
314
|
+
if not use_https:
|
|
315
|
+
print(" Tip: Use --https for a trusted HTTPS URL (required for Claude.ai)")
|
|
316
|
+
print()
|
|
317
|
+
print(" Press Ctrl+C to stop.")
|
|
318
|
+
print()
|
|
319
|
+
|
|
320
|
+
# ---- Bearer token auth (required for non-loopback binds) ----
|
|
321
|
+
bearer_token: str | None = None
|
|
322
|
+
is_loopback = host in ("127.0.0.1", "::1", "localhost")
|
|
323
|
+
if not is_loopback:
|
|
324
|
+
bearer_token = _get_or_create_token()
|
|
325
|
+
print(f" ── Auth (non-loopback) ──────────────────────")
|
|
326
|
+
print(f" Bearer token: {bearer_token}")
|
|
327
|
+
print(f" All /mcp requests require: Authorization: Bearer <token>")
|
|
328
|
+
print(f" Token stored in: ~/.codevira/{_TOKEN_FILE_NAME}")
|
|
329
|
+
print()
|
|
330
|
+
|
|
331
|
+
# ---- Run uvicorn ----
|
|
332
|
+
app = create_app(bearer_token=bearer_token)
|
|
333
|
+
|
|
334
|
+
uvicorn.run(
|
|
335
|
+
app,
|
|
336
|
+
host=host,
|
|
337
|
+
port=port,
|
|
338
|
+
ssl_certfile=ssl_certfile,
|
|
339
|
+
ssl_keyfile=ssl_keyfile,
|
|
340
|
+
log_level="warning",
|
|
341
|
+
)
|