Esta simple idea es construir un servidor MCP pero almacenar las herramientas MCP dentro de un DBF :)
De esta forma podemos modificar la funcionalidad del servidor MCP sin tener que reconstruir el servidor MCP, manteniéndolo muy simple
dbmcp.prg // Puede ser usado para snippets, etc
go64.bat
dbmcp.hbp
De esta forma podemos modificar la funcionalidad del servidor MCP sin tener que reconstruir el servidor MCP, manteniéndolo muy simple
dbmcp.prg // Puede ser usado para snippets, etc
#define LOG_FILE "c:\temp\hbmcp.log"
PROCEDURE Main()
LOCAL cInput, cResponse
// Comprobar si la base de datos tools.dbf existe, si no, crearla
IF !File("tools.dbf")
DBCreate("tools.dbf", {{"NAME", "C", 50}, {"DESCRIPTION", "C", 255}, {"CODE", "C", 255}})
LogFile("info: ", "Base de datos tools.dbf creada")
ENDIF
// Bucle para leer continuamente desde stdin
WHILE .T.
cInput := StdIn()
IF Empty( cInput )
EXIT // Salir si no hay entrada (EOF)
ENDIF
// Procesar el mensaje
LogFile( "in: ", cInput )
cResponse := ProcessMessage( cInput )
IF ! Empty( cResponse )
LogFile( "out: ", cResponse )
StdOut( cResponse )
ENDIF
END
LogFile( "exit: ", "termina" )
RETURN
// Función para procesar mensajes JSON-RPC
FUNCTION ProcessMessage( cInput )
LOCAL cResponse := ""
LOCAL hJson, cId, cMethod, hParams, cToolName, cToolCode
// Decodificar el JSON para obtener el método y el ID
hb_jsonDecode( cInput, @hJson )
cMethod = hJson[ "method" ]
LogFile( "method: ", cMethod )
if hb_HHasKey( hJson, "id" )
cId = AllTrim( Str( hJson[ "id" ] ) )
endif
do case
case cMethod == "initialize"
LogFile( "initialize: ", cMethod )
cResponse := ;
'{' + ;
'"jsonrpc":"2.0",' + ;
'"id":' + cId + ',' + ;
'"result":{' + ;
'"protocolVersion":"2025-03-26",' + ;
'"capabilities":{' + ;
'"tools":{},' + ;
'"resources":{},' + ;
'"prompts":{}' + ;
'}' + ;
'}' + ;
'}' + hb_eol()
case cMethod == "notifications/initialized"
cResponse := ;
'{' + ;
'"jsonrpc":"2.0",' + ;
'"result":{' + ;
'"protocolVersion":"2025-03-26",' + ;
'"capabilities":{' + ;
'"tools":{},' + ;
'"resources":{},' + ;
'"prompts":{}' + ;
'}' + ;
'}' + ;
'}' + hb_eol()
case cMethod == "tools/list"
cResponse := ;
'{' + ;
'"jsonrpc":"2.0",' + ;
'"id":' + cId + ',' + ;
'"result":{' + ;
'"tools": ['
// Leer herramientas de la base de datos
USE tools.dbf ALIAS tools
GO TOP
DO WHILE !EOF()
cResponse += ;
'{' + ;
'"name":"' + tools->name + '",' + ;
'"description":"' + tools->description + '",' + ;
'"schema":{' + ;
'"type":"object",' + ;
'"properties":{},' + ;
'"additionalProperties":false,' + ;
'"returns":{' + ;
'"type":"string"' + ;
'}' + ;
'}' + ;
'},'
SKIP
END DO
USE
cResponse := Left( cResponse, Len( cResponse ) - 1 ) + ;
']' + ;
'}' + ;
'}' + hb_eol()
case cMethod == "tools/call"
cToolName = hJson[ "params" ][ "name" ]
// Buscar herramienta en la base de datos
USE tools.dbf ALIAS tools
FIND FIRST name = cToolName
IF FOUND()
cToolCode := tools->code
USE
// Ejecutar código de la herramienta
cResponse := ;
'{' + ;
'"jsonrpc":"2.0",' + ;
'"id":' + cId + ',' + ;
'"result":{"content":[{"type":"text","text":"' + EvalBlock( cToolCode ) + '"}]}' + ;
'}' + hb_eol()
ELSE
// Error: herramienta no encontrada
cResponse := ;
'{' + ;
'"jsonrpc":"2.0",' + ;
'"id":' + cId + ',' + ;
'"error":{' + ;
'"code":-32602,' + ;
'"message":"Invalid params"' + ;
'}' + ;
'}' + hb_eol()
USE
ENDIF
// Manejar métodos no soportados
otherwise
cResponse := ;
'{' + ;
'"jsonrpc":"2.0",' + ;
'"id":' + cId + ',' + ;
'"error":{' + ;
'"code":-32604,' + ;
'"message":"Method not found"' + cMethod +;
' }' + ;
'}' + hb_eol()
endcase
RETURN cResponse
// Función para escribir en el archivo de registro
FUNCTION LogFile( cKey, cValue )
LOCAL hFile, cLogEntry
hFile := FOpen( LOG_FILE, "a" ) // Modo append
IF hFile != -1
cLogEntry := hb_eol() + cKey + cValue
FWrite( hFile, cLogEntry )
FClose( hFile )
ELSE
? "Error al abrir el archivo de registro: " + LOG_FILE
ENDIF
RETURN
#pragma BEGINDUMP
#include <hbapi.h>
HB_FUNC( STDIN )
{
char buffer[ 1024 ];
if( fgets( buffer, sizeof( buffer ), stdin ) != NULL )
{
// Eliminar salto de línea final, si existe
size_t len = strlen( buffer );
if( len > 0 && buffer[ len - 1 ] == '\n' )
buffer[ len - 1 ] = '\0';
hb_retc( buffer );
}
else
{
hb_retc( "" ); // Retornar cadena vacía en caso de EOF
}
}
HB_FUNC( STDOUT )
{
if( HB_ISCHAR( 1 ) )
{
fputs( hb_parc( 1 ), stdout );
fflush( stdout ); // Forzar la escritura inmediata
}
}
#pragma ENDDUMP@setlocal
call "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
c:\harbour\bin\win\msvc64\hbmk2 dbmcp.hbp -comp=msvc64
@endlocaldbmcp.prg
-lgdiplus
-lole32
-lOleDlg
-lversion
-lucrt
-luxtheme
xhb.hbc
hbct.hbc
hbwin.hbc
hbmzip.hbc
hbziparc.hbc
hbfoxpro.hbc
-ldflag=/NODEFAULTLIB:msvcrt
-ldflag+=/NODEFAULTLIB:libucrt