In Your first CLI we built dbf, a console tool in Visual FoxPro that inspects and exports tables. Today we give that same tool to an AI agent. By the end you will have this: you ask Claude “put the Northwind customers into a CSV”, and Claude finds the exportar command, fills in the table and the destination, runs it and tells you how many records it copied. Nobody has explained to it what dbf is or how its command line is written.
You are not going to write a single new line of code. The commands, their parameters and their descriptions are the ones you already put in the *!* blocks, and the code that runs is the same 76-line main.prg.
What makes this possible is called MCP (Model Context Protocol): it is how an agent asks a program which tools it has and then calls them. Since FoxForge 0.4.0, every CLI you build comes with its MCP server included.
What you need
- The
dbfCLI from the previous tutorial, or any other one of yours. - FoxCli and FoxCli MCP, with FoxForge 0.4.0 or later. If your project was made with an older FoxForge, building offers to bring it up to date.
- The 32-bit .NET 10 runtime on the machine where the agent runs. The FoxCli installer looks for it and, if it is missing, offers to download it from Microsoft. The console CLI does not need it: only the server does.
- An MCP client: Claude Code or Claude Desktop, for example.
The runtime has to be the 32-bit one (x86). The 64-bit one will not do even if it is installed, because the server works with Visual FoxPro, which is 32-bit. If you installed it by hand, check that you downloaded the “x86” one.
Step 1. Build as always
Open the dbf project in the IDE and press Ctrl+F7, as in the tutorial. Two more files than last time show up in dist\:
dbf.mcp.exe dbf's MCP server
dbf.mcp.exe.manifestAnd pack.ps1 puts them in the zip:
11 ficheros -> C:\a1\tutorial-b\dbf\dbf-dist.zip
Se despliega descomprimiendo: no hace falta registrar nada ni ser administrador.That is the 9 from before plus the server’s two. If you are going to hand the CLI out to a machine where it is only used from the console, pack.ps1 -NoMcp leaves them out and you are back to 9.
Unzip it wherever you keep your tools. I have it in C:\herramientas\dbf, as in the tutorial. All the server asks is to live in the same folder as dbf.exe and to be named like the CLI with .mcp.exe after it: that name is how it knows which CLI it serves.
Step 2. Tell the agent
You register a server with an MCP client in one line. In Claude Code, in a .mcp.json file in the folder of the project you are going to work in:
{
"mcpServers": {
"dbf": {
"command": "C:\\herramientas\\dbf\\dbf.mcp.exe"
}
}
}In Claude Desktop it is the same thing, "dbf": { "command": ... } inside "mcpServers", in its configuration file (Settings > Developer > Edit Config).
The backslashes are doubled because it is JSON. There are no arguments to add and nothing else to configure.
Step 3. Ask it for something
I have opened Claude Code in the folder with the tutorial’s copy of Northwind (C:\pruebas-cli\northwind). The first thing the agent does when it starts is ask the server what it can do, and it gets your three commands, each with its description and its parameters. This is how exportar announces itself:
{
"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"
]
}
}All of that comes from your *!* block: the description is the first line, formato only accepts csv or xls and is csv if nothing is said, and the table and the destination are required. The agent reads it and already knows how to use the command.
I ask it: “how many customers are there in customers.dbf?”. It calls info with the table customers.dbf and gets the same thing you would see in the console:
Registros: 91
Campos: 11
Modificada: 2004-12-09I ask it: “put them into clientes.csv”. It calls exportar with tabla = customers.dbf and destino = clientes.csv, and the answer is the message exportar writes on the error channel:
stderr: 91 registros copiados a clientes.csvclientes.csv shows up in the Northwind folder, with its accented letters in place, the same as when you do it yourself from the console.
When something goes wrong, the agent finds out the same way you do. If I ask it for the information on facturas.dbf, which does not exist, the call comes back marked as an error with your code’s message:
stderr: dbf: no encuentro la tabla facturas.dbfAnd if it makes up a format, FoxCli stops it before it reaches your code and answers with the command’s help, which is what the agent needs to correct itself:
dbf: '--formato txt' no es válido. Valores: csv, xlsStep 4. @readonly: the commands that only look
info and campos read the table and change nothing. exportar writes a file. The agent has no way of knowing that, so it treats all three the same, and depending on how you have it configured it will ask for your permission every time it wants to use any of them.
You tell it with one line in the block of each command that only looks:
*!* 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)Rebuild with Ctrl+F7. Now info and campos reach the agent with the readOnlyHint mark, and a client that respects it lets them run without asking you every time. exportar still has no mark, and for that one it will keep asking your permission, which is what you want from a command that writes to your disk.
Only put it on the ones that write nothing, because the agent trusts the mark and does not ask again.
Step 5. What changes when the program lives for the whole session
In the console, every time you type dbf info a process starts, does its work and ends. With the agent, the server starts once at the beginning of the session and handles every call, one at a time and in the order they arrive. That changes three things.
Whatever a command leaves open, FoxCli closes. exportar opens the table and closes it with USE at the end. But if COPY TO fails halfway (a full disk, a destination in a folder without permission), the final USE never runs and the table stays open. In the console that would not matter, because the process ends. Here, the next call would find it open. That is why, after every call, including the ones that fail, FoxCli runs AfterCommand, a method of the base class that does CLOSE DATABASES ALL and CLEAR MEMORY. You do not have to write it: it comes with the base class.
What it does not touch are the SETs. If a command of yours changes SET DELETED or SET DATE, the change is still there for the next one. If you need to, you override AfterCommand in your class and put things back as they were, with DODEFAULT() at the end so it keeps closing the tables:
PROCEDURE AfterCommand
SET DELETED ON
DODEFAULT()
ENDPROCAfterCommand never shows up as a command, even though it is public.
The working folder is the agent’s. I asked for plain customers.dbf, with no path, and it found it because Claude Code is working in the Northwind folder. It is the same as in the console, where dbf info customers.dbf looks for the table in the folder you are in. If your client does not work in any particular folder, ask it for full paths.
Nobody answers questions. In the console, a command can stop to ask you something with Console.Prompt("¿Sobrescribo el fichero? ") (“overwrite the file?”) and wait for you to type the answer. Under MCP there is nobody at the keyboard, because the program’s input carries the agent’s requests. If a command of yours calls Console.Prompt(), it gets .NULL. straight away, as if the input had run out, and the answer to the agent carries a notice. dbf asks nothing, so it is not affected. If yours ask, pass that piece of information as a parameter of the command, which the agent does know how to fill in.
The license
The server has its own license, FoxCli MCP: two files, foxcli-mcp.lic and foxcli-mcp.seal, which you load in FoxForge > FoxCli license…, the same as FoxCli’s. If you have them in the same folder, they are installed together. Without a license it works the same and never expires; the only thing it does is add this line at the end of every answer to the agent, and also tell it when it connects:
Hecho con FoxCli MCP (evaluación) - irwinrodriguez.dev/foxcli-mcpWith the license loaded, Ctrl+F7 seals the server with your name and the line disappears.
Which one is which
I have three products that speak MCP, and each one is for something different:
- FoxAgent connects an agent to a VFP application that is open: it sees its forms, its data and its state while you use it.
- FoxCli MCP is today’s: your VFP logic, packaged as tools, on the same machine as the agent.
- FoxServer puts your VFP logic on a server, so it can be called from another machine.
What next
- The FoxCli guide, in FoxForge > Help > FoxCli guide, has a chapter on the MCP server with the details that did not come up here.
- If something does not start, put
"env": { "FOXCLI_MCP_TRACE": "1" }next to the"command"in the configuration: the server will report what it is doing on the error channel, and your MCP client usually shows that channel in its log. - An idea to keep going with
dbf: thecontarcommand with a filter that I suggested in the previous tutorial is a good candidate for@readonly. With it, you can ask Claude how many customers there are in Spain without giving it permission for anything else.