From 60b6472fa082156e1ddabf2f7de3365b852ea76d Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Sun, 5 Oct 2025 17:51:41 +0200 Subject: [PATCH] feat: Add Agentic MCP support with a new chat/completion endpoint (#6381) * WIP - add endpoint Signed-off-by: Ettore Di Giacinto * Rename Signed-off-by: Ettore Di Giacinto * Wire the Completion API Signed-off-by: Ettore Di Giacinto * Try to make it functional Signed-off-by: Ettore Di Giacinto * Almost functional Signed-off-by: Ettore Di Giacinto * Bump golang versions used in tests Signed-off-by: Ettore Di Giacinto * Add description of the tool Signed-off-by: Ettore Di Giacinto * Make it working Signed-off-by: Ettore Di Giacinto * Small optimizations Signed-off-by: Ettore Di Giacinto * Cleanup/refactor Signed-off-by: Ettore Di Giacinto * Update docs Signed-off-by: Ettore Di Giacinto --------- Signed-off-by: Ettore Di Giacinto --- .github/workflows/build-test.yaml | 6 +- .github/workflows/test.yml | 4 +- README.md | 3 + core/backend/soundgeneration.go | 2 +- core/backend/tts.go | 2 +- core/cli/run.go | 1 + core/config/application_config.go | 8 + .../{backend_config.go => model_config.go} | 39 +++ ...onfig_filter.go => model_config_filter.go} | 0 ...onfig_loader.go => model_config_loader.go} | 0 ...nd_config_test.go => model_config_test.go} | 0 core/config/{config_test.go => model_test.go} | 0 core/http/app_test.go | 2 +- core/http/endpoints/mcp/tools.go | 232 ++++++++++++++++++ core/http/endpoints/openai/completion.go | 7 +- core/http/endpoints/openai/mcp.go | 135 ++++++++++ core/http/routes/openai.go | 10 + docs/content/docs/features/mcp.md | 200 +++++++++++++++ docs/content/docs/overview.md | 2 + go.mod | 21 +- go.sum | 40 +-- 21 files changed, 679 insertions(+), 35 deletions(-) rename core/config/{backend_config.go => model_config.go} (95%) rename core/config/{backend_config_filter.go => model_config_filter.go} (100%) rename core/config/{backend_config_loader.go => model_config_loader.go} (100%) rename core/config/{backend_config_test.go => model_config_test.go} (100%) rename core/config/{config_test.go => model_test.go} (100%) create mode 100644 core/http/endpoints/mcp/tools.go create mode 100644 core/http/endpoints/openai/mcp.go create mode 100644 docs/content/docs/features/mcp.md diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index a35aee706..f71ef31e7 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -17,7 +17,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.23 + go-version: 1.25 - name: Run GoReleaser run: | make dev-dist @@ -31,7 +31,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.23 + go-version: 1.25 - name: Build launcher for macOS ARM64 run: | make build-launcher-darwin @@ -53,7 +53,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.23 + go-version: 1.25 - name: Build launcher for Linux run: | sudo apt-get update diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 91354a62c..ff2c82ae4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go-version: ['1.21.x'] + go-version: ['1.25.x'] steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main @@ -193,7 +193,7 @@ jobs: runs-on: macOS-14 strategy: matrix: - go-version: ['1.21.x'] + go-version: ['1.25.x'] steps: - name: Clone uses: actions/checkout@v5 diff --git a/README.md b/README.md index 414511d13..09f273eed 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,8 @@ For more information, see [💻 Getting started](https://localai.io/basics/getti ## 📰 Latest project news +- October 2025: 🔌 [Model Context Protocol (MCP)](https://localai.io/features/mcp/) support added for agentic capabilities with external tools +- September 2025: New Launcher application for MacOS and Linux, extended support to many backends for Mac and Nvidia L4T devices. Models: Added MLX-Audio, WAN 2.2. WebUI improvements and Python-based backends now ships portable python environments. - August 2025: MLX, MLX-VLM, Diffusers and llama.cpp are now supported on Mac M1/M2/M3+ chips ( with `development` suffix in the gallery ): https://github.com/mudler/LocalAI/pull/6049 https://github.com/mudler/LocalAI/pull/6119 https://github.com/mudler/LocalAI/pull/6121 https://github.com/mudler/LocalAI/pull/6060 - July/August 2025: 🔍 [Object Detection](https://localai.io/features/object-detection/) added to the API featuring [rf-detr](https://github.com/roboflow/rf-detr) - July 2025: All backends migrated outside of the main binary. LocalAI is now more lightweight, small, and automatically downloads the required backend to run the model. [Read the release notes](https://github.com/mudler/LocalAI/releases/tag/v3.2.0) @@ -235,6 +237,7 @@ Roadmap items: [List of issues](https://github.com/mudler/LocalAI/issues?q=is%3A - 🔍 [Object Detection](https://localai.io/features/object-detection/) - 📈 [Reranker API](https://localai.io/features/reranker/) - 🆕🖧 [P2P Inferencing](https://localai.io/features/distribute/) +- 🆕🔌 [Model Context Protocol (MCP)](https://localai.io/features/mcp/) - Agentic capabilities with external tools - [Agentic capabilities](https://github.com/mudler/LocalAGI) - 🔊 Voice activity detection (Silero-VAD support) - 🌍 Integrated WebUI! diff --git a/core/backend/soundgeneration.go b/core/backend/soundgeneration.go index 29ba856bc..2c91958cf 100644 --- a/core/backend/soundgeneration.go +++ b/core/backend/soundgeneration.go @@ -60,7 +60,7 @@ func SoundGeneration( // return RPC error if any if !res.Success { - return "", nil, fmt.Errorf(res.Message) + return "", nil, fmt.Errorf("error during sound generation: %s", res.Message) } return filePath, res, err diff --git a/core/backend/tts.go b/core/backend/tts.go index 2a9a9c93d..7b478a5fc 100644 --- a/core/backend/tts.go +++ b/core/backend/tts.go @@ -70,7 +70,7 @@ func ModelTTS( // return RPC error if any if !res.Success { - return "", nil, fmt.Errorf(res.Message) + return "", nil, fmt.Errorf("error during TTS: %s", res.Message) } return filePath, res, err diff --git a/core/cli/run.go b/core/cli/run.go index 999e05d29..473440041 100644 --- a/core/cli/run.go +++ b/core/cli/run.go @@ -127,6 +127,7 @@ func (r *RunCMD) Run(ctx *cliContext.Context) error { config.WithP2PNetworkID(r.Peer2PeerNetworkID), config.WithLoadToMemory(r.LoadToMemory), config.WithMachineTag(r.MachineTag), + config.WithAPIAddress(r.Address), } if r.DisableMetricsEndpoint { diff --git a/core/config/application_config.go b/core/config/application_config.go index 775e30f66..d98c8ba40 100644 --- a/core/config/application_config.go +++ b/core/config/application_config.go @@ -63,6 +63,8 @@ type ApplicationConfig struct { WatchDogBusyTimeout, WatchDogIdleTimeout time.Duration MachineTag string + + APIAddress string } type AppOption func(*ApplicationConfig) @@ -343,6 +345,12 @@ func WithDisableApiKeyRequirementForHttpGet(required bool) AppOption { } } +func WithAPIAddress(address string) AppOption { + return func(o *ApplicationConfig) { + o.APIAddress = address + } +} + var DisableMetricsEndpoint AppOption = func(o *ApplicationConfig) { o.DisableMetrics = true } diff --git a/core/config/backend_config.go b/core/config/model_config.go similarity index 95% rename from core/config/backend_config.go rename to core/config/model_config.go index 31173ecf8..84edd0fde 100644 --- a/core/config/backend_config.go +++ b/core/config/model_config.go @@ -73,6 +73,45 @@ type ModelConfig struct { Options []string `yaml:"options" json:"options"` Overrides []string `yaml:"overrides" json:"overrides"` + + MCP MCPConfig `yaml:"mcp" json:"mcp"` +} + +type MCPConfig struct { + Servers string `yaml:"remote" json:"remote"` + Stdio string `yaml:"stdio" json:"stdio"` +} + +func (c *MCPConfig) MCPConfigFromYAML() (MCPGenericConfig[MCPRemoteServers], MCPGenericConfig[MCPSTDIOServers]) { + var remote MCPGenericConfig[MCPRemoteServers] + var stdio MCPGenericConfig[MCPSTDIOServers] + + if err := yaml.Unmarshal([]byte(c.Servers), &remote); err != nil { + return remote, stdio + } + + if err := yaml.Unmarshal([]byte(c.Stdio), &stdio); err != nil { + return remote, stdio + } + + return remote, stdio +} + +type MCPGenericConfig[T any] struct { + Servers T `yaml:"mcpServers" json:"mcpServers"` +} +type MCPRemoteServers map[string]MCPRemoteServer +type MCPSTDIOServers map[string]MCPSTDIOServer + +type MCPRemoteServer struct { + URL string `json:"url"` + Token string `json:"token"` +} + +type MCPSTDIOServer struct { + Args []string `json:"args"` + Env map[string]string `json:"env"` + Command string `json:"command"` } // Pipeline defines other models to use for audio-to-audio diff --git a/core/config/backend_config_filter.go b/core/config/model_config_filter.go similarity index 100% rename from core/config/backend_config_filter.go rename to core/config/model_config_filter.go diff --git a/core/config/backend_config_loader.go b/core/config/model_config_loader.go similarity index 100% rename from core/config/backend_config_loader.go rename to core/config/model_config_loader.go diff --git a/core/config/backend_config_test.go b/core/config/model_config_test.go similarity index 100% rename from core/config/backend_config_test.go rename to core/config/model_config_test.go diff --git a/core/config/config_test.go b/core/config/model_test.go similarity index 100% rename from core/config/config_test.go rename to core/config/model_test.go diff --git a/core/http/app_test.go b/core/http/app_test.go index 09726c19b..c9c752df5 100644 --- a/core/http/app_test.go +++ b/core/http/app_test.go @@ -799,7 +799,7 @@ var _ = Describe("API test", func() { It("returns errors", func() { _, err := client.CreateCompletion(context.TODO(), openai.CompletionRequest{Model: "foomodel", Prompt: testPrompt}) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("error, status code: 500, message: could not load model - all backends returned error:")) + Expect(err.Error()).To(ContainSubstring("error, status code: 500, status: 500 Internal Server Error, message: could not load model - all backends returned error:")) }) It("shows the external backend", func() { diff --git a/core/http/endpoints/mcp/tools.go b/core/http/endpoints/mcp/tools.go new file mode 100644 index 000000000..83b4ad570 --- /dev/null +++ b/core/http/endpoints/mcp/tools.go @@ -0,0 +1,232 @@ +package mcp + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "os" + "os/exec" + "os/signal" + "syscall" + "time" + + "github.com/mudler/LocalAI/core/config" + "github.com/sashabaranov/go-openai" + "github.com/tmc/langchaingo/jsonschema" + + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/rs/zerolog/log" +) + +func ToolsFromMCPConfig(ctx context.Context, remote config.MCPGenericConfig[config.MCPRemoteServers], stdio config.MCPGenericConfig[config.MCPSTDIOServers]) ([]*MCPTool, error) { + allTools := []*MCPTool{} + + // Get the list of all the tools that the Agent will be esposed to + for _, server := range remote.Servers { + + // Create HTTP client with custom roundtripper for bearer token injection + client := &http.Client{ + Timeout: 360 * time.Second, + Transport: newBearerTokenRoundTripper(server.Token, http.DefaultTransport), + } + + tools, err := mcpToolsFromTransport(ctx, + &mcp.StreamableClientTransport{Endpoint: server.URL, HTTPClient: client}, + ) + if err != nil { + return nil, err + } + + allTools = append(allTools, tools...) + } + + for _, server := range stdio.Servers { + log.Debug().Msgf("[MCP stdio server] Configuration : %+v", server) + command := exec.Command(server.Command, server.Args...) + command.Env = os.Environ() + for key, value := range server.Env { + command.Env = append(command.Env, key+"="+value) + } + tools, err := mcpToolsFromTransport(ctx, + &mcp.CommandTransport{ + Command: command}, + ) + if err != nil { + return nil, err + } + + allTools = append(allTools, tools...) + } + + return allTools, nil +} + +// bearerTokenRoundTripper is a custom roundtripper that injects a bearer token +// into HTTP requests +type bearerTokenRoundTripper struct { + token string + base http.RoundTripper +} + +// RoundTrip implements the http.RoundTripper interface +func (rt *bearerTokenRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + if rt.token != "" { + req.Header.Set("Authorization", "Bearer "+rt.token) + } + return rt.base.RoundTrip(req) +} + +// newBearerTokenRoundTripper creates a new roundtripper that injects the given token +func newBearerTokenRoundTripper(token string, base http.RoundTripper) http.RoundTripper { + if base == nil { + base = http.DefaultTransport + } + return &bearerTokenRoundTripper{ + token: token, + base: base, + } +} + +type MCPTool struct { + name, description string + inputSchema ToolInputSchema + session *mcp.ClientSession + ctx context.Context + props map[string]jsonschema.Definition +} + +func (t *MCPTool) Run(args map[string]any) (string, error) { + + // Call a tool on the server. + params := &mcp.CallToolParams{ + Name: t.name, + Arguments: args, + } + res, err := t.session.CallTool(t.ctx, params) + if err != nil { + log.Error().Msgf("CallTool failed: %v", err) + return "", err + } + if res.IsError { + log.Error().Msgf("tool failed") + return "", errors.New("tool failed") + } + + result := "" + for _, c := range res.Content { + result += c.(*mcp.TextContent).Text + } + + return result, nil +} + +func (t *MCPTool) Tool() openai.Tool { + + return openai.Tool{ + Type: openai.ToolTypeFunction, + Function: &openai.FunctionDefinition{ + Name: t.name, + Description: t.description, + Parameters: jsonschema.Definition{ + Type: jsonschema.Object, + Properties: t.props, + Required: t.inputSchema.Required, + }, + }, + } +} + +func (t *MCPTool) Close() { + t.session.Close() +} + +type ToolInputSchema struct { + Type string `json:"type"` + Properties map[string]interface{} `json:"properties,omitempty"` + Required []string `json:"required,omitempty"` +} + +// probe the MCP remote and generate tools that are compliant with cogito +// TODO: Maybe move this to cogito? +func mcpToolsFromTransport(ctx context.Context, transport mcp.Transport) ([]*MCPTool, error) { + allTools := []*MCPTool{} + + // Create a new client, with no features. + client := mcp.NewClient(&mcp.Implementation{Name: "LocalAI", Version: "v1.0.0"}, nil) + session, err := client.Connect(ctx, transport, nil) + if err != nil { + log.Error().Msgf("Error connecting to MCP server: %v", err) + return nil, err + } + + tools, err := session.ListTools(ctx, nil) + if err != nil { + log.Error().Msgf("Error listing tools: %v", err) + return nil, err + } + + for _, tool := range tools.Tools { + dat, err := json.Marshal(tool.InputSchema) + if err != nil { + log.Error().Msgf("Error marshalling input schema: %v", err) + continue + } + + // XXX: This is a wild guess, to verify (data types might be incompatible) + var inputSchema ToolInputSchema + err = json.Unmarshal(dat, &inputSchema) + if err != nil { + log.Error().Msgf("Error unmarshalling input schema: %v", err) + continue + } + + props := map[string]jsonschema.Definition{} + dat, err = json.Marshal(inputSchema.Properties) + if err != nil { + log.Error().Msgf("Error marshalling input schema: %v", err) + continue + } + err = json.Unmarshal(dat, &props) + if err != nil { + log.Error().Msgf("Error unmarshalling input schema properties: %v", err) + continue + } + + allTools = append(allTools, &MCPTool{ + name: tool.Name, + description: tool.Description, + session: session, + ctx: ctx, + props: props, + inputSchema: inputSchema, + }) + } + + // We make sure we run Close on signal + handleSignal(allTools) + + return allTools, nil +} + +func handleSignal(tools []*MCPTool) { + + // Create a channel to receive OS signals + sigChan := make(chan os.Signal, 1) + + // Register for interrupt and terminate signals + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + // Handle signals in a separate goroutine + go func() { + sig := <-sigChan + log.Printf("Received signal %v, shutting down gracefully...", sig) + + for _, t := range tools { + t.Close() + } + + // Exit the application + os.Exit(0) + }() +} diff --git a/core/http/endpoints/openai/completion.go b/core/http/endpoints/openai/completion.go index 3b7fccc85..03f5f95e2 100644 --- a/core/http/endpoints/openai/completion.go +++ b/core/http/endpoints/openai/completion.go @@ -28,10 +28,10 @@ import ( // @Success 200 {object} schema.OpenAIResponse "Response" // @Router /v1/completions [post] func CompletionEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, evaluator *templates.Evaluator, appConfig *config.ApplicationConfig) func(c *fiber.Ctx) error { - created := int(time.Now().Unix()) - process := func(id string, s string, req *schema.OpenAIRequest, config *config.ModelConfig, loader *model.ModelLoader, responses chan schema.OpenAIResponse, extraUsage bool) error { tokenCallback := func(s string, tokenUsage backend.TokenUsage) bool { + created := int(time.Now().Unix()) + usage := schema.OpenAIUsage{ PromptTokens: tokenUsage.Prompt, CompletionTokens: tokenUsage.Completion, @@ -65,6 +65,9 @@ func CompletionEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, eva } return func(c *fiber.Ctx) error { + + created := int(time.Now().Unix()) + // Handle Correlation id := c.Get("X-Correlation-ID", uuid.New().String()) extraUsage := c.Get("Extra-Usage", "") != "" diff --git a/core/http/endpoints/openai/mcp.go b/core/http/endpoints/openai/mcp.go new file mode 100644 index 000000000..ee8e3a11c --- /dev/null +++ b/core/http/endpoints/openai/mcp.go @@ -0,0 +1,135 @@ +package openai + +import ( + "encoding/json" + "errors" + "strings" + "sync" + "time" + + "github.com/mudler/LocalAI/core/config" + "github.com/mudler/LocalAI/core/http/endpoints/mcp" + "github.com/mudler/LocalAI/core/http/middleware" + + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" + "github.com/mudler/LocalAI/core/schema" + "github.com/mudler/LocalAI/core/templates" + "github.com/mudler/LocalAI/pkg/model" + "github.com/mudler/cogito" + "github.com/rs/zerolog/log" +) + +// MCPCompletionEndpoint is the OpenAI Completion API endpoint https://platform.openai.com/docs/api-reference/completions +// @Summary Generate completions for a given prompt and model. +// @Param request body schema.OpenAIRequest true "query params" +// @Success 200 {object} schema.OpenAIResponse "Response" +// @Router /mcp/v1/completions [post] +func MCPCompletionEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, evaluator *templates.Evaluator, appConfig *config.ApplicationConfig) func(c *fiber.Ctx) error { + + toolsCache := map[string][]*mcp.MCPTool{} + mu := sync.Mutex{} + + // We do not support streaming mode (Yet?) + return func(c *fiber.Ctx) error { + created := int(time.Now().Unix()) + + ctx := c.Context() + + // Handle Correlation + id := c.Get("X-Correlation-ID", uuid.New().String()) + + input, ok := c.Locals(middleware.CONTEXT_LOCALS_KEY_LOCALAI_REQUEST).(*schema.OpenAIRequest) + if !ok || input.Model == "" { + return fiber.ErrBadRequest + } + + config, ok := c.Locals(middleware.CONTEXT_LOCALS_KEY_MODEL_CONFIG).(*config.ModelConfig) + if !ok || config == nil { + return fiber.ErrBadRequest + } + + allTools := []*mcp.MCPTool{} + + // Get MCP config from model config + remote, stdio := config.MCP.MCPConfigFromYAML() + + // Check if we have tools in cache, or we have to have an initial connection + mu.Lock() + tools, exists := toolsCache[config.Name] + if exists { + allTools = append(allTools, tools...) + } else { + tools, err := mcp.ToolsFromMCPConfig(ctx, remote, stdio) + if err != nil { + mu.Unlock() + return err + } + + toolsCache[config.Name] = tools + + allTools = append(allTools, tools...) + } + mu.Unlock() + + cogitoTools := []cogito.Tool{} + for _, tool := range allTools { + cogitoTools = append(cogitoTools, tool) + // defer tool.Close() + } + + fragment := cogito.NewEmptyFragment() + + for _, message := range input.Messages { + fragment = fragment.AddMessage(message.Role, message.StringContent) + } + + port := appConfig.APIAddress[strings.LastIndex(appConfig.APIAddress, ":")+1:] + apiKey := "" + if appConfig.ApiKeys != nil { + apiKey = appConfig.ApiKeys[0] + } + // TODO: instead of connecting to the API, we should just wire this internally + // and act like completion.go. + // We can do this as cogito expects an interface and we can create one that + // we satisfy to just call internally ComputeChoices + defaultLLM := cogito.NewOpenAILLM(config.Name, apiKey, "http://127.0.0.1:"+port) + + f, err := cogito.ExecuteTools( + defaultLLM, fragment, + cogito.WithStatusCallback(func(s string) { + log.Debug().Msgf("[model agent] [model: %s] Status: %s", config.Name, s) + }), + cogito.WithContext(ctx), + // TODO: move these to configs + cogito.EnableToolReEvaluator, + cogito.WithIterations(3), + cogito.WithMaxAttempts(3), + cogito.WithTools( + cogitoTools..., + ), + ) + if err != nil && !errors.Is(err, cogito.ErrNoToolSelected) { + return err + } + + f, err = defaultLLM.Ask(ctx, f) + if err != nil { + return err + } + + resp := &schema.OpenAIResponse{ + ID: id, + Created: created, + Model: input.Model, // we have to return what the user sent here, due to OpenAI spec. + Choices: []schema.Choice{{Text: f.LastMessage().Content}}, + Object: "text_completion", + } + + jsonResult, _ := json.Marshal(resp) + log.Debug().Msgf("Response: %s", jsonResult) + + // Return the prediction in the response body + return c.JSON(resp) + } +} diff --git a/core/http/routes/openai.go b/core/http/routes/openai.go index e4e31f700..cf7241228 100644 --- a/core/http/routes/openai.go +++ b/core/http/routes/openai.go @@ -54,6 +54,16 @@ func RegisterOpenAIRoutes(app *fiber.App, app.Post("/completions", completionChain...) app.Post("/v1/engines/:model/completions", completionChain...) + // MCPcompletion + mcpCompletionChain := []fiber.Handler{ + re.BuildFilteredFirstAvailableDefaultModel(config.BuildUsecaseFilterFn(config.FLAG_CHAT)), + re.SetModelAndConfig(func() schema.LocalAIRequest { return new(schema.OpenAIRequest) }), + re.SetOpenAIRequest, + openai.MCPCompletionEndpoint(application.ModelConfigLoader(), application.ModelLoader(), application.TemplatesEvaluator(), application.ApplicationConfig()), + } + app.Post("/mcp/v1/chat/completions", mcpCompletionChain...) + app.Post("/mcp/chat/completions", mcpCompletionChain...) + // embeddings embeddingChain := []fiber.Handler{ re.BuildFilteredFirstAvailableDefaultModel(config.BuildUsecaseFilterFn(config.FLAG_EMBEDDINGS)), diff --git a/docs/content/docs/features/mcp.md b/docs/content/docs/features/mcp.md new file mode 100644 index 000000000..37b210963 --- /dev/null +++ b/docs/content/docs/features/mcp.md @@ -0,0 +1,200 @@ ++++ +title = "Model Context Protocol (MCP)" +weight = 20 +toc = true +description = "Agentic capabilities with Model Context Protocol integration" +tags = ["MCP", "Agents", "Tools", "Advanced"] +categories = ["Features"] +icon = "plug" ++++ + +# Model Context Protocol (MCP) Support + +LocalAI now supports the **Model Context Protocol (MCP)**, enabling powerful agentic capabilities by connecting AI models to external tools and services. This feature allows your LocalAI models to interact with various MCP servers, providing access to real-time data, APIs, and specialized tools. + +## What is MCP? + +The Model Context Protocol is a standard for connecting AI models to external tools and data sources. It enables AI agents to: + +- Access real-time information from external APIs +- Execute commands and interact with external systems +- Use specialized tools for specific tasks +- Maintain context across multiple tool interactions + +## Key Features + +- **🔄 Real-time Tool Access**: Connect to external MCP servers for live data +- **🛠️ Multiple Server Support**: Configure both remote HTTP and local stdio servers +- **⚡ Cached Connections**: Efficient tool caching for better performance +- **🔒 Secure Authentication**: Support for bearer token authentication +- **🎯 OpenAI Compatible**: Uses the familiar `/mcp/v1/chat/completions` endpoint + +## Configuration + +MCP support is configured in your model's YAML configuration file using the `mcp` section: + +```yaml +name: my-agentic-model +backend: llama-cpp +parameters: + model: qwen3-4b.gguf + +# MCP Configuration +mcp: + remote: | + { + "mcpServers": { + "weather-api": { + "url": "https://api.weather.com/v1", + "token": "your-api-token" + }, + "search-engine": { + "url": "https://search.example.com/mcp", + "token": "your-search-token" + } + } + } + + stdio: | + { + "mcpServers": { + "file-manager": { + "command": "python", + "args": ["-m", "mcp_file_manager"], + "env": { + "API_KEY": "your-key" + } + }, + "database-tools": { + "command": "node", + "args": ["database-mcp-server.js"], + "env": { + "DB_URL": "postgresql://localhost/mydb" + } + } + } + } +``` + +### Configuration Options + +#### Remote Servers (`remote`) +Configure HTTP-based MCP servers: + +- **`url`**: The MCP server endpoint URL +- **`token`**: Bearer token for authentication (optional) + +#### STDIO Servers (`stdio`) +Configure local command-based MCP servers: + +- **`command`**: The executable command to run +- **`args`**: Array of command-line arguments +- **`env`**: Environment variables (optional) + +## Usage + +### API Endpoint + +Use the MCP-enabled completion endpoint: + +```bash +curl http://localhost:8080/mcp/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "my-agentic-model", + "messages": [ + {"role": "user", "content": "What is the current weather in New York?"} + ], + "temperature": 0.7 + }' +``` + +### Example Response + +```json +{ + "id": "chatcmpl-123", + "created": 1699123456, + "model": "my-agentic-model", + "choices": [ + { + "text": "The current weather in New York is 72°F (22°C) with partly cloudy skies. The humidity is 65% and there's a light breeze from the west at 8 mph." + } + ], + "object": "text_completion" +} +``` + +## Example Configurations + + +### Docker-based Tools + +```yaml +name: docker-agent +backend: llama-cpp +parameters: + model: qwen3-4b.gguf + +mcp: + stdio: | + { + "mcpServers": { + "searxng": { + "command": "docker", + "args": [ + "run", "-i", "--rm", + "quay.io/mudler/tests:duckduckgo-localai" + ] + } + } + } +``` + +## How It Works + +1. **Tool Discovery**: LocalAI connects to configured MCP servers and discovers available tools +2. **Tool Caching**: Tools are cached per model for efficient reuse +3. **Agent Execution**: The AI model uses the [Cogito](https://github.com/mudler/cogito) framework to execute tools +4. **Response Generation**: The model generates responses incorporating tool results + +## Supported MCP Servers + +LocalAI is compatible with any MCP-compliant server. + +## Best Practices + +### Security +- Use environment variables for sensitive tokens +- Validate MCP server endpoints before deployment +- Implement proper authentication for remote servers + +### Performance +- Cache frequently used tools +- Use appropriate timeout values for external APIs +- Monitor resource usage for stdio servers + +### Error Handling +- Implement fallback mechanisms for tool failures +- Log tool execution for debugging +- Handle network timeouts gracefully + +### With External Applications + +Use MCP-enabled models in your applications: + +```python +import openai + +client = openai.OpenAI( + base_url="http://localhost:8080/mcp/v1", + api_key="your-api-key" +) + +response = client.chat.completions.create( + model="my-agentic-model", + messages=[ + {"role": "user", "content": "Analyze the latest research papers on AI"} + ] +) +``` diff --git a/docs/content/docs/overview.md b/docs/content/docs/overview.md index 8d530ef1f..16fabcf9b 100644 --- a/docs/content/docs/overview.md +++ b/docs/content/docs/overview.md @@ -30,6 +30,7 @@ LocalAI is more than just a single tool - it's a complete ecosystem: 1. **[LocalAI Core](https://github.com/mudler/LocalAI)** - OpenAI-compatible API - Multiple model support (LLMs, image, audio) + - Model Context Protocol (MCP) for agentic capabilities - No GPU required - Fast inference with native bindings - [Github repository](https://github.com/mudler/LocalAI) @@ -78,6 +79,7 @@ For more detailed installation options and configurations, see our [Getting Star - **Vision API**: Image understanding and analysis - **Embeddings**: Vector database support - **Functions**: OpenAI-compatible function calling +- **MCP Support**: Model Context Protocol for agentic capabilities - **P2P**: Distributed inference capabilities ## Community and Support diff --git a/go.mod b/go.mod index 088a8c428..a7a4e34f4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/mudler/LocalAI -go 1.23.8 +go 1.24.1 toolchain go1.24.5 @@ -33,10 +33,12 @@ require ( github.com/libp2p/go-libp2p v0.43.0 github.com/mholt/archiver/v3 v3.5.1 github.com/microcosm-cc/bluemonday v1.0.27 + github.com/modelcontextprotocol/go-sdk v1.0.0 + github.com/mudler/cogito v0.1.0 github.com/mudler/edgevpn v0.31.0 github.com/mudler/go-processmanager v0.0.0-20240820160718-8b802d3ecf82 github.com/nikolalohinski/gonja/v2 v2.3.2 - github.com/onsi/ginkgo/v2 v2.25.1 + github.com/onsi/ginkgo/v2 v2.25.3 github.com/onsi/gomega v1.38.2 github.com/otiai10/copy v1.14.1 github.com/otiai10/openaigo v1.7.0 @@ -44,13 +46,13 @@ require ( github.com/prometheus/client_golang v1.23.0 github.com/rs/zerolog v1.33.0 github.com/russross/blackfriday v1.6.0 - github.com/sashabaranov/go-openai v1.26.2 + github.com/sashabaranov/go-openai v1.41.2 github.com/schollz/progressbar/v3 v3.14.4 github.com/shirou/gopsutil/v3 v3.24.5 github.com/streamer45/silero-vad-go v0.2.1 github.com/stretchr/testify v1.11.1 github.com/swaggo/swag v1.16.6 - github.com/testcontainers/testcontainers-go v0.35.0 + github.com/testcontainers/testcontainers-go v0.38.0 github.com/tmc/langchaingo v0.1.13 github.com/valyala/fasthttp v1.55.0 go.opentelemetry.io/otel v1.38.0 @@ -58,6 +60,7 @@ require ( go.opentelemetry.io/otel/metric v1.38.0 go.opentelemetry.io/otel/sdk/metric v1.38.0 google.golang.org/grpc v1.67.1 + google.golang.org/protobuf v1.36.8 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 oras.land/oras-go/v2 v2.6.0 @@ -90,6 +93,7 @@ require ( github.com/go-text/render v0.2.0 // indirect github.com/go-text/typesetting v0.2.1 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/google/jsonschema-go v0.3.0 // indirect github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/hack-pad/go-indexeddb v0.3.2 // indirect github.com/hack-pad/safejs v0.1.0 // indirect @@ -97,11 +101,10 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect github.com/libp2p/go-yamux/v5 v5.0.1 // indirect - github.com/magiconair/properties v1.8.7 // indirect + github.com/magiconair/properties v1.8.10 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/go-archive v0.1.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect - github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -134,11 +137,12 @@ require ( github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 // indirect github.com/rymdport/portal v0.4.1 // indirect github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect - github.com/shirou/gopsutil/v4 v4.24.7 // indirect + github.com/shirou/gopsutil/v4 v4.25.5 // indirect github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect github.com/wlynxg/anet v0.0.5 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect @@ -146,7 +150,6 @@ require ( go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/image v0.25.0 // indirect golang.org/x/time v0.12.0 // indirect - google.golang.org/protobuf v1.36.8 // indirect ) require ( @@ -167,7 +170,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/containerd/continuity v0.4.4 // indirect - github.com/containerd/errdefs v0.3.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/creachadair/otp v0.5.0 // indirect diff --git a/go.sum b/go.sum index 188b9c8ad..2ae269f3a 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,8 @@ github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFqu github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0= github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= -github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= -github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= @@ -282,6 +282,8 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q= +github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= @@ -433,8 +435,8 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/lufia/plan9stats v0.0.0-20240819163618-b1d8f4d146e7 h1:5RK988zAqB3/AN3opGfRpoQgAVqr6/A5+qRTi67VUZY= github.com/lufia/plan9stats v0.0.0-20240819163618-b1d8f4d146e7/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= @@ -495,6 +497,8 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modelcontextprotocol/go-sdk v1.0.0 h1:Z4MSjLi38bTgLrd/LjSmofqRqyBiVKRyQSJgw8q8V74= +github.com/modelcontextprotocol/go-sdk v1.0.0/go.mod h1:nYtYQroQ2KQiM0/SbyEPUWQ6xs4B95gJjEalc9AQyOs= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -506,6 +510,8 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mudler/cogito v0.1.0 h1:RybskLSPuLkBlR9Z+y4LJgIU5wVscYoHuF9+ubXsHgM= +github.com/mudler/cogito v0.1.0/go.mod h1:MiipcWbTr+fcW3HiirQRrYYjEIamZFCLkpqvdgk/Nfw= github.com/mudler/edgevpn v0.31.0 h1:CXwxQ2ZygzE7iKGl1J+vq9pL5PvsW2uc3qI/zgpNpp4= github.com/mudler/edgevpn v0.31.0/go.mod h1:DKgh9Wu/NM3UbZoPyheMXFvpu1dSLkXrqAOy3oKJN3I= github.com/mudler/go-piper v0.0.0-20241023091659-2494246fd9fc h1:RxwneJl1VgvikiX28EkpdAyL4yQVnJMrbquKospjHyA= @@ -556,8 +562,8 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.25.1 h1:Fwp6crTREKM+oA6Cz4MsO8RhKQzs2/gOIVOUscMAfZY= -github.com/onsi/ginkgo/v2 v2.25.1/go.mod h1:ppTWQ1dh9KM/F1XgpeRqelR+zHVwV81DGRSDnFxK7Sk= +github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw= +github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -680,8 +686,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA= github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4= -github.com/sashabaranov/go-openai v1.26.2 h1:cVlQa3gn3eYqNXRW03pPlpy6zLG52EU4g0FrWXc0EFI= -github.com/sashabaranov/go-openai v1.26.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/sashabaranov/go-openai v1.41.2 h1:vfPRBZNMpnqu8ELsclWcAvF19lDNgh1t6TVfFFOPiSM= +github.com/sashabaranov/go-openai v1.41.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8= github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= github.com/schollz/progressbar/v3 v3.14.4 h1:W9ZrDSJk7eqmQhd3uxFNNcTr0QL+xuGNI9dEMrw0r74= @@ -689,8 +695,8 @@ github.com/schollz/progressbar/v3 v3.14.4/go.mod h1:aT3UQ7yGm+2ZjeXPqsjTenwL3ddU github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= -github.com/shirou/gopsutil/v4 v4.24.7 h1:V9UGTK4gQ8HvcnPKf6Zt3XHyQq/peaekfxpJ2HSocJk= -github.com/shirou/gopsutil/v4 v4.24.7/go.mod h1:0uW/073rP7FYLOkvxolUQM5rMOLTNmRXnFKafpb71rw= +github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc= +github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= @@ -765,8 +771,8 @@ github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0J github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo= -github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4= +github.com/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw= +github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w= github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po= github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= @@ -807,6 +813,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -826,8 +834,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuH go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= @@ -840,8 +848,8 @@ go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6 go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE= +go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=