En Tu primera CLI hicimos dbf, una herramienta de consola en Visual FoxPro que mira y exporta tablas. Hoy le damos esa misma herramienta a un agente de IA. Al final vas a tener esto: le pides a Claude «pásame los clientes de Northwind a un CSV», y Claude encuentra el comando exportar, rellena la tabla y el destino, lo ejecuta y te dice cuántos registros ha copiado. Nadie le ha explicado qué es dbf ni cómo se escribe su línea de comandos.
No vas a escribir ni una línea de código nueva. Los comandos, sus parámetros y sus descripciones son los que ya pusiste en los bloques *!*, y el código que se ejecuta es el mismo main.prg de 76 líneas.
Lo que lo hace posible se llama MCP (Model Context Protocol): es la forma en que un agente pregunta a un programa qué herramientas tiene y las llama. Desde FoxForge 0.4.0, cada CLI que compilas trae su servidor MCP puesto.
Lo que necesitas
- La CLI
dbfdel tutorial anterior, o cualquier otra tuya. - FoxCli y FoxCli MCP, con FoxForge 0.4.0 o posterior. Si tu proyecto es de un FoxForge anterior, al compilar te ofrece ponerlo al día.
- El runtime de .NET 10 de 32 bits en la máquina donde corre el agente. El instalador de FoxCli lo busca y, si falta, ofrece bajarlo de Microsoft. La CLI de consola no lo necesita: solo el servidor.
- Un cliente MCP: Claude Code o Claude Desktop, por ejemplo.
El runtime tiene que ser el de 32 bits (x86). El de 64 bits no sirve aunque esté instalado, porque el servidor trabaja con Visual FoxPro, que es de 32 bits. Si lo instalaste a mano, comprueba que bajaste el «x86».
Paso 1. Compilar como siempre
Abre el proyecto dbf en el IDE y pulsa Ctrl+F7, como en el tutorial. En dist\ aparecen dos ficheros más que la otra vez:
dbf.mcp.exe el servidor MCP de dbf
dbf.mcp.exe.manifestY pack.ps1 los mete en el zip:
11 ficheros -> C:\a1\tutorial-b\dbf\dbf-dist.zip
Se despliega descomprimiendo: no hace falta registrar nada ni ser administrador.Son los 9 de antes y los dos del servidor. Si vas a repartir la CLI a un puesto donde solo se usa desde la consola, pack.ps1 -NoMcp los deja fuera y vuelven a ser 9.
Descomprime el zip donde tengas tus herramientas. Yo lo tengo en C:\herramientas\dbf, igual que en el tutorial. Lo único que pide el servidor es vivir en la misma carpeta que dbf.exe y llamarse como la CLI con .mcp.exe detrás: por ese nombre sabe a qué CLI sirve.
Paso 2. Decírselo al agente
A un cliente MCP se le da de alta el servidor con una línea. En Claude Code, en un fichero .mcp.json en la carpeta del proyecto donde vayas a trabajar:
{
"mcpServers": {
"dbf": {
"command": "C:\\herramientas\\dbf\\dbf.mcp.exe"
}
}
}En Claude Desktop va lo mismo, "dbf": { "command": ... } dentro de "mcpServers", en su fichero de configuración (Configuración > Desarrollador > Editar configuración).
Las barras van dobles porque es JSON. No hay argumentos que poner ni nada más que configurar.
Paso 3. Pedirle algo
Yo he abierto Claude Code en la carpeta con la copia de Northwind del tutorial (C:\pruebas-cli\northwind). Lo primero que hace el agente al arrancar es preguntar al servidor qué sabe hacer, y recibe tus tres comandos, cada uno con su descripción y sus parámetros. Así se anuncia exportar:
{
"name": "exportar",
"description": "Copia una tabla a un fichero CSV o XLS",
"inputSchema": {
"type": "object",
"properties": {
"formato": {
"type": "string",
"enum": [
"csv",
"xls"
],
"description": "Formato del fichero",
"default": "csv"
},
"tabla": {
"type": "string",
"description": "La tabla (.dbf)"
},
"destino": {
"type": "string",
"description": "El fichero que se crea"
}
},
"required": [
"tabla",
"destino"
]
}
}Todo eso sale de tu bloque *!*: la descripción es la primera línea, formato solo admite csv o xls y vale csv si no se dice nada, y la tabla y el destino son obligatorios. El agente lo lee y ya sabe usar el comando.
Le pido: «¿cuántos clientes hay en customers.dbf?». Llama a info con la tabla customers.dbf y recibe lo mismo que verías en la consola:
Registros: 91
Campos: 11
Modificada: 2004-12-09Le pido: «pásalos a clientes.csv». Llama a exportar con tabla = customers.dbf y destino = clientes.csv, y la respuesta es el aviso que exportar escribe por el canal de error:
stderr: 91 registros copiados a clientes.csvEl clientes.csv aparece en la carpeta de Northwind, con las tildes en su sitio, igual que cuando lo haces tú desde la consola.
Cuando algo no sale, el agente se entera de la misma forma que tú. Si le pido la información de facturas.dbf, que no existe, la llamada vuelve marcada como error con el mensaje de tu código:
stderr: dbf: no encuentro la tabla facturas.dbfY si se inventa un formato, FoxCli lo para antes de llegar a tu código y le contesta con la ayuda del comando, que es lo que necesita para corregirse:
dbf: '--formato txt' no es válido. Valores: csv, xlsPaso 4. @readonly: los comandos que solo miran
info y campos leen la tabla y no cambian nada. exportar escribe un fichero. El agente no tiene forma de saberlo, así que trata los tres igual, y según cómo lo tengas configurado te pedirá permiso cada vez que quiera usar cualquiera de ellos.
Se lo dices con una línea en el bloque de cada comando que solo mira:
*!* Cuántos registros y campos tiene una tabla
*!* @readonly
*!* @tcTabla <> La tabla (.dbf)
*!* @example dbf info customers.dbf
PROCEDURE Info(tcTabla AS String)
*!* La estructura de una tabla: nombre, tipo y ancho de cada campo
*!* @readonly
*!* @tcTabla <> La tabla (.dbf)
*!* @example dbf campos customers.dbf --format json
PROCEDURE Campos(tcTabla AS String)Recompila con Ctrl+F7. Ahora info y campos llegan al agente con la marca readOnlyHint, y un cliente que la respeta los deja usar sin preguntarte cada vez. exportar sigue sin marca, y para ese te seguirá pidiendo permiso, que es lo que quieres de un comando que escribe en tu disco.
Pónsela solo a los que no escriben nada, porque el agente se fía de la marca y no vuelve a preguntar.
Paso 5. Lo que cambia cuando el programa vive toda la sesión
En la consola, cada vez que escribes dbf info arranca un proceso, hace su trabajo y termina. Con el agente, el servidor arranca una vez al principio de la sesión y atiende todas las llamadas, de una en una y en el orden en que llegan. Eso cambia tres cosas.
Lo que un comando deja abierto, lo cierra FoxCli. exportar abre la tabla y la cierra con USE al final. Pero si COPY TO falla a mitad (un disco lleno, un destino en una carpeta sin permiso), el USE del final no llega a ejecutarse y la tabla se queda abierta. En la consola daría igual, porque el proceso termina. Aquí, la siguiente llamada se la encontraría abierta. Por eso, después de cada llamada, también de las que fallan, FoxCli ejecuta AfterCommand, un método de la clase base que hace CLOSE DATABASES ALL y CLEAR MEMORY. No tienes que escribirlo: ya viene.
Lo que no toca son los SET. Si un comando tuyo cambia SET DELETED o SET DATE, el cambio sigue ahí para el siguiente. Si lo necesitas, sobrescribes AfterCommand en tu clase y lo dejas como estaba, con DODEFAULT() al final para que siga cerrando las tablas:
PROCEDURE AfterCommand
SET DELETED ON
DODEFAULT()
ENDPROCAfterCommand nunca aparece como comando, aunque sea público.
La carpeta de trabajo es la del agente. Le he pedido customers.dbf a secas, sin ruta, y lo ha encontrado porque Claude Code trabaja en la carpeta de Northwind. Es lo mismo que en la consola, donde dbf info customers.dbf busca la tabla en la carpeta en la que estás. Si tu cliente no trabaja en ninguna carpeta concreta, pídele las rutas completas.
Nadie contesta preguntas. En la consola, un comando puede pararse a preguntarte algo con Console.Prompt("¿Sobrescribo el fichero? ") y esperar a que escribas la respuesta. Bajo MCP no hay nadie delante del teclado, porque por la entrada del programa llegan las peticiones del agente. Si un comando tuyo llama a Console.Prompt(), recibe .NULL. al momento, como si la entrada se hubiera acabado, y la respuesta al agente lleva un aviso. dbf no pregunta nada, así que no le afecta. Si los tuyos preguntan, pasa ese dato como parámetro del comando, que el agente sí sabe rellenar.
La licencia
El servidor tiene su propia licencia, FoxCli MCP: dos ficheros, foxcli-mcp.lic y foxcli-mcp.seal, que se cargan en FoxForge > Licencia de FoxCli…, igual que los de FoxCli. Si los tienes en la misma carpeta, se instalan juntos. Sin licencia funciona igual y sin fecha de caducidad; lo único que hace es añadir esta línea al final de cada respuesta al agente, y decírsela también al conectarse:
Hecho con FoxCli MCP (evaluación) - irwinrodriguez.dev/foxcli-mcpCon la licencia cargada, Ctrl+F7 sella el servidor con tu nombre y la línea desaparece.
Cuál es cuál
Tengo tres productos que hablan MCP, y cada uno sirve para una cosa:
- FoxAgent conecta un agente a una aplicación VFP abierta: ve sus formularios, sus datos y su estado mientras la usas.
- FoxCli MCP es lo de hoy: tu lógica en VFP, empaquetada como herramientas, en la misma máquina que el agente.
- FoxServer pone tu lógica VFP en un servidor, para que la llamen desde otra máquina.
Y ahora qué
- La guía de FoxCli, en FoxForge > Ayuda > Guía de FoxCli, tiene un capítulo del servidor MCP con los detalles que aquí no han salido.
- Si algo no arranca, pon
"env": { "FOXCLI_MCP_TRACE": "1" }junto al"command"de la configuración: el servidor contará por el canal de error lo que va haciendo, y tu cliente MCP suele enseñar ese canal en su registro. - Una idea para seguir con
dbf: el comandocontarcon filtro que propuse en el tutorial anterior es un buen candidato para@readonly. Con él, le puedes preguntar a Claude cuántos clientes hay en España sin darle permiso para nada más.