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`**
|
**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:
|
||||||
|
|
||||||
|
|||||||
136
src/server.js
136
src/server.js
@@ -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,32 +442,6 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||||||
properties: {},
|
properties: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "_webmcp_agregar-tool",
|
|
||||||
description: "Registra una nueva herramienta dinamicamente. El agente puede usar esto para crear herramientas nuevas en tiempo real.",
|
|
||||||
inputSchema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
nombre: { type: "string", description: "Nombre de la herramienta" },
|
|
||||||
descripcion: { type: "string", description: "Descripcion de lo que hace" },
|
|
||||||
parametros: { type: "string", description: "JSON string con las properties del schema, ej: {\"msg\":{\"type\":\"string\",\"description\":\"mensaje\"}}" },
|
|
||||||
codigo: { type: "string", description: "Codigo JavaScript del body de la funcion. Recibe \"args\" como parametro. Debe retornar un string." }
|
|
||||||
},
|
|
||||||
required: ["nombre", "descripcion", "codigo"]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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.",
|
|
||||||
inputSchema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
nombre: { type: "string", description: "Nombre de la herramienta a quitar" },
|
|
||||||
listar: { type: "boolean", description: "Si es true, lista las herramientas en vez de quitar" },
|
|
||||||
todas: { type: "boolean", description: "Si es true, quita todas las herramientas" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "_webmcp_clear-cache",
|
name: "_webmcp_clear-cache",
|
||||||
description: "Clears all registered tools, prompts and resources from the WebMCP server cache. Use this to force a clean state.",
|
description: "Clears all registered tools, prompts and resources from the WebMCP server cache. Use this to force a clean state.",
|
||||||
@@ -479,71 +450,39 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||||||
properties: {},
|
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. " +
|
// Tools de desarrollo: solo disponibles con --dev o WEBMCP_DEV=true
|
||||||
"A webmcp token is not required for this.",
|
if (CONFIG.dev) {
|
||||||
inputSchema: {
|
builtInTools.push(
|
||||||
type: "object",
|
{
|
||||||
description: "The schema which describes the tool.",
|
name: "_webmcp_agregar-tool",
|
||||||
properties: {
|
description: "[DEV] Registra una nueva herramienta dinamicamente. El agente puede usar esto para crear herramientas nuevas en tiempo real.",
|
||||||
name: {
|
inputSchema: {
|
||||||
type: "string",
|
type: "object",
|
||||||
description: "The name of the tool"
|
properties: {
|
||||||
|
nombre: { type: "string", description: "Nombre de la herramienta" },
|
||||||
|
descripcion: { type: "string", description: "Descripcion de lo que hace" },
|
||||||
|
parametros: { type: "string", description: "JSON string con las properties del schema, ej: {\"msg\":{\"type\":\"string\",\"description\":\"mensaje\"}}" },
|
||||||
|
codigo: { type: "string", description: "Codigo JavaScript del body de la funcion. Recibe \"args\" como parametro. Debe retornar un string." }
|
||||||
},
|
},
|
||||||
description: {
|
required: ["nombre", "descripcion", "codigo"]
|
||||||
type: "string",
|
},
|
||||||
description: "Provides a clear and concise description of the tool and what it is used for."
|
},
|
||||||
},
|
{
|
||||||
inputSchema: {
|
name: "_webmcp_quitar-tool",
|
||||||
type: "object",
|
description: "[DEV] Desregistra herramientas. Usa listar=true para ver las disponibles, todas=true para quitar todas, o nombre para quitar una especifica.",
|
||||||
description: "The inputSchema required or optional for the tool.",
|
inputSchema: {
|
||||||
properties: {
|
type: "object",
|
||||||
type: {
|
properties: {
|
||||||
type: "string",
|
nombre: { type: "string", description: "Nombre de la herramienta a quitar" },
|
||||||
enum: ["object", "array", "string", "number", "boolean", "enum"],
|
listar: { type: "boolean", description: "Si es true, lista las herramientas en vez de quitar" },
|
||||||
description: "The type of the parameter being defined."
|
todas: { type: "boolean", description: "Si es true, quita todas las herramientas" }
|
||||||
},
|
|
||||||
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 };
|
||||||
|
|||||||
@@ -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`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user