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 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', 'github:VK-Amogh/TRACE', 'mcp']
55
- : ['-y', 'github:VK-Amogh/TRACE', 'mcp'],
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 github:VK-Amogh/TRACE mcp\x1b[0m\n');
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",
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "trace-sec"
7
- version = "1.0.0"
7
+ version = "2.1.5"
8
8
  description = "Threat Reconnaissance & Attack-path Correlation Engine"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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
- """Start the TRACE Model Context Protocol (MCP) server over stdio for Claude Code / Codex."""
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
- server = TraceMCPServer()
832
- server.run_stdio()
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 (Section 64)."""
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]Connect to Claude Code:[/bold white]")
847
- console.print(" [green]claude mcp add trace -- cmd /c npx -y @trace-security/trace mcp[/green]")
848
- console.print("\n[bold white]Connect to Codex / Cursor / Antigravity:[/bold white]")
849
- console.print(" [dim]The generated .mcp.json is already recognized by compatible IDEs and CLI agents.[/dim]\n")
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
@@ -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.home() / ".trace/models/laya-finetuned",
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 laya
129
- self._agent = laya.load(self.model_id)
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.")
@@ -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.home() / ".trace/models/securebert-finetuned",
43
+ Path("D:/Startup/TRACE/models/securebert-finetuned"),
44
+ Path("D:/Startup/TRACE/.trace/models/securebert-finetuned"),
41
45
  ]
42
- default_model = "ehsanaghaei/SecureBERT"
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}]How TRACE Works & Architecture Guide[/{BOLD_WHITE}] [{GREEN}]★ 5 Q&A[/{GREEN}] [{DIM_WHITE}]─ Interactive architectural diagram & neural models[/{DIM_WHITE}]")
292
- console.print(f" [{ORANGE}][5][/{ORANGE}] [{BOLD_WHITE}]Environment Diagnostics (Doctor)[/{BOLD_WHITE}] [{DIM_WHITE}]─ Inspect local security tools and dependencies[/{DIM_WHITE}]")
293
- console.print(f" [{ORANGE}][6][/{ORANGE}] [{BOLD_WHITE}]Exit[/{BOLD_WHITE}]\n")
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 == "6":
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
- run_architecture_guide()
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("."))
@@ -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()