tzero-mcp 3.0.4__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.
- addmcp.py +1015 -0
- main.py +15 -0
- tzero.py +32 -0
- tzero_deps.py +209 -0
- tzero_features.py +1031 -0
- tzero_mcp-3.0.4.dist-info/METADATA +1035 -0
- tzero_mcp-3.0.4.dist-info/RECORD +13 -0
- tzero_mcp-3.0.4.dist-info/WHEEL +5 -0
- tzero_mcp-3.0.4.dist-info/entry_points.txt +4 -0
- tzero_mcp-3.0.4.dist-info/licenses/LICENSE +21 -0
- tzero_mcp-3.0.4.dist-info/top_level.txt +7 -0
- tzero_mcp.py +787 -0
- tzero_v3.py +4999 -0
addmcp.py
ADDED
|
@@ -0,0 +1,1015 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Install the bundled T-Zero MCP server into detected IDE configurations.
|
|
3
|
+
|
|
4
|
+
Supports both a clean Dark GUI and a headless CLI (--all, --list, --targets).
|
|
5
|
+
Automatically copies the standalone TZeroMCP runtime or configures the Python script,
|
|
6
|
+
guaranteeing zero configuration headaches on any computer.
|
|
7
|
+
|
|
8
|
+
Developer: Toprak Ahmet Aydoğmuş (Siber Akademi)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import hashlib
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import shutil
|
|
16
|
+
import sys
|
|
17
|
+
import tempfile
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Callable, Dict, List, Optional, Tuple
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
APP_VERSION = "3.0.4"
|
|
25
|
+
SERVER_NAME = "tzero"
|
|
26
|
+
SERVER_FILENAME = "TZeroMCP.exe"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _attach_windows_console() -> None:
|
|
30
|
+
"""If run from CMD or PowerShell on Windows with --noconsole, attach parent console."""
|
|
31
|
+
if os.name == "nt":
|
|
32
|
+
try:
|
|
33
|
+
import ctypes
|
|
34
|
+
if ctypes.windll.kernel32.AttachConsole(-1):
|
|
35
|
+
if sys.stdout is None or getattr(sys.stdout, "closed", True):
|
|
36
|
+
sys.stdout = open("CONOUT$", "w", encoding="utf-8", errors="replace")
|
|
37
|
+
if sys.stderr is None or getattr(sys.stderr, "closed", True):
|
|
38
|
+
sys.stderr = open("CONOUT$", "w", encoding="utf-8", errors="replace")
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class ClientTarget:
|
|
45
|
+
key: str
|
|
46
|
+
name: str
|
|
47
|
+
config_path: Path
|
|
48
|
+
root_key: str
|
|
49
|
+
detected: bool
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _get_antigravity_config_path(home: Path) -> Path:
|
|
53
|
+
"""Resolves primary Google Antigravity MCP config path."""
|
|
54
|
+
config_in_config = home / ".gemini" / "config" / "mcp_config.json"
|
|
55
|
+
if config_in_config.exists() or (home / ".gemini" / "config").exists():
|
|
56
|
+
return config_in_config
|
|
57
|
+
return home / ".gemini" / "antigravity" / "mcp_config.json"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _get_claude_desktop_config_path(app_data: Path, local_app_data: Path) -> Tuple[Path, bool]:
|
|
61
|
+
"""Resolves Claude Desktop configuration path, handling both MSIX packages and standard AppData."""
|
|
62
|
+
# 1. Standard AppData Roaming
|
|
63
|
+
standard_cfg = app_data / "Claude" / "claude_desktop_config.json"
|
|
64
|
+
if standard_cfg.exists():
|
|
65
|
+
return standard_cfg, True
|
|
66
|
+
|
|
67
|
+
# 2. Windows Store / MSIX Package sandboxed Roaming
|
|
68
|
+
packages_dir = local_app_data / "Packages"
|
|
69
|
+
if packages_dir.is_dir():
|
|
70
|
+
for claude_pkg in packages_dir.glob("Claude_*"):
|
|
71
|
+
candidate = claude_pkg / "LocalCache" / "Roaming" / "Claude" / "claude_desktop_config.json"
|
|
72
|
+
if candidate.exists() or (claude_pkg / "LocalCache" / "Roaming" / "Claude").is_dir():
|
|
73
|
+
return candidate, True
|
|
74
|
+
|
|
75
|
+
detected = any([
|
|
76
|
+
(app_data / "Claude").exists(),
|
|
77
|
+
(local_app_data / "Programs" / "Claude" / "Claude.exe").exists(),
|
|
78
|
+
bool(list(packages_dir.glob("Claude_*"))) if packages_dir.is_dir() else False,
|
|
79
|
+
])
|
|
80
|
+
return standard_cfg, detected
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def build_client_targets(
|
|
84
|
+
home: Optional[Path] = None,
|
|
85
|
+
app_data: Optional[Path] = None,
|
|
86
|
+
local_app_data: Optional[Path] = None,
|
|
87
|
+
which: Callable[[str], Optional[str]] = shutil.which,
|
|
88
|
+
) -> List[ClientTarget]:
|
|
89
|
+
"""Return known per-user MCP configs and whether their client is installed."""
|
|
90
|
+
home = Path(home or Path.home())
|
|
91
|
+
app_data = Path(app_data or os.environ.get("APPDATA", home / "AppData/Roaming"))
|
|
92
|
+
local_app_data = Path(
|
|
93
|
+
local_app_data or os.environ.get("LOCALAPPDATA", home / "AppData/Local")
|
|
94
|
+
)
|
|
95
|
+
vscode_user = app_data / "Code" / "User"
|
|
96
|
+
vscode_extensions = home / ".vscode" / "extensions"
|
|
97
|
+
|
|
98
|
+
def any_path_exists(*paths: Path) -> bool:
|
|
99
|
+
return any(path.exists() for path in paths)
|
|
100
|
+
|
|
101
|
+
def extension_installed(prefix: str) -> bool:
|
|
102
|
+
return bool(list(vscode_extensions.glob(f"{prefix}-*")))
|
|
103
|
+
|
|
104
|
+
antigravity_config = _get_antigravity_config_path(home)
|
|
105
|
+
claude_config, claude_detected = _get_claude_desktop_config_path(app_data, local_app_data)
|
|
106
|
+
|
|
107
|
+
return [
|
|
108
|
+
ClientTarget(
|
|
109
|
+
"cursor",
|
|
110
|
+
"Cursor",
|
|
111
|
+
home / ".cursor" / "mcp.json",
|
|
112
|
+
"mcpServers",
|
|
113
|
+
any_path_exists(
|
|
114
|
+
home / ".cursor",
|
|
115
|
+
app_data / "Cursor",
|
|
116
|
+
local_app_data / "Programs" / "Cursor" / "Cursor.exe",
|
|
117
|
+
),
|
|
118
|
+
),
|
|
119
|
+
ClientTarget(
|
|
120
|
+
"vscode",
|
|
121
|
+
"Visual Studio Code / Copilot",
|
|
122
|
+
home / ".copilot" / "mcp-config.json",
|
|
123
|
+
"mcpServers",
|
|
124
|
+
any_path_exists(
|
|
125
|
+
app_data / "Code",
|
|
126
|
+
local_app_data / "Programs" / "Microsoft VS Code" / "Code.exe",
|
|
127
|
+
)
|
|
128
|
+
or which("code") is not None,
|
|
129
|
+
),
|
|
130
|
+
ClientTarget(
|
|
131
|
+
"claude-desktop",
|
|
132
|
+
"Claude Desktop",
|
|
133
|
+
claude_config,
|
|
134
|
+
"mcpServers",
|
|
135
|
+
claude_detected,
|
|
136
|
+
),
|
|
137
|
+
ClientTarget(
|
|
138
|
+
"antigravity",
|
|
139
|
+
"Google Antigravity",
|
|
140
|
+
antigravity_config,
|
|
141
|
+
"mcpServers",
|
|
142
|
+
any_path_exists(
|
|
143
|
+
home / ".gemini" / "config",
|
|
144
|
+
home / ".gemini" / "antigravity",
|
|
145
|
+
local_app_data / "Programs" / "Antigravity" / "Antigravity.exe",
|
|
146
|
+
),
|
|
147
|
+
),
|
|
148
|
+
ClientTarget(
|
|
149
|
+
"claude-code",
|
|
150
|
+
"Claude Code",
|
|
151
|
+
home / ".claude.json",
|
|
152
|
+
"mcpServers",
|
|
153
|
+
home.joinpath(".claude.json").exists() or which("claude") is not None,
|
|
154
|
+
),
|
|
155
|
+
ClientTarget(
|
|
156
|
+
"cline",
|
|
157
|
+
"Cline",
|
|
158
|
+
vscode_user
|
|
159
|
+
/ "globalStorage"
|
|
160
|
+
/ "saoudrizwan.claude-dev"
|
|
161
|
+
/ "settings"
|
|
162
|
+
/ "cline_mcp_settings.json",
|
|
163
|
+
"mcpServers",
|
|
164
|
+
(vscode_user / "globalStorage" / "saoudrizwan.claude-dev").exists()
|
|
165
|
+
or extension_installed("saoudrizwan.claude-dev"),
|
|
166
|
+
),
|
|
167
|
+
ClientTarget(
|
|
168
|
+
"roo-code",
|
|
169
|
+
"Roo Code",
|
|
170
|
+
vscode_user
|
|
171
|
+
/ "globalStorage"
|
|
172
|
+
/ "rooveterinaryinc.roo-cline"
|
|
173
|
+
/ "settings"
|
|
174
|
+
/ "mcp_settings.json",
|
|
175
|
+
"mcpServers",
|
|
176
|
+
(vscode_user / "globalStorage" / "rooveterinaryinc.roo-cline").exists()
|
|
177
|
+
or extension_installed("rooveterinaryinc.roo-cline"),
|
|
178
|
+
),
|
|
179
|
+
ClientTarget(
|
|
180
|
+
"windsurf",
|
|
181
|
+
"Windsurf",
|
|
182
|
+
home / ".codeium" / "windsurf" / "mcp_config.json",
|
|
183
|
+
"mcpServers",
|
|
184
|
+
any_path_exists(
|
|
185
|
+
home / ".codeium" / "windsurf",
|
|
186
|
+
app_data / "Windsurf",
|
|
187
|
+
local_app_data / "Programs" / "Windsurf" / "Windsurf.exe",
|
|
188
|
+
)
|
|
189
|
+
or which("windsurf") is not None,
|
|
190
|
+
),
|
|
191
|
+
ClientTarget(
|
|
192
|
+
"gemini-cli",
|
|
193
|
+
"Gemini CLI",
|
|
194
|
+
home / ".gemini" / "settings.json",
|
|
195
|
+
"mcpServers",
|
|
196
|
+
home.joinpath(".gemini", "settings.json").exists()
|
|
197
|
+
or which("gemini") is not None,
|
|
198
|
+
),
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def server_entry(target: ClientTarget, server_path: Path) -> Dict[str, object]:
|
|
203
|
+
"""Generates the MCP configuration dictionary block for the target."""
|
|
204
|
+
if server_path.suffix.lower() == ".py":
|
|
205
|
+
entry: Dict[str, object] = {
|
|
206
|
+
"command": sys.executable,
|
|
207
|
+
"args": [str(server_path.resolve())],
|
|
208
|
+
}
|
|
209
|
+
else:
|
|
210
|
+
entry: Dict[str, object] = {
|
|
211
|
+
"command": str(server_path.resolve()),
|
|
212
|
+
"args": [],
|
|
213
|
+
}
|
|
214
|
+
if target.root_key == "servers":
|
|
215
|
+
entry["type"] = "stdio"
|
|
216
|
+
return entry
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _backup_path(path: Path) -> Path:
|
|
220
|
+
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S-%f")
|
|
221
|
+
return path.with_name(f"{path.name}.backup-{timestamp}")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def merge_server_config(
|
|
225
|
+
target: ClientTarget, server_path: Path, server_name: str = SERVER_NAME
|
|
226
|
+
) -> Dict[str, object]:
|
|
227
|
+
"""Safely merge one server entry, keeping the rest of the client's config."""
|
|
228
|
+
config: Dict[str, object] = {}
|
|
229
|
+
if target.config_path.exists():
|
|
230
|
+
with target.config_path.open("r", encoding="utf-8-sig") as config_file:
|
|
231
|
+
loaded = json.load(config_file)
|
|
232
|
+
if not isinstance(loaded, dict):
|
|
233
|
+
raise ValueError("Configuration root must be a JSON object")
|
|
234
|
+
config = loaded
|
|
235
|
+
|
|
236
|
+
servers = config.get(target.root_key, {})
|
|
237
|
+
if not isinstance(servers, dict):
|
|
238
|
+
raise ValueError(f"'{target.root_key}' must be a JSON object")
|
|
239
|
+
|
|
240
|
+
entry = server_entry(target, server_path)
|
|
241
|
+
if servers.get(server_name) == entry:
|
|
242
|
+
if target.key == "antigravity":
|
|
243
|
+
_sync_alternate_antigravity_config(target, server_path, server_name)
|
|
244
|
+
elif target.key == "claude-desktop":
|
|
245
|
+
_sync_alternate_claude_desktop_config(target, server_path, server_name)
|
|
246
|
+
return {"status": "unchanged", "backup": None}
|
|
247
|
+
|
|
248
|
+
backup = None
|
|
249
|
+
if target.config_path.exists():
|
|
250
|
+
backup = _backup_path(target.config_path)
|
|
251
|
+
shutil.copy2(target.config_path, backup)
|
|
252
|
+
|
|
253
|
+
updated_servers = dict(servers)
|
|
254
|
+
updated_servers[server_name] = entry
|
|
255
|
+
config[target.root_key] = updated_servers
|
|
256
|
+
target.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
257
|
+
|
|
258
|
+
temporary_path = None
|
|
259
|
+
try:
|
|
260
|
+
with tempfile.NamedTemporaryFile(
|
|
261
|
+
"w",
|
|
262
|
+
encoding="utf-8",
|
|
263
|
+
dir=str(target.config_path.parent),
|
|
264
|
+
prefix=f".{target.config_path.name}.",
|
|
265
|
+
suffix=".tmp",
|
|
266
|
+
delete=False,
|
|
267
|
+
) as temporary_file:
|
|
268
|
+
json.dump(config, temporary_file, indent=2, ensure_ascii=False)
|
|
269
|
+
temporary_file.write("\n")
|
|
270
|
+
temporary_path = Path(temporary_file.name)
|
|
271
|
+
os.replace(temporary_path, target.config_path)
|
|
272
|
+
finally:
|
|
273
|
+
if temporary_path is not None and temporary_path.exists():
|
|
274
|
+
temporary_path.unlink()
|
|
275
|
+
|
|
276
|
+
# If this is Google Antigravity or Claude Desktop, sync alternate config paths if present
|
|
277
|
+
if target.key == "antigravity":
|
|
278
|
+
_sync_alternate_antigravity_config(target, server_path, server_name)
|
|
279
|
+
elif target.key == "claude-desktop":
|
|
280
|
+
_sync_alternate_claude_desktop_config(target, server_path, server_name)
|
|
281
|
+
|
|
282
|
+
return {"status": "installed", "backup": backup}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _sync_alternate_antigravity_config(
|
|
286
|
+
target: ClientTarget, server_path: Path, server_name: str
|
|
287
|
+
) -> None:
|
|
288
|
+
"""Keep ~/.gemini/config/mcp_config.json and ~/.gemini/antigravity/mcp_config.json in sync."""
|
|
289
|
+
try:
|
|
290
|
+
home = Path.home()
|
|
291
|
+
alt_paths = [
|
|
292
|
+
home / ".gemini" / "config" / "mcp_config.json",
|
|
293
|
+
home / ".gemini" / "antigravity" / "mcp_config.json",
|
|
294
|
+
]
|
|
295
|
+
entry = server_entry(target, server_path)
|
|
296
|
+
for alt_path in alt_paths:
|
|
297
|
+
if alt_path.resolve() == target.config_path.resolve():
|
|
298
|
+
continue
|
|
299
|
+
if alt_path.parent.exists():
|
|
300
|
+
cfg: Dict[str, object] = {}
|
|
301
|
+
if alt_path.exists():
|
|
302
|
+
try:
|
|
303
|
+
with alt_path.open("r", encoding="utf-8-sig") as fp:
|
|
304
|
+
cfg = json.load(fp)
|
|
305
|
+
except Exception:
|
|
306
|
+
continue
|
|
307
|
+
if not isinstance(cfg, dict):
|
|
308
|
+
cfg = {}
|
|
309
|
+
servers = cfg.setdefault("mcpServers", {})
|
|
310
|
+
if isinstance(servers, dict) and servers.get(server_name) != entry:
|
|
311
|
+
servers[server_name] = entry
|
|
312
|
+
alt_path.parent.mkdir(parents=True, exist_ok=True)
|
|
313
|
+
with alt_path.open("w", encoding="utf-8") as fp:
|
|
314
|
+
json.dump(cfg, fp, indent=2, ensure_ascii=False)
|
|
315
|
+
fp.write("\n")
|
|
316
|
+
# Also install the /tzero slash skills, plugin, and instructions into Antigravity
|
|
317
|
+
_install_antigravity_skill(server_path)
|
|
318
|
+
except Exception:
|
|
319
|
+
pass
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _install_antigravity_skill(server_path: Optional[Path] = None) -> None:
|
|
323
|
+
"""Installs the /tzero slash skills, Antigravity plugin, and MCP instructions."""
|
|
324
|
+
try:
|
|
325
|
+
home = Path.home()
|
|
326
|
+
# 1. Global skills directories
|
|
327
|
+
skills_targets = [
|
|
328
|
+
home / ".gemini" / "config" / "skills",
|
|
329
|
+
home / ".gemini" / "skills",
|
|
330
|
+
]
|
|
331
|
+
|
|
332
|
+
# If running within a git or project workspace, also install workspace skill
|
|
333
|
+
cwd = Path.cwd()
|
|
334
|
+
if (cwd / ".git").is_dir() or (cwd / ".agents").is_dir():
|
|
335
|
+
skills_targets.append(cwd / ".agents" / "skills")
|
|
336
|
+
|
|
337
|
+
all_skills = {
|
|
338
|
+
"tzero": _ANTIGRAVITY_SKILL_CONTENT,
|
|
339
|
+
"tzero-tree": _ANTIGRAVITY_TREE_SKILL_CONTENT,
|
|
340
|
+
"tzero-audit": _ANTIGRAVITY_AUDIT_SKILL_CONTENT,
|
|
341
|
+
"tzero-arch": _ANTIGRAVITY_ARCH_SKILL_CONTENT,
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
for tool_name, (desc, action) in _MCP_TOOLS_SPEC.items():
|
|
345
|
+
all_skills[tool_name] = f"""---
|
|
346
|
+
name: {tool_name}
|
|
347
|
+
description: >-
|
|
348
|
+
{desc}
|
|
349
|
+
Activate when the user types "/{tool_name}" or asks to run {tool_name}.
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
# /{tool_name} — T-Zero MCP Tool
|
|
353
|
+
|
|
354
|
+
{desc}
|
|
355
|
+
|
|
356
|
+
## Execution
|
|
357
|
+
{action}
|
|
358
|
+
"""
|
|
359
|
+
|
|
360
|
+
for target_base in skills_targets:
|
|
361
|
+
for skill_name, content in all_skills.items():
|
|
362
|
+
s_dir = target_base / skill_name
|
|
363
|
+
s_dir.mkdir(parents=True, exist_ok=True)
|
|
364
|
+
(s_dir / "SKILL.md").write_text(content, encoding="utf-8")
|
|
365
|
+
|
|
366
|
+
# 2. Antigravity Plugin (~/.gemini/config/plugins/tzero)
|
|
367
|
+
plugin_dir = home / ".gemini" / "config" / "plugins" / "tzero"
|
|
368
|
+
plugin_dir.mkdir(parents=True, exist_ok=True)
|
|
369
|
+
(plugin_dir / "plugin.json").write_text(_ANTIGRAVITY_PLUGIN_JSON, encoding="utf-8")
|
|
370
|
+
(plugin_dir / "installed_version.json").write_text('{"version": "3.0.4"}\n', encoding="utf-8")
|
|
371
|
+
|
|
372
|
+
# Plugin skills
|
|
373
|
+
for skill_name, content in all_skills.items():
|
|
374
|
+
p_skill_dir = plugin_dir / "skills" / skill_name
|
|
375
|
+
p_skill_dir.mkdir(parents=True, exist_ok=True)
|
|
376
|
+
(p_skill_dir / "SKILL.md").write_text(content, encoding="utf-8")
|
|
377
|
+
|
|
378
|
+
# Plugin rules
|
|
379
|
+
p_rules_dir = plugin_dir / "rules"
|
|
380
|
+
p_rules_dir.mkdir(parents=True, exist_ok=True)
|
|
381
|
+
(p_rules_dir / "tzero-token-optimization.md").write_text(_ANTIGRAVITY_RULE_CONTENT, encoding="utf-8")
|
|
382
|
+
|
|
383
|
+
# Plugin mcp_config.json
|
|
384
|
+
cmd = str(server_path.resolve()) if server_path else str(home / "AppData" / "Local" / "TZeroAlgorithm" / SERVER_FILENAME)
|
|
385
|
+
plugin_mcp = {
|
|
386
|
+
"mcpServers": {
|
|
387
|
+
SERVER_NAME: {
|
|
388
|
+
"command": cmd,
|
|
389
|
+
"args": []
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
(plugin_dir / "mcp_config.json").write_text(json.dumps(plugin_mcp, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
394
|
+
|
|
395
|
+
# 3. Enable plugin in ~/.gemini/config/config.json
|
|
396
|
+
cfg_file = home / ".gemini" / "config" / "config.json"
|
|
397
|
+
if cfg_file.exists():
|
|
398
|
+
try:
|
|
399
|
+
cfg_data = json.loads(cfg_file.read_text(encoding="utf-8-sig"))
|
|
400
|
+
if isinstance(cfg_data, dict):
|
|
401
|
+
plugins_dict = cfg_data.setdefault("plugins", {})
|
|
402
|
+
if isinstance(plugins_dict, dict) and "tzero" not in plugins_dict:
|
|
403
|
+
plugins_dict["tzero"] = {"enabled": True}
|
|
404
|
+
cfg_file.write_text(json.dumps(cfg_data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
405
|
+
except Exception:
|
|
406
|
+
pass
|
|
407
|
+
|
|
408
|
+
# 4. MCP instructions in ~/.gemini/antigravity-ide/mcp/tzero/instructions.md
|
|
409
|
+
ide_mcp_dir = home / ".gemini" / "antigravity-ide" / "mcp" / SERVER_NAME
|
|
410
|
+
if ide_mcp_dir.is_dir():
|
|
411
|
+
(ide_mcp_dir / "instructions.md").write_text(_ANTIGRAVITY_MCP_INSTRUCTIONS, encoding="utf-8")
|
|
412
|
+
|
|
413
|
+
except Exception:
|
|
414
|
+
pass
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
_ANTIGRAVITY_SKILL_CONTENT = """---
|
|
418
|
+
name: tzero
|
|
419
|
+
description: >-
|
|
420
|
+
Enterprise-Grade Codebase Context Architect, AST Signatures Analyzer, & Token Reducer.
|
|
421
|
+
Activate when the user types "/tzero", asks to reduce tokens, scan codebase architecture,
|
|
422
|
+
audit code smells, analyze change impact, enforce architectural boundaries, or optimize prompt context for LLMs.
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
# /tzero — Siber Akademi T-Zero Context Engine
|
|
426
|
+
|
|
427
|
+
Enterprise-Grade Codebase Context Architect, AST Signatures Analyzer, and Token Reducer.
|
|
428
|
+
Cuts LLM context ingestion overhead by up to 95% while preserving structural fidelity for AI agents.
|
|
429
|
+
|
|
430
|
+
Developer: Toprak Ahmet Aydoğmuş (Siber Akademi)
|
|
431
|
+
Repository: https://github.com/toprakahmetaydogmus/TZeroAlgorithm
|
|
432
|
+
|
|
433
|
+
## Slash Command Modes
|
|
434
|
+
|
|
435
|
+
When the user enters `/tzero [subcommand]` or asks for T-Zero assistance, dispatch to the matching capability:
|
|
436
|
+
|
|
437
|
+
| Subcommand | Description | Action / Tool |
|
|
438
|
+
|:---|:---|:---|
|
|
439
|
+
| `/tzero` or `/tzero tree` | Builds full multi-tier hierarchical context tree (T-1 to T-4) with ultra token pruning. | Call `get_project_context_tree` MCP tool |
|
|
440
|
+
| `/tzero audit` | Runs AST static code smell check and checks for 100% Zero-Leak security (hardcoded secrets). | Call `audit_codebase_quality` MCP tool |
|
|
441
|
+
| `/tzero arch` | Generates system architecture specification (`ARCHITECTURE.md`) with live Mermaid diagrams. | Call `generate_architecture_blueprint` MCP tool |
|
|
442
|
+
| `/tzero deps [file]` | Traces internal & external module import topology and dependency graphs. | Call `query_module_dependencies` MCP tool |
|
|
443
|
+
| `/tzero impact <symbol>` | Analyzes change impact and blast radius (0-100 score) before modifying a class/function. | Call `analyze_change_impact` MCP tool |
|
|
444
|
+
| `/tzero rules` | Generates AI agent instruction files (`.cursorrules`, `.cursor/rules/*.mdc`, `.clinerules`). | Call `export_agent_rules` MCP tool |
|
|
445
|
+
| `/tzero search <query>` | Performs 100% private local BM25 hybrid semantic code search without cloud embedding APIs. | Call `search_codebase_semantic` MCP tool |
|
|
446
|
+
| `/tzero savings` | Computes quantitative token reduction metrics and team financial ROI savings (USD/month). | Call `get_token_savings_metrics` MCP tool |
|
|
447
|
+
| `/tzero boundaries` | Enforces T-4 architectural boundaries defined in `tzero.rules.json` to prevent architectural erosion. | Call `enforce_architecture_boundaries` MCP tool |
|
|
448
|
+
| `/tzero repomap` | Generates ultra-compact AST symbol token map for chat context windows. | Call `generate_repo_map` MCP tool |
|
|
449
|
+
| `/tzero duplicity` | Scans workspace for duplicate or copy-pasted code blocks (6+ lines) across modules. | Call `find_code_duplicity` MCP tool |
|
|
450
|
+
| `/tzero doctor` | Inspects environment, Python runtime, and dependencies. | Run `python -m tzero_deps` or CLI `--doctor` |
|
|
451
|
+
|
|
452
|
+
## Execution Guidelines
|
|
453
|
+
|
|
454
|
+
1. **Prefer Native MCP Tools:** When the `tzero` MCP server is registered, invoke the corresponding MCP tool directly (`get_project_context_tree`, `audit_codebase_quality`, etc.).
|
|
455
|
+
2. **Fallback to CLI/Engine:** If MCP is temporarily unavailable, use the local CLI `tzero_v3.py` or `TZeroAlgorithm.exe` with matching flags (`--scan`, `--audit`, `--export-arch`, etc.).
|
|
456
|
+
3. **100% Zero-Leak Guarantee:** Never output, log, or commit sensitive API keys or user credentials.
|
|
457
|
+
4. **Summary & Presentation:** Format the output with clear GitHub-style Markdown, tables, and alerts (`> [!TIP]`, `> [!IMPORTANT]`).
|
|
458
|
+
"""
|
|
459
|
+
|
|
460
|
+
_ANTIGRAVITY_TREE_SKILL_CONTENT = """---
|
|
461
|
+
name: tzero-tree
|
|
462
|
+
description: >-
|
|
463
|
+
Generate multi-tier hierarchical AST context tree with up to 95% token reduction for AI context ingestion.
|
|
464
|
+
Activate when the user types "/tzero-tree" or asks to build a code tree.
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
# /tzero-tree — AST Hierarchical Context Tree Generator
|
|
468
|
+
|
|
469
|
+
Generate a multi-tier pruned AST context tree to provide AI agents with full architectural understanding at up to 95% lower token cost.
|
|
470
|
+
|
|
471
|
+
## Modes & Tiers
|
|
472
|
+
- **Tier 1 (T-1):** High-level architectural map & entry points.
|
|
473
|
+
- **Tier 2 (T-2):** Public API & class/function signatures with types.
|
|
474
|
+
- **Tier 3 (T-3):** Full signatures, method contracts, and docstrings.
|
|
475
|
+
- **Tier 4 (T-4):** Full AST graph with import dependencies.
|
|
476
|
+
|
|
477
|
+
## Execution
|
|
478
|
+
Call the `get_project_context_tree` MCP tool with the target path (default: current workspace) and desired tier (default: `T-2`).
|
|
479
|
+
"""
|
|
480
|
+
|
|
481
|
+
_ANTIGRAVITY_AUDIT_SKILL_CONTENT = """---
|
|
482
|
+
name: tzero-audit
|
|
483
|
+
description: >-
|
|
484
|
+
Run AST static code analysis, code smells check, cyclomatic complexity report, and 100% Zero-Leak security audit for hardcoded secrets.
|
|
485
|
+
Activate when the user types "/tzero-audit" or asks to audit code quality.
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
# /tzero-audit — AST Code Quality & Zero-Leak Security Audit
|
|
489
|
+
|
|
490
|
+
Perform a comprehensive AST quality audit and verify 100% Zero-Leak security:
|
|
491
|
+
1. **Code Smells:** Functions >50 lines, classes >300 lines, high complexity.
|
|
492
|
+
2. **Dead Code:** Unused imports and unreferenced private functions.
|
|
493
|
+
3. **Security (Zero-Leak):** High-entropy strings, hardcoded API keys, tokens, and credentials.
|
|
494
|
+
|
|
495
|
+
## Execution
|
|
496
|
+
Call the `audit_codebase_quality` MCP tool on the target path (default: `.`).
|
|
497
|
+
"""
|
|
498
|
+
|
|
499
|
+
_ANTIGRAVITY_ARCH_SKILL_CONTENT = """---
|
|
500
|
+
name: tzero-arch
|
|
501
|
+
description: >-
|
|
502
|
+
Generate complete system architecture specification (ARCHITECTURE.md) with live Mermaid component diagrams.
|
|
503
|
+
Activate when the user types "/tzero-arch" or asks for system architecture blueprint.
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
# /tzero-arch — System Architecture Blueprint Generator
|
|
507
|
+
|
|
508
|
+
Generate an enterprise-grade architectural specification:
|
|
509
|
+
1. Module decomposition and layer boundaries.
|
|
510
|
+
2. Inter-module import topology.
|
|
511
|
+
3. Interactive Mermaid diagram showing system component relationships.
|
|
512
|
+
4. Auto-exports or updates `ARCHITECTURE.md`.
|
|
513
|
+
|
|
514
|
+
## Execution
|
|
515
|
+
Call the `generate_architecture_blueprint` MCP tool on the target path (default: `.`).
|
|
516
|
+
"""
|
|
517
|
+
|
|
518
|
+
_MCP_TOOLS_SPEC = {
|
|
519
|
+
"get_project_context_tree": (
|
|
520
|
+
"Scans target project directory and builds an enterprise-grade multi-tier T-Zero hierarchical context tree (T-1 to T-4) with up to 95% token reduction.",
|
|
521
|
+
"Invoke the `get_project_context_tree` MCP tool with arguments `{\"project_root\": \".\", \"reduction_mode\": \"ultra\"}`."
|
|
522
|
+
),
|
|
523
|
+
"query_module_dependencies": (
|
|
524
|
+
"Analyzes import/export dependencies, module relationships, and internal vs external package dependencies across codebase or for a specific file.",
|
|
525
|
+
"Invoke the `query_module_dependencies` MCP tool."
|
|
526
|
+
),
|
|
527
|
+
"query_architecture_boundaries": (
|
|
528
|
+
"Retrieves active T-4 architectural boundaries, operational constraints, and 100% Zero-Leak security rules.",
|
|
529
|
+
"Invoke the `query_architecture_boundaries` MCP tool."
|
|
530
|
+
),
|
|
531
|
+
"generate_architecture_blueprint": (
|
|
532
|
+
"Generates a complete system architecture specification (ARCHITECTURE.md) including auto-generated Mermaid topology diagrams.",
|
|
533
|
+
"Invoke the `generate_architecture_blueprint` MCP tool."
|
|
534
|
+
),
|
|
535
|
+
"generate_repo_map": (
|
|
536
|
+
"Generates an ultra-compact AST symbol token map for chat context windows.",
|
|
537
|
+
"Invoke the `generate_repo_map` MCP tool."
|
|
538
|
+
),
|
|
539
|
+
"audit_codebase_quality": (
|
|
540
|
+
"Performs an AST static code smell audit and 100% Zero-Leak security check for hardcoded secret leaks across workspace files.",
|
|
541
|
+
"Invoke the `audit_codebase_quality` MCP tool."
|
|
542
|
+
),
|
|
543
|
+
"find_code_duplicity": (
|
|
544
|
+
"Scans workspace for duplicate or copy-pasted code blocks (6+ lines) across modules.",
|
|
545
|
+
"Invoke the `find_code_duplicity` MCP tool."
|
|
546
|
+
),
|
|
547
|
+
"estimate_token_cost": (
|
|
548
|
+
"Calculates exact token cost comparison across OpenAI, Anthropic, Gemini, DeepSeek models.",
|
|
549
|
+
"Invoke the `estimate_token_cost` MCP tool."
|
|
550
|
+
),
|
|
551
|
+
"analyze_change_impact": (
|
|
552
|
+
"Analyzes change blast radius (0-100 score) and downstream dependents before modifying a class/function.",
|
|
553
|
+
"Invoke the `analyze_change_impact` MCP tool."
|
|
554
|
+
),
|
|
555
|
+
"enforce_architecture_boundaries": (
|
|
556
|
+
"Enforces architectural boundary rules defined in tzero.rules.json to prevent architectural erosion.",
|
|
557
|
+
"Invoke the `enforce_architecture_boundaries` MCP tool."
|
|
558
|
+
),
|
|
559
|
+
"search_codebase_semantic": (
|
|
560
|
+
"Performs 100% private local BM25 hybrid semantic code search across codebase without cloud embedding APIs.",
|
|
561
|
+
"Invoke the `search_codebase_semantic` MCP tool."
|
|
562
|
+
),
|
|
563
|
+
"export_agent_rules": (
|
|
564
|
+
"Exports AI agent rule specifications (.cursorrules, .clinerules, .cursor/rules/*.mdc).",
|
|
565
|
+
"Invoke the `export_agent_rules` MCP tool."
|
|
566
|
+
),
|
|
567
|
+
"get_token_savings_metrics": (
|
|
568
|
+
"Computes quantitative token reduction metrics and monthly team financial ROI savings.",
|
|
569
|
+
"Invoke the `get_token_savings_metrics` MCP tool."
|
|
570
|
+
),
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
_ANTIGRAVITY_PLUGIN_JSON = """{
|
|
574
|
+
"name": "tzero",
|
|
575
|
+
"displayName": "Siber Akademi T-Zero Context Engine",
|
|
576
|
+
"version": "3.0.4",
|
|
577
|
+
"description": "Enterprise-Grade Codebase Context Architect, AST Signatures Analyzer, and Token Reducer. Cuts LLM prompt context by up to 95%, audits code smells & security, enforces architecture boundaries, and traces module dependencies.",
|
|
578
|
+
"suggestedPrompts": [
|
|
579
|
+
"/tzero tree - Build multi-tier AST hierarchical context tree with 95% token savings",
|
|
580
|
+
"/tzero audit - Run AST code quality and 100% Zero-Leak security leak audit",
|
|
581
|
+
"/tzero arch - Generate system architecture specification (ARCHITECTURE.md) with Mermaid diagrams",
|
|
582
|
+
"/tzero impact <symbol> - Analyze blast radius and change impact before editing code",
|
|
583
|
+
"/tzero savings - Compute token reduction metrics and financial ROI"
|
|
584
|
+
],
|
|
585
|
+
"author": {
|
|
586
|
+
"name": "Toprak Ahmet Aydoğmuş (Siber Akademi)",
|
|
587
|
+
"url": "https://github.com/toprakahmetaydogmus/TZeroAlgorithm"
|
|
588
|
+
},
|
|
589
|
+
"keywords": [
|
|
590
|
+
"tzero",
|
|
591
|
+
"token-reduction",
|
|
592
|
+
"ast",
|
|
593
|
+
"codebase-context",
|
|
594
|
+
"mcp",
|
|
595
|
+
"architecture",
|
|
596
|
+
"security-audit"
|
|
597
|
+
],
|
|
598
|
+
"homepage": "https://github.com/toprakahmetaydogmus/TZeroAlgorithm",
|
|
599
|
+
"repository": "https://github.com/toprakahmetaydogmus/TZeroAlgorithm",
|
|
600
|
+
"license": "MIT"
|
|
601
|
+
}
|
|
602
|
+
"""
|
|
603
|
+
|
|
604
|
+
_ANTIGRAVITY_RULE_CONTENT = """---
|
|
605
|
+
description: Suggest using T-Zero MCP tools for token pruning and AST architecture mapping on codebases.
|
|
606
|
+
trigger: model_decision
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
# T-Zero Token Efficiency & Architecture Rule
|
|
610
|
+
|
|
611
|
+
When exploring large repositories or analyzing multi-file impact:
|
|
612
|
+
1. Prefer `tzero` MCP tools (`get_project_context_tree`, `analyze_change_impact`, `audit_codebase_quality`) to drastically reduce token usage and preserve context window limit.
|
|
613
|
+
2. Ensure Zero-Leak safety before committing or suggesting code with credentials or secrets.
|
|
614
|
+
"""
|
|
615
|
+
|
|
616
|
+
_ANTIGRAVITY_MCP_INSTRUCTIONS = """# T-Zero MCP Server Instructions & Best Practices
|
|
617
|
+
|
|
618
|
+
T-Zero is an Enterprise-Grade Codebase Context Architect and Token Reduction Engine.
|
|
619
|
+
Developer: Toprak Ahmet Aydoğmuş (Siber Akademi)
|
|
620
|
+
Repository: https://github.com/toprakahmetaydogmus/TZeroAlgorithm
|
|
621
|
+
|
|
622
|
+
## When to Use T-Zero MCP Tools:
|
|
623
|
+
1. **Large Codebases or Deep Trees:** Before reading massive directories or hundreds of files into prompt context, invoke `get_project_context_tree` with `tier="T-2"` or `tier="T-3"`. This reduces token consumption by up to 95% while keeping all class/function AST signatures and types.
|
|
624
|
+
2. **Refactoring & Symbol Edits:** Before modifying, deleting, or renaming functions/classes, call `analyze_change_impact` to assess the blast radius and downstream dependents across the workspace.
|
|
625
|
+
3. **Architecture Mapping:** Use `generate_architecture_blueprint` to produce comprehensive `ARCHITECTURE.md` and live Mermaid dependency diagrams.
|
|
626
|
+
4. **Code Quality & Security:** Use `audit_codebase_quality` to scan for code smells, cyclomatic complexity, dead code, and hardcoded secrets (100% Zero-Leak check).
|
|
627
|
+
5. **Architectural Guardrails:** Enforce boundary rules in `tzero.rules.json` using `enforce_architecture_boundaries` to prevent architectural erosion.
|
|
628
|
+
"""
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def _sync_alternate_claude_desktop_config(
|
|
632
|
+
target: ClientTarget, server_path: Path, server_name: str
|
|
633
|
+
) -> None:
|
|
634
|
+
"""Keep standard AppData and Windows Store / MSIX Claude configs in sync."""
|
|
635
|
+
try:
|
|
636
|
+
home = Path.home()
|
|
637
|
+
app_data = Path(os.environ.get("APPDATA", home / "AppData/Roaming"))
|
|
638
|
+
local_app_data = Path(os.environ.get("LOCALAPPDATA", home / "AppData/Local"))
|
|
639
|
+
entry = server_entry(target, server_path)
|
|
640
|
+
candidates = [app_data / "Claude" / "claude_desktop_config.json"]
|
|
641
|
+
packages_dir = local_app_data / "Packages"
|
|
642
|
+
if packages_dir.is_dir():
|
|
643
|
+
for claude_pkg in packages_dir.glob("Claude_*"):
|
|
644
|
+
candidates.append(
|
|
645
|
+
claude_pkg / "LocalCache" / "Roaming" / "Claude" / "claude_desktop_config.json"
|
|
646
|
+
)
|
|
647
|
+
for alt_path in candidates:
|
|
648
|
+
if alt_path.resolve() == target.config_path.resolve():
|
|
649
|
+
continue
|
|
650
|
+
if alt_path.exists() or alt_path.parent.is_dir():
|
|
651
|
+
cfg: Dict[str, object] = {}
|
|
652
|
+
if alt_path.exists():
|
|
653
|
+
try:
|
|
654
|
+
with alt_path.open("r", encoding="utf-8-sig") as fp:
|
|
655
|
+
cfg = json.load(fp)
|
|
656
|
+
except Exception:
|
|
657
|
+
continue
|
|
658
|
+
if not isinstance(cfg, dict):
|
|
659
|
+
cfg = {}
|
|
660
|
+
servers = cfg.setdefault("mcpServers", {})
|
|
661
|
+
if isinstance(servers, dict) and servers.get(server_name) != entry:
|
|
662
|
+
servers[server_name] = entry
|
|
663
|
+
alt_path.parent.mkdir(parents=True, exist_ok=True)
|
|
664
|
+
with alt_path.open("w", encoding="utf-8") as fp:
|
|
665
|
+
json.dump(cfg, fp, indent=2, ensure_ascii=False)
|
|
666
|
+
fp.write("\n")
|
|
667
|
+
except Exception:
|
|
668
|
+
pass
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def _resource_path(filename: str) -> Path:
|
|
672
|
+
bundle_root = Path(getattr(sys, "_MEIPASS", Path(__file__).resolve().parent))
|
|
673
|
+
path = bundle_root / filename
|
|
674
|
+
if path.is_file():
|
|
675
|
+
return path
|
|
676
|
+
for cand in [
|
|
677
|
+
bundle_root / "dist" / filename,
|
|
678
|
+
Path.cwd() / "dist" / filename,
|
|
679
|
+
Path.cwd() / filename,
|
|
680
|
+
Path(__file__).resolve().parent / "dist" / filename,
|
|
681
|
+
]:
|
|
682
|
+
if cand.is_file():
|
|
683
|
+
return cand
|
|
684
|
+
return path
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
def _install_server_binary(
|
|
688
|
+
source: Path, local_app_data: Optional[Path] = None
|
|
689
|
+
) -> Path:
|
|
690
|
+
if not source.is_file():
|
|
691
|
+
raise FileNotFoundError(f"Bundled MCP server not found: {source}")
|
|
692
|
+
|
|
693
|
+
local_app_data = Path(
|
|
694
|
+
local_app_data or os.environ.get("LOCALAPPDATA", Path.home() / "AppData/Local")
|
|
695
|
+
)
|
|
696
|
+
destination = local_app_data / "TZeroAlgorithm" / SERVER_FILENAME
|
|
697
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
698
|
+
|
|
699
|
+
if destination.exists() and _sha256(source) == _sha256(destination):
|
|
700
|
+
return destination
|
|
701
|
+
|
|
702
|
+
if destination.exists():
|
|
703
|
+
shutil.copy2(destination, _backup_path(destination))
|
|
704
|
+
|
|
705
|
+
temporary_path = destination.with_name(f".{destination.name}.tmp")
|
|
706
|
+
try:
|
|
707
|
+
shutil.copy2(source, temporary_path)
|
|
708
|
+
os.replace(temporary_path, destination)
|
|
709
|
+
finally:
|
|
710
|
+
if temporary_path.exists():
|
|
711
|
+
temporary_path.unlink()
|
|
712
|
+
return destination
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def _sha256(path: Path) -> str:
|
|
716
|
+
digest = hashlib.sha256()
|
|
717
|
+
with path.open("rb") as source_file:
|
|
718
|
+
for chunk in iter(lambda: source_file.read(1024 * 1024), b""):
|
|
719
|
+
digest.update(chunk)
|
|
720
|
+
return digest.hexdigest()
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def resolve_and_install_server() -> Path:
|
|
724
|
+
"""Finds TZeroMCP binary or falls back to tzero_mcp.py script."""
|
|
725
|
+
# 1. Check for bundled or built binary
|
|
726
|
+
primary = _resource_path(SERVER_FILENAME)
|
|
727
|
+
if primary.is_file():
|
|
728
|
+
try:
|
|
729
|
+
return _install_server_binary(primary)
|
|
730
|
+
except Exception:
|
|
731
|
+
return primary
|
|
732
|
+
|
|
733
|
+
# 2. Candidate folders
|
|
734
|
+
root = Path(__file__).resolve().parent
|
|
735
|
+
for cand in [
|
|
736
|
+
root / "dist" / SERVER_FILENAME,
|
|
737
|
+
Path.cwd() / "dist" / SERVER_FILENAME,
|
|
738
|
+
Path.cwd() / SERVER_FILENAME,
|
|
739
|
+
root / SERVER_FILENAME,
|
|
740
|
+
]:
|
|
741
|
+
if cand.is_file():
|
|
742
|
+
try:
|
|
743
|
+
return _install_server_binary(cand)
|
|
744
|
+
except Exception:
|
|
745
|
+
return cand
|
|
746
|
+
|
|
747
|
+
# 3. Python script fallback
|
|
748
|
+
for script_cand in [
|
|
749
|
+
root / "tzero_mcp.py",
|
|
750
|
+
Path.cwd() / "tzero_mcp.py",
|
|
751
|
+
]:
|
|
752
|
+
if script_cand.is_file():
|
|
753
|
+
return script_cand.resolve()
|
|
754
|
+
|
|
755
|
+
raise FileNotFoundError(
|
|
756
|
+
f"Neither '{SERVER_FILENAME}' nor 'tzero_mcp.py' could be found."
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def install_targets(
|
|
761
|
+
targets: List[ClientTarget],
|
|
762
|
+
log_fn: Optional[Callable[[str], None]] = None,
|
|
763
|
+
server_path: Optional[Path] = None,
|
|
764
|
+
) -> Tuple[int, int]:
|
|
765
|
+
"""Installs server to specified client targets, returning (successes, failures)."""
|
|
766
|
+
if log_fn is None:
|
|
767
|
+
log_fn = lambda msg: print(f"[T-Zero MCP] {msg}")
|
|
768
|
+
|
|
769
|
+
if server_path is None:
|
|
770
|
+
server_path = resolve_and_install_server()
|
|
771
|
+
|
|
772
|
+
log_fn(f"Runtime resolved: {server_path}")
|
|
773
|
+
successes = 0
|
|
774
|
+
failures = 0
|
|
775
|
+
for target in targets:
|
|
776
|
+
try:
|
|
777
|
+
result = merge_server_config(target, server_path)
|
|
778
|
+
status = str(result["status"]).capitalize()
|
|
779
|
+
log_fn(f"{status}: {target.name} -> {target.config_path}")
|
|
780
|
+
if result.get("backup"):
|
|
781
|
+
log_fn(f"Backup created: {result['backup']}")
|
|
782
|
+
successes += 1
|
|
783
|
+
except Exception as error:
|
|
784
|
+
failures += 1
|
|
785
|
+
log_fn(f"Failed for {target.name}: {error}")
|
|
786
|
+
|
|
787
|
+
return successes, failures
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
class AddMCPApp:
|
|
791
|
+
def __init__(self, root: object):
|
|
792
|
+
import tkinter as tk
|
|
793
|
+
from tkinter import ttk
|
|
794
|
+
self.root = root # type: ignore
|
|
795
|
+
self.targets = build_client_targets()
|
|
796
|
+
self.check_vars: Dict[str, tk.BooleanVar] = {}
|
|
797
|
+
self.root.title("T-Zero MCP Installer")
|
|
798
|
+
self.root.geometry("720x650")
|
|
799
|
+
self.root.minsize(600, 540)
|
|
800
|
+
self.root.configure(bg="#11151c")
|
|
801
|
+
|
|
802
|
+
icon_path = _resource_path("siber_akademi.ico")
|
|
803
|
+
if icon_path.is_file():
|
|
804
|
+
try:
|
|
805
|
+
self.root.iconbitmap(str(icon_path))
|
|
806
|
+
except Exception:
|
|
807
|
+
pass
|
|
808
|
+
|
|
809
|
+
self._create_widgets()
|
|
810
|
+
|
|
811
|
+
def _create_widgets(self) -> None:
|
|
812
|
+
import tkinter as tk
|
|
813
|
+
from tkinter import ttk
|
|
814
|
+
|
|
815
|
+
container = tk.Frame(self.root, bg="#11151c", padx=24, pady=20)
|
|
816
|
+
container.pack(fill=tk.BOTH, expand=True)
|
|
817
|
+
|
|
818
|
+
tk.Label(
|
|
819
|
+
container,
|
|
820
|
+
text="SIBER AKADEMI / T-ZERO MCP",
|
|
821
|
+
bg="#11151c",
|
|
822
|
+
fg="#22d3ee",
|
|
823
|
+
font=("Segoe UI", 16, "bold"),
|
|
824
|
+
).pack(anchor="w")
|
|
825
|
+
tk.Label(
|
|
826
|
+
container,
|
|
827
|
+
text="Install the bundled local MCP server in supported IDE configurations.",
|
|
828
|
+
bg="#11151c",
|
|
829
|
+
fg="#cbd5e1",
|
|
830
|
+
font=("Segoe UI", 10),
|
|
831
|
+
).pack(anchor="w", pady=(5, 14))
|
|
832
|
+
|
|
833
|
+
self.detected_label = tk.Label(
|
|
834
|
+
container,
|
|
835
|
+
text="Detected clients are selected by default. Existing config files are backed up before changes.",
|
|
836
|
+
bg="#11151c",
|
|
837
|
+
fg="#94a3b8",
|
|
838
|
+
font=("Segoe UI", 9),
|
|
839
|
+
wraplength=660,
|
|
840
|
+
justify="left",
|
|
841
|
+
)
|
|
842
|
+
self.detected_label.pack(anchor="w", pady=(0, 8))
|
|
843
|
+
|
|
844
|
+
list_frame = tk.Frame(container, bg="#1a202b", padx=12, pady=8)
|
|
845
|
+
list_frame.pack(fill=tk.BOTH, expand=True)
|
|
846
|
+
for target in self.targets:
|
|
847
|
+
state = "Detected" if target.detected else "Not detected"
|
|
848
|
+
variable = tk.BooleanVar(value=target.detected)
|
|
849
|
+
self.check_vars[target.key] = variable
|
|
850
|
+
row = tk.Frame(list_frame, bg="#1a202b")
|
|
851
|
+
row.pack(fill=tk.X, pady=2)
|
|
852
|
+
tk.Checkbutton(
|
|
853
|
+
row,
|
|
854
|
+
text=target.name,
|
|
855
|
+
variable=variable,
|
|
856
|
+
bg="#1a202b",
|
|
857
|
+
fg="#f1f5f9",
|
|
858
|
+
activebackground="#1a202b",
|
|
859
|
+
activeforeground="#f1f5f9",
|
|
860
|
+
selectcolor="#0f172a",
|
|
861
|
+
font=("Segoe UI", 10),
|
|
862
|
+
).pack(side=tk.LEFT)
|
|
863
|
+
tk.Label(
|
|
864
|
+
row,
|
|
865
|
+
text=state,
|
|
866
|
+
bg="#1a202b",
|
|
867
|
+
fg="#4ade80" if target.detected else "#64748b",
|
|
868
|
+
font=("Segoe UI", 9),
|
|
869
|
+
).pack(side=tk.RIGHT, padx=6)
|
|
870
|
+
|
|
871
|
+
self.log_text = tk.Text(
|
|
872
|
+
container,
|
|
873
|
+
height=7,
|
|
874
|
+
bg="#080b10",
|
|
875
|
+
fg="#a5f3fc",
|
|
876
|
+
insertbackground="#f8fafc",
|
|
877
|
+
relief=tk.FLAT,
|
|
878
|
+
font=("Consolas", 9),
|
|
879
|
+
state=tk.DISABLED,
|
|
880
|
+
)
|
|
881
|
+
self.log_text.pack(fill=tk.BOTH, expand=False, pady=(12, 8))
|
|
882
|
+
|
|
883
|
+
buttons = tk.Frame(container, bg="#11151c")
|
|
884
|
+
buttons.pack(fill=tk.X)
|
|
885
|
+
ttk.Button(buttons, text="Select all", command=self._select_all).pack(side=tk.LEFT)
|
|
886
|
+
self.install_button = ttk.Button(
|
|
887
|
+
buttons, text="Install to selected IDEs", command=self.install_selected
|
|
888
|
+
)
|
|
889
|
+
self.install_button.pack(side=tk.RIGHT)
|
|
890
|
+
|
|
891
|
+
tk.Label(
|
|
892
|
+
container,
|
|
893
|
+
text="Zero configuration required. Restart IDEs after installation.",
|
|
894
|
+
bg="#11151c",
|
|
895
|
+
fg="#94a3b8",
|
|
896
|
+
font=("Segoe UI", 9),
|
|
897
|
+
).pack(anchor="w", pady=(10, 0))
|
|
898
|
+
|
|
899
|
+
def _select_all(self) -> None:
|
|
900
|
+
for variable in self.check_vars.values():
|
|
901
|
+
variable.set(True)
|
|
902
|
+
|
|
903
|
+
def _log(self, message: str) -> None:
|
|
904
|
+
import tkinter as tk
|
|
905
|
+
self.log_text.configure(state=tk.NORMAL)
|
|
906
|
+
self.log_text.insert(tk.END, message + "\n")
|
|
907
|
+
self.log_text.see(tk.END)
|
|
908
|
+
self.log_text.configure(state=tk.DISABLED)
|
|
909
|
+
|
|
910
|
+
def install_selected(self) -> None:
|
|
911
|
+
import tkinter as tk
|
|
912
|
+
from tkinter import messagebox
|
|
913
|
+
|
|
914
|
+
selected = [
|
|
915
|
+
target for target in self.targets if self.check_vars[target.key].get()
|
|
916
|
+
]
|
|
917
|
+
if not selected:
|
|
918
|
+
messagebox.showwarning("T-Zero MCP Installer", "Select at least one client.")
|
|
919
|
+
return
|
|
920
|
+
|
|
921
|
+
self.install_button.configure(state=tk.DISABLED)
|
|
922
|
+
try:
|
|
923
|
+
successes, failures = install_targets(selected, log_fn=self._log)
|
|
924
|
+
message = f"Configured {successes} client(s)."
|
|
925
|
+
if failures:
|
|
926
|
+
message += f" {failures} client(s) were skipped; see log."
|
|
927
|
+
messagebox.showinfo("T-Zero MCP Installer", message)
|
|
928
|
+
except Exception as error:
|
|
929
|
+
self._log(f"Installer failed: {error}")
|
|
930
|
+
messagebox.showerror("T-Zero MCP Installer", f"Installer failed: {error}")
|
|
931
|
+
finally:
|
|
932
|
+
self.install_button.configure(state=tk.NORMAL)
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
def run_cli() -> int:
|
|
936
|
+
"""Executes the CLI interface when called from command line or with flags."""
|
|
937
|
+
_attach_windows_console()
|
|
938
|
+
|
|
939
|
+
parser = argparse.ArgumentParser(
|
|
940
|
+
prog="addmcp",
|
|
941
|
+
description="T-Zero MCP 1-Click Installer for Cursor, Claude Desktop, Antigravity, VS Code, Windsurf, etc."
|
|
942
|
+
)
|
|
943
|
+
parser.add_argument(
|
|
944
|
+
"--all", "-a", action="store_true",
|
|
945
|
+
help="Install into all detected IDEs without GUI."
|
|
946
|
+
)
|
|
947
|
+
parser.add_argument(
|
|
948
|
+
"--targets", "-t", metavar="KEYS",
|
|
949
|
+
help="Comma-separated list of target keys to install to (e.g. cursor,claude-desktop,antigravity)."
|
|
950
|
+
)
|
|
951
|
+
parser.add_argument(
|
|
952
|
+
"--list", "-l", action="store_true",
|
|
953
|
+
help="List all supported and detected IDE targets."
|
|
954
|
+
)
|
|
955
|
+
parser.add_argument(
|
|
956
|
+
"--gui", "-g", action="store_true",
|
|
957
|
+
help="Explicitly launch the GUI installer window."
|
|
958
|
+
)
|
|
959
|
+
parser.add_argument(
|
|
960
|
+
"--version", "-v", action="version",
|
|
961
|
+
version=f"T-Zero MCP Installer {APP_VERSION}"
|
|
962
|
+
)
|
|
963
|
+
|
|
964
|
+
args, unknown = parser.parse_known_args()
|
|
965
|
+
all_targets = build_client_targets()
|
|
966
|
+
|
|
967
|
+
if args.list:
|
|
968
|
+
print(f"\n[T-Zero MCP Installer v{APP_VERSION}] Available IDE Targets:\n")
|
|
969
|
+
for t in all_targets:
|
|
970
|
+
state = "DETECTED" if t.detected else "not found"
|
|
971
|
+
print(f" [{state:^9}] {t.key:<15} : {t.name} ({t.config_path})")
|
|
972
|
+
print()
|
|
973
|
+
return 0
|
|
974
|
+
|
|
975
|
+
if args.all or args.targets:
|
|
976
|
+
if args.all:
|
|
977
|
+
selected = [t for t in all_targets if t.detected]
|
|
978
|
+
if not selected:
|
|
979
|
+
print("[T-Zero MCP] No installed IDE clients detected automatically.")
|
|
980
|
+
selected = all_targets
|
|
981
|
+
else:
|
|
982
|
+
keys = [k.strip().lower() for k in args.targets.split(",")]
|
|
983
|
+
selected = [t for t in all_targets if t.key.lower() in keys]
|
|
984
|
+
|
|
985
|
+
if not selected:
|
|
986
|
+
print("[T-Zero MCP] No valid targets matched.")
|
|
987
|
+
return 1
|
|
988
|
+
|
|
989
|
+
print(f"\n[T-Zero MCP Installer v{APP_VERSION}] Installing to {len(selected)} target(s)...")
|
|
990
|
+
successes, failures = install_targets(selected, log_fn=lambda m: print(f" -> {m}"))
|
|
991
|
+
print(f"[T-Zero MCP] Completed: {successes} configured, {failures} failed.\n")
|
|
992
|
+
return 0 if failures == 0 else 1
|
|
993
|
+
|
|
994
|
+
# Default action: launch GUI if display available, otherwise list targets
|
|
995
|
+
try:
|
|
996
|
+
import tkinter as tk
|
|
997
|
+
root = tk.Tk()
|
|
998
|
+
AddMCPApp(root)
|
|
999
|
+
root.mainloop()
|
|
1000
|
+
return 0
|
|
1001
|
+
except Exception as e:
|
|
1002
|
+
print(f"[T-Zero MCP] GUI unavailable ({e}). Defaulting to automatic CLI install:")
|
|
1003
|
+
selected = [t for t in all_targets if t.detected]
|
|
1004
|
+
if not selected:
|
|
1005
|
+
selected = all_targets
|
|
1006
|
+
successes, failures = install_targets(selected, log_fn=lambda m: print(f" -> {m}"))
|
|
1007
|
+
return 0 if failures == 0 else 1
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
def main() -> None:
|
|
1011
|
+
sys.exit(run_cli())
|
|
1012
|
+
|
|
1013
|
+
|
|
1014
|
+
if __name__ == "__main__":
|
|
1015
|
+
main()
|