Modo desarrollo para agregar-tool/quitar-tool, eliminar define-mcp-tool

- agregar-tool y quitar-tool solo disponibles con --dev o WEBMCP_DEV=true
- Eliminar built-in define-mcp-tool (sin utilidad en este fork)
- Advertencia [DEV] en logs al iniciar en modo desarrollo
- Documentar modo dev, parametros y flujos en README
This commit is contained in:
2026-02-13 00:18:44 -06:00
parent 78e1e72b89
commit 2a07e89a17
3 changed files with 131 additions and 115 deletions

View File

@@ -24,27 +24,67 @@ Este fork agrega **registro dinámico de herramientas desde el agente**, algo qu
**3 archivos modificados: `src/server.js`, `src/webmcp.js`, `src/websocket-server.js`** **3 archivos modificados: `src/server.js`, `src/webmcp.js`, `src/websocket-server.js`**
#### 1. `agregar-tool` — Registro dinámico de herramientas (server.js, websocket-server.js, webmcp.js) #### 1. `agregar-tool` — Registro dinámico de herramientas (solo modo dev)
- Tool built-in `_webmcp_agregar-tool` que permite al agente registrar herramientas nuevas en el navegador en tiempo real
- El agente envía nombre, descripción, parámetros (JSON schema) y código JavaScript
- El código se compila con `new Function('args', codigo)` en el navegador y se registra como tool
- El flujo es: Agente -> MCP Server -> WebSocket Server -> Navegador (createTool) -> respuesta de vuelta
- Después de registrar, se emite `sendToolListChanged()` para que el MCP client refresque su lista
#### 2. `quitar-tool` — Eliminación de herramientas (server.js, websocket-server.js, webmcp.js) Tool built-in `_webmcp_agregar-tool` que permite al agente registrar herramientas nuevas en el navegador en tiempo real. Solo disponible en modo desarrollo (`--dev`).
- Tool built-in `_webmcp_quitar-tool` con tres modos:
- `listar: true` — lista las herramientas activas registradas en el navegador
- `nombre: "x"` — elimina una herramienta específica por nombre
- `todas: true` — elimina todas las herramientas de una vez
- Notifica al navegador para que sincronice su estado local
#### 3. Manejo de mensajes nuevos en el cliente web (webmcp.js) **Parámetros:**
| Param | Requerido | Tipo | Descripción |
|---|---|---|---|
| `nombre` | Sí | string | Nombre de la herramienta que Claude verá |
| `descripcion` | Sí | string | Qué hace la herramienta (Claude lee esto para decidir cuándo usarla) |
| `codigo` | Sí | string | Body de una función JS. Recibe `args` como parámetro. Debe retornar un string. |
| `parametros` | No | string | JSON string con las properties del inputSchema |
**Flujo de ejecución:**
```
Agente envía nombre + descripcion + codigo + parametros
→ server.js valida y reenvía al WebSocket Server
→ websocket-server.js busca el primer navegador conectado
→ navegador recibe el mensaje 'createTool'
→ webmcp.js ejecuta new Function('args', codigo) para compilar el código
→ webmcp.js llama registerTool() para registrar la herramienta
→ respuesta 'toolResponse' viaja de vuelta
→ websocket-server.js enruta la respuesta al MCP server
→ server.js llama sendToolListChanged()
→ Claude ahora puede usar la nueva herramienta
```
El código se ejecuta en el contexto del navegador, con acceso a `document`, `window`, `fetch()`, `localStorage`, `navigator`, y todas las APIs web. Solo tiene acceso al scope global (no a closures/variables locales de la página, salvo que estén en `window`).
#### 2. `quitar-tool` — Eliminación de herramientas (solo modo dev)
Tool built-in `_webmcp_quitar-tool` con tres modos de operación. Solo disponible en modo desarrollo (`--dev`).
**Parámetros (todos opcionales, pero debe usarse al menos uno):**
| Param | Tipo | Efecto |
|---|---|---|
| `listar` | boolean | Si `true`, lista las herramientas activas sin borrar nada |
| `nombre` | string | Nombre de la herramienta específica a eliminar |
| `todas` | boolean | Si `true`, elimina todas las herramientas de una vez |
**Comportamiento por modo:**
- **`listar: true`** — Se resuelve en websocket-server.js leyendo el `toolsRegistry`. No toca el navegador. Retorna `"Herramientas activas: tool1, tool2"`.
- **`nombre: "x"`** — Busca en `toolsRegistry` por nombre, la borra, y envía `removeTool` al navegador correspondiente para que sincronice su estado local.
- **`todas: true`** — Borra todo el `toolsRegistry`, envía `removeAllTools` a todos los navegadores conectados, y retorna `"N herramientas desregistradas"`.
En los tres casos, después de resolver se llama `sendToolListChanged()` para que Claude actualice su lista.
#### 3. Eliminación de `define-mcp-tool`
Se eliminó la built-in `_webmcp_define-mcp-tool` del upstream por no tener utilidad real en este fork.
#### 4. Manejo de mensajes nuevos en el cliente web (webmcp.js)
- `createTool` — recibe la definición de herramienta del server, la compila y registra localmente - `createTool` — recibe la definición de herramienta del server, la compila y registra localmente
- `removeTool` — elimina una herramienta específica del registro local - `removeTool` — elimina una herramienta específica del registro local
- `removeAllTools` — limpia todas las herramientas del registro local - `removeAllTools` — limpia todas las herramientas del registro local
- `clipboardCopy` — permite copiar texto al portapapeles del usuario (usado para tokens) - `clipboardCopy` — permite copiar texto al portapapeles del usuario (usado para tokens)
#### 4. Ruteo en WebSocket Server (websocket-server.js) #### 5. Ruteo en WebSocket Server (websocket-server.js)
- `handleCreateTool()` — recibe la petición del MCP server, encuentra un navegador conectado y le reenvía la instrucción de crear la herramienta - `handleCreateTool()` — recibe la petición del MCP server, encuentra un navegador conectado y le reenvía la instrucción de crear la herramienta
- `handleRemoveTool()` — maneja listar/eliminar herramientas, sincroniza entre el registry del server y los navegadores conectados - `handleRemoveTool()` — maneja listar/eliminar herramientas, sincroniza entre el registry del server y los navegadores conectados
@@ -94,6 +134,34 @@ En `.claude/settings.json` o settings del proyecto:
} }
``` ```
### Modo desarrollo
Las tools `agregar-tool` y `quitar-tool` solo están disponibles en modo desarrollo. Para activarlo:
**Opción 1: Flag de línea de comandos**
```json
{
"mcpServers": {
"webmcp": {
"command": "node",
"args": ["node_modules/@nucleoriofrio/webmcp/src/websocket-server.js", "--mcp", "--dev"]
}
}
}
```
**Opción 2: Variable de entorno**
```bash
WEBMCP_DEV=true node src/websocket-server.js --mcp
```
Cuando el modo dev está activo, el servidor muestra una advertencia al iniciar:
```
[DEV] Modo desarrollo activo — agregar-tool y quitar-tool habilitados
```
En producción (sin `--dev`), estas tools no aparecen en el listado de herramientas y si se intentan llamar directamente retornan un error.
### Flujo de conexión ### Flujo de conexión
1. El agente ejecuta `get-token` para generar un token de conexión 1. El agente ejecuta `get-token` para generar un token de conexión
@@ -101,7 +169,7 @@ En `.claude/settings.json` o settings del proyecto:
3. La página se conecta al WebSocket server y registra sus herramientas 3. La página se conecta al WebSocket server y registra sus herramientas
4. El agente puede usar las herramientas de la página y crear nuevas dinámicamente con `agregar-tool` 4. El agente puede usar las herramientas de la página y crear nuevas dinámicamente con `agregar-tool`
### Crear herramientas dinámicas desde el agente ### Crear herramientas dinámicas desde el agente (requiere --dev)
Con este fork, el agente puede crear herramientas sin modificar el HTML: Con este fork, el agente puede crear herramientas sin modificar el HTML:

