55 "fmt"
66 "strings"
77 "time"
8+ "net"
89
910 "github.com/google/uuid"
1011 "github.com/labstack/echo/v4"
@@ -19,6 +20,9 @@ import (
1920 pb "github.com/mudler/LocalAI/pkg/grpc/proto"
2021 "github.com/mudler/LocalAI/pkg/model"
2122
23+ "github.com/mudler/cogito"
24+ "github.com/mudler/cogito/clients"
25+ mcpTools "github.com/mudler/LocalAI/core/http/endpoints/mcp"
2226 "github.com/mudler/xlog"
2327)
2428
@@ -401,6 +405,17 @@ func ChatEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, evaluator
401405
402406 xlog .Debug ("Chat endpoint configuration read" , "config" , config )
403407
408+
409+ // Check for MCP configuration and handle if present
410+ mcpHandled , err := handleMCPToolExecution (c , input , config , startupOptions )
411+ if err != nil {
412+ xlog .Error ("[MCP] Failed to handle MCP request" , "error" , err )
413+ return err
414+ }
415+ if mcpHandled {
416+ // MCP handled the request, return early
417+ return nil
418+ }
404419 funcs := input .Functions
405420 shouldUseFn := len (input .Functions ) > 0 && config .ShouldUseFunctions ()
406421 strictMode := false
@@ -866,6 +881,132 @@ func ChatEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, evaluator
866881 }
867882}
868883
884+ // handleMCPToolExecution handles MCP tool execution using cogito when MCP config is set
885+ // It returns true if MCP was processed, false otherwise
886+
887+ // handleMCPToolExecution handles MCP tool execution using cogito when MCP config is set
888+ // It returns true if MCP was processed (and response sent), false otherwise
889+ func handleMCPToolExecution (
890+ ctx echo.Context ,
891+ req * schema.OpenAIRequest ,
892+ config * config.ModelConfig ,
893+ appConfig * config.ApplicationConfig ,
894+ ) (bool , error ) {
895+ // Check if MCP is configured
896+ if config .MCP .Stdio .Servers == nil || len (config .MCP .Stdio .Servers ) == 0 {
897+ if config .MCP .Servers .Servers == nil || len (config .MCP .Servers .Servers ) == 0 {
898+ return false , nil
899+ }
900+ }
901+
902+ xlog .Debug ("[MCP] MCP configuration detected, initializing sessions" , "model" , config .Name )
903+
904+ // Get MCP sessions from config
905+ sessions , err := mcpTools .SessionsFromMCPConfig (config .Name , config .MCP .Servers , config .MCP .Stdio )
906+ if err != nil {
907+ xlog .Error ("[MCP] Failed to create MCP sessions" , "error" , err )
908+ // Fall back to standard processing
909+ return false , nil
910+ }
911+
912+ if len (sessions ) == 0 {
913+ xlog .Warn ("[MCP] No working MCP servers found, falling back to standard function calling" )
914+ return false , nil
915+ }
916+
917+ xlog .Debug ("[MCP] Connected to MCP servers" , "count" , len (sessions ))
918+
919+ // Get API address and key for cogito LLM client
920+ apiHost , apiPort , err := net .SplitHostPort (appConfig .APIAddress )
921+ if err != nil {
922+ xlog .Error ("[MCP] Failed to parse API address" , "error" , err )
923+ return false , nil
924+ }
925+ apiKey := ""
926+ if len (appConfig .ApiKeys ) > 0 {
927+ apiKey = appConfig .ApiKeys [0 ]
928+ }
929+
930+ // Build fragment from chat messages
931+ fragment := cogito .NewEmptyFragment ()
932+ for _ , message := range req .Messages {
933+ fragment = fragment .AddMessage (cogito .MessageRole (message .Role ), message .StringContent )
934+ }
935+
936+ // Create OpenAI LLM client for cogito
937+ llm := clients .NewLocalAILLM (config .Name , apiKey , "http://" + apiHost + ":" + apiPort )
938+
939+ // Build cogito options
940+ cogitoOpts := config .BuildCogitoOptions ()
941+ cogitoOpts = append (cogitoOpts ,
942+ cogito .WithContext (ctx .Request ().Context ()),
943+ cogito .WithMCPs (sessions ... ),
944+ cogito .WithStatusCallback (func (s string ) {
945+ xlog .Debug ("[MCP Chat] Status" , "model" , config .Name , "status" , s )
946+ }),
947+ cogito .WithReasoningCallback (func (s string ) {
948+ xlog .Debug ("[MCP Chat] Reasoning" , "model" , config .Name , "reasoning" , s )
949+ }),
950+ cogito .WithToolCallBack (func (t * cogito.ToolChoice , state * cogito.SessionState ) cogito.ToolCallDecision {
951+ xlog .Debug ("[MCP Chat] Tool call" , "model" , config .Name , "tool" , t .Name , "reasoning" , t .Reasoning , "arguments" , t .Arguments )
952+ return cogito.ToolCallDecision {Approved : true }
953+ }),
954+ cogito .WithToolCallResultCallback (func (t cogito.ToolStatus ) {
955+ xlog .Debug ("[MCP Chat] Tool result" , "model" , config .Name , "tool" , t .Name , "result" , t .Result )
956+ }),
957+ )
958+
959+ // Execute tools via cogito
960+ resultFragment , err := cogito .ExecuteTools (llm , fragment , cogitoOpts ... )
961+ if err != nil {
962+ if err == cogito .ErrNoToolSelected {
963+ // No tools were selected, fall back to standard processing
964+ xlog .Debug ("[MCP] No tools selected, falling back to standard function calling" )
965+ return false , nil
966+ }
967+ xlog .Error ("[MCP] Tool execution failed" , "error" , err )
968+ // Fall back to standard processing
969+ return false , nil
970+ }
971+
972+ // Get the assistant's response from the fragment
973+ lastMsg := resultFragment .LastMessage ()
974+ if lastMsg == nil || lastMsg .Content == "" {
975+ xlog .Debug ("[MCP] No message content in fragment, falling back to standard function calling" )
976+ return false , nil
977+ }
978+
979+ xlog .Debug ("[MCP] MCP processing complete, response generated" , "model" , config .Name , "content_length" , len (lastMsg .Content ))
980+
981+ // Build response with the assistant's message
982+ content := lastMsg .Content
983+ stopReason := FinishReasonStop
984+ choice := schema.Choice {
985+ FinishReason : & stopReason ,
986+ Message : & schema.Message {
987+ Role : "assistant" ,
988+ Content : & content ,
989+ },
990+ }
991+
992+ // Include reasoning if present
993+ if lastMsg .Reasoning != "" {
994+ choice .Message .Reasoning = & lastMsg .Reasoning
995+ }
996+
997+ resp := & schema.OpenAIResponse {
998+ ID : uuid .New ().String (),
999+ Created : int (time .Now ().Unix ()),
1000+ Model : req .Model ,
1001+ Choices : []schema.Choice {choice },
1002+ Object : "chat.completion" ,
1003+ }
1004+
1005+ respData , _ := json .Marshal (resp )
1006+ xlog .Debug ("[MCP] Response" , "response" , string (respData ))
1007+
1008+ return ctx .JSON (200 , resp ) == nil , nil
1009+ }
8691010func handleQuestion (config * config.ModelConfig , funcResults []functions.FuncCallResults , result , prompt string ) (string , error ) {
8701011
8711012 if len (funcResults ) == 0 && result != "" {
0 commit comments