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:
98
README.md
98
README.md
@@ -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`**
|
||||
|
||||
#### 1. `agregar-tool` — Registro dinámico 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
|
||||
- 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
|
||||
#### 1. `agregar-tool` — Registro dinámico de herramientas (solo modo dev)
|
||||
|
||||
#### 2. `quitar-tool` — Eliminación de herramientas (server.js, websocket-server.js, webmcp.js)
|
||||
- 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
|
||||
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`).
|
||||
|
||||
#### 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
|
||||
- `removeTool` — elimina una herramienta específica del registro local
|
||||
- `removeAllTools` — limpia todas las herramientas del registro local
|
||||
- `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
|
||||
- `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
|
||||
|
||||
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
|
||||
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:
|
||||
|
||||
|
||||
108
src/server.js
108
src/server.js
@@ -307,6 +307,9 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
}
|
||||
|
||||
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) {
|
||||
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 (!CONFIG.dev) {
|
||||
return { content: [{ type: "text", text: "quitar-tool solo esta disponible en modo desarrollo (--dev)" }], isError: true };
|
||||
}
|
||||
if (!wsClient || wsClient.readyState !== WebSocket.OPEN) {
|
||||
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) {
|
||||
return {
|
||||
content: [{
|
||||
@@ -445,9 +442,22 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
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",
|
||||
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: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -461,7 +471,7 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
},
|
||||
{
|
||||
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: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -470,80 +480,9 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
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) {
|
||||
return {tools: builtInTools};
|
||||
@@ -843,6 +782,9 @@ async function runMcpServer(serverToken) {
|
||||
const transport = new StdioServerTransport();
|
||||
await mcpServer.connect(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 };
|
||||
|
||||
@@ -1585,6 +1585,7 @@ const parseArgs = async () => {
|
||||
let cleanTokens = false;
|
||||
let encodedPair = null;
|
||||
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++) {
|
||||
const arg = args[i];
|
||||
@@ -1629,6 +1630,8 @@ const parseArgs = async () => {
|
||||
cleanTokens = true;
|
||||
} else if (arg === '-f' || arg === '--foreground') {
|
||||
daemon = false;
|
||||
} else if (arg === '--dev') {
|
||||
dev = true;
|
||||
} else if (arg === '--forked') {
|
||||
// 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
|
||||
@@ -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 = () => {
|
||||
@@ -1656,6 +1659,7 @@ Options:
|
||||
-f, --foreground Run in foreground (don't daemonize)
|
||||
-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
|
||||
--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 --clean to remove all authorized tokens when you want to start fresh.
|
||||
@@ -1756,10 +1760,12 @@ const main = async () => {
|
||||
// Start the server
|
||||
const PORT = CONFIG.port;
|
||||
httpServer.listen(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 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`);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user