View File

@@ -307,6 +307,9 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
} }
if (request.params.name === "_webmcp_agregar-tool") { if (request.params.name === "_webmcp_agregar-tool") {
if (!CONFIG.dev) {
return { content: [{ type: "text", text: "agregar-tool solo esta disponible en modo desarrollo (--dev)" }], isError: true };
}
if (!wsClient || wsClient.readyState !== WebSocket.OPEN) { if (!wsClient || wsClient.readyState !== WebSocket.OPEN) {
return { content: [{ type: "text", text: "No hay conexion al servidor WebSocket" }], isError: true }; return { content: [{ type: "text", text: "No hay conexion al servidor WebSocket" }], isError: true };
} }
@@ -344,6 +347,9 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
} }
if (request.params.name === "_webmcp_quitar-tool") { if (request.params.name === "_webmcp_quitar-tool") {
if (!CONFIG.dev) {
return { content: [{ type: "text", text: "quitar-tool solo esta disponible en modo desarrollo (--dev)" }], isError: true };
}
if (!wsClient || wsClient.readyState !== WebSocket.OPEN) { if (!wsClient || wsClient.readyState !== WebSocket.OPEN) {
return { content: [{ type: "text", text: "No hay conexion al servidor WebSocket" }], isError: true }; return { content: [{ type: "text", text: "No hay conexion al servidor WebSocket" }], isError: true };
} }
@@ -376,15 +382,6 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
} }
} }
if (request.params.name === "_webmcp_define-mcp-tool") {
return {
content: [{
type: "text",
text: "Instruct the user to view the result from the tool call. Do not say anything else.",
}]
}
}
if (!wsClient || wsClient.readyState !== WebSocket.OPEN) { if (!wsClient || wsClient.readyState !== WebSocket.OPEN) {
return { return {
content: [{ content: [{
@@ -445,9 +442,22 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
properties: {}, properties: {},
}, },
}, },
{
name: "_webmcp_clear-cache",
description: "Clears all registered tools, prompts and resources from the WebMCP server cache. Use this to force a clean state.",
inputSchema: {
type: "object",
properties: {},
},
},
];
// Tools de desarrollo: solo disponibles con --dev o WEBMCP_DEV=true
if (CONFIG.dev) {
builtInTools.push(
{ {
name: "_webmcp_agregar-tool", name: "_webmcp_agregar-tool",
description: "Registra una nueva herramienta dinamicamente. El agente puede usar esto para crear herramientas nuevas en tiempo real.", description: "[DEV] Registra una nueva herramienta dinamicamente. El agente puede usar esto para crear herramientas nuevas en tiempo real.",
inputSchema: { inputSchema: {
type: "object", type: "object",
properties: { properties: {
@@ -461,7 +471,7 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
}, },
{ {
name: "_webmcp_quitar-tool", name: "_webmcp_quitar-tool",
description: "Desregistra herramientas. Usa listar=true para ver las disponibles, todas=true para quitar todas, o nombre para quitar una especifica.", description: "[DEV] Desregistra herramientas. Usa listar=true para ver las disponibles, todas=true para quitar todas, o nombre para quitar una especifica.",
inputSchema: { inputSchema: {
type: "object", type: "object",
properties: { properties: {
@@ -470,80 +480,9 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
todas: { type: "boolean", description: "Si es true, quita todas las herramientas" } todas: { type: "boolean", description: "Si es true, quita todas las herramientas" }
} }
}, },
},
{
name: "_webmcp_clear-cache",
description: "Clears all registered tools, prompts and resources from the WebMCP server cache. Use this to force a clean state.",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "_webmcp_define-mcp-tool",
description: "Used to define an 'mcp tool'. Only use this if the user specifically asks for an mcp tool. " +
"A webmcp token is not required for this.",
inputSchema: {
type: "object",
description: "The schema which describes the tool.",
properties: {
name: {
type: "string",
description: "The name of the tool"
},
description: {
type: "string",
description: "Provides a clear and concise description of the tool and what it is used for."
},
inputSchema: {
type: "object",
description: "The inputSchema required or optional for the tool.",
properties: {
type: {
type: "string",
enum: ["object", "array", "string", "number", "boolean", "enum"],
description: "The type of the parameter being defined."
},
properties: {
type: "object",
description: "The properties of the parameter if it's an object type.",
additionalProperties: {
type: "object",
properties: {
type: {
type: "string",
description: "The data type of the property.",
enum: ["object", "array", "string", "number", "boolean", "enum"]
},
description: {
type: "string",
description: "A brief description of the property."
},
enum: {
type: "array",
description: "A list of allowed values for the property.",
items: {
type: "string"
} }
);
} }
},
required: ["type", "description"]
}
},
required: {
type: "array",
items: {
type: "string"
}
}
},
required: ["type", "description", "properties"]
}
},
required: ["name", "description", "inputSchema"]
},
}
];
if (!wsClient || wsClient.readyState !== WebSocket.OPEN) { if (!wsClient || wsClient.readyState !== WebSocket.OPEN) {
return {tools: builtInTools}; return {tools: builtInTools};
@@ -843,6 +782,9 @@ async function runMcpServer(serverToken) {
const transport = new StdioServerTransport(); const transport = new StdioServerTransport();
await mcpServer.connect(transport); await mcpServer.connect(transport);
console.error("MCP server running with stdio transport"); console.error("MCP server running with stdio transport");
if (CONFIG.dev) {
console.error("[DEV] Modo desarrollo activo — agregar-tool y quitar-tool habilitados");
}
} }
export { runMcpServer }; export { runMcpServer };

View File

@@ -1585,6 +1585,7 @@ const parseArgs = async () => {
let cleanTokens = false; let cleanTokens = false;
let encodedPair = null; let encodedPair = null;
let daemon = true; // Default to daemonize let daemon = true; // Default to daemonize
let dev = process.env.WEBMCP_DEV === 'true' || process.env.WEBMCP_DEV === '1';
for (let i = 0; i < args.length; i++) { for (let i = 0; i < args.length; i++) {
const arg = args[i]; const arg = args[i];
@@ -1629,6 +1630,8 @@ const parseArgs = async () => {
cleanTokens = true; cleanTokens = true;
} else if (arg === '-f' || arg === '--foreground') { } else if (arg === '-f' || arg === '--foreground') {
daemon = false; daemon = false;
} else if (arg === '--dev') {
dev = true;
} else if (arg === '--forked') { } else if (arg === '--forked') {
// This is an internal flag to indicate we're the forked child │ │ // This is an internal flag to indicate we're the forked child │ │
// No need to do anything with it here, just don't error on it // No need to do anything with it here, just don't error on it
@@ -1639,7 +1642,7 @@ const parseArgs = async () => {
} }
} }
return {port, quit, newToken, cleanTokens, encodedPair, daemon, startMCP}; return {port, quit, newToken, cleanTokens, encodedPair, daemon, startMCP, dev};
}; };
const showHelp = () => { const showHelp = () => {
@@ -1656,6 +1659,7 @@ Options:
-f, --foreground Run in foreground (don't daemonize) -f, --foreground Run in foreground (don't daemonize)
-m, --mcp Internal WebMCP Server codepath, likely only used in MCP client config -m, --mcp Internal WebMCP Server codepath, likely only used in MCP client config
-d, --docker Tell the MCP client that WebMCP is running in docker -d, --docker Tell the MCP client that WebMCP is running in docker
--dev Enable development mode (agregar-tool, quitar-tool)
Use --new to generate a token which clients can use to register on the /register endpoint. Use --new to generate a token which clients can use to register on the /register endpoint.
Use --clean to remove all authorized tokens when you want to start fresh. Use --clean to remove all authorized tokens when you want to start fresh.
@@ -1756,10 +1760,12 @@ const main = async () => {
// Start the server // Start the server
const PORT = CONFIG.port; const PORT = CONFIG.port;
httpServer.listen(PORT, () => { httpServer.listen(PORT, () => {
console.error(`WebSocket server running at http://${HOST}:${PORT}`);
console.error(`WebSocket server running at http://${HOST}:${PORT}`); console.error(`WebSocket server running at http://${HOST}:${PORT}`);
console.error(`WebMCP client token (for MCP path): ${serverToken}`); console.error(`WebMCP client token (for MCP path): ${serverToken}`);
console.error(`WebMCP client URL: ws://${HOST}:${PORT}${MCP_PATH}?token=${serverToken}`); console.error(`WebMCP client URL: ws://${HOST}:${PORT}${MCP_PATH}?token=${serverToken}`);
if (CONFIG.dev) {
console.error(`[DEV] Modo desarrollo activo — agregar-tool y quitar-tool habilitados`);
}
console.error(`Use 'node websocket-server.js --new <encoded-pair>' to authorize a channel-token pair`); console.error(`Use 'node websocket-server.js --new <encoded-pair>' to authorize a channel-token pair`);
}); });