FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index FiveWin para Harbour/xHarbour Nuevo dbMcp.prg server / El poder de los DBFs
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Nuevo dbMcp.prg server / El poder de los DBFs
Posted: Sat Aug 02, 2025 08:38 AM
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
#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
go64.bat
@setlocal
call "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
c:\harbour\bin\win\msvc64\hbmk2 dbmcp.hbp -comp=msvc64
@endlocal
dbmcp.hbp
dbmcp.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
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 1816
Joined: Wed Oct 26, 2005 02:49 PM
Re: Nuevo dbMcp.prg server / El poder de los DBFs
Posted: Sat Aug 02, 2025 03:56 PM

Buen día Antonio

Una pregunta y que pena la ignorancia, que es un servidor MCP, y como lo implementaríamos, ya le pregunte a la IA, pero me responde muy técnico y con otras siglas que no conocemos, podrías con tus palabras explicar que es y para que sirve.

Gracias de antemano.

Saludos
LEANDRO AREVALO
Bogotá (Colombia)
https://hymlyma.com
https://hymplus.com/
leandroalfonso111@gmail.com
leandroalfonso111@hotmail.com

[ Turbo Incremental Link64 6.98 Embarcadero 7.70 ] [ FiveWin 25.01 ] [ xHarbour 64 bits) ]
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: Nuevo dbMcp.prg server / El poder de los DBFs
Posted: Sat Aug 02, 2025 04:05 PM
Querido Leandro,

Un servidor MCP (model context protocol) es como brazos, piernas, ojos, etc. para la IA :) Es decir, le permite a la IA poder realizar acciones (no solo escribir).

En este caso, para pruebas, lo usamos desde vscode a traves de Copilot Agent.

Estamos revisándolo y lo vamos a publicar modificado con varios ejemplos.
regards, saludos

Antonio Linares
www.fivetechsoft.com

Continue the discussion