Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/internal/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import (
_ "github.com/googleapis/mcp-toolbox/internal/tools/dataproc/dataproclistjobs"
_ "github.com/googleapis/mcp-toolbox/internal/tools/dgraph"
_ "github.com/googleapis/mcp-toolbox/internal/tools/elasticsearch/elasticsearchesql"
_ "github.com/googleapis/mcp-toolbox/internal/tools/elasticsearch/elasticsearchexecuteesql"
_ "github.com/googleapis/mcp-toolbox/internal/tools/firebird/firebirdexecutesql"
_ "github.com/googleapis/mcp-toolbox/internal/tools/firebird/firebirdsql"
_ "github.com/googleapis/mcp-toolbox/internal/tools/firestore/firestoreadddocuments"
Expand Down
22 changes: 11 additions & 11 deletions docs/en/integrations/elasticsearch/tools/elasticsearch-esql.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,14 @@ Execute ES|QL queries.
This tool allows you to execute ES|QL queries against your Elasticsearch
cluster. You can use this to perform complex searches and aggregations.

See the [official
documentation](https://www.elastic.co/docs/reference/query-languages/esql/esql-getting-started)
See the
[official documentation](https://www.elastic.co/docs/reference/query-languages/esql/esql-getting-started)
for more information.

## Compatible Sources

{{< compatible-sources >}}

## Parameters

| **name** | **type** | **required** | **description** |
|------------|:---------------------------------------:|:------------:|-----------------------------------------------------------------------------------------------------------------------------------------------------|
| query | string | false | The ES\|QL query to run. Can also be passed by parameters. |
| format | string | false | The format of the query. Default is json. Valid values are csv, json, tsv, txt, yaml, cbor, smile, or arrow. |
| timeout | integer | false | The timeout for the query in seconds. Default is 60 (1 minute). |
| parameters | [parameters](../../../documentation/configuration/tools/_index.md#specifying-parameters) | false | List of [parameters](../../../documentation/configuration/tools/_index.md#specifying-parameters) that will be used with the ES\|QL query.<br/>Only supports “string”, “integer”, “float”, “boolean”. |

## Example

```yaml
Expand All @@ -48,3 +39,12 @@ parameters:
description: Limit the number of results.
required: true
```

## Reference

| **field** | **type** | **required** | **description** |
| ---------- | :-------------------------------------: | :----------: | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| query | string | false | The ES\|QL query to run. Can also be passed by parameters. |
| format | string | false | The format of the query. Default is json. Valid values are `csv`, `json`, `tsv`, `txt`, `yaml`, `cbor`, `smile`, or `arrow`. |
| timeout | integer | false | The timeout for the query in seconds. Default is 60 (1 minute). |
| parameters | [parameters](../#specifying-parameters) | false | List of [parameters](../#specifying-parameters) that will be used with the ES\|QL query.<br/>Only supports “string”, “integer”, “float”, “boolean”. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
title: "elasticsearch-execute-esql"
type: docs
weight: 3
description: >
Execute arbitrary ES|QL statements.
---

## About

Execute arbitrary ES|QL statements.

This tool allows you to execute arbitrary ES|QL statements against your
Elasticsearch cluster at runtime. This is useful for ad-hoc queries where the
statement is not known beforehand.

See the
[official documentation](https://www.elastic.co/docs/reference/query-languages/esql/esql-getting-started)
for more information.

## Compatible Sources

{{< compatible-sources >}}

## Parameters

| **name** | **type** | **required** | **description** |
| -------- | :------: | :----------: | -------------------------------- |
| query | string | true | The ES|QL statement to execute. |

## Example

```yaml
kind: tool
name: execute_ad_hoc_esql
type: elasticsearch-execute-esql
source: elasticsearch-source
description: Use this tool to execute arbitrary ES|QL statements.
format: json
```

## Reference

| **field** | **type** | **required** | **description** |
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
| type | string | true | Must be "elasticsearch-execute-esql". |
| source | string | true | Name of the source the ES|QL should execute on. |
| description | string | true | Description of the tool that is passed to the LLM. |
| format | string | false | The format of the query. Default is json. Valid values are `csv`, `json`, `tsv`, `txt`, `yaml`, `cbor`, `smile`, or `arrow`. |

Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package elasticsearchexecuteesql

import (
"context"
"fmt"
"net/http"

yaml "github.com/goccy/go-yaml"
"github.com/googleapis/mcp-toolbox/internal/embeddingmodels"
"github.com/googleapis/mcp-toolbox/internal/sources"
es "github.com/googleapis/mcp-toolbox/internal/sources/elasticsearch"
"github.com/googleapis/mcp-toolbox/internal/tools"
"github.com/googleapis/mcp-toolbox/internal/util"
"github.com/googleapis/mcp-toolbox/internal/util/parameters"
)

const resourceType string = "elasticsearch-execute-esql"

func init() {
if !tools.Register(resourceType, newConfig) {
panic(fmt.Sprintf("tool type %q already registered", resourceType))
}
}

func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}

type compatibleSource interface {
ElasticsearchClient() es.EsClient
RunSQL(ctx context.Context, format, query string, params []map[string]any) (any, error)
}

type Config struct {
Name string `yaml:"name" validate:"required"`
Type string `yaml:"type" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
Format string `yaml:"format"`
Annotations *tools.ToolAnnotations `yaml:"annotations,omitempty"`
}

var _ tools.ToolConfig = Config{}

func (cfg Config) ToolConfigType() string {
return resourceType
}

func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
queryParameter := parameters.NewStringParameter("query", "The ES|QL statement to execute.")
params := parameters.Parameters{queryParameter}

annotations := tools.GetAnnotationsOrDefault(cfg.Annotations, tools.NewDestructiveAnnotations)
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, params, annotations)

t := Tool{
Config: cfg,
Parameters: params,
manifest: tools.Manifest{Description: cfg.Description, Parameters: params.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}

var _ tools.Tool = Tool{}

type Tool struct {
Config
Parameters parameters.Parameters `yaml:"parameters"`
manifest tools.Manifest
mcpManifest tools.McpManifest
}

func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, util.ToolboxError) {
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
if err != nil {
return nil, util.NewClientServerError("source used is not compatible with the tool", http.StatusInternalServerError, err)
}

paramsMap := params.AsMap()
query, ok := paramsMap["query"].(string)
if !ok {
return nil, util.NewAgentError(fmt.Sprintf("unable to get cast %s", paramsMap["query"]), nil)
}

// Default to json format
format := t.Format
if format == "" {
format = "json"
}

// Get logger
logger, err := util.LoggerFromContext(ctx)
if err != nil {
return nil, util.NewClientServerError("error getting logger", http.StatusInternalServerError, err)
}
logger.DebugContext(ctx, fmt.Sprintf("executing `%s` tool query: %s with format: %s", resourceType, query, format))

resp, err := source.RunSQL(ctx, format, query, nil)
if err != nil {
return nil, util.ProcessGeneralError(err)
}
return resp, nil
}

func (t Tool) EmbedParams(ctx context.Context, paramValues parameters.ParamValues, embeddingModelsMap map[string]embeddingmodels.EmbeddingModel) (parameters.ParamValues, error) {
return parameters.EmbedParams(ctx, t.Parameters, paramValues, embeddingModelsMap, nil)
}

func (t Tool) Manifest() tools.Manifest {
return t.manifest
}

func (t Tool) McpManifest() tools.McpManifest {
return t.mcpManifest
}

func (t Tool) Authorized(verifiedAuthServices []string) bool {
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

func (t Tool) RequiresClientAuthorization(resourceMgr tools.SourceProvider) (bool, error) {
return false, nil
}

func (t Tool) ToConfig() tools.ToolConfig {
return t.Config
}

func (t Tool) GetAuthTokenHeaderName(resourceMgr tools.SourceProvider) (string, error) {
return "Authorization", nil
}

func (t Tool) GetParameters() parameters.Parameters {
return t.Parameters
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package elasticsearchexecuteesql

import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/googleapis/mcp-toolbox/internal/server"
"github.com/googleapis/mcp-toolbox/internal/testutils"
)

func TestParseFromYamlElasticsearchExecuteEsql(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tcs := []struct {
desc string
in string
want server.ToolConfigs
}{
{
desc: "basic execute tool example",
in: `
kind: tool
name: example_tool
type: elasticsearch-execute-esql
source: my-elasticsearch-instance
description: Elasticsearch execute ES|QL tool
`,
want: server.ToolConfigs{
"example_tool": Config{
Name: "example_tool",
Type: "elasticsearch-execute-esql",
Source: "my-elasticsearch-instance",
Description: "Elasticsearch execute ES|QL tool",
AuthRequired: []string{},
},
},
},
{
desc: "execute tool with format",
in: `
kind: tool
name: example_tool_csv
type: elasticsearch-execute-esql
source: my-elasticsearch-instance
description: Elasticsearch execute ES|QL tool in CSV
format: csv
`,
want: server.ToolConfigs{
"example_tool_csv": Config{
Name: "example_tool_csv",
Type: "elasticsearch-execute-esql",
Source: "my-elasticsearch-instance",
Description: "Elasticsearch execute ES|QL tool in CSV",
AuthRequired: []string{},
Format: "csv",
},
},
},
}

for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
_, _, _, got, _, _, err := server.UnmarshalResourceConfig(ctx, testutils.FormatYaml(tc.in))
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Fatalf("incorrect parse: diff %v", diff)
}
})
}
}
Loading
Loading