trace-sec 2.1.3 → 2.1.5
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.
- package/bin/trace.js +3 -3
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/trace_engine/__pycache__/cli.cpython-311.pyc +0 -0
- package/src/trace_engine/__pycache__/interactive.cpython-311.pyc +0 -0
- package/src/trace_engine/cli.py +122 -9
- package/src/trace_engine/intelligence/__pycache__/downloader.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/downloader.py +267 -0
- package/src/trace_engine/intelligence/laya/__pycache__/router.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/router.py +17 -3
- package/src/trace_engine/intelligence/securebert/__pycache__/classifier.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/securebert/classifier.py +18 -2
- package/src/trace_engine/interactive.py +17 -6
- package/src/trace_engine/mcp/__pycache__/server.cpython-311.pyc +0 -0
- package/src/trace_engine/mcp/server.py +171 -0
package/bin/trace.js
CHANGED
|
@@ -51,8 +51,8 @@ if (args[0] === 'install-skill' || args[0] === 'setup-agent' || args[0] === 'ins
|
|
|
51
51
|
trace: {
|
|
52
52
|
command: isWin ? 'cmd.exe' : 'npx',
|
|
53
53
|
args: isWin
|
|
54
|
-
? ['/c', 'npx', '-y', '
|
|
55
|
-
: ['-y', '
|
|
54
|
+
? ['/c', 'npx', '-y', 'trace-sec', 'mcp']
|
|
55
|
+
: ['-y', 'trace-sec', 'mcp'],
|
|
56
56
|
env: {
|
|
57
57
|
PYTHONUNBUFFERED: '1',
|
|
58
58
|
},
|
|
@@ -73,7 +73,7 @@ if (args[0] === 'install-skill' || args[0] === 'setup-agent' || args[0] === 'ins
|
|
|
73
73
|
console.log(' • \x1b[32mAntigravity & Agentic IDEs:\x1b[0m .agents/plugins/trace-security active');
|
|
74
74
|
console.log(' • \x1b[32mVerification Tools:\x1b[0m trace_scan, trace_findings, trace_verify, trace_harness_task');
|
|
75
75
|
console.log('\n\x1b[90mTo register in Claude Code CLI directly:\x1b[0m');
|
|
76
|
-
console.log(' \x1b[37mclaude mcp add trace -- npx
|
|
76
|
+
console.log(' \x1b[37mclaude mcp add trace -- npx trace-sec mcp\x1b[0m\n');
|
|
77
77
|
process.exit(0);
|
|
78
78
|
} else {
|
|
79
79
|
console.error(`[TRACE] Bundle directory not found at ${bundleDir}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trace-sec",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.5",
|
|
4
4
|
"description": "Threat Reconnaissance & Attack-path Correlation Engine — Security Verification Layer for Coding Agents with Fine-Tuned SecureBERT 2.0 & Laya AI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/trace.js",
|
package/pyproject.toml
CHANGED
|
Binary file
|
|
Binary file
|
package/src/trace_engine/cli.py
CHANGED
|
@@ -320,6 +320,10 @@ def scan(
|
|
|
320
320
|
):
|
|
321
321
|
"""Execute end-to-end scan: Ingestion -> APM -> Signals -> Runtime Tests -> Correlated Findings."""
|
|
322
322
|
project_dir = path.resolve()
|
|
323
|
+
from trace_engine.intelligence.downloader import prompt_and_bootstrap_models
|
|
324
|
+
if not prompt_and_bootstrap_models(interactive=True):
|
|
325
|
+
raise typer.Exit(code=1)
|
|
326
|
+
|
|
323
327
|
config = load_config(project_dir)
|
|
324
328
|
|
|
325
329
|
console.print(f"\n[bold green]Starting TRACE Full Scan[/bold green] on [white]{project_dir.name}[/white]")
|
|
@@ -438,6 +442,11 @@ def test_all(
|
|
|
438
442
|
from trace_engine.intelligence.orchestrator import IntelligenceOrchestrator
|
|
439
443
|
|
|
440
444
|
project_dir = path.resolve()
|
|
445
|
+
if models:
|
|
446
|
+
from trace_engine.intelligence.downloader import prompt_and_bootstrap_models
|
|
447
|
+
if not prompt_and_bootstrap_models(interactive=True):
|
|
448
|
+
raise typer.Exit(code=1)
|
|
449
|
+
|
|
441
450
|
config = load_config(project_dir)
|
|
442
451
|
|
|
443
452
|
resolved_target = target if target else (config.target.url or "http://127.0.0.1:18080")
|
|
@@ -824,29 +833,53 @@ def verify(
|
|
|
824
833
|
|
|
825
834
|
|
|
826
835
|
@app.command()
|
|
827
|
-
def mcp(
|
|
828
|
-
|
|
836
|
+
def mcp(
|
|
837
|
+
server: bool = typer.Option(False, "--server", "-s", help="Run local HTTP/SSE server instead of stdio"),
|
|
838
|
+
host: str = typer.Option("127.0.0.1", "--host", "-h", help="Host interface to bind HTTP/SSE server"),
|
|
839
|
+
port: int = typer.Option(8765, "--port", "-p", help="Port to bind HTTP/SSE server"),
|
|
840
|
+
):
|
|
841
|
+
"""Start TRACE Model Context Protocol (MCP) server for Claude Code and coding agents."""
|
|
842
|
+
from trace_engine.mcp.server import TraceMCPServer
|
|
843
|
+
|
|
844
|
+
mcp_obj = TraceMCPServer()
|
|
845
|
+
if server:
|
|
846
|
+
mcp_obj.run_sse_server(host=host, port=port)
|
|
847
|
+
else:
|
|
848
|
+
mcp_obj.run_stdio()
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
@app.command(name="mcp-server")
|
|
852
|
+
@app.command(name="serve-mcp")
|
|
853
|
+
def mcp_server_cmd(
|
|
854
|
+
host: str = typer.Option("127.0.0.1", "--host", "-h", help="Host interface to bind HTTP/SSE server"),
|
|
855
|
+
port: int = typer.Option(8765, "--port", "-p", help="Port to bind HTTP/SSE server"),
|
|
856
|
+
):
|
|
857
|
+
"""Start local HTTP/SSE TRACE MCP server and display connection link for Claude Code."""
|
|
829
858
|
from trace_engine.mcp.server import TraceMCPServer
|
|
830
859
|
|
|
831
|
-
|
|
832
|
-
|
|
860
|
+
mcp_obj = TraceMCPServer()
|
|
861
|
+
mcp_obj.run_sse_server(host=host, port=port)
|
|
833
862
|
|
|
834
863
|
|
|
835
864
|
@app.command("setup-mcp")
|
|
836
865
|
def setup_mcp(
|
|
837
866
|
path: Path = typer.Argument(Path("."), help="Path to project repository")
|
|
838
867
|
):
|
|
839
|
-
"""Generate .mcp.json and configure MCP coding agent integration
|
|
868
|
+
"""Generate .mcp.json and configure MCP coding agent integration."""
|
|
840
869
|
from trace_engine.mcp.config import generate_mcp_json
|
|
841
870
|
|
|
842
871
|
project_dir = path.resolve()
|
|
843
872
|
mcp_file = generate_mcp_json(project_dir)
|
|
844
873
|
|
|
845
874
|
console.print(f"\n[bold green]Configured TRACE MCP[/bold green] in [white]{mcp_file}[/white]")
|
|
846
|
-
console.print("\n[bold white]
|
|
847
|
-
console.print(" [green]claude mcp add trace --
|
|
848
|
-
console.print("\n[bold white]
|
|
849
|
-
console.print("
|
|
875
|
+
console.print("\n[bold white]Option A: Direct Stdio Integration in Claude Code:[/bold white]")
|
|
876
|
+
console.print(" [green]claude mcp add trace -- npx -y trace-sec mcp[/green]")
|
|
877
|
+
console.print("\n[bold white]Option B: Local HTTP/SSE Server Link (Real-Time Bridge):[/bold white]")
|
|
878
|
+
console.print(" 1. Launch server: [bold green]trace mcp-server[/bold green]")
|
|
879
|
+
console.print(" 2. Connect via CLI: [green]claude mcp add --transport sse trace http://127.0.0.1:8765/sse[/green]")
|
|
880
|
+
console.print(" 3. Or paste [cyan]http://127.0.0.1:8765/sse[/cyan] directly in your Claude Code chat!")
|
|
881
|
+
console.print("\n[bold white]Option C: Cursor IDE / Antigravity / Claude Code:[/bold white]")
|
|
882
|
+
console.print(" [dim]The created .mcp.json is already active in your workspace.[/dim]\n")
|
|
850
883
|
|
|
851
884
|
|
|
852
885
|
@app.command()
|
|
@@ -885,6 +918,10 @@ def remediate(
|
|
|
885
918
|
"""Execute autonomous Agent Harness self-healing and remediation loop: patch vulnerabilities and verify fixes."""
|
|
886
919
|
from trace_engine.harness.engine import AgentHarness
|
|
887
920
|
|
|
921
|
+
from trace_engine.intelligence.downloader import prompt_and_bootstrap_models
|
|
922
|
+
if not prompt_and_bootstrap_models(interactive=True):
|
|
923
|
+
raise typer.Exit(code=1)
|
|
924
|
+
|
|
888
925
|
target_path = repo or path
|
|
889
926
|
_, resolved_dir = resolve_finding_store(target_path.resolve())
|
|
890
927
|
resolved_target = target or "http://127.0.0.1:18082"
|
|
@@ -1178,6 +1215,82 @@ def bench_intelligence():
|
|
|
1178
1215
|
run_intelligence_benchmark()
|
|
1179
1216
|
|
|
1180
1217
|
|
|
1218
|
+
@app.command(name="setup-models")
|
|
1219
|
+
@app.command(name="download-models")
|
|
1220
|
+
def setup_models():
|
|
1221
|
+
"""Download and provision fine-tuned SecureBERT 2.0 and Laya System 1 weights into ~/.trace/models."""
|
|
1222
|
+
from trace_engine.intelligence.downloader import ensure_all_models
|
|
1223
|
+
console.print("\n[bold green]TRACE Neural Model Provisioner[/bold green]")
|
|
1224
|
+
console.print("[dim]Fetching fine-tuned weights from TRACE Git LFS Hub into ~/.trace/models...[/dim]\n")
|
|
1225
|
+
ensure_all_models()
|
|
1226
|
+
console.print("[bold green]✓ All fine-tuned models are installed, verified, and ready.[/bold green]\n")
|
|
1227
|
+
|
|
1228
|
+
|
|
1229
|
+
@app.command(name="install-skill")
|
|
1230
|
+
@app.command(name="setup-agent")
|
|
1231
|
+
def install_skill(
|
|
1232
|
+
target_dir: Path = typer.Option(Path("."), "--target", "-t", help="Target repository directory to install skills and MCP"),
|
|
1233
|
+
):
|
|
1234
|
+
"""Install TRACE autonomous agent skill & MCP configurations for Claude Code, Antigravity, and Cursor."""
|
|
1235
|
+
import shutil
|
|
1236
|
+
project_root = Path(__file__).resolve().parents[2]
|
|
1237
|
+
bundle_dir = project_root / "src" / "trace_engine" / "plugin" / "bundle"
|
|
1238
|
+
target = target_dir.resolve()
|
|
1239
|
+
|
|
1240
|
+
console.print("\n[bold green]Installing TRACE Autonomous Security Agent Skill & MCP Tools...[/bold green]")
|
|
1241
|
+
|
|
1242
|
+
if bundle_dir.exists():
|
|
1243
|
+
# 1. Antigravity plugin (.agents/plugins/trace-security)
|
|
1244
|
+
agent_plugin_dir = target / ".agents" / "plugins" / "trace-security"
|
|
1245
|
+
agent_plugin_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
1246
|
+
shutil.copytree(bundle_dir, agent_plugin_dir, dirs_exist_ok=True)
|
|
1247
|
+
console.print(f" [bold green]✓[/bold green] Antigravity Plugin : [white]{agent_plugin_dir}[/white]")
|
|
1248
|
+
|
|
1249
|
+
# 2. Agent Skill & Claude Code Skill
|
|
1250
|
+
skill_src = bundle_dir / "skills" / "trace-security-harness"
|
|
1251
|
+
if skill_src.exists():
|
|
1252
|
+
agent_skill_dir = target / ".agents" / "skills" / "trace-security-harness"
|
|
1253
|
+
agent_skill_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
1254
|
+
shutil.copytree(skill_src, agent_skill_dir, dirs_exist_ok=True)
|
|
1255
|
+
console.print(f" [bold green]✓[/bold green] Agent Skill : [white]{agent_skill_dir}[/white]")
|
|
1256
|
+
|
|
1257
|
+
claude_skill_dir = target / ".claude" / "skills" / "trace-security"
|
|
1258
|
+
claude_skill_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
1259
|
+
shutil.copytree(skill_src, claude_skill_dir, dirs_exist_ok=True)
|
|
1260
|
+
console.print(f" [bold green]✓[/bold green] Claude Code Skill : [white]{claude_skill_dir}[/white]")
|
|
1261
|
+
|
|
1262
|
+
# 3. MCP Configuration
|
|
1263
|
+
import json
|
|
1264
|
+
is_win = sys.platform == "win32"
|
|
1265
|
+
mcp_config = {
|
|
1266
|
+
"mcpServers": {
|
|
1267
|
+
"trace": {
|
|
1268
|
+
"command": "cmd.exe" if is_win else "npx",
|
|
1269
|
+
"args": ["/c", "npx", "-y", "trace-sec", "mcp"] if is_win else ["-y", "trace-sec", "mcp"],
|
|
1270
|
+
"env": {
|
|
1271
|
+
"PYTHONUNBUFFERED": "1"
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
mcp_json = target / ".mcp.json"
|
|
1277
|
+
mcp_json.write_text(json.dumps(mcp_config, indent=2), encoding="utf-8")
|
|
1278
|
+
console.print(f" [bold green]✓[/bold green] Claude Code MCP : [white]{mcp_json}[/white]")
|
|
1279
|
+
|
|
1280
|
+
cursor_dir = target / ".cursor"
|
|
1281
|
+
cursor_dir.mkdir(parents=True, exist_ok=True)
|
|
1282
|
+
(cursor_dir / "mcp.json").write_text(json.dumps(mcp_config, indent=2), encoding="utf-8")
|
|
1283
|
+
console.print(f" [bold green]✓[/bold green] Cursor IDE MCP : [white]{cursor_dir / 'mcp.json'}[/white]")
|
|
1284
|
+
|
|
1285
|
+
console.print("\n[bold]Capabilities Enabled for AI Coding Agents:[/bold]")
|
|
1286
|
+
console.print(" • [bold green]Claude Code Integration:[/bold green] Skills + MCP server configured")
|
|
1287
|
+
console.print(" • [bold green]Antigravity & Agentic IDEs:[/bold green] .agents/plugins/trace-security active")
|
|
1288
|
+
console.print(" • [bold green]Verification Tools:[/bold green] trace_scan, trace_findings, trace_verify, trace_harness_task\n")
|
|
1289
|
+
else:
|
|
1290
|
+
console.print(f"[bold red]Error:[/bold red] Plugin bundle not found at {bundle_dir}")
|
|
1291
|
+
raise typer.Exit(code=1)
|
|
1292
|
+
|
|
1293
|
+
|
|
1181
1294
|
if __name__ == "__main__":
|
|
1182
1295
|
app()
|
|
1183
1296
|
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""Automated model downloader, verification gate, and provisioner for TRACE fine-tuned weights."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import logging
|
|
6
|
+
import urllib.request
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional, Dict, List, Any
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
REPO_OWNER = "VK-Amogh"
|
|
13
|
+
REPO_NAME = "TRACE"
|
|
14
|
+
BRANCH = "main"
|
|
15
|
+
|
|
16
|
+
MODEL_REGISTRY: Dict[str, Dict[str, Any]] = {
|
|
17
|
+
"securebert-finetuned": {
|
|
18
|
+
"files": {
|
|
19
|
+
"config.json": {
|
|
20
|
+
"urls": [
|
|
21
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/securebert-finetuned/config.json",
|
|
22
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/securebert-finetuned/config.json",
|
|
23
|
+
],
|
|
24
|
+
"min_size": 500,
|
|
25
|
+
},
|
|
26
|
+
"tokenizer.json": {
|
|
27
|
+
"urls": [
|
|
28
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/securebert-finetuned/tokenizer.json",
|
|
29
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/securebert-finetuned/tokenizer.json",
|
|
30
|
+
],
|
|
31
|
+
"min_size": 1000000,
|
|
32
|
+
},
|
|
33
|
+
"tokenizer_config.json": {
|
|
34
|
+
"urls": [
|
|
35
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/securebert-finetuned/tokenizer_config.json",
|
|
36
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/securebert-finetuned/tokenizer_config.json",
|
|
37
|
+
],
|
|
38
|
+
"min_size": 200,
|
|
39
|
+
},
|
|
40
|
+
"model.safetensors": {
|
|
41
|
+
"urls": [
|
|
42
|
+
f"https://media.githubusercontent.com/media/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/securebert-finetuned/model.safetensors",
|
|
43
|
+
f"https://media.githubusercontent.com/media/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/securebert-finetuned/model.safetensors",
|
|
44
|
+
],
|
|
45
|
+
"min_size": 100000000, # ~498 MB
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
"laya-finetuned": {
|
|
50
|
+
"files": {
|
|
51
|
+
"laya_metadata.json": {
|
|
52
|
+
"urls": [
|
|
53
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/laya-finetuned/laya_metadata.json",
|
|
54
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/laya-finetuned/laya_metadata.json",
|
|
55
|
+
],
|
|
56
|
+
"min_size": 300,
|
|
57
|
+
},
|
|
58
|
+
"tokenizer.json": {
|
|
59
|
+
"urls": [
|
|
60
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/laya-finetuned/tokenizer.json",
|
|
61
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/laya-finetuned/tokenizer.json",
|
|
62
|
+
],
|
|
63
|
+
"min_size": 500000,
|
|
64
|
+
},
|
|
65
|
+
"tokenizer_config.json": {
|
|
66
|
+
"urls": [
|
|
67
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/laya-finetuned/tokenizer_config.json",
|
|
68
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/laya-finetuned/tokenizer_config.json",
|
|
69
|
+
],
|
|
70
|
+
"min_size": 200,
|
|
71
|
+
},
|
|
72
|
+
"laya_dual_head.onnx": {
|
|
73
|
+
"urls": [
|
|
74
|
+
f"https://media.githubusercontent.com/media/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/laya-finetuned/laya_dual_head.onnx",
|
|
75
|
+
f"https://media.githubusercontent.com/media/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/laya-finetuned/laya_dual_head.onnx",
|
|
76
|
+
],
|
|
77
|
+
"min_size": 50000000, # ~267 MB
|
|
78
|
+
},
|
|
79
|
+
"laya_dual_head.pt": {
|
|
80
|
+
"urls": [
|
|
81
|
+
f"https://media.githubusercontent.com/media/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/laya-finetuned/laya_dual_head.pt",
|
|
82
|
+
f"https://media.githubusercontent.com/media/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/laya-finetuned/laya_dual_head.pt",
|
|
83
|
+
],
|
|
84
|
+
"min_size": 50000000, # ~267 MB
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_default_models_dir() -> Path:
|
|
92
|
+
"""Returns the default persistent directory for TRACE models (~/.trace/models)."""
|
|
93
|
+
target = Path.home() / ".trace" / "models"
|
|
94
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
95
|
+
return target
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def is_model_installed(model_name: str, candidate_dir: Optional[Path] = None) -> bool:
|
|
99
|
+
"""Checks if all required model files are present and valid (not empty or git lfs pointer stubs)."""
|
|
100
|
+
if model_name not in MODEL_REGISTRY:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
spec = MODEL_REGISTRY[model_name]
|
|
104
|
+
target_dir = candidate_dir or (get_default_models_dir() / model_name)
|
|
105
|
+
if not target_dir.exists():
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
for filename, meta in spec["files"].items():
|
|
109
|
+
file_path = target_dir / filename
|
|
110
|
+
if not file_path.exists():
|
|
111
|
+
return False
|
|
112
|
+
if file_path.stat().st_size < meta["min_size"]:
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def is_environment_ready() -> bool:
|
|
119
|
+
"""Checks if both fine-tuned SecureBERT 2.0 and Laya System 1 models are installed and valid."""
|
|
120
|
+
repo_root = Path(__file__).resolve().parents[4]
|
|
121
|
+
candidate_roots = [
|
|
122
|
+
Path.home() / ".trace" / "models",
|
|
123
|
+
Path("models"),
|
|
124
|
+
Path(".trace/models"),
|
|
125
|
+
repo_root / "models",
|
|
126
|
+
repo_root / ".trace/models",
|
|
127
|
+
Path("D:/Startup/TRACE/models"),
|
|
128
|
+
Path("D:/Startup/TRACE/.trace/models"),
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
sb_ok = False
|
|
132
|
+
laya_ok = False
|
|
133
|
+
|
|
134
|
+
for r in candidate_roots:
|
|
135
|
+
if not sb_ok and is_model_installed("securebert-finetuned", r / "securebert-finetuned"):
|
|
136
|
+
sb_ok = True
|
|
137
|
+
if not laya_ok and is_model_installed("laya-finetuned", r / "laya-finetuned"):
|
|
138
|
+
laya_ok = True
|
|
139
|
+
if sb_ok and laya_ok:
|
|
140
|
+
return True
|
|
141
|
+
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def ensure_model(model_name: str) -> Path:
|
|
146
|
+
"""Ensures model is installed locally; downloads missing files from GitHub LFS/Repo if needed."""
|
|
147
|
+
models_dir = get_default_models_dir()
|
|
148
|
+
target_dir = models_dir / model_name
|
|
149
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
150
|
+
|
|
151
|
+
if is_model_installed(model_name, target_dir):
|
|
152
|
+
return target_dir
|
|
153
|
+
|
|
154
|
+
if model_name not in MODEL_REGISTRY:
|
|
155
|
+
raise ValueError(f"Unknown model: {model_name}")
|
|
156
|
+
|
|
157
|
+
spec = MODEL_REGISTRY[model_name]
|
|
158
|
+
print(f"\n\033[38;2;16;185;129m[TRACE Model Hub]\033[0m Downloading fine-tuned {model_name} neural weights...")
|
|
159
|
+
|
|
160
|
+
for filename, meta in spec["files"].items():
|
|
161
|
+
file_path = target_dir / filename
|
|
162
|
+
if file_path.exists() and file_path.stat().st_size >= meta["min_size"]:
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
temp_path = target_dir / f"{filename}.tmp"
|
|
166
|
+
urls = meta.get("urls", [])
|
|
167
|
+
print(f" \033[38;2;255;158;59m›\033[0m Fetching {filename}...")
|
|
168
|
+
|
|
169
|
+
success = False
|
|
170
|
+
last_error = None
|
|
171
|
+
for url in urls:
|
|
172
|
+
try:
|
|
173
|
+
req = urllib.request.Request(
|
|
174
|
+
url,
|
|
175
|
+
headers={"User-Agent": "TRACE-Security-Engine/2.1 (github:VK-Amogh/TRACE)"},
|
|
176
|
+
)
|
|
177
|
+
with urllib.request.urlopen(req, timeout=120) as resp:
|
|
178
|
+
total_size = int(resp.headers.get("Content-Length", 0))
|
|
179
|
+
downloaded = 0
|
|
180
|
+
block_size = 1024 * 1024 # 1 MB chunk
|
|
181
|
+
|
|
182
|
+
with open(temp_path, "wb") as f:
|
|
183
|
+
while True:
|
|
184
|
+
chunk = resp.read(block_size)
|
|
185
|
+
if not chunk:
|
|
186
|
+
break
|
|
187
|
+
f.write(chunk)
|
|
188
|
+
downloaded += len(chunk)
|
|
189
|
+
if total_size > 0:
|
|
190
|
+
percent = (downloaded / total_size) * 100
|
|
191
|
+
mb_down = downloaded / (1024 * 1024)
|
|
192
|
+
mb_tot = total_size / (1024 * 1024)
|
|
193
|
+
sys.stdout.write(f"\r [{percent:.1f}%] {mb_down:.1f} MB / {mb_tot:.1f} MB")
|
|
194
|
+
sys.stdout.flush()
|
|
195
|
+
|
|
196
|
+
print()
|
|
197
|
+
if temp_path.exists():
|
|
198
|
+
temp_path.replace(file_path)
|
|
199
|
+
success = True
|
|
200
|
+
break
|
|
201
|
+
|
|
202
|
+
except Exception as e:
|
|
203
|
+
last_error = e
|
|
204
|
+
if temp_path.exists():
|
|
205
|
+
try:
|
|
206
|
+
temp_path.unlink()
|
|
207
|
+
except Exception:
|
|
208
|
+
pass
|
|
209
|
+
continue
|
|
210
|
+
|
|
211
|
+
if not success:
|
|
212
|
+
logger.warning(f"Failed to download {filename}: {last_error}")
|
|
213
|
+
raise RuntimeError(f"Could not download {filename} for {model_name}: {last_error}")
|
|
214
|
+
|
|
215
|
+
print(f"\033[38;2;16;185;129m✓ Successfully provisioned {model_name}\033[0m\n")
|
|
216
|
+
return target_dir
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def ensure_all_models() -> None:
|
|
220
|
+
"""Proactively ensures all fine-tuned models are downloaded and verified."""
|
|
221
|
+
for model_name in MODEL_REGISTRY:
|
|
222
|
+
ensure_model(model_name)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def prompt_and_bootstrap_models(interactive: bool = True) -> bool:
|
|
226
|
+
"""Interactive gate: checks if models are installed, prompts user to agree and downloads them."""
|
|
227
|
+
if is_environment_ready():
|
|
228
|
+
return True
|
|
229
|
+
|
|
230
|
+
CYAN = "\033[38;2;56;189;248m"
|
|
231
|
+
GREEN = "\033[38;2;16;185;129m"
|
|
232
|
+
ORANGE = "\033[38;2;255;158;59m"
|
|
233
|
+
BOLD = "\033[1m"
|
|
234
|
+
DIM = "\033[2m"
|
|
235
|
+
RESET = "\033[0m"
|
|
236
|
+
|
|
237
|
+
print(f"\n {GREEN}┌{'─' * 74}┐{RESET}")
|
|
238
|
+
|
|
239
|
+
print(f" {GREEN}│{RESET} {BOLD}TRACE Neural Intelligence Engine — First-Time Environment Setup{RESET} {GREEN}│{RESET}")
|
|
240
|
+
print(f" {GREEN}├{'─' * 74}┤{RESET}")
|
|
241
|
+
print(f" {GREEN}│{RESET} TRACE requires fine-tuned neural models for autonomous AST attack-path {GREEN}│{RESET}")
|
|
242
|
+
print(f" {GREEN}│{RESET} correlation and vulnerability verification: {GREEN}│{RESET}")
|
|
243
|
+
print(f" {GREEN}│{RESET} • {CYAN}SecureBERT 2.0{RESET} (Fine-Tuned OWASP API Vulnerability Classifier) {GREEN}│{RESET}")
|
|
244
|
+
print(f" {GREEN}│{RESET} • {CYAN}Laya System 1{RESET} (Non-Autoregressive Fast Decision & Priority Router) {GREEN}│{RESET}")
|
|
245
|
+
print(f" {GREEN}│{RESET} {GREEN}│{RESET}")
|
|
246
|
+
print(f" {GREEN}│{RESET} {DIM}Dedicated Location : ~/.trace/models/{RESET} {GREEN}│{RESET}")
|
|
247
|
+
print(f" {GREEN}│{RESET} {DIM}Source : Official TRACE Git LFS Hub (github:VK-Amogh/TRACE){RESET} {GREEN}│{RESET}")
|
|
248
|
+
print(f" {GREEN}└{'─' * 74}┘{RESET}\n")
|
|
249
|
+
|
|
250
|
+
if not interactive or not sys.stdin.isatty():
|
|
251
|
+
print(f" {ORANGE}[!] Non-interactive environment detected. Run 'trace setup-models' to install.{RESET}\n")
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
ans = input(f" {ORANGE}›{RESET} {BOLD}Download and configure fine-tuned neural models now? (y/n) [y]:{RESET} ").strip().lower()
|
|
256
|
+
if ans in ("", "y", "yes"):
|
|
257
|
+
ensure_all_models()
|
|
258
|
+
print(f" {GREEN}✓ TRACE Neural Intelligence Engine initialized successfully.{RESET}\n")
|
|
259
|
+
return True
|
|
260
|
+
else:
|
|
261
|
+
print(f"\n {ORANGE}[!] TRACE setup cancelled.{RESET}")
|
|
262
|
+
print(f" {DIM}Fine-tuned neural intelligence models are required to perform security audits.{RESET}")
|
|
263
|
+
print(f" {DIM}Run 'trace setup-models' at any time to configure the engine.{RESET}\n")
|
|
264
|
+
return False
|
|
265
|
+
except (KeyboardInterrupt, EOFError):
|
|
266
|
+
print(f"\n {ORANGE}[!] Cancelled.{RESET}\n")
|
|
267
|
+
return False
|
|
Binary file
|
|
@@ -57,10 +57,14 @@ class LayaDecisionEngine:
|
|
|
57
57
|
def _resolve_laya_dir(self) -> Optional[Path]:
|
|
58
58
|
"""Resolves Laya model directory across project root, package dir, and home."""
|
|
59
59
|
candidates = [
|
|
60
|
+
Path.home() / ".trace/models/laya-finetuned",
|
|
61
|
+
Path("models/laya-finetuned"),
|
|
60
62
|
Path(".trace/models/laya-finetuned"),
|
|
63
|
+
Path(__file__).resolve().parents[4] / "models/laya-finetuned",
|
|
61
64
|
Path(__file__).resolve().parents[4] / ".trace/models/laya-finetuned",
|
|
62
65
|
Path(__file__).resolve().parents[2] / "models/laya-finetuned",
|
|
63
|
-
Path
|
|
66
|
+
Path("D:/Startup/TRACE/models/laya-finetuned"),
|
|
67
|
+
Path("D:/Startup/TRACE/.trace/models/laya-finetuned"),
|
|
64
68
|
]
|
|
65
69
|
repo_root = Path(__file__).resolve().parents[4]
|
|
66
70
|
for p in candidates:
|
|
@@ -76,6 +80,13 @@ class LayaDecisionEngine:
|
|
|
76
80
|
return p.resolve()
|
|
77
81
|
if onnx_file.exists() and onnx_file.stat().st_size >= 10000:
|
|
78
82
|
return p.resolve()
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
from trace_engine.intelligence.downloader import ensure_model
|
|
86
|
+
return ensure_model("laya-finetuned")
|
|
87
|
+
except Exception as e:
|
|
88
|
+
logger.debug(f"Automatic Laya download deferred: {e}")
|
|
89
|
+
|
|
79
90
|
return None
|
|
80
91
|
|
|
81
92
|
def _load_onnx_model(self) -> None:
|
|
@@ -125,8 +136,11 @@ class LayaDecisionEngine:
|
|
|
125
136
|
def _load_agent(self) -> None:
|
|
126
137
|
"""Attempt to load Laya in-process model."""
|
|
127
138
|
try:
|
|
128
|
-
import
|
|
129
|
-
|
|
139
|
+
import warnings
|
|
140
|
+
with warnings.catch_warnings():
|
|
141
|
+
warnings.filterwarnings("ignore", category=RuntimeWarning)
|
|
142
|
+
import laya
|
|
143
|
+
self._agent = laya.load(self.model_id)
|
|
130
144
|
logger.info(f"Laya agent loaded: {self.model_id}")
|
|
131
145
|
except Exception as e:
|
|
132
146
|
logger.debug(f"Laya in-process loading: {e}. Using calibrated fallback.")
|
|
Binary file
|
|
@@ -34,12 +34,17 @@ class SecureBERTClassifier:
|
|
|
34
34
|
cache_path: Optional[Path] = None,
|
|
35
35
|
):
|
|
36
36
|
candidates = [
|
|
37
|
+
Path.home() / ".trace/models/securebert-finetuned",
|
|
38
|
+
Path("models/securebert-finetuned"),
|
|
37
39
|
Path(".trace/models/securebert-finetuned"),
|
|
40
|
+
Path(__file__).resolve().parents[4] / "models/securebert-finetuned",
|
|
38
41
|
Path(__file__).resolve().parents[4] / ".trace/models/securebert-finetuned",
|
|
39
42
|
Path(__file__).resolve().parents[2] / "models/securebert-finetuned",
|
|
40
|
-
Path
|
|
43
|
+
Path("D:/Startup/TRACE/models/securebert-finetuned"),
|
|
44
|
+
Path("D:/Startup/TRACE/.trace/models/securebert-finetuned"),
|
|
41
45
|
]
|
|
42
|
-
default_model =
|
|
46
|
+
default_model = None
|
|
47
|
+
repo_root = Path(__file__).resolve().parents[4]
|
|
43
48
|
for cand in candidates:
|
|
44
49
|
weight_file = cand / "model.safetensors"
|
|
45
50
|
if weight_file.exists():
|
|
@@ -53,6 +58,15 @@ class SecureBERTClassifier:
|
|
|
53
58
|
default_model = str(cand.resolve())
|
|
54
59
|
break
|
|
55
60
|
|
|
61
|
+
if not default_model:
|
|
62
|
+
try:
|
|
63
|
+
from trace_engine.intelligence.downloader import ensure_model
|
|
64
|
+
model_dir = ensure_model("securebert-finetuned")
|
|
65
|
+
default_model = str(model_dir.resolve())
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.debug(f"Automatic model download deferred: {e}")
|
|
68
|
+
default_model = "ehsanaghaei/SecureBERT"
|
|
69
|
+
|
|
56
70
|
self.model_name = model_name or default_model
|
|
57
71
|
cache_file = cache_path or Path(".trace/cache/securebert_cache.json")
|
|
58
72
|
self.cache = SecureBERTCache(cache_file)
|
|
@@ -68,6 +82,8 @@ class SecureBERTClassifier:
|
|
|
68
82
|
self._initialized = True
|
|
69
83
|
try:
|
|
70
84
|
import torch
|
|
85
|
+
import transformers
|
|
86
|
+
transformers.logging.set_verbosity_error()
|
|
71
87
|
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
|
72
88
|
|
|
73
89
|
self._device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
@@ -278,6 +278,10 @@ def run_interactive_story() -> None:
|
|
|
278
278
|
"""Executes the complete interactive, step-by-step security audit story."""
|
|
279
279
|
render_big_banner(animated=True)
|
|
280
280
|
|
|
281
|
+
from trace_engine.intelligence.downloader import prompt_and_bootstrap_models
|
|
282
|
+
if not prompt_and_bootstrap_models(interactive=True):
|
|
283
|
+
return
|
|
284
|
+
|
|
281
285
|
console.print(f" [{WHITE}]Welcome, Security Operator. TRACE stands ready to audit, verify, and heal codebases.[/{WHITE}]")
|
|
282
286
|
console.print(f" [{DIM_WHITE}]Navigate using the options below (type 'exit' or 'q' at any prompt to quit).[/{DIM_WHITE}]\n")
|
|
283
287
|
|
|
@@ -288,26 +292,33 @@ def run_interactive_story() -> None:
|
|
|
288
292
|
console.print(f" [{ORANGE}][1][/{ORANGE}] [{BOLD_WHITE}]Full Security Audit[/{BOLD_WHITE}] [{GREEN}]★ RECOMMENDED[/{GREEN}] [{DIM_WHITE}]─ Multi-language AST, APM graph, & Neural AI[/{DIM_WHITE}]")
|
|
289
293
|
console.print(f" [{ORANGE}][2][/{ORANGE}] [{BOLD_WHITE}]Targeted Vulnerability Probes[/{BOLD_WHITE}] [{DIM_WHITE}]─ Select specific testpacks (BOLA, SQLi, SSRF, BFLA)[/{DIM_WHITE}]")
|
|
290
294
|
console.print(f" [{ORANGE}][3][/{ORANGE}] [{BOLD_WHITE}]Autonomous AST Self-Healing[/{BOLD_WHITE}] [{DIM_WHITE}]─ Surgical refactoring with live rollback verification[/{DIM_WHITE}]")
|
|
291
|
-
console.print(f" [{ORANGE}][4][/{ORANGE}] [{BOLD_WHITE}]
|
|
292
|
-
console.print(f" [{ORANGE}][5][/{ORANGE}] [{BOLD_WHITE}]
|
|
293
|
-
console.print(f" [{ORANGE}][6][/{ORANGE}] [{BOLD_WHITE}]
|
|
295
|
+
console.print(f" [{ORANGE}][4][/{ORANGE}] [{BOLD_WHITE}]Start Claude Code Local MCP Server[/{BOLD_WHITE}] [{GREEN}]★ LOCAL MCP[/{GREEN}] [{DIM_WHITE}]─ Run HTTP/SSE bridge for Claude Code chat[/{DIM_WHITE}]")
|
|
296
|
+
console.print(f" [{ORANGE}][5][/{ORANGE}] [{BOLD_WHITE}]How TRACE Works & Architecture Guide[/{BOLD_WHITE}] [{DIM_WHITE}]─ Interactive architectural diagram & neural models[/{DIM_WHITE}]")
|
|
297
|
+
console.print(f" [{ORANGE}][6][/{ORANGE}] [{BOLD_WHITE}]Environment Diagnostics (Doctor)[/{BOLD_WHITE}] [{DIM_WHITE}]─ Inspect local security tools and dependencies[/{DIM_WHITE}]")
|
|
298
|
+
console.print(f" [{ORANGE}][7][/{ORANGE}] [{BOLD_WHITE}]Exit[/{BOLD_WHITE}]\n")
|
|
294
299
|
|
|
295
300
|
choice = ask_input(
|
|
296
301
|
f" [{ORANGE}]›[/{ORANGE}] [{BOLD_WHITE}]Choose option[/{BOLD_WHITE}]",
|
|
297
|
-
choices=["1", "2", "3", "4", "5", "6"],
|
|
302
|
+
choices=["1", "2", "3", "4", "5", "6", "7"],
|
|
298
303
|
default="1",
|
|
299
304
|
show_default=True,
|
|
300
305
|
)
|
|
301
306
|
|
|
302
|
-
if choice == "
|
|
307
|
+
if choice == "7":
|
|
303
308
|
console.print(f"\n [{GREEN}]✓ TRACE session closed. Happy hacking![/{GREEN}]\n")
|
|
304
309
|
return
|
|
305
310
|
|
|
306
311
|
if choice == "4":
|
|
307
|
-
|
|
312
|
+
from trace_engine.mcp.server import TraceMCPServer
|
|
313
|
+
mcp_obj = TraceMCPServer()
|
|
314
|
+
mcp_obj.run_sse_server(host="127.0.0.1", port=8765)
|
|
308
315
|
return
|
|
309
316
|
|
|
310
317
|
if choice == "5":
|
|
318
|
+
run_architecture_guide()
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
if choice == "6":
|
|
311
322
|
display_story_header(2, 2, "Environment Diagnostics", "Auditing local dependencies and runtime requirements")
|
|
312
323
|
from trace_engine.doctor import run_doctor
|
|
313
324
|
report = run_doctor(Path("."))
|
|
Binary file
|
|
@@ -396,3 +396,174 @@ class TraceMCPServer:
|
|
|
396
396
|
}
|
|
397
397
|
sys.stdout.write(json.dumps(err_resp) + "\n")
|
|
398
398
|
sys.stdout.flush()
|
|
399
|
+
|
|
400
|
+
def run_sse_server(self, host: str = "127.0.0.1", port: int = 8765):
|
|
401
|
+
"""Run an MCP HTTP/SSE transport server for Claude Code and local AI coding agents."""
|
|
402
|
+
import uuid
|
|
403
|
+
import queue
|
|
404
|
+
import urllib.parse
|
|
405
|
+
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
|
|
406
|
+
|
|
407
|
+
sessions: Dict[str, queue.Queue] = {}
|
|
408
|
+
server_instance = self
|
|
409
|
+
|
|
410
|
+
class MCPHandler(BaseHTTPRequestHandler):
|
|
411
|
+
def log_message(self, format, *args):
|
|
412
|
+
pass # Suppress request clutter in terminal
|
|
413
|
+
|
|
414
|
+
def do_OPTIONS(self):
|
|
415
|
+
self.send_response(200)
|
|
416
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
417
|
+
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
|
418
|
+
self.send_header("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
|
419
|
+
self.end_headers()
|
|
420
|
+
|
|
421
|
+
def do_GET(self):
|
|
422
|
+
parsed = urllib.parse.urlparse(self.path)
|
|
423
|
+
if parsed.path == "/sse":
|
|
424
|
+
session_id = uuid.uuid4().hex
|
|
425
|
+
msg_queue: queue.Queue = queue.Queue()
|
|
426
|
+
sessions[session_id] = msg_queue
|
|
427
|
+
|
|
428
|
+
self.send_response(200)
|
|
429
|
+
self.send_header("Content-Type", "text/event-stream")
|
|
430
|
+
self.send_header("Cache-Control", "no-cache")
|
|
431
|
+
self.send_header("Connection", "keep-alive")
|
|
432
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
433
|
+
self.end_headers()
|
|
434
|
+
|
|
435
|
+
# Emit endpoint event as defined in MCP SSE transport specification
|
|
436
|
+
endpoint_msg = f"event: endpoint\r\ndata: /message?sessionId={session_id}\r\n\r\n"
|
|
437
|
+
self.wfile.write(endpoint_msg.encode("utf-8"))
|
|
438
|
+
self.wfile.flush()
|
|
439
|
+
|
|
440
|
+
try:
|
|
441
|
+
while server_instance.running:
|
|
442
|
+
try:
|
|
443
|
+
msg = msg_queue.get(timeout=1.0)
|
|
444
|
+
if msg is None:
|
|
445
|
+
break
|
|
446
|
+
event_str = f"event: message\r\ndata: {json.dumps(msg)}\r\n\r\n"
|
|
447
|
+
self.wfile.write(event_str.encode("utf-8"))
|
|
448
|
+
self.wfile.flush()
|
|
449
|
+
except queue.Empty:
|
|
450
|
+
self.wfile.write(b": keepalive\r\n\r\n")
|
|
451
|
+
self.wfile.flush()
|
|
452
|
+
except (BrokenPipeError, ConnectionResetError):
|
|
453
|
+
pass
|
|
454
|
+
finally:
|
|
455
|
+
sessions.pop(session_id, None)
|
|
456
|
+
|
|
457
|
+
elif parsed.path in ("/health", "/status"):
|
|
458
|
+
self.send_response(200)
|
|
459
|
+
self.send_header("Content-Type", "application/json")
|
|
460
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
461
|
+
self.end_headers()
|
|
462
|
+
status_info = {
|
|
463
|
+
"status": "online",
|
|
464
|
+
"server": "TRACE MCP Server v2.1",
|
|
465
|
+
"transport": "sse",
|
|
466
|
+
"sse_url": f"http://{host}:{port}/sse",
|
|
467
|
+
"active_sessions": len(sessions),
|
|
468
|
+
"tools": [t["name"] for t in TRACE_TOOLS],
|
|
469
|
+
}
|
|
470
|
+
self.wfile.write(json.dumps(status_info, indent=2).encode("utf-8"))
|
|
471
|
+
|
|
472
|
+
else:
|
|
473
|
+
self.send_response(200)
|
|
474
|
+
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
475
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
476
|
+
self.end_headers()
|
|
477
|
+
html = f"""<!DOCTYPE html>
|
|
478
|
+
<html>
|
|
479
|
+
<head>
|
|
480
|
+
<meta charset="utf-8">
|
|
481
|
+
<title>TRACE Local MCP Server</title>
|
|
482
|
+
<style>
|
|
483
|
+
body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0b0f17; color: #f8fafc; padding: 40px; margin: 0; }}
|
|
484
|
+
.container {{ max-width: 680px; margin: 0 auto; background: #131b2e; border: 1px solid #10b981; border-radius: 12px; padding: 32px; box-shadow: 0 10px 25px rgba(0,0,0,0.5); }}
|
|
485
|
+
h1 {{ color: #10b981; margin-top: 0; display: flex; align-items: center; gap: 10px; font-size: 24px; }}
|
|
486
|
+
.badge {{ background: #10b981; color: #0b0f17; padding: 3px 8px; border-radius: 4px; font-size: 12px; font-weight: bold; }}
|
|
487
|
+
p {{ color: #94a3b8; font-size: 14px; line-height: 1.6; }}
|
|
488
|
+
.cmd-box {{ background: #070a12; border: 1px solid #1e293b; border-radius: 6px; padding: 12px 16px; margin: 12px 0; font-family: monospace; color: #38bdf8; font-size: 13px; word-break: break-all; }}
|
|
489
|
+
.section-title {{ color: #ffffff; font-size: 14px; font-weight: bold; margin-top: 20px; }}
|
|
490
|
+
</style>
|
|
491
|
+
</head>
|
|
492
|
+
<body>
|
|
493
|
+
<div class="container">
|
|
494
|
+
<h1>TRACE MCP Server <span class="badge">ONLINE</span></h1>
|
|
495
|
+
<p>Local Model Context Protocol (MCP) server listening for Claude Code and AI coding agents.</p>
|
|
496
|
+
|
|
497
|
+
<div class="section-title">Local MCP Endpoint Link:</div>
|
|
498
|
+
<div class="cmd-box">http://{host}:{port}/sse</div>
|
|
499
|
+
|
|
500
|
+
<div class="section-title">Connect via Claude Code CLI:</div>
|
|
501
|
+
<div class="cmd-box">claude mcp add --transport sse trace http://{host}:{port}/sse</div>
|
|
502
|
+
|
|
503
|
+
<div class="section-title">Connect in Claude Chat:</div>
|
|
504
|
+
<p>Paste the local MCP link into your Claude chat to grant autonomous AST scanning and verification powers:</p>
|
|
505
|
+
<div class="cmd-box">http://{host}:{port}/sse</div>
|
|
506
|
+
</div>
|
|
507
|
+
</body>
|
|
508
|
+
</html>"""
|
|
509
|
+
self.wfile.write(html.encode("utf-8"))
|
|
510
|
+
|
|
511
|
+
def do_POST(self):
|
|
512
|
+
parsed = urllib.parse.urlparse(self.path)
|
|
513
|
+
if parsed.path.startswith("/message"):
|
|
514
|
+
query = urllib.parse.parse_qs(parsed.query)
|
|
515
|
+
session_id = query.get("sessionId", [None])[0]
|
|
516
|
+
|
|
517
|
+
content_len = int(self.headers.get("Content-Length", 0))
|
|
518
|
+
body = self.rfile.read(content_len).decode("utf-8")
|
|
519
|
+
try:
|
|
520
|
+
req = json.loads(body)
|
|
521
|
+
resp = server_instance.handle_request(req)
|
|
522
|
+
except Exception as e:
|
|
523
|
+
resp = {
|
|
524
|
+
"jsonrpc": "2.0",
|
|
525
|
+
"id": None,
|
|
526
|
+
"error": {"code": -32603, "message": str(e)},
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if session_id and session_id in sessions and resp:
|
|
530
|
+
sessions[session_id].put(resp)
|
|
531
|
+
|
|
532
|
+
self.send_response(200)
|
|
533
|
+
self.send_header("Content-Type", "application/json")
|
|
534
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
535
|
+
self.end_headers()
|
|
536
|
+
self.wfile.write(json.dumps(resp or {"jsonrpc": "2.0", "result": None}).encode("utf-8"))
|
|
537
|
+
else:
|
|
538
|
+
self.send_response(404)
|
|
539
|
+
self.end_headers()
|
|
540
|
+
|
|
541
|
+
GREEN = "\033[38;2;16;185;129m"
|
|
542
|
+
CYAN = "\033[38;2;56;189;248m"
|
|
543
|
+
ORANGE = "\033[38;2;255;158;59m"
|
|
544
|
+
BOLD = "\033[1m"
|
|
545
|
+
DIM = "\033[2m"
|
|
546
|
+
RESET = "\033[0m"
|
|
547
|
+
|
|
548
|
+
print(f"\n {GREEN}┌{'─' * 74}┐{RESET}")
|
|
549
|
+
print(f" {GREEN}│{RESET} {BOLD}TRACE Local MCP Server (Model Context Protocol){RESET} {GREEN}│{RESET}")
|
|
550
|
+
print(f" {GREEN}├{'─' * 74}┤{RESET}")
|
|
551
|
+
print(f" {GREEN}│{RESET} {BOLD}Local MCP Link :{RESET} {CYAN}http://{host}:{port}/sse{RESET}{' ' * max(0, 39 - len(f'http://{host}:{port}/sse'))}{GREEN}│{RESET}")
|
|
552
|
+
print(f" {GREEN}│{RESET} {BOLD}Web Dashboard :{RESET} {CYAN}http://{host}:{port}/{RESET}{' ' * max(0, 42 - len(f'http://{host}:{port}/'))}{GREEN}│{RESET}")
|
|
553
|
+
print(f" {GREEN}│{RESET} {BOLD}Status :{RESET} {GREEN}ONLINE{RESET} (Listening for Claude Code & AI Coding Agents) {GREEN}│{RESET}")
|
|
554
|
+
print(f" {GREEN}│{RESET} {GREEN}│{RESET}")
|
|
555
|
+
print(f" {GREEN}│{RESET} {BOLD}Connect in Claude Code CLI:{RESET} {GREEN}│{RESET}")
|
|
556
|
+
print(f" {GREEN}│{RESET} {ORANGE}›{RESET} {BOLD}claude mcp add --transport sse trace http://{host}:{port}/sse{RESET} {GREEN}│{RESET}")
|
|
557
|
+
print(f" {GREEN}│{RESET} {GREEN}│{RESET}")
|
|
558
|
+
print(f" {GREEN}│{RESET} {BOLD}Or paste this link directly in your Claude Code chat:{RESET} {GREEN}│{RESET}")
|
|
559
|
+
print(f" {GREEN}│{RESET} {CYAN}http://{host}:{port}/sse{RESET}{' ' * max(0, 49 - len(f'http://{host}:{port}/sse'))}{GREEN}│{RESET}")
|
|
560
|
+
print(f" {GREEN}└{'─' * 74}┘{RESET}\n")
|
|
561
|
+
print(f" {DIM}Press Ctrl+C to terminate the local MCP server.{RESET}\n")
|
|
562
|
+
|
|
563
|
+
httpd = ThreadingHTTPServer((host, port), MCPHandler)
|
|
564
|
+
try:
|
|
565
|
+
httpd.serve_forever()
|
|
566
|
+
except KeyboardInterrupt:
|
|
567
|
+
print(f"\n {ORANGE}[TRACE] MCP Server stopped.{RESET}\n")
|
|
568
|
+
finally:
|
|
569
|
+
httpd.server_close()
|