zerochat 8.3.0__tar.gz → 8.5.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.3.0/zerochat.egg-info → zerochat-8.5.0}/PKG-INFO +1 -1
- {zerochat-8.3.0 → zerochat-8.5.0}/pyproject.toml +1 -1
- {zerochat-8.3.0 → zerochat-8.5.0/zerochat.egg-info}/PKG-INFO +1 -1
- {zerochat-8.3.0 → zerochat-8.5.0}/zerochat.py +38 -261
- {zerochat-8.3.0 → zerochat-8.5.0}/LICENSE +0 -0
- {zerochat-8.3.0 → zerochat-8.5.0}/README.md +0 -0
- {zerochat-8.3.0 → zerochat-8.5.0}/setup.cfg +0 -0
- {zerochat-8.3.0 → zerochat-8.5.0}/zerochat.egg-info/SOURCES.txt +0 -0
- {zerochat-8.3.0 → zerochat-8.5.0}/zerochat.egg-info/dependency_links.txt +0 -0
- {zerochat-8.3.0 → zerochat-8.5.0}/zerochat.egg-info/entry_points.txt +0 -0
- {zerochat-8.3.0 → zerochat-8.5.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.5.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."""
|
|
@@ -388,6 +388,24 @@ def check_version():
|
|
|
388
388
|
# Modo offline o timeout ignorado de forma segura
|
|
389
389
|
pass
|
|
390
390
|
# ==============================================================================
|
|
391
|
+
# Generated from services/ by scripts/build-managed-services.mjs. Do not edit.
|
|
392
|
+
# ==============================================================================
|
|
393
|
+
|
|
394
|
+
import json
|
|
395
|
+
|
|
396
|
+
MANAGED_SERVICE_FILES: dict[str, str] = json.loads("{\n \"composio/installer.json\": \"{\\n \\\"schemaVersion\\\": 1,\\n \\\"type\\\": \\\"npm\\\",\\n \\\"product\\\": {\\n \\\"package\\\": \\\"mcp-remote\\\",\\n \\\"version\\\": \\\"0.13.5\\\"\\n },\\n \\\"requirements\\\": {\\n \\\"node\\\": {\\n \\\"minimumMajor\\\": 18\\n }\\n }\\n}\\n\",\n \"composio/service.json\": \"{\\n \\\"schemaVersion\\\": 1,\\n \\\"id\\\": \\\"composio\\\",\\n \\\"displayName\\\": {\\n \\\"es\\\": \\\"Composio Connect\\\",\\n \\\"en\\\": \\\"Composio Connect\\\"\\n },\\n \\\"description\\\": {\\n \\\"es\\\": \\\"MCP remoto para acceder a aplicaciones como Gmail, Google Drive, Slack o GitHub. Composio solicita la autorización de cada aplicación cuando sea necesaria.\\\",\\n \\\"en\\\": \\\"Remote MCP for apps such as Gmail, Google Drive, Slack, and GitHub. Composio requests authorization for each app when it is needed.\\\"\\n },\\n \\\"help\\\": {\\n \\\"url\\\": \\\"https://docs.composio.dev/docs/composio-connect\\\",\\n \\\"label\\\": {\\n \\\"es\\\": \\\"Guía oficial de Composio Connect\\\",\\n \\\"en\\\": \\\"Composio Connect official guide\\\"\\n }\\n },\\n \\\"enabledByDefault\\\": false,\\n \\\"transport\\\": \\\"stdio\\\",\\n \\\"remote\\\": {\\n \\\"type\\\": \\\"mcp-remote\\\",\\n \\\"transport\\\": \\\"streamable-http\\\",\\n \\\"url\\\": \\\"https://connect.composio.dev/mcp\\\",\\n \\\"authentication\\\": \\\"composio-connect\\\"\\n },\\n \\\"launch\\\": {\\n \\\"executable\\\": \\\"${nodeExecutable}\\\",\\n \\\"args\\\": [\\n \\\"${serviceDir}/node_modules/mcp-remote/dist/proxy.js\\\",\\n \\\"https://connect.composio.dev/mcp\\\",\\n \\\"--protocol\\\",\\n \\\"legacy\\\",\\n \\\"--static-oauth-client-metadata\\\",\\n \\\"{\\\\\\\"client_name\\\\\\\":\\\\\\\"zerochat-mcp-remote\\\\\\\"}\\\",\\n \\\"--auth-timeout\\\",\\n \\\"120\\\"\\n ],\\n \\\"cwd\\\": \\\"${serviceDir}\\\",\\n \\\"env\\\": {\\n \\\"HOME\\\": \\\"${serviceDir}\\\",\\n \\\"USERPROFILE\\\": \\\"${serviceDir}\\\"\\n },\\n \\\"handshakeTimeoutSeconds\\\": 150\\n }\\n}\\n\",\n \"ejemplo/ejemplo_mcp_server.py\": \"#!/usr/bin/env python3\\nimport json\\nimport sys\\n\\ndef reply(req_id, result=None, error=None):\\n resp = {\\\"jsonrpc\\\": \\\"2.0\\\", \\\"id\\\": req_id}\\n if error:\\n resp[\\\"error\\\"] = error\\n else:\\n resp[\\\"result\\\"] = result\\n sys.stdout.write(json.dumps(resp) + \\\"\\\\n\\\")\\n sys.stdout.flush()\\n\\nfor raw in sys.stdin:\\n try:\\n req = json.loads(raw)\\n except Exception:\\n continue\\n req_id = req.get(\\\"id\\\")\\n method = req.get(\\\"method\\\")\\n params = req.get(\\\"params\\\", {})\\n if method == \\\"initialize\\\":\\n reply(req_id, {\\n \\\"protocolVersion\\\": \\\"2024-11-05\\\",\\n \\\"serverInfo\\\": {\\\"name\\\": \\\"ZeroChat MCP Example\\\", \\\"version\\\": \\\"1.0.0\\\"},\\n \\\"capabilities\\\": {\\\"tools\\\": {}}\\n })\\n elif method == \\\"tools/list\\\":\\n reply(req_id, {\\\"tools\\\": [{\\n \\\"name\\\": \\\"echo\\\",\\n \\\"description\\\": \\\"Echo back a message for testing.\\\",\\n \\\"inputSchema\\\": {\\n \\\"type\\\": \\\"object\\\",\\n \\\"properties\\\": {\\\"message\\\": {\\\"type\\\": \\\"string\\\", \\\"description\\\": \\\"Message to echo.\\\"}},\\n \\\"required\\\": [\\\"message\\\"]\\n }\\n }]})\\n elif method == \\\"tools/call\\\":\\n if params.get(\\\"name\\\") != \\\"echo\\\":\\n reply(req_id, error={\\\"code\\\": -32601, \\\"message\\\": \\\"Tool not found\\\"})\\n continue\\n msg = params.get(\\\"arguments\\\", {}).get(\\\"message\\\", \\\"\\\")\\n reply(req_id, {\\n \\\"content\\\": [{\\\"type\\\": \\\"text\\\", \\\"text\\\": f\\\"echo: {msg}\\\"}],\\n \\\"isError\\\": False\\n })\\n\",\n \"ejemplo/installer.json.example\": \"{\\n \\\"schemaVersion\\\": 1,\\n \\\"type\\\": \\\"npm\\\",\\n \\\"product\\\": {\\n \\\"package\\\": \\\"@scope/mcp-package\\\",\\n \\\"version\\\": \\\"1.2.3\\\"\\n },\\n \\\"requirements\\\": {\\n \\\"node\\\": {\\n \\\"minimumMajor\\\": 18\\n }\\n }\\n}\\n\",\n \"ejemplo/README.md\": \"# Crear un MCP gestionado por ZeroChat\\n\\nEsta carpeta es la referencia para crear servicios MCP gestionados. La ubicación de producción es el directorio personal del usuario, seguido de `zerochat/services` (por ejemplo, `/home/usuario/zerochat/services`). No uses `~`, `$HOME` ni una ruta relativa como `services/`: primero identifica la ruta absoluta del directorio personal y úsala para localizar este archivo. El nuevo servicio se crea como carpeta hermana de `ejemplo`, nunca dentro de ella.\\n\\n## Procedimiento obligatorio\\n\\n1. Lee `README.md`, `service.json` y `installer.json.example` de la carpeta `ejemplo` situada en el directorio de datos de ZeroChat.\\n2. Consulta solo la documentación oficial del MCP solicitado.\\n3. Crea una carpeta con un identificador seguro y descriptivo junto a `ejemplo`. Usa un `id` único en `service.json`, también cuando copies otro servicio.\\n4. Crea `service.json` copiando esta estructura y adapta únicamente los valores necesarios.\\n5. Crea `installer.json` solo si el MCP se distribuye como paquete npm. Copia `installer.json.example`, fija la versión exacta y declara la versión mínima de Node.js.\\n6. Añade `help.url` con la página oficial de instalación y etiquetas en español e inglés.\\n7. Si requiere una API key, declara la variable que exige el MCP en `launch.env` con el valor literal `PASTE_API_KEY_HERE`. No pidas la clave al usuario ni la escribas en el chat: indica la ruta absoluta del JSON y el campo que debe editar a mano. El usuario guardará su clave únicamente en el JSON local de su carpeta propia.\\n8. No instales ni inicies el servicio. Al terminar, informa de los archivos creados, fuentes oficiales y requisitos pendientes.\\n\\n## Límites de ZeroChat\\n\\nZeroChat gestiona procesos MCP por `stdio`. Puede instalar paquetes npm mediante `installer.json` y arrancarlos con `${nodeExecutable}`. También puede arrancar un ejecutable o script ya disponible con `launch`.\\n\\nZeroChat también puede usar `mcp-remote` como puente para un MCP remoto HTTP/SSE con OAuth o API key. Decláralo como un servicio `stdio`: el proceso local debe ser `mcp-remote`, mientras que `remote` describe el endpoint remoto para la interfaz. Conserva las credenciales y el almacén OAuth dentro de `${serviceDir}`.\\n\\n## Contratos JSON\\n\\n- `service.json` es obligatorio. Conserva `schemaVersion`, `id`, textos bilingües, `transport: \\\"stdio\\\"` y `launch`.\\n- En `launch`, usa `${serviceDir}`, `${nodeExecutable}` o `${pythonExecutable}`; no uses rutas absolutas de usuario.\\n- `installer.json` es opcional y hoy solo admite `type: \\\"npm\\\"`.\\n- `help` es opcional, pero debe incluirse en todo servicio creado:\\n\\n```json\\n\\\"help\\\": {\\n \\\"url\\\": \\\"https://documentacion-oficial.example/install\\\",\\n \\\"label\\\": { \\\"es\\\": \\\"Instalación oficial\\\", \\\"en\\\": \\\"Official installation\\\" }\\n}\\n```\\n\\n## Prompt para el usuario\\n\\nSustituye únicamente `<nombre-del-mcp>`:\\n\\n```text\\nPara instalar el MCP <nombre-del-mcp>, lee primero services/ejemplo/README.md y sigue exactamente sus instrucciones.\\n```\\n\\n## Servicios remotos y claves API\\n\\nPara un remoto, lee también `service.json` e `installer.json` de la carpeta hermana `composio`. Crea una carpeta nueva con nombre e `id` propios; usa Composio como ejemplo del puente local, conservando `transport: \\\"stdio\\\"`. Consulta la documentación oficial del proveedor y de [mcp-remote](https://github.com/punkpeye/mcp-remote#custom-headers) para adaptar endpoint, transporte, cabeceras y autenticación. No copies sin verificar las opciones `--protocol`, los metadatos de cliente ni los parámetros OAuth específicos de Composio. Conserva `HOME` y `USERPROFILE` dentro de `${serviceDir}`.\\n\\nPara un MCP local que requiera una variable, deja este campo dentro de `launch`, conservando sus demás campos y usando el nombre de variable documentado por el MCP:\\n\\n```json\\n\\\"env\\\": {\\n \\\"SERVICE_API_KEY\\\": \\\"PASTE_API_KEY_HERE\\\"\\n}\\n```\\n\\nPara un remoto que acepte `Authorization: Bearer`, los campos dentro de `launch` pueden ser:\\n\\n```json\\n\\\"args\\\": [\\n \\\"${serviceDir}/node_modules/mcp-remote/dist/proxy.js\\\",\\n \\\"https://mcp.example.com/mcp\\\",\\n \\\"--header\\\",\\n \\\"Authorization: Bearer ${REMOTE_API_KEY}\\\"\\n],\\n\\\"env\\\": {\\n \\\"HOME\\\": \\\"${serviceDir}\\\",\\n \\\"USERPROFILE\\\": \\\"${serviceDir}\\\",\\n \\\"REMOTE_API_KEY\\\": \\\"PASTE_API_KEY_HERE\\\"\\n}\\n```\\n\\nAdapta la cabecera y su formato a lo que exige el proveedor. `remote.authentication` solo describe el servicio; no configura la autenticación. La referencia `${REMOTE_API_KEY}` en la cabecera la resuelve `mcp-remote`. ZeroChat no resuelve variables de entorno genéricas: en `launch.env`, `${SERVICE_API_KEY}` se pasaría como texto literal. Solo sustituye `${serviceDir}`, `${nodeExecutable}`, `${pythonExecutable}` y `${option:...}`. No pongas claves reales en argumentos ni en URLs.\\n\\n### Pasos para el usuario\\n\\n1. Usa una carpeta propia bajo el directorio de servicios, con nombre e `id` únicos. Si copias un servicio incluido, copia sus definiciones, no sus dependencias ni credenciales OAuth. No edites `ejemplo`, `composio`, `playwright` ni `memory`: sus archivos distribuidos se restauran al arrancar.\\n2. Abre el `service.json` de tu carpeta y sustituye `PASTE_API_KEY_HERE` por la clave real en `launch.env`. Mantén el JSON válido y los demás campos. No necesitas `export`, `.env` ni otro JSON de credenciales.\\n3. Restringe el archivo a tu usuario con `chmod 600 /ruta/absoluta/zerochat/services/mi-servicio/service.json` en Linux o Termux. La clave queda en texto plano: no compartas el archivo ni lo subas al repositorio o al chat.\\n4. Inicia el servicio desde Ajustes → MCP. Si la nueva carpeta no aparece, reinicia ZeroChat y conecta de nuevo. Para cambiar una clave, detén primero el servicio, edita el JSON y vuelve a iniciarlo.\\n5. ZeroChat conserva los servicios propios al reiniciar y actualizar. Borrar el directorio de datos elimina también esas claves: guarda una copia privada si la necesitas.\\n\\n## English: creating local and remote services\\n\\n1. Locate the user's absolute home directory and read `README.md`, `service.json` and `installer.json.example` in `zerochat/services/ejemplo`.\\n2. Read the requested MCP's official documentation. Create a sibling folder with its own name and unique JSON `id`; do not modify bundled definitions. Preserve bilingual text, `schemaVersion`, `transport: \\\"stdio\\\"`, `launch` and official `help` links.\\n3. For npm services, add `installer.json` with an exact package version and minimum Node.js major version. Otherwise use an existing executable or script.\\n4. For remote HTTP/SSE MCPs, also read the sibling `composio` JSON files and use its managed `mcp-remote` bridge as the example. Adapt endpoint, transport and authentication to the official provider and bridge documentation. Verify Composio-specific protocol and OAuth options before copying them. Keep `HOME` and `USERPROFILE` isolated inside `${serviceDir}`.\\n5. If an API key is required, put the literal `PASTE_API_KEY_HERE` under the required variable in `launch.env`. Do not request the user's key or place real secrets in chat, arguments or URLs. Give the absolute JSON path and exact field to edit. For remote header authentication, use `--header` with the provider's required header and a reference to the variable, as shown above; `mcp-remote` resolves that reference. `remote.authentication` is descriptive only.\\n6. Do not install or start the service. Report created files, official sources and outstanding requirements; the user will start it from Settings → MCP.\\n\\nThe user replaces `PASTE_API_KEY_HERE` directly in their own folder's JSON, preserves valid JSON and existing fields, and restricts the file with `chmod 600 /absolute/path/zerochat/services/my-service/service.json` on Linux/Termux. No shell export, `.env` or separate credentials JSON is needed. ZeroChat only expands service/interpreter paths and `${option:...}`, not generic environment placeholders in `launch.env`. The key is stored in plain text: do not share or commit the file or paste it into chat.\\n\\nIf a new folder does not appear, restart ZeroChat and reconnect. Stop an active service before editing its key, then start it again. User-created folders survive restarts and updates. Bundled definitions are restored on startup: copy them into a new folder and change the JSON `id` to customize them, without copying dependencies or OAuth credentials. Deleting the data directory also deletes custom services and their keys; keep a private backup if needed.\\n\",\n \"ejemplo/service.json\": \"{\\n \\\"schemaVersion\\\": 1,\\n \\\"id\\\": \\\"ejemplo\\\",\\n \\\"displayName\\\": {\\n \\\"es\\\": \\\"Ejemplo de MCP\\\",\\n \\\"en\\\": \\\"MCP example\\\"\\n },\\n \\\"description\\\": {\\n \\\"es\\\": \\\"Plantilla mínima para crear servicios MCP gestionados por ZeroChat. Muestra el protocolo stdio y la estructura de configuración que debe seguir un nuevo MCP.\\\",\\n \\\"en\\\": \\\"Minimal template for creating ZeroChat-managed MCP services. It demonstrates the stdio protocol and configuration structure for a new MCP.\\\"\\n },\\n \\\"help\\\": {\\n \\\"url\\\": \\\"help/mcp.html#crear-mcp-con-agente\\\",\\n \\\"label\\\": {\\n \\\"es\\\": \\\"Guía para crear un MCP gestionado\\\",\\n \\\"en\\\": \\\"Guide to creating a managed MCP\\\"\\n }\\n },\\n \\\"enabledByDefault\\\": false,\\n \\\"transport\\\": \\\"stdio\\\",\\n \\\"launch\\\": {\\n \\\"executable\\\": \\\"${pythonExecutable}\\\",\\n \\\"args\\\": [\\\"${serviceDir}/ejemplo_mcp_server.py\\\"],\\n \\\"cwd\\\": \\\"${serviceDir}\\\",\\n \\\"env\\\": {},\\n \\\"handshakeTimeoutSeconds\\\": 10\\n }\\n}\\n\",\n \"memory/installer.json\": \"{\\n \\\"schemaVersion\\\": 1,\\n \\\"type\\\": \\\"npm\\\",\\n \\\"product\\\": {\\n \\\"package\\\": \\\"@modelcontextprotocol/server-memory\\\",\\n \\\"version\\\": \\\"2026.8.31\\\"\\n },\\n \\\"requirements\\\": {\\n \\\"node\\\": {\\n \\\"minimumMajor\\\": 18\\n }\\n }\\n}\\n\",\n \"memory/service.json\": \"{\\n \\\"schemaVersion\\\": 1,\\n \\\"id\\\": \\\"memory\\\",\\n \\\"displayName\\\": {\\n \\\"es\\\": \\\"Memoria y Grafos (Knowledge Graph)\\\",\\n \\\"en\\\": \\\"Memory & Knowledge Graph\\\"\\n },\\n \\\"description\\\": {\\n \\\"es\\\": \\\"Almacenamiento persistente de entidades, preferencias y contexto histórico estructurado en un grafo de conocimiento.\\\",\\n \\\"en\\\": \\\"Persistent storage of entities, preferences, and historical context structured as a knowledge graph.\\\"\\n },\\n \\\"enabledByDefault\\\": false,\\n \\\"transport\\\": \\\"stdio\\\",\\n \\\"launch\\\": {\\n \\\"executable\\\": \\\"${nodeExecutable}\\\",\\n \\\"args\\\": [\\\"${serviceDir}/node_modules/@modelcontextprotocol/server-memory/dist/index.js\\\"],\\n \\\"cwd\\\": \\\"${serviceDir}\\\",\\n \\\"env\\\": {\\n \\\"MEMORY_FILE_PATH\\\": \\\"${serviceDir}/memory.jsonl\\\"\\n },\\n \\\"handshakeTimeoutSeconds\\\": 30\\n }\\n}\\n\\n\",\n \"playwright/installer.json\": \"{\\n \\\"schemaVersion\\\": 1,\\n \\\"type\\\": \\\"npm\\\",\\n \\\"product\\\": {\\n \\\"package\\\": \\\"@playwright/mcp\\\",\\n \\\"version\\\": \\\"0.0.81\\\",\\n \\\"browser\\\": \\\"chromium\\\"\\n },\\n \\\"requirements\\\": {\\n \\\"node\\\": {\\n \\\"minimumMajor\\\": 18\\n }\\n }\\n}\\n\",\n \"playwright/service.json\": \"{\\n \\\"schemaVersion\\\": 1,\\n \\\"id\\\": \\\"playwright\\\",\\n \\\"displayName\\\": {\\n \\\"es\\\": \\\"Playwright MCP\\\",\\n \\\"en\\\": \\\"Playwright MCP\\\"\\n },\\n \\\"description\\\": {\\n \\\"es\\\": \\\"Automatización de navegador mediante el servidor MCP oficial de Playwright.\\\",\\n \\\"en\\\": \\\"Browser automation through the official Playwright MCP server.\\\"\\n },\\n \\\"enabledByDefault\\\": false,\\n \\\"transport\\\": \\\"stdio\\\",\\n \\\"launch\\\": {\\n \\\"executable\\\": \\\"${nodeExecutable}\\\",\\n \\\"args\\\": [\\\"${serviceDir}/node_modules/@playwright/mcp/cli.js\\\", \\\"--browser=chromium\\\"],\\n \\\"cwd\\\": \\\"${serviceDir}\\\",\\n \\\"env\\\": {},\\n \\\"handshakeTimeoutSeconds\\\": 30\\n },\\n \\\"options\\\": [\\n {\\n \\\"id\\\": \\\"headless\\\",\\n \\\"type\\\": \\\"boolean\\\",\\n \\\"label\\\": {\\n \\\"es\\\": \\\"Navegación en segundo plano (Headless)\\\",\\n \\\"en\\\": \\\"Headless background mode\\\"\\n },\\n \\\"description\\\": {\\n \\\"es\\\": \\\"Desactívalo para ver la ventana del navegador durante la automatización\\\",\\n \\\"en\\\": \\\"Disable to display the browser window during automation\\\"\\n },\\n \\\"default\\\": true,\\n \\\"argsWhenTrue\\\": [\\\"--headless\\\"],\\n \\\"argsWhenFalse\\\": []\\n }\\n ]\\n}\\n\\n\",\n \"scripts/xdg-open\": \"#!/data/data/com.termux/files/usr/bin/bash\\nexec \\\"${PREFIX:-/data/data/com.termux/files/usr}/bin/termux-open-url\\\" \\\"$@\\\"\\n\"\n}")
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def materialize_managed_services(services_root: Path | None = None) -> None:
|
|
400
|
+
"""Installs the managed service files without deleting user data or services."""
|
|
401
|
+
root = Path(services_root) if services_root else get_data_dir() / "services"
|
|
402
|
+
for relative_path, content in MANAGED_SERVICE_FILES.items():
|
|
403
|
+
destination = root / relative_path
|
|
404
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
405
|
+
destination.write_text(content, encoding="utf-8")
|
|
406
|
+
if relative_path.startswith("scripts/"):
|
|
407
|
+
destination.chmod(0o755)
|
|
408
|
+
# ==============================================================================
|
|
391
409
|
# Herramientas Locales Core
|
|
392
410
|
# ==============================================================================
|
|
393
411
|
|
|
@@ -1491,9 +1509,11 @@ def public_tool_name(server_id: str, original: str) -> str:
|
|
|
1491
1509
|
def encode(value: str, tool: bool = False) -> str:
|
|
1492
1510
|
if not isinstance(value, str) or not value or len(value) > 256:
|
|
1493
1511
|
raise ValueError("Componente de nombre MCP no válido")
|
|
1494
|
-
|
|
1512
|
+
# MCP function names preserve ASCII case, which keeps remote tool
|
|
1513
|
+
# names readable while still escaping unsupported characters.
|
|
1514
|
+
return "".join(ch if ("a" <= ch <= "z" or "A" <= ch <= "Z" or "0" <= ch <= "9" or (tool and ch == "_"))
|
|
1495
1515
|
else f"z{ord(ch):x}z" for ch in value)
|
|
1496
|
-
name = f"
|
|
1516
|
+
name = f"{encode(server_id)}_{encode(original, True)}"
|
|
1497
1517
|
if len(name) > 64:
|
|
1498
1518
|
raise ValueError(f"El nombre público de la herramienta MCP excede 64 caracteres: {name}")
|
|
1499
1519
|
return name
|
|
@@ -1504,6 +1524,7 @@ def _mcp_trace_text(value: object, max_len: int = 1000) -> str:
|
|
|
1504
1524
|
text = " ".join(str(value).strip().splitlines())
|
|
1505
1525
|
text = re.sub(r"(?i)(\b(?:api[_-]?key|token|secret|password)\b\s*[=:]\s*)\S+", r"\1***", text)
|
|
1506
1526
|
text = re.sub(r"(?i)(--(?:api[_-]?key|token|secret|password)\s+)\S+", r"\1***", text)
|
|
1527
|
+
text = re.sub(r"(?i)([?&](?:access_token|code|id_token|refresh_token|state)=)[^&\s]+", r"\1***", text)
|
|
1507
1528
|
return text[:max_len - 3] + "..." if len(text) > max_len else text
|
|
1508
1529
|
|
|
1509
1530
|
|
|
@@ -1654,7 +1675,7 @@ class McpServiceManager:
|
|
|
1654
1675
|
self.states: dict[str, str] = {}
|
|
1655
1676
|
self.errors: dict[str, str] = {}
|
|
1656
1677
|
self._lock = threading.Lock()
|
|
1657
|
-
self.
|
|
1678
|
+
materialize_managed_services(self.services_root)
|
|
1658
1679
|
self.services = self._load_services()
|
|
1659
1680
|
self.preferences = self._load_preferences()
|
|
1660
1681
|
|
|
@@ -1662,246 +1683,6 @@ class McpServiceManager:
|
|
|
1662
1683
|
def _trace(server_id: str, message: str):
|
|
1663
1684
|
console_log(f"[{time.strftime('%H:%M:%S')}] [MCP {server_id}] {_mcp_trace_text(message)}", flush=True)
|
|
1664
1685
|
|
|
1665
|
-
def _ensure_default_services(self):
|
|
1666
|
-
example_dir = self.services_root / "ejemplo"
|
|
1667
|
-
example_dir.mkdir(parents=True, exist_ok=True)
|
|
1668
|
-
service_json_file = example_dir / "service.json"
|
|
1669
|
-
example_server_file = example_dir / "ejemplo_mcp_server.py"
|
|
1670
|
-
example_readme_file = example_dir / "README.md"
|
|
1671
|
-
installer_example_file = example_dir / "installer.json.example"
|
|
1672
|
-
|
|
1673
|
-
if not service_json_file.exists():
|
|
1674
|
-
service_json_file.write_text(json.dumps({
|
|
1675
|
-
"schemaVersion": 1,
|
|
1676
|
-
"id": "ejemplo",
|
|
1677
|
-
"displayName": {
|
|
1678
|
-
"es": "Ejemplo de MCP",
|
|
1679
|
-
"en": "MCP example"
|
|
1680
|
-
},
|
|
1681
|
-
"description": {
|
|
1682
|
-
"es": "Plantilla mínima para crear servicios MCP gestionados por ZeroChat. Muestra el protocolo stdio y la estructura de configuración que debe seguir un nuevo MCP.",
|
|
1683
|
-
"en": "Minimal template for creating ZeroChat-managed MCP services. It demonstrates the stdio protocol and configuration structure for a new MCP."
|
|
1684
|
-
},
|
|
1685
|
-
"help": {
|
|
1686
|
-
"url": "help/mcp.html#crear-mcp-con-agente",
|
|
1687
|
-
"label": {
|
|
1688
|
-
"es": "Guía para crear un MCP gestionado",
|
|
1689
|
-
"en": "Guide to creating a managed MCP"
|
|
1690
|
-
}
|
|
1691
|
-
},
|
|
1692
|
-
"enabledByDefault": False,
|
|
1693
|
-
"transport": "stdio",
|
|
1694
|
-
"launch": {
|
|
1695
|
-
"executable": "${pythonExecutable}",
|
|
1696
|
-
"args": ["${serviceDir}/ejemplo_mcp_server.py"],
|
|
1697
|
-
"cwd": "${serviceDir}",
|
|
1698
|
-
"env": {},
|
|
1699
|
-
"handshakeTimeoutSeconds": 10
|
|
1700
|
-
}
|
|
1701
|
-
}, indent=2), encoding="utf-8")
|
|
1702
|
-
|
|
1703
|
-
if not example_server_file.exists():
|
|
1704
|
-
example_server_file.write_text('''#!/usr/bin/env python3
|
|
1705
|
-
import json, sys
|
|
1706
|
-
|
|
1707
|
-
def reply(req_id, result=None, error=None):
|
|
1708
|
-
resp = {"jsonrpc": "2.0", "id": req_id}
|
|
1709
|
-
if error: resp["error"] = error
|
|
1710
|
-
else: resp["result"] = result
|
|
1711
|
-
sys.stdout.write(json.dumps(resp) + "\\n")
|
|
1712
|
-
sys.stdout.flush()
|
|
1713
|
-
|
|
1714
|
-
for raw in sys.stdin:
|
|
1715
|
-
try: req = json.loads(raw)
|
|
1716
|
-
except: continue
|
|
1717
|
-
req_id, method, params = req.get("id"), req.get("method"), req.get("params", {})
|
|
1718
|
-
if method == "initialize":
|
|
1719
|
-
reply(req_id, {
|
|
1720
|
-
"protocolVersion": "2024-11-05",
|
|
1721
|
-
"serverInfo": {"name": "ZeroChat MCP Example", "version": "1.0.0"},
|
|
1722
|
-
"capabilities": {"tools": {}}
|
|
1723
|
-
})
|
|
1724
|
-
elif method == "tools/list":
|
|
1725
|
-
reply(req_id, {"tools": [{
|
|
1726
|
-
"name": "echo",
|
|
1727
|
-
"description": "Echo back a message for testing.",
|
|
1728
|
-
"inputSchema": {
|
|
1729
|
-
"type": "object",
|
|
1730
|
-
"properties": {"message": {"type": "string", "description": "Message to echo."}},
|
|
1731
|
-
"required": ["message"]
|
|
1732
|
-
}
|
|
1733
|
-
}]})
|
|
1734
|
-
elif method == "tools/call":
|
|
1735
|
-
if params.get("name") != "echo":
|
|
1736
|
-
reply(req_id, error={"code": -32601, "message": "Tool not found"})
|
|
1737
|
-
continue
|
|
1738
|
-
msg = params.get("arguments", {}).get("message", "")
|
|
1739
|
-
reply(req_id, {
|
|
1740
|
-
"content": [{"type": "text", "text": f"echo: {msg}"}],
|
|
1741
|
-
"isError": False
|
|
1742
|
-
})
|
|
1743
|
-
''', encoding="utf-8")
|
|
1744
|
-
|
|
1745
|
-
if not installer_example_file.exists():
|
|
1746
|
-
installer_example_file.write_text(json.dumps({
|
|
1747
|
-
"schemaVersion": 1,
|
|
1748
|
-
"type": "npm",
|
|
1749
|
-
"product": {
|
|
1750
|
-
"package": "@scope/mcp-package",
|
|
1751
|
-
"version": "1.2.3"
|
|
1752
|
-
},
|
|
1753
|
-
"requirements": {
|
|
1754
|
-
"node": {"minimumMajor": 18}
|
|
1755
|
-
}
|
|
1756
|
-
}, indent=2) + "\n", encoding="utf-8")
|
|
1757
|
-
|
|
1758
|
-
if not example_readme_file.exists():
|
|
1759
|
-
example_readme_file.write_text('''# Ejemplo de MCP gestionado\n\nEsta carpeta es la plantilla para que un agente cree un servicio MCP gestionado por ZeroChat. Para crear uno nuevo, copie esta estructura en `~/zerochat/services/<id-seguro>/`; no modifique este ejemplo.\n\n## Archivos\n\n- `service.json` es obligatorio. Define el identificador, los textos visibles, el proceso y el campo estándar opcional `help`.\n- `installer.json` es opcional. Créelo a partir de `installer.json.example` solo si ZeroChat debe instalar un paquete npm. Fije siempre una versión exacta.\n- El ejecutable o script de arranque se declara en `launch`. Use `${serviceDir}`, `${pythonExecutable}` o `${nodeExecutable}` en lugar de rutas de usuario.\n\n## Campo de ayuda\n\nTodos los servicios pueden incluir:\n\n```json\n"help": {\n "url": "help/mcp.html",\n "label": { "es": "Texto en español", "en": "English text" }\n}\n```\n\nLa interfaz muestra ese enlace en la tarjeta del servicio. `url` debe ser una ruta relativa de ZeroChat o una URL `https:` o `http:`.\n\n## Instrucción para un agente\n\nCopia este texto y sustituye solo `<nombre-del-mcp>`:\n\n```text\nSiguiendo estrictamente la estructura de ~/zerochat/services/ejemplo, busca en Internet la documentación oficial de instalación del MCP <nombre-del-mcp> y crea en ~/zerochat/services/ el directorio y los JSON necesarios para instalarlo y ejecutarlo en ZeroChat. Usa exclusivamente fuentes oficiales, fija versiones concretas, añade la sección help con el enlace a la documentación oficial, no incluyas secretos y no inicies el servicio. Al terminar, resume los archivos creados, las fuentes consultadas y los requisitos pendientes.\n```\n''', encoding="utf-8")
|
|
1760
|
-
|
|
1761
|
-
example_readme_file.write_text('''# Crear un MCP gestionado por ZeroChat
|
|
1762
|
-
|
|
1763
|
-
Esta carpeta es la referencia para crear servicios MCP gestionados. La ubicación de producción es el directorio personal del usuario, seguido de `zerochat/services` (por ejemplo, `/home/usuario/zerochat/services`). No uses `~`, `$HOME` ni una ruta relativa como `services/`: primero identifica la ruta absoluta del directorio personal y úsala para localizar este archivo. El nuevo servicio se crea como carpeta hermana de `ejemplo`, nunca dentro de ella.
|
|
1764
|
-
|
|
1765
|
-
## Procedimiento obligatorio
|
|
1766
|
-
|
|
1767
|
-
1. Lee `README.md`, `service.json` y `installer.json.example` de la carpeta `ejemplo` situada en el directorio de datos de ZeroChat.
|
|
1768
|
-
2. Consulta solo la documentación oficial del MCP solicitado.
|
|
1769
|
-
3. Crea una carpeta con un identificador seguro y descriptivo junto a `ejemplo`.
|
|
1770
|
-
4. Crea `service.json` copiando esta estructura y adapta únicamente los valores necesarios.
|
|
1771
|
-
5. Crea `installer.json` solo si el MCP se distribuye como paquete npm. Copia `installer.json.example`, fija la versión exacta y declara la versión mínima de Node.js.
|
|
1772
|
-
6. Añade `help.url` con la página oficial de instalación y etiquetas en español e inglés.
|
|
1773
|
-
7. No incluyas tokens, contraseñas ni valores privados. Decláralos en `launch.env` con un marcador, por ejemplo `${HOME_ASSISTANT_TOKEN}`.
|
|
1774
|
-
8. No inicies el servicio. Al terminar, informa de los archivos creados, fuentes oficiales y requisitos pendientes.
|
|
1775
|
-
|
|
1776
|
-
## Límites de ZeroChat
|
|
1777
|
-
|
|
1778
|
-
ZeroChat gestiona procesos MCP por `stdio`. Puede instalar paquetes npm mediante `installer.json` y arrancarlos con `${nodeExecutable}`. También puede arrancar un ejecutable o script ya disponible con `launch`.
|
|
1779
|
-
|
|
1780
|
-
No inventes una configuración para un MCP que requiera OAuth interactivo, transporte remoto HTTP/SSE, Docker, Python u otro instalador no soportado por estos JSON. En ese caso, no crees archivos: explica el requisito y qué soporte faltaría.
|
|
1781
|
-
|
|
1782
|
-
## Contratos JSON
|
|
1783
|
-
|
|
1784
|
-
- `service.json` es obligatorio. Conserva `schemaVersion`, `id`, textos bilingües, `transport: "stdio"` y `launch`.
|
|
1785
|
-
- En `launch`, usa `${serviceDir}`, `${nodeExecutable}` o `${pythonExecutable}`; no uses rutas absolutas de usuario.
|
|
1786
|
-
- `installer.json` es opcional y hoy solo admite `type: "npm"`.
|
|
1787
|
-
- `help` es opcional, pero debe incluirse en todo servicio creado:
|
|
1788
|
-
|
|
1789
|
-
```json
|
|
1790
|
-
"help": {
|
|
1791
|
-
"url": "https://documentacion-oficial.example/install",
|
|
1792
|
-
"label": { "es": "Instalación oficial", "en": "Official installation" }
|
|
1793
|
-
}
|
|
1794
|
-
```
|
|
1795
|
-
|
|
1796
|
-
## Prompt para el usuario
|
|
1797
|
-
|
|
1798
|
-
Sustituye únicamente `<nombre-del-mcp>`:
|
|
1799
|
-
|
|
1800
|
-
```text
|
|
1801
|
-
Para instalar el MCP <nombre-del-mcp>, localiza el directorio personal del usuario y lee primero el archivo zerochat/services/ejemplo/README.md usando su ruta absoluta. Sigue exactamente sus instrucciones.
|
|
1802
|
-
```
|
|
1803
|
-
''', encoding="utf-8")
|
|
1804
|
-
|
|
1805
|
-
# 2. playwright
|
|
1806
|
-
playwright_dir = self.services_root / "playwright"
|
|
1807
|
-
playwright_dir.mkdir(parents=True, exist_ok=True)
|
|
1808
|
-
pw_service = playwright_dir / "service.json"
|
|
1809
|
-
pw_installer = playwright_dir / "installer.json"
|
|
1810
|
-
if not pw_service.exists():
|
|
1811
|
-
pw_service.write_text(json.dumps({
|
|
1812
|
-
"schemaVersion": 1,
|
|
1813
|
-
"id": "playwright",
|
|
1814
|
-
"displayName": {
|
|
1815
|
-
"es": "Playwright MCP",
|
|
1816
|
-
"en": "Playwright MCP"
|
|
1817
|
-
},
|
|
1818
|
-
"description": {
|
|
1819
|
-
"es": "Automatización de navegador mediante el servidor MCP oficial de Playwright.",
|
|
1820
|
-
"en": "Browser automation through the official Playwright MCP server."
|
|
1821
|
-
},
|
|
1822
|
-
"enabledByDefault": False,
|
|
1823
|
-
"transport": "stdio",
|
|
1824
|
-
"launch": {
|
|
1825
|
-
"executable": "${nodeExecutable}",
|
|
1826
|
-
"args": ["${serviceDir}/node_modules/@playwright/mcp/cli.js", "--browser=chromium"],
|
|
1827
|
-
"cwd": "${serviceDir}",
|
|
1828
|
-
"env": {},
|
|
1829
|
-
"handshakeTimeoutSeconds": 30
|
|
1830
|
-
},
|
|
1831
|
-
"options": [
|
|
1832
|
-
{
|
|
1833
|
-
"id": "headless",
|
|
1834
|
-
"type": "boolean",
|
|
1835
|
-
"label": {
|
|
1836
|
-
"es": "Navegación en segundo plano (Headless)",
|
|
1837
|
-
"en": "Headless background mode"
|
|
1838
|
-
},
|
|
1839
|
-
"description": {
|
|
1840
|
-
"es": "Desactívalo para ver la ventana del navegador durante la automatización",
|
|
1841
|
-
"en": "Disable to display the browser window during automation"
|
|
1842
|
-
},
|
|
1843
|
-
"default": True,
|
|
1844
|
-
"argsWhenTrue": ["--headless"],
|
|
1845
|
-
"argsWhenFalse": []
|
|
1846
|
-
}
|
|
1847
|
-
]
|
|
1848
|
-
}, indent=2), encoding="utf-8")
|
|
1849
|
-
if not pw_installer.exists():
|
|
1850
|
-
pw_installer.write_text(json.dumps({
|
|
1851
|
-
"schemaVersion": 1,
|
|
1852
|
-
"type": "npm",
|
|
1853
|
-
"product": {
|
|
1854
|
-
"package": "@playwright/mcp",
|
|
1855
|
-
"version": "0.0.81",
|
|
1856
|
-
"browser": "chromium"
|
|
1857
|
-
},
|
|
1858
|
-
"requirements": {
|
|
1859
|
-
"node": {"minimumMajor": 18}
|
|
1860
|
-
}
|
|
1861
|
-
}, indent=2), encoding="utf-8")
|
|
1862
|
-
|
|
1863
|
-
# 3. memory
|
|
1864
|
-
memory_dir = self.services_root / "memory"
|
|
1865
|
-
memory_dir.mkdir(parents=True, exist_ok=True)
|
|
1866
|
-
mem_service = memory_dir / "service.json"
|
|
1867
|
-
mem_installer = memory_dir / "installer.json"
|
|
1868
|
-
if not mem_service.exists():
|
|
1869
|
-
mem_service.write_text(json.dumps({
|
|
1870
|
-
"schemaVersion": 1,
|
|
1871
|
-
"id": "memory",
|
|
1872
|
-
"displayName": {
|
|
1873
|
-
"es": "Memoria y Grafos (Knowledge Graph)",
|
|
1874
|
-
"en": "Memory & Knowledge Graph"
|
|
1875
|
-
},
|
|
1876
|
-
"description": {
|
|
1877
|
-
"es": "Almacenamiento persistente de entidades, preferencias y contexto histórico estructurado en un grafo de conocimiento.",
|
|
1878
|
-
"en": "Persistent storage of entities, preferences, and historical context structured as a knowledge graph."
|
|
1879
|
-
},
|
|
1880
|
-
"enabledByDefault": False,
|
|
1881
|
-
"transport": "stdio",
|
|
1882
|
-
"launch": {
|
|
1883
|
-
"executable": "${nodeExecutable}",
|
|
1884
|
-
"args": ["${serviceDir}/node_modules/@modelcontextprotocol/server-memory/dist/index.js"],
|
|
1885
|
-
"cwd": "${serviceDir}",
|
|
1886
|
-
"env": {
|
|
1887
|
-
"MEMORY_FILE_PATH": "${serviceDir}/memory.jsonl"
|
|
1888
|
-
},
|
|
1889
|
-
"handshakeTimeoutSeconds": 30
|
|
1890
|
-
}
|
|
1891
|
-
}, indent=2), encoding="utf-8")
|
|
1892
|
-
if not mem_installer.exists():
|
|
1893
|
-
mem_installer.write_text(json.dumps({
|
|
1894
|
-
"schemaVersion": 1,
|
|
1895
|
-
"type": "npm",
|
|
1896
|
-
"product": {
|
|
1897
|
-
"package": "@modelcontextprotocol/server-memory",
|
|
1898
|
-
"version": "2026.8.31"
|
|
1899
|
-
},
|
|
1900
|
-
"requirements": {
|
|
1901
|
-
"node": {"minimumMajor": 18}
|
|
1902
|
-
}
|
|
1903
|
-
}, indent=2), encoding="utf-8")
|
|
1904
|
-
|
|
1905
1686
|
def _load_services(self) -> dict[str, dict]:
|
|
1906
1687
|
servers = {}
|
|
1907
1688
|
for directory in sorted(self.services_root.iterdir()):
|
|
@@ -1948,6 +1729,7 @@ Para instalar el MCP <nombre-del-mcp>, localiza el directorio personal del usuar
|
|
|
1948
1729
|
"displayName": server.get("displayName", {}),
|
|
1949
1730
|
"description": server.get("description", {}),
|
|
1950
1731
|
"help": server.get("help"),
|
|
1732
|
+
"remote": server.get("remote"),
|
|
1951
1733
|
"enabled": pref.get("enabled", server.get("enabledByDefault", False)),
|
|
1952
1734
|
"status": "running" if running else self.states.get(server_id, "stopped"),
|
|
1953
1735
|
"toolCount": len(client.tools) if running else 0,
|
|
@@ -2093,6 +1875,9 @@ Para instalar el MCP <nombre-del-mcp>, localiza el directorio personal del usuar
|
|
|
2093
1875
|
env = os.environ.copy()
|
|
2094
1876
|
for k, v in launch.get("env", {}).items():
|
|
2095
1877
|
env[k] = self._expand(v, values)
|
|
1878
|
+
if is_termux_environment():
|
|
1879
|
+
scripts_dir = str(self.services_root / "scripts")
|
|
1880
|
+
env["PATH"] = scripts_dir + (os.pathsep + env["PATH"] if env.get("PATH") else "")
|
|
2096
1881
|
|
|
2097
1882
|
client = StdioMcpClient(
|
|
2098
1883
|
command, args, str(service_dir), env,
|
|
@@ -2412,7 +2197,10 @@ class ZeroChatServerHandler(BaseHTTPRequestHandler):
|
|
|
2412
2197
|
self.send_header("Content-Length", str(len(body)))
|
|
2413
2198
|
self.send_cors_headers()
|
|
2414
2199
|
self.end_headers()
|
|
2415
|
-
|
|
2200
|
+
try:
|
|
2201
|
+
self.wfile.write(body)
|
|
2202
|
+
except (BrokenPipeError, ConnectionResetError):
|
|
2203
|
+
self.close_connection = True
|
|
2416
2204
|
|
|
2417
2205
|
def do_GET(self):
|
|
2418
2206
|
t0 = time.monotonic()
|
|
@@ -2737,21 +2525,6 @@ class ZeroChatServerHandler(BaseHTTPRequestHandler):
|
|
|
2737
2525
|
"content": [{"type": "text", "text": json.dumps({"success": False, "error": str(ex)}, ensure_ascii=False)}],
|
|
2738
2526
|
"isError": True
|
|
2739
2527
|
}
|
|
2740
|
-
elif tool_name.startswith("mcp_"):
|
|
2741
|
-
try:
|
|
2742
|
-
result = GLOBAL_MCP_MANAGER.call(tool_name, tool_args)
|
|
2743
|
-
if isinstance(result, dict) and result.get("isError"):
|
|
2744
|
-
c_list = result.get("content", [])
|
|
2745
|
-
if c_list and isinstance(c_list, list) and isinstance(c_list[0], dict):
|
|
2746
|
-
tool_error_info = c_list[0].get("text", "Error en herramienta MCP")
|
|
2747
|
-
else:
|
|
2748
|
-
tool_error_info = "Error en herramienta MCP"
|
|
2749
|
-
except Exception as ex:
|
|
2750
|
-
tool_error_info = str(ex)
|
|
2751
|
-
result = {
|
|
2752
|
-
"content": [{"type": "text", "text": json.dumps({"success": False, "error": str(ex)}, ensure_ascii=False)}],
|
|
2753
|
-
"isError": True
|
|
2754
|
-
}
|
|
2755
2528
|
else:
|
|
2756
2529
|
error = {"code": -32601, "message": f"Herramienta local '{tool_name}' no encontrada."}
|
|
2757
2530
|
elif method == "zerochat/external/status":
|
|
@@ -2793,7 +2566,11 @@ class ZeroChatServerHandler(BaseHTTPRequestHandler):
|
|
|
2793
2566
|
self.send_header("Content-Length", str(len(resp_bytes)))
|
|
2794
2567
|
self.send_cors_headers()
|
|
2795
2568
|
self.end_headers()
|
|
2796
|
-
|
|
2569
|
+
try:
|
|
2570
|
+
self.wfile.write(resp_bytes)
|
|
2571
|
+
except (BrokenPipeError, ConnectionResetError):
|
|
2572
|
+
self.close_connection = True
|
|
2573
|
+
return
|
|
2797
2574
|
self._log_res(200, f"{safe_path} {action_tag}", (time.monotonic() - t0) * 1000, error_info=error_info)
|
|
2798
2575
|
|
|
2799
2576
|
def log_message(self, format, *args):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|