zerochat 8.1.0__tar.gz → 8.2.0__tar.gz
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.
- {zerochat-8.1.0/zerochat.egg-info → zerochat-8.2.0}/PKG-INFO +1 -1
- {zerochat-8.1.0 → zerochat-8.2.0}/pyproject.toml +1 -1
- {zerochat-8.1.0 → zerochat-8.2.0/zerochat.egg-info}/PKG-INFO +1 -1
- {zerochat-8.1.0 → zerochat-8.2.0}/zerochat.py +60 -51
- {zerochat-8.1.0 → zerochat-8.2.0}/LICENSE +0 -0
- {zerochat-8.1.0 → zerochat-8.2.0}/README.md +0 -0
- {zerochat-8.1.0 → zerochat-8.2.0}/setup.cfg +0 -0
- {zerochat-8.1.0 → zerochat-8.2.0}/zerochat.egg-info/SOURCES.txt +0 -0
- {zerochat-8.1.0 → zerochat-8.2.0}/zerochat.egg-info/dependency_links.txt +0 -0
- {zerochat-8.1.0 → zerochat-8.2.0}/zerochat.egg-info/entry_points.txt +0 -0
- {zerochat-8.1.0 → zerochat-8.2.0}/zerochat.egg-info/top_level.txt +0 -0
|
@@ -42,7 +42,7 @@ import webbrowser
|
|
|
42
42
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
43
43
|
from pathlib import Path
|
|
44
44
|
|
|
45
|
-
SOURCE_BACKEND_VERSION = "8.
|
|
45
|
+
SOURCE_BACKEND_VERSION = "8.2.0"
|
|
46
46
|
|
|
47
47
|
def _read_source_version(filename: str) -> str | None:
|
|
48
48
|
"""Lee la versión de un archivo del repositorio cuando se ejecuta desde fuentes."""
|
|
@@ -1488,8 +1488,16 @@ def public_tool_name(server_id: str, original: str) -> str:
|
|
|
1488
1488
|
return name
|
|
1489
1489
|
|
|
1490
1490
|
|
|
1491
|
+
def _mcp_trace_text(value: object, max_len: int = 1000) -> str:
|
|
1492
|
+
"""Normaliza diagnósticos MCP para consola sin exponer secretos comunes."""
|
|
1493
|
+
text = " ".join(str(value).strip().splitlines())
|
|
1494
|
+
text = re.sub(r"(?i)(\b(?:api[_-]?key|token|secret|password)\b\s*[=:]\s*)\S+", r"\1***", text)
|
|
1495
|
+
text = re.sub(r"(?i)(--(?:api[_-]?key|token|secret|password)\s+)\S+", r"\1***", text)
|
|
1496
|
+
return text[:max_len - 3] + "..." if len(text) > max_len else text
|
|
1497
|
+
|
|
1498
|
+
|
|
1491
1499
|
class StdioMcpClient:
|
|
1492
|
-
def __init__(self, command: str, args: list[str], cwd: str, env: dict[str, str]):
|
|
1500
|
+
def __init__(self, command: str, args: list[str], cwd: str, env: dict[str, str], trace=None):
|
|
1493
1501
|
self.command = command
|
|
1494
1502
|
self.args = args
|
|
1495
1503
|
self.cwd = cwd
|
|
@@ -1500,12 +1508,14 @@ class StdioMcpClient:
|
|
|
1500
1508
|
self._lock = threading.Lock()
|
|
1501
1509
|
self._alive = False
|
|
1502
1510
|
self.tools: list[dict] = []
|
|
1511
|
+
self._trace = trace or (lambda _message: None)
|
|
1503
1512
|
|
|
1504
1513
|
def running(self) -> bool:
|
|
1505
1514
|
return self._alive and self.process is not None and self.process.poll() is None
|
|
1506
1515
|
|
|
1507
1516
|
def start(self, handshake_timeout: int = 30):
|
|
1508
1517
|
cmd = [self.command] + self.args
|
|
1518
|
+
self._trace(f"launching: {_mcp_trace_text(' '.join(cmd))}")
|
|
1509
1519
|
self.process = subprocess.Popen(
|
|
1510
1520
|
cmd,
|
|
1511
1521
|
stdin=subprocess.PIPE,
|
|
@@ -1530,11 +1540,14 @@ class StdioMcpClient:
|
|
|
1530
1540
|
self.notify("notifications/initialized")
|
|
1531
1541
|
tools_resp = self.request("tools/list", {}, timeout=10)
|
|
1532
1542
|
self.tools = tools_resp.get("tools", [])
|
|
1543
|
+
self._trace(f"handshake completed; {len(self.tools)} tool(s) available")
|
|
1533
1544
|
|
|
1534
1545
|
def _drain_stderr(self):
|
|
1535
1546
|
if self.process and self.process.stderr:
|
|
1536
|
-
for
|
|
1537
|
-
|
|
1547
|
+
for line in self.process.stderr:
|
|
1548
|
+
line = _mcp_trace_text(line)
|
|
1549
|
+
if line:
|
|
1550
|
+
self._trace(f"stderr: {line}")
|
|
1538
1551
|
|
|
1539
1552
|
def _read_stdout(self):
|
|
1540
1553
|
try:
|
|
@@ -1556,6 +1569,10 @@ class StdioMcpClient:
|
|
|
1556
1569
|
waiter.put(msg)
|
|
1557
1570
|
finally:
|
|
1558
1571
|
self._alive = False
|
|
1572
|
+
if self.process:
|
|
1573
|
+
return_code = self.process.poll()
|
|
1574
|
+
if return_code is not None:
|
|
1575
|
+
self._trace(f"process exited with code {return_code}")
|
|
1559
1576
|
with self._lock:
|
|
1560
1577
|
pending = list(self._pending.values())
|
|
1561
1578
|
self._pending.clear()
|
|
@@ -1634,6 +1651,10 @@ class McpServiceManager:
|
|
|
1634
1651
|
self.services = self._load_services()
|
|
1635
1652
|
self.preferences = self._load_preferences()
|
|
1636
1653
|
|
|
1654
|
+
@staticmethod
|
|
1655
|
+
def _trace(server_id: str, message: str):
|
|
1656
|
+
console_log(f"[{time.strftime('%H:%M:%S')}] [MCP {server_id}] {_mcp_trace_text(message)}", flush=True)
|
|
1657
|
+
|
|
1637
1658
|
def _ensure_default_services(self):
|
|
1638
1659
|
dummy_dir = self.services_root / "dummy_mcp"
|
|
1639
1660
|
dummy_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -1756,8 +1777,10 @@ for raw in sys.stdin:
|
|
|
1756
1777
|
"product": {
|
|
1757
1778
|
"package": "@playwright/mcp",
|
|
1758
1779
|
"version": "0.0.81",
|
|
1759
|
-
"browser": "chromium"
|
|
1760
|
-
|
|
1780
|
+
"browser": "chromium"
|
|
1781
|
+
},
|
|
1782
|
+
"requirements": {
|
|
1783
|
+
"node": {"minimumMajor": 18}
|
|
1761
1784
|
}
|
|
1762
1785
|
}, indent=2), encoding="utf-8")
|
|
1763
1786
|
|
|
@@ -1796,46 +1819,10 @@ for raw in sys.stdin:
|
|
|
1796
1819
|
"type": "npm",
|
|
1797
1820
|
"product": {
|
|
1798
1821
|
"package": "@modelcontextprotocol/server-memory",
|
|
1799
|
-
"version": "2026.8.31"
|
|
1800
|
-
"nodeMajor": 18
|
|
1801
|
-
}
|
|
1802
|
-
}, indent=2), encoding="utf-8")
|
|
1803
|
-
|
|
1804
|
-
# 4. lsp
|
|
1805
|
-
lsp_dir = self.services_root / "lsp"
|
|
1806
|
-
lsp_dir.mkdir(parents=True, exist_ok=True)
|
|
1807
|
-
lsp_service = lsp_dir / "service.json"
|
|
1808
|
-
lsp_installer = lsp_dir / "installer.json"
|
|
1809
|
-
if not lsp_service.exists():
|
|
1810
|
-
lsp_service.write_text(json.dumps({
|
|
1811
|
-
"schemaVersion": 1,
|
|
1812
|
-
"id": "lsp",
|
|
1813
|
-
"displayName": {
|
|
1814
|
-
"es": "LSP y Navegación de Código",
|
|
1815
|
-
"en": "LSP & Code Intelligence"
|
|
1822
|
+
"version": "2026.8.31"
|
|
1816
1823
|
},
|
|
1817
|
-
"
|
|
1818
|
-
"
|
|
1819
|
-
"en": "Language Server Protocol (LSP) server: jump to definitions, symbol search, references, and type inspection without context overload."
|
|
1820
|
-
},
|
|
1821
|
-
"enabledByDefault": False,
|
|
1822
|
-
"transport": "stdio",
|
|
1823
|
-
"launch": {
|
|
1824
|
-
"executable": "${nodeExecutable}",
|
|
1825
|
-
"args": ["${serviceDir}/node_modules/@axivo/mcp-lsp/dist/index.js"],
|
|
1826
|
-
"cwd": "${serviceDir}",
|
|
1827
|
-
"env": {},
|
|
1828
|
-
"handshakeTimeoutSeconds": 30
|
|
1829
|
-
}
|
|
1830
|
-
}, indent=2), encoding="utf-8")
|
|
1831
|
-
if not lsp_installer.exists():
|
|
1832
|
-
lsp_installer.write_text(json.dumps({
|
|
1833
|
-
"schemaVersion": 1,
|
|
1834
|
-
"type": "npm",
|
|
1835
|
-
"product": {
|
|
1836
|
-
"package": "@axivo/mcp-lsp",
|
|
1837
|
-
"version": "1.0.5",
|
|
1838
|
-
"nodeMajor": 24
|
|
1824
|
+
"requirements": {
|
|
1825
|
+
"node": {"minimumMajor": 18}
|
|
1839
1826
|
}
|
|
1840
1827
|
}, indent=2), encoding="utf-8")
|
|
1841
1828
|
|
|
@@ -1907,6 +1894,7 @@ for raw in sys.stdin:
|
|
|
1907
1894
|
npm = shutil.which("npm") or "npm"
|
|
1908
1895
|
|
|
1909
1896
|
if installer_file.exists():
|
|
1897
|
+
self._trace(server_id, f"reading installer: {installer_file.name}")
|
|
1910
1898
|
marker = service_dir / ".installed.json"
|
|
1911
1899
|
try:
|
|
1912
1900
|
installer = json.loads(installer_file.read_text(encoding="utf-8"))
|
|
@@ -1922,16 +1910,24 @@ for raw in sys.stdin:
|
|
|
1922
1910
|
if not shutil.which("node") or not shutil.which("npm"):
|
|
1923
1911
|
raise RuntimeError("Node.js y npm son necesarios para instalar este servicio MCP")
|
|
1924
1912
|
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1913
|
+
requirements = installer.get("requirements", {})
|
|
1914
|
+
node_requirement = requirements.get("node")
|
|
1915
|
+
if node_requirement is None:
|
|
1916
|
+
# Compatibilidad con instaladores creados antes de requirements.node.
|
|
1917
|
+
required_node_major = int(product.get("nodeMajor", 18))
|
|
1918
|
+
elif not isinstance(node_requirement, dict):
|
|
1919
|
+
raise RuntimeError("El requisito Node.js debe ser un objeto en installer.json")
|
|
1920
|
+
else:
|
|
1921
|
+
required_node_major = node_requirement.get("minimumMajor")
|
|
1922
|
+
if not isinstance(required_node_major, int) or isinstance(required_node_major, bool) or required_node_major < 1:
|
|
1923
|
+
raise RuntimeError("El requisito Node.js debe definir minimumMajor como un entero positivo")
|
|
1929
1924
|
version_result = subprocess.run(
|
|
1930
1925
|
[node, "--version"], capture_output=True, text=True, timeout=5
|
|
1931
1926
|
)
|
|
1932
|
-
version_match = re.match(r"v(
|
|
1927
|
+
version_match = re.match(r"v(\d+)", version_result.stdout.strip())
|
|
1933
1928
|
if version_result.returncode != 0 or not version_match:
|
|
1934
1929
|
raise RuntimeError("No se pudo comprobar la versión de Node.js necesaria para este servicio MCP")
|
|
1930
|
+
self._trace(server_id, f"Node.js detected: {version_result.stdout.strip()} (minimum {required_node_major})")
|
|
1935
1931
|
if int(version_match.group(1)) < required_node_major:
|
|
1936
1932
|
raise RuntimeError(
|
|
1937
1933
|
f"{server_id} requiere Node.js {required_node_major}+; se detectó {version_result.stdout.strip()}"
|
|
@@ -1949,11 +1945,13 @@ for raw in sys.stdin:
|
|
|
1949
1945
|
|
|
1950
1946
|
if needs_install:
|
|
1951
1947
|
self.states[server_id] = "installing"
|
|
1948
|
+
self._trace(server_id, f"installing npm package: {package}@{version}")
|
|
1952
1949
|
manifest = service_dir / "package.json"
|
|
1953
1950
|
manifest.write_text(json.dumps({"private": True, "dependencies": {package: version}}, indent=2), encoding="utf-8")
|
|
1954
1951
|
res = subprocess.run([npm, "install", "--ignore-scripts", "--no-audit", "--no-fund"], cwd=service_dir, capture_output=True, text=True, timeout=600)
|
|
1955
1952
|
if res.returncode != 0:
|
|
1956
1953
|
raise RuntimeError(f"Fallo instalando dependencias npm: {res.stderr or res.stdout}")
|
|
1954
|
+
self._trace(server_id, f"npm package installed: {package}@{version}")
|
|
1957
1955
|
installation = {"type": "npm", "package": package, "version": version, "nodeExecutable": node}
|
|
1958
1956
|
browser = product.get("browser")
|
|
1959
1957
|
if browser:
|
|
@@ -1971,6 +1969,8 @@ for raw in sys.stdin:
|
|
|
1971
1969
|
)
|
|
1972
1970
|
installation["browser"] = browser
|
|
1973
1971
|
marker.write_text(json.dumps(installation, indent=2), encoding="utf-8")
|
|
1972
|
+
else:
|
|
1973
|
+
self._trace(server_id, f"npm package already installed: {package}@{version}")
|
|
1974
1974
|
|
|
1975
1975
|
return {
|
|
1976
1976
|
"serviceDir": str(service_dir),
|
|
@@ -1988,6 +1988,8 @@ for raw in sys.stdin:
|
|
|
1988
1988
|
if current and current.running():
|
|
1989
1989
|
return self.list_servers()
|
|
1990
1990
|
self.states[server_id] = "starting"
|
|
1991
|
+
client = None
|
|
1992
|
+
self._trace(server_id, "start requested")
|
|
1991
1993
|
try:
|
|
1992
1994
|
service_dir = server["_directory"]
|
|
1993
1995
|
values = self._prepare_service(server_id, server)
|
|
@@ -2015,7 +2017,10 @@ for raw in sys.stdin:
|
|
|
2015
2017
|
for k, v in launch.get("env", {}).items():
|
|
2016
2018
|
env[k] = self._expand(v, values)
|
|
2017
2019
|
|
|
2018
|
-
client = StdioMcpClient(
|
|
2020
|
+
client = StdioMcpClient(
|
|
2021
|
+
command, args, str(service_dir), env,
|
|
2022
|
+
trace=lambda message: self._trace(server_id, message)
|
|
2023
|
+
)
|
|
2019
2024
|
client.start(int(launch.get("handshakeTimeoutSeconds", 15)))
|
|
2020
2025
|
self.clients[server_id] = client
|
|
2021
2026
|
self.states[server_id] = "running"
|
|
@@ -2023,11 +2028,15 @@ for raw in sys.stdin:
|
|
|
2023
2028
|
entry = self.preferences.setdefault(server_id, {})
|
|
2024
2029
|
entry["enabled"] = True
|
|
2025
2030
|
self._save_preferences()
|
|
2031
|
+
self._trace(server_id, "started successfully")
|
|
2026
2032
|
except Exception as exc:
|
|
2027
2033
|
self.states[server_id] = "error"
|
|
2028
2034
|
self.errors[server_id] = str(exc)
|
|
2035
|
+
self._trace(server_id, f"start failed: {exc}")
|
|
2029
2036
|
if server_id in self.clients:
|
|
2030
2037
|
self.clients.pop(server_id).stop()
|
|
2038
|
+
elif client:
|
|
2039
|
+
client.stop()
|
|
2031
2040
|
return self.list_servers()
|
|
2032
2041
|
|
|
2033
2042
|
def stop(self, server_id: str) -> list[dict]:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|