codeshield-ai 0.1.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.
- codeshield/__init__.py +62 -0
- codeshield/api_server.py +438 -0
- codeshield/cli.py +48 -0
- codeshield/contextvault/__init__.py +1 -0
- codeshield/contextvault/capture.py +174 -0
- codeshield/contextvault/restore.py +115 -0
- codeshield/mcp/__init__.py +1 -0
- codeshield/mcp/hooks.py +65 -0
- codeshield/mcp/server.py +319 -0
- codeshield/styleforge/__init__.py +1 -0
- codeshield/styleforge/corrector.py +298 -0
- codeshield/trustgate/__init__.py +1 -0
- codeshield/trustgate/checker.py +384 -0
- codeshield/trustgate/sandbox.py +101 -0
- codeshield/utils/__init__.py +9 -0
- codeshield/utils/daytona.py +233 -0
- codeshield/utils/leanmcp.py +258 -0
- codeshield/utils/llm.py +423 -0
- codeshield/utils/metrics.py +543 -0
- codeshield/utils/token_optimizer.py +605 -0
- codeshield_ai-0.1.0.dist-info/METADATA +565 -0
- codeshield_ai-0.1.0.dist-info/RECORD +24 -0
- codeshield_ai-0.1.0.dist-info/WHEEL +4 -0
- codeshield_ai-0.1.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ContextVault Capture - Save coding context
|
|
3
|
+
|
|
4
|
+
Saves your mental state like a game save file:
|
|
5
|
+
- Open files
|
|
6
|
+
- Cursor position
|
|
7
|
+
- Recent changes
|
|
8
|
+
- Timestamps
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import sqlite3
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from dataclasses import dataclass, asdict
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
DB_PATH = Path.home() / ".codeshield" / "context_vault.sqlite"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class CodingContext:
|
|
24
|
+
"""A saved coding context"""
|
|
25
|
+
name: str
|
|
26
|
+
created_at: str
|
|
27
|
+
files: list[str]
|
|
28
|
+
cursor: Optional[dict] = None
|
|
29
|
+
notes: Optional[str] = None
|
|
30
|
+
last_edited_file: Optional[str] = None
|
|
31
|
+
|
|
32
|
+
def to_dict(self) -> dict:
|
|
33
|
+
return asdict(self)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _ensure_db():
|
|
37
|
+
"""Ensure database exists and has schema"""
|
|
38
|
+
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
|
|
40
|
+
conn = sqlite3.connect(str(DB_PATH))
|
|
41
|
+
cursor = conn.cursor()
|
|
42
|
+
|
|
43
|
+
cursor.execute("""
|
|
44
|
+
CREATE TABLE IF NOT EXISTS contexts (
|
|
45
|
+
name TEXT PRIMARY KEY,
|
|
46
|
+
created_at TEXT NOT NULL,
|
|
47
|
+
files TEXT NOT NULL,
|
|
48
|
+
cursor TEXT,
|
|
49
|
+
notes TEXT,
|
|
50
|
+
last_edited_file TEXT
|
|
51
|
+
)
|
|
52
|
+
""")
|
|
53
|
+
|
|
54
|
+
conn.commit()
|
|
55
|
+
conn.close()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def save_context(
|
|
59
|
+
name: str,
|
|
60
|
+
files: list[str] = None,
|
|
61
|
+
cursor: dict = None,
|
|
62
|
+
notes: str = None,
|
|
63
|
+
last_edited_file: str = None,
|
|
64
|
+
) -> dict:
|
|
65
|
+
"""
|
|
66
|
+
Save coding context.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
name: Unique name for this context
|
|
70
|
+
files: List of open file paths
|
|
71
|
+
cursor: Cursor position {file, line, column}
|
|
72
|
+
notes: Optional notes about current work
|
|
73
|
+
last_edited_file: Last file that was edited
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Dict with save confirmation
|
|
77
|
+
"""
|
|
78
|
+
_ensure_db()
|
|
79
|
+
|
|
80
|
+
context = CodingContext(
|
|
81
|
+
name=name,
|
|
82
|
+
created_at=datetime.now().isoformat(),
|
|
83
|
+
files=files or [],
|
|
84
|
+
cursor=cursor,
|
|
85
|
+
notes=notes,
|
|
86
|
+
last_edited_file=last_edited_file,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
conn = sqlite3.connect(str(DB_PATH))
|
|
90
|
+
cursor_db = conn.cursor()
|
|
91
|
+
|
|
92
|
+
cursor_db.execute("""
|
|
93
|
+
INSERT OR REPLACE INTO contexts
|
|
94
|
+
(name, created_at, files, cursor, notes, last_edited_file)
|
|
95
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
96
|
+
""", (
|
|
97
|
+
context.name,
|
|
98
|
+
context.created_at,
|
|
99
|
+
json.dumps(context.files),
|
|
100
|
+
json.dumps(context.cursor) if context.cursor else None,
|
|
101
|
+
context.notes,
|
|
102
|
+
context.last_edited_file,
|
|
103
|
+
))
|
|
104
|
+
|
|
105
|
+
conn.commit()
|
|
106
|
+
conn.close()
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
"success": True,
|
|
110
|
+
"message": f"Context '{name}' saved at {context.created_at}",
|
|
111
|
+
"context": context.to_dict(),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def list_contexts() -> list[dict]:
|
|
116
|
+
"""List all saved contexts"""
|
|
117
|
+
_ensure_db()
|
|
118
|
+
|
|
119
|
+
conn = sqlite3.connect(str(DB_PATH))
|
|
120
|
+
cursor = conn.cursor()
|
|
121
|
+
|
|
122
|
+
cursor.execute("SELECT name, created_at, notes FROM contexts ORDER BY created_at DESC")
|
|
123
|
+
rows = cursor.fetchall()
|
|
124
|
+
|
|
125
|
+
conn.close()
|
|
126
|
+
|
|
127
|
+
return [
|
|
128
|
+
{"name": row[0], "created_at": row[1], "notes": row[2]}
|
|
129
|
+
for row in rows
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_context(name: str) -> Optional[CodingContext]:
|
|
134
|
+
"""Get a specific context by name"""
|
|
135
|
+
_ensure_db()
|
|
136
|
+
|
|
137
|
+
conn = sqlite3.connect(str(DB_PATH))
|
|
138
|
+
cursor = conn.cursor()
|
|
139
|
+
|
|
140
|
+
cursor.execute("""
|
|
141
|
+
SELECT name, created_at, files, cursor, notes, last_edited_file
|
|
142
|
+
FROM contexts WHERE name = ?
|
|
143
|
+
""", (name,))
|
|
144
|
+
|
|
145
|
+
row = cursor.fetchone()
|
|
146
|
+
conn.close()
|
|
147
|
+
|
|
148
|
+
if not row:
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
return CodingContext(
|
|
152
|
+
name=row[0],
|
|
153
|
+
created_at=row[1],
|
|
154
|
+
files=json.loads(row[2]),
|
|
155
|
+
cursor=json.loads(row[3]) if row[3] else None,
|
|
156
|
+
notes=row[4],
|
|
157
|
+
last_edited_file=row[5],
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def delete_context(name: str) -> bool:
|
|
162
|
+
"""Delete a context by name"""
|
|
163
|
+
_ensure_db()
|
|
164
|
+
|
|
165
|
+
conn = sqlite3.connect(str(DB_PATH))
|
|
166
|
+
cursor = conn.cursor()
|
|
167
|
+
|
|
168
|
+
cursor.execute("DELETE FROM contexts WHERE name = ?", (name,))
|
|
169
|
+
deleted = cursor.rowcount > 0
|
|
170
|
+
|
|
171
|
+
conn.commit()
|
|
172
|
+
conn.close()
|
|
173
|
+
|
|
174
|
+
return deleted
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ContextVault Restore - Restore coding context
|
|
3
|
+
|
|
4
|
+
Restores your mental state with AI briefing:
|
|
5
|
+
- Reopens files
|
|
6
|
+
- Returns AI summary of where you left off
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
from codeshield.contextvault.capture import get_context, CodingContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def restore_context(name: str) -> dict:
|
|
16
|
+
"""
|
|
17
|
+
Restore a previously saved coding context.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
name: Name of context to restore
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Dict with context info and AI briefing
|
|
24
|
+
"""
|
|
25
|
+
context = get_context(name)
|
|
26
|
+
|
|
27
|
+
if not context:
|
|
28
|
+
return {
|
|
29
|
+
"success": False,
|
|
30
|
+
"error": f"Context '{name}' not found",
|
|
31
|
+
"available_contexts": [], # Could list available ones
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Generate AI briefing
|
|
35
|
+
briefing = generate_briefing(context)
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
"success": True,
|
|
39
|
+
"context": context.to_dict(),
|
|
40
|
+
"briefing": briefing,
|
|
41
|
+
"files_to_open": context.files,
|
|
42
|
+
"cursor_position": context.cursor,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def generate_briefing(context: CodingContext) -> str:
|
|
47
|
+
"""
|
|
48
|
+
Generate a natural language briefing about the context.
|
|
49
|
+
|
|
50
|
+
Uses LLM if available, otherwise generates basic briefing.
|
|
51
|
+
"""
|
|
52
|
+
# Calculate time since context was saved
|
|
53
|
+
try:
|
|
54
|
+
saved_time = datetime.fromisoformat(context.created_at)
|
|
55
|
+
time_diff = datetime.now() - saved_time
|
|
56
|
+
|
|
57
|
+
if time_diff.days > 0:
|
|
58
|
+
time_str = f"{time_diff.days} days ago"
|
|
59
|
+
elif time_diff.seconds > 3600:
|
|
60
|
+
hours = time_diff.seconds // 3600
|
|
61
|
+
time_str = f"{hours} hours ago"
|
|
62
|
+
else:
|
|
63
|
+
minutes = time_diff.seconds // 60
|
|
64
|
+
time_str = f"{minutes} minutes ago"
|
|
65
|
+
except:
|
|
66
|
+
time_str = "unknown time ago"
|
|
67
|
+
|
|
68
|
+
# Try to use LLM for briefing
|
|
69
|
+
try:
|
|
70
|
+
from codeshield.utils.llm import get_llm_client
|
|
71
|
+
|
|
72
|
+
client = get_llm_client()
|
|
73
|
+
llm_briefing = client.generate_context_briefing({
|
|
74
|
+
"files": context.files,
|
|
75
|
+
"last_edited": context.last_edited_file,
|
|
76
|
+
"cursor": context.cursor,
|
|
77
|
+
"notes": context.notes,
|
|
78
|
+
"time_ago": time_str,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
if llm_briefing:
|
|
82
|
+
return llm_briefing
|
|
83
|
+
except:
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
# Fallback to basic briefing
|
|
87
|
+
briefing_parts = []
|
|
88
|
+
|
|
89
|
+
if context.files:
|
|
90
|
+
file_count = len(context.files)
|
|
91
|
+
last_file = context.files[-1].split('/')[-1].split('\\')[-1] if context.files else "unknown"
|
|
92
|
+
briefing_parts.append(f"You had {file_count} files open. Last working on '{last_file}'.")
|
|
93
|
+
|
|
94
|
+
if context.cursor:
|
|
95
|
+
line = context.cursor.get('line', '?')
|
|
96
|
+
briefing_parts.append(f"Cursor was at line {line}.")
|
|
97
|
+
|
|
98
|
+
if context.notes:
|
|
99
|
+
briefing_parts.append(f"Your notes: {context.notes}")
|
|
100
|
+
|
|
101
|
+
briefing_parts.append(f"This was saved {time_str}.")
|
|
102
|
+
|
|
103
|
+
return " ".join(briefing_parts)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def quick_restore() -> Optional[dict]:
|
|
107
|
+
"""Quick restore the most recent context"""
|
|
108
|
+
from codeshield.contextvault.capture import list_contexts
|
|
109
|
+
|
|
110
|
+
contexts = list_contexts()
|
|
111
|
+
if not contexts:
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
most_recent = contexts[0]["name"]
|
|
115
|
+
return restore_context(most_recent)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""MCP Server module"""
|
codeshield/mcp/hooks.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Code Interception Hooks
|
|
3
|
+
|
|
4
|
+
This module provides hooks that can be integrated into:
|
|
5
|
+
1. Pre-commit hooks (Git)
|
|
6
|
+
2. CI/CD pipelines
|
|
7
|
+
3. IDE save actions (if supported)
|
|
8
|
+
|
|
9
|
+
It serves as the entry point for automated code verification.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from codeshield.trustgate.checker import verify_code, verify_file
|
|
16
|
+
|
|
17
|
+
def pre_commit_hook(files: list[str]) -> bool:
|
|
18
|
+
"""
|
|
19
|
+
Git pre-commit hook: Verify all changed files.
|
|
20
|
+
Returns True if verification passes, False otherwise.
|
|
21
|
+
"""
|
|
22
|
+
print("🛡️ CodeShield Verification Hook Running...")
|
|
23
|
+
|
|
24
|
+
all_passed = True
|
|
25
|
+
|
|
26
|
+
for file_path in files:
|
|
27
|
+
if not file_path.endswith('.py'):
|
|
28
|
+
continue
|
|
29
|
+
|
|
30
|
+
print(f"Checking {file_path}...")
|
|
31
|
+
|
|
32
|
+
# Verify file
|
|
33
|
+
try:
|
|
34
|
+
result = verify_file(file_path)
|
|
35
|
+
|
|
36
|
+
if not result.is_valid:
|
|
37
|
+
all_passed = False
|
|
38
|
+
print(f"❌ Issues in {file_path}:")
|
|
39
|
+
for issue in result.issues:
|
|
40
|
+
print(f" [{issue.severity}] {issue.message}")
|
|
41
|
+
else:
|
|
42
|
+
print(f"✅ {file_path} passed ({result.confidence_score:.0%} confidence)")
|
|
43
|
+
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print(f"⚠️ Error checking {file_path}: {e}")
|
|
46
|
+
all_passed = False
|
|
47
|
+
|
|
48
|
+
return all_passed
|
|
49
|
+
|
|
50
|
+
def code_interceptor(code: str, source: str = "unknown") -> dict:
|
|
51
|
+
"""
|
|
52
|
+
General purpose interceptor for any code snippet.
|
|
53
|
+
Useful for IDE plugins or content scripts to verify code on the fly.
|
|
54
|
+
"""
|
|
55
|
+
result = verify_code(code)
|
|
56
|
+
return result.to_dict()
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
# Example CLI usage: python -m codeshield.mcp.hooks file1.py file2.py
|
|
60
|
+
if len(sys.argv) > 1:
|
|
61
|
+
files = sys.argv[1:]
|
|
62
|
+
success = pre_commit_hook(files)
|
|
63
|
+
sys.exit(0 if success else 1)
|
|
64
|
+
else:
|
|
65
|
+
print("Usage: python -m codeshield.mcp.hooks <file1> <file2> ...")
|
codeshield/mcp/server.py
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Server - Proper FastMCP implementation with LeanMCP Observability
|
|
3
|
+
|
|
4
|
+
Uses the official MCP Python SDK (FastMCP) to expose CodeShield tools
|
|
5
|
+
as a proper MCP server that integrates with Claude, Cursor, etc.
|
|
6
|
+
|
|
7
|
+
Integrations:
|
|
8
|
+
- FastMCP: MCP protocol implementation
|
|
9
|
+
- LeanMCP: Observability, metrics, and analytics tracking
|
|
10
|
+
- CometAPI/Novita/AIML: LLM providers for code fixes
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Optional, Any
|
|
14
|
+
import json
|
|
15
|
+
import time
|
|
16
|
+
|
|
17
|
+
# Try to import FastMCP, fallback to simple HTTP if not available
|
|
18
|
+
try:
|
|
19
|
+
from mcp.server.fastmcp import FastMCP
|
|
20
|
+
HAS_FASTMCP = True
|
|
21
|
+
except ImportError:
|
|
22
|
+
HAS_FASTMCP = False
|
|
23
|
+
FastMCP = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def create_mcp_server():
|
|
27
|
+
"""Create and configure the CodeShield MCP server with LeanMCP observability"""
|
|
28
|
+
|
|
29
|
+
if not HAS_FASTMCP:
|
|
30
|
+
raise ImportError(
|
|
31
|
+
"MCP SDK not installed. Run: pip install mcp"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Initialize LeanMCP tracking
|
|
35
|
+
from codeshield.utils.leanmcp import get_leanmcp_client
|
|
36
|
+
leanmcp = get_leanmcp_client()
|
|
37
|
+
|
|
38
|
+
# Create FastMCP server
|
|
39
|
+
mcp = FastMCP("CodeShield")
|
|
40
|
+
|
|
41
|
+
# ============================================
|
|
42
|
+
# TOOL: verify_code
|
|
43
|
+
# ============================================
|
|
44
|
+
@mcp.tool()
|
|
45
|
+
def verify_code(code: str, auto_fix: bool = True) -> dict:
|
|
46
|
+
"""
|
|
47
|
+
Verify Python code for syntax errors, missing imports, and other issues.
|
|
48
|
+
Returns verification report with confidence score and optional auto-fixes.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
code: Python code to verify
|
|
52
|
+
auto_fix: Whether to automatically fix issues (default: True)
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Verification report with issues, fixes, and confidence score
|
|
56
|
+
"""
|
|
57
|
+
start_time = time.time()
|
|
58
|
+
try:
|
|
59
|
+
from codeshield.trustgate.checker import verify_code as _verify
|
|
60
|
+
|
|
61
|
+
result = _verify(code, auto_fix=auto_fix)
|
|
62
|
+
duration_ms = int((time.time() - start_time) * 1000)
|
|
63
|
+
leanmcp.track_tool_call("verify_code", duration_ms=duration_ms, success=True)
|
|
64
|
+
return result.to_dict()
|
|
65
|
+
except Exception as e:
|
|
66
|
+
duration_ms = int((time.time() - start_time) * 1000)
|
|
67
|
+
leanmcp.track_tool_call("verify_code", duration_ms=duration_ms, success=False, error_message=str(e))
|
|
68
|
+
raise
|
|
69
|
+
|
|
70
|
+
# ============================================
|
|
71
|
+
# TOOL: full_verify (with sandbox execution)
|
|
72
|
+
# ============================================
|
|
73
|
+
@mcp.tool()
|
|
74
|
+
def full_verify(code: str) -> dict:
|
|
75
|
+
"""
|
|
76
|
+
Complete verification: syntax + imports + sandbox execution.
|
|
77
|
+
Runs code in secure sandbox (Daytona) to confirm it actually works.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
code: Python code to verify
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Comprehensive verification report including execution results
|
|
84
|
+
"""
|
|
85
|
+
start_time = time.time()
|
|
86
|
+
try:
|
|
87
|
+
from codeshield.trustgate.sandbox import full_verification
|
|
88
|
+
|
|
89
|
+
result = full_verification(code)
|
|
90
|
+
duration_ms = int((time.time() - start_time) * 1000)
|
|
91
|
+
leanmcp.track_tool_call("full_verify", duration_ms=duration_ms, success=True)
|
|
92
|
+
return result
|
|
93
|
+
except Exception as e:
|
|
94
|
+
duration_ms = int((time.time() - start_time) * 1000)
|
|
95
|
+
leanmcp.track_tool_call("full_verify", duration_ms=duration_ms, success=False, error_message=str(e))
|
|
96
|
+
raise
|
|
97
|
+
|
|
98
|
+
# ============================================
|
|
99
|
+
# TOOL: check_style
|
|
100
|
+
# ============================================
|
|
101
|
+
@mcp.tool()
|
|
102
|
+
def check_style(code: str, codebase_path: str = ".") -> dict:
|
|
103
|
+
"""
|
|
104
|
+
Check code against codebase conventions.
|
|
105
|
+
Detects naming mismatches and suggests corrections.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
code: Code to check
|
|
109
|
+
codebase_path: Path to codebase for convention extraction
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Style check results with issues and corrections
|
|
113
|
+
"""
|
|
114
|
+
start_time = time.time()
|
|
115
|
+
try:
|
|
116
|
+
from codeshield.styleforge.corrector import check_style as _check
|
|
117
|
+
|
|
118
|
+
result = _check(code, codebase_path)
|
|
119
|
+
duration_ms = int((time.time() - start_time) * 1000)
|
|
120
|
+
leanmcp.track_tool_call("check_style", duration_ms=duration_ms, success=True)
|
|
121
|
+
return result.to_dict()
|
|
122
|
+
except Exception as e:
|
|
123
|
+
duration_ms = int((time.time() - start_time) * 1000)
|
|
124
|
+
leanmcp.track_tool_call("check_style", duration_ms=duration_ms, success=False, error_message=str(e))
|
|
125
|
+
raise
|
|
126
|
+
|
|
127
|
+
# ============================================
|
|
128
|
+
# TOOL: save_context
|
|
129
|
+
# ============================================
|
|
130
|
+
@mcp.tool()
|
|
131
|
+
def save_context(
|
|
132
|
+
name: str,
|
|
133
|
+
files: list[str] = None,
|
|
134
|
+
cursor: dict = None,
|
|
135
|
+
notes: str = None,
|
|
136
|
+
) -> dict:
|
|
137
|
+
"""
|
|
138
|
+
Save current coding context for later restoration.
|
|
139
|
+
Like a game save file for your coding session.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
name: Unique name for this context snapshot
|
|
143
|
+
files: List of open file paths
|
|
144
|
+
cursor: Current cursor position {file, line, column}
|
|
145
|
+
notes: Optional notes about current work
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Confirmation of saved context
|
|
149
|
+
"""
|
|
150
|
+
from codeshield.contextvault.capture import save_context as _save
|
|
151
|
+
|
|
152
|
+
return _save(
|
|
153
|
+
name=name,
|
|
154
|
+
files=files or [],
|
|
155
|
+
cursor=cursor,
|
|
156
|
+
notes=notes,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# ============================================
|
|
160
|
+
# TOOL: restore_context
|
|
161
|
+
# ============================================
|
|
162
|
+
@mcp.tool()
|
|
163
|
+
def restore_context(name: str) -> dict:
|
|
164
|
+
"""
|
|
165
|
+
Restore a previously saved coding context.
|
|
166
|
+
Returns AI briefing of where you left off.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
name: Name of context to restore
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Context info with AI briefing
|
|
173
|
+
"""
|
|
174
|
+
from codeshield.contextvault.restore import restore_context as _restore
|
|
175
|
+
|
|
176
|
+
return _restore(name=name)
|
|
177
|
+
|
|
178
|
+
# ============================================
|
|
179
|
+
# TOOL: list_contexts
|
|
180
|
+
# ============================================
|
|
181
|
+
@mcp.tool()
|
|
182
|
+
def list_contexts() -> list[dict]:
|
|
183
|
+
"""
|
|
184
|
+
List all saved coding contexts.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
List of saved contexts with names and timestamps
|
|
188
|
+
"""
|
|
189
|
+
from codeshield.contextvault.capture import list_contexts as _list
|
|
190
|
+
|
|
191
|
+
return _list()
|
|
192
|
+
|
|
193
|
+
# ============================================
|
|
194
|
+
# TOOL: mcp_health (Observability)
|
|
195
|
+
# ============================================
|
|
196
|
+
@mcp.tool()
|
|
197
|
+
def mcp_health() -> dict:
|
|
198
|
+
"""
|
|
199
|
+
Check MCP server health and connectivity status.
|
|
200
|
+
Returns status of all integrated services and providers.
|
|
201
|
+
|
|
202
|
+
Use this to verify:
|
|
203
|
+
- MCP server is running correctly
|
|
204
|
+
- LLM providers are configured (CometAPI, Novita, AIML)
|
|
205
|
+
- All CodeShield modules are loaded
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Health status with provider configurations and stats
|
|
209
|
+
"""
|
|
210
|
+
from codeshield.utils.llm import get_llm_client, get_provider_stats
|
|
211
|
+
|
|
212
|
+
# Check LLM providers
|
|
213
|
+
llm = get_llm_client()
|
|
214
|
+
provider_status = llm.get_status()
|
|
215
|
+
provider_stats = get_provider_stats()
|
|
216
|
+
|
|
217
|
+
# Check module availability
|
|
218
|
+
modules_status = {}
|
|
219
|
+
try:
|
|
220
|
+
from codeshield.trustgate import checker
|
|
221
|
+
modules_status["trustgate"] = "loaded"
|
|
222
|
+
except ImportError:
|
|
223
|
+
modules_status["trustgate"] = "not_available"
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
from codeshield.styleforge import corrector
|
|
227
|
+
modules_status["styleforge"] = "loaded"
|
|
228
|
+
except ImportError:
|
|
229
|
+
modules_status["styleforge"] = "not_available"
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
from codeshield.contextvault import capture
|
|
233
|
+
modules_status["contextvault"] = "loaded"
|
|
234
|
+
except ImportError:
|
|
235
|
+
modules_status["contextvault"] = "not_available"
|
|
236
|
+
|
|
237
|
+
# Get LeanMCP metrics
|
|
238
|
+
leanmcp_status = leanmcp.get_status()
|
|
239
|
+
leanmcp_metrics = leanmcp.get_metrics()
|
|
240
|
+
|
|
241
|
+
# Check Daytona configuration
|
|
242
|
+
import os
|
|
243
|
+
daytona_configured = bool(os.getenv("DAYTONA_API_KEY"))
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
"status": "healthy",
|
|
247
|
+
"mcp_server": "CodeShield",
|
|
248
|
+
"version": "1.0.0",
|
|
249
|
+
"integrations": {
|
|
250
|
+
"leanmcp": leanmcp_status,
|
|
251
|
+
"daytona": {
|
|
252
|
+
"configured": daytona_configured,
|
|
253
|
+
"api_url": os.getenv("DAYTONA_API_URL", "https://app.daytona.io/api")
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
"llm_providers": provider_status,
|
|
257
|
+
"llm_stats": provider_stats,
|
|
258
|
+
"mcp_metrics": leanmcp_metrics,
|
|
259
|
+
"modules": modules_status,
|
|
260
|
+
"message": "MCP server is running with LeanMCP observability."
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
# ============================================
|
|
264
|
+
# TOOL: test_llm_connection
|
|
265
|
+
# ============================================
|
|
266
|
+
@mcp.tool()
|
|
267
|
+
def test_llm_connection(provider: str = None) -> dict:
|
|
268
|
+
"""
|
|
269
|
+
Test LLM provider connectivity with a simple request.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
provider: Optional specific provider to test (cometapi, novita, aiml)
|
|
273
|
+
If not specified, tests the first available provider.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
Connection test result with provider used and response time
|
|
277
|
+
"""
|
|
278
|
+
import time
|
|
279
|
+
from codeshield.utils.llm import get_llm_client
|
|
280
|
+
|
|
281
|
+
llm = get_llm_client()
|
|
282
|
+
if provider:
|
|
283
|
+
llm.preferred_provider = provider
|
|
284
|
+
|
|
285
|
+
start_time = time.time()
|
|
286
|
+
response = llm.chat(
|
|
287
|
+
prompt="Reply with exactly: 'CodeShield MCP connected'",
|
|
288
|
+
max_tokens=20
|
|
289
|
+
)
|
|
290
|
+
elapsed = time.time() - start_time
|
|
291
|
+
|
|
292
|
+
if response:
|
|
293
|
+
return {
|
|
294
|
+
"success": True,
|
|
295
|
+
"provider": response.provider,
|
|
296
|
+
"model": response.model,
|
|
297
|
+
"response": response.content,
|
|
298
|
+
"response_time_ms": round(elapsed * 1000),
|
|
299
|
+
"tokens_used": response.tokens_used
|
|
300
|
+
}
|
|
301
|
+
else:
|
|
302
|
+
return {
|
|
303
|
+
"success": False,
|
|
304
|
+
"error": "No LLM provider available or all providers failed",
|
|
305
|
+
"hint": "Check that at least one of COMETAPI_KEY, NOVITA_API_KEY, or AIML_API_KEY is set"
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return mcp
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def run_mcp_server():
|
|
312
|
+
"""Run the MCP server using stdio transport"""
|
|
313
|
+
mcp = create_mcp_server()
|
|
314
|
+
mcp.run()
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
# For direct execution
|
|
318
|
+
if __name__ == "__main__":
|
|
319
|
+
run_mcp_server()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""StyleForge - Convention enforcement module"""
|