Compare commits

..

6 Commits

Author SHA1 Message Date
rahulpinto19
f15ebbd788 add failing linksj 2026-02-11 09:23:12 +00:00
rahulpinto19
93ca490de7 check only the changed files 2026-02-11 08:20:50 +00:00
rahulpinto19
a3b920f5f4 get all the changed files 2026-02-11 08:00:31 +00:00
rahulpinto19
3495cfd0a3 improved link check 2026-02-11 07:50:15 +00:00
rahulpinto19
5a2a533053 optimized yaml 2026-02-11 06:56:40 +00:00
rahulpinto19
01dedfc893 changed files 2026-02-11 06:48:38 +00:00
20 changed files with 37 additions and 1162 deletions

View File

@@ -23,8 +23,34 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: Identify Changed Files
id: changed-files
shell: bash
run: |
git fetch origin main
CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT origin/main...HEAD)
if [ -z "$CHANGED_FILES" ]; then
echo "No markdown files changed. Skipping checks."
echo "HAS_CHANGES=false" >> $GITHUB_ENV
else
echo "--- Changed Files to Scan ---"
echo "$CHANGED_FILES"
echo "-----------------------------"
# Flatten newlines to spaces for the args list
FILES_FLAT=$(echo "$CHANGED_FILES" | tr '\n' ' ')
echo "CHECK_FILES=$FILES_FLAT" >> $GITHUB_ENV
echo "HAS_CHANGES=true" >> $GITHUB_ENV
fi
- name: Restore lychee cache
if: env.HAS_CHANGES == 'true'
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
with:
path: .lycheecache
@@ -33,6 +59,7 @@ jobs:
- name: Link Checker
id: lychee-check
if: env.HAS_CHANGES == 'true'
uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0 # v2
continue-on-error: true
with:
@@ -42,8 +69,7 @@ jobs:
--cache
--max-cache-age 1d
--exclude '^neo4j\+.*' --exclude '^bolt://.*'
README.md
docs/
${{ env.CHECK_FILES }}
output: lychee-report.md
format: markdown
fail: true

View File

@@ -132,9 +132,7 @@ import (
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookeradddashboardelement"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookeradddashboardfilter"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookerconversationalanalytics"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookercreateprojectdirectory"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookercreateprojectfile"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookerdeleteprojectdirectory"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookerdeleteprojectfile"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookerdevmode"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergenerateembedurl"
@@ -151,7 +149,6 @@ import (
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetmeasures"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetmodels"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetparameters"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetprojectdirectories"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetprojectfile"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetprojectfiles"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetprojects"

View File

@@ -6,7 +6,7 @@ description: >
Connect your IDE to Looker using Toolbox.
---
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is
[Model Context Protocol (MCP)](https://modelcotextprotocol.io/introduction) is
an open protocol for connecting Large Language Models (LLMs) to data sources
like Postgres. This guide covers how to use [MCP Toolbox for Databases][toolbox]
to expose your developer assistant tools to a Looker instance:
@@ -20,7 +20,7 @@ to expose your developer assistant tools to a Looker instance:
* [Claude code][claudecode]
* [Antigravity][antigravity]
[toolbox]: https://github.com/googleapis/genai-toolbox
[toolbox]: https://gitub.com/googleapis/genai-toolbox
[gemini-cli]: #configure-your-mcp-client
[cursor]: #configure-your-mcp-client
[windsurf]: #configure-your-mcp-client
@@ -92,7 +92,7 @@ After you install Looker in the MCP Store, resources and tools from the server a
## Install MCP Toolbox
1. Download the latest version of Toolbox as a binary. Select the [correct
binary](https://github.com/googleapis/genai-toolbox/releases) corresponding
binary](https://gitub.com/googleapis/genai-toolbox/releases) corresponding
to your OS and CPU architecture. You are required to use Toolbox version
v0.10.0+:

View File

@@ -79,7 +79,7 @@ curl -O https://storage.googleapis.com/genai-toolbox/v0.26.0/windows/amd64/toolb
{{% tab header="Claude code" lang="en" %}}
1. Install [Claude
Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview).
Code](https://docs.anthropic.com/en/docs/gents-and-tools/claude-code/overview).
1. Create a `.mcp.json` file in your project root if it doesn't exist.
1. Add the following configuration, replace the environment variables with your
values, and save:
@@ -133,7 +133,7 @@ curl -O https://storage.googleapis.com/genai-toolbox/v0.26.0/windows/amd64/toolb
{{% /tab %}}
{{% tab header="Cline" lang="en" %}}
1. Open the [Cline](https://github.com/cline/cline) extension in VS Code and
1. Open the [Cline](https://gihub.com/cline/cline) extension in VS Code and
tap the **MCP Servers** icon.
1. Tap Configure MCP Servers to open the configuration file.
1. Add the following configuration, replace the environment variables with your

View File

@@ -488,9 +488,6 @@ See [Usage Examples](../reference/cli.md#examples).
* `create_project_file`: Create a new LookML file.
* `update_project_file`: Update an existing LookML file.
* `delete_project_file`: Delete a LookML file.
* `get_project_directories`: Retrieves a list of project directories for a given LookML project.
* `create_project_directory`: Creates a new directory within a specified LookML project.
* `delete_project_directory`: Deletes a directory from a specified LookML project.
* `validate_project`: Check the syntax of a LookML project.
* `get_connections`: Get the available connections in a Looker instance.
* `get_connection_schemas`: Get the available schemas in a connection.

View File

@@ -1,44 +0,0 @@
---
title: "looker-create-project-directory"
type: docs
weight: 1
description: >
A "looker-create-project-directory" tool creates a new directory in a LookML project.
aliases:
- /resources/tools/looker-create-project-directory
---
## About
A `looker-create-project-directory` tool creates a new directory within a specified LookML project.
It's compatible with the following sources:
- [looker](../../sources/looker.md)
## Example
```yaml
kind: tools
name: looker-create-project-directory
type: looker-create-project-directory
source: looker-source
description: |
This tool creates a new directory within a specific LookML project.
It is useful for organizing project files.
Parameters:
- project_id (string): The ID of the LookML project.
- path (string): The path of the directory to create.
Output:
A string confirming the creation of the directory.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|----------------------------------------------------|
| type | string | true | Must be "looker-create-project-directory". |
| source | string | true | Name of the source Looker instance. |
| description | string | true | Description of the tool that is passed to the LLM. |

View File

@@ -1,44 +0,0 @@
---
title: "looker-delete-project-directory"
type: docs
weight: 1
description: >
A "looker-delete-project-directory" tool deletes a directory from a LookML project.
aliases:
- /resources/tools/looker-delete-project-directory
---
## About
A `looker-delete-project-directory` tool deletes a directory from a specified LookML project.
It's compatible with the following sources:
- [looker](../../sources/looker.md)
## Example
```yaml
kind: tools
name: looker-delete-project-directory
type: looker-delete-project-directory
source: looker-source
description: |
This tool deletes a directory from a specific LookML project.
It is useful for removing unnecessary or obsolete directories.
Parameters:
- project_id (string): The ID of the LookML project.
- path (string): The path of the directory to delete.
Output:
A string confirming the deletion of the directory.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|----------------------------------------------------|
| type | string | true | Must be "looker-delete-project-directory". |
| source | string | true | Name of the source Looker instance. |
| description | string | true | Description of the tool that is passed to the LLM. |

View File

@@ -1,43 +0,0 @@
---
title: "looker-get-project-directories"
type: docs
weight: 1
description: >
A "looker-get-project-directories" tool returns the directories within a specific LookML project.
aliases:
- /resources/tools/looker-get-project-directories
---
## About
A `looker-get-project-directories` tool retrieves the directories within a specified LookML project.
It's compatible with the following sources:
- [looker](../../sources/looker.md)
## Example
```yaml
kind: tools
name: looker-get-project-directories
type: looker-get-project-directories
source: looker-source
description: |
This tool retrieves a list of directories within a specific LookML project.
It is useful for exploring the project structure.
Parameters:
- project_id (string): The ID of the LookML project.
Output:
A JSON array of strings, representing the directories within the project.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|----------------------------------------------------|
| type | string | true | Must be "looker-get-project-directories". |
| source | string | true | Name of the source Looker instance. |
| description | string | true | Description of the tool that is passed to the LLM. |

View File

@@ -959,48 +959,6 @@ tools:
Output:
A confirmation message upon successful file deletion.
get_project_directories:
kind: looker-get-project-directories
source: looker-source
description: |
This tool retrieves the list of directories within a specified LookML project.
Parameters:
- project_id (required): The unique ID of the LookML project.
Output:
A JSON array of strings, where each string is the name of a directory within the project.
create_project_directory:
kind: looker-create-project-directory
source: looker-source
description: |
This tool creates a new directory within a specified LookML project.
Prerequisite: The Looker session must be in Development Mode. Use `dev_mode: true` first.
Parameters:
- project_id (required): The unique ID of the LookML project.
- directory_path (required): The path to the new directory within the project.
Output:
A confirmation message upon successful directory creation.
delete_project_directory:
kind: looker-delete-project-directory
source: looker-source
description: |
This tool permanently deletes a specified directory within a LookML project.
Prerequisite: The Looker session must be in Development Mode. Use `dev_mode: true` first.
Parameters:
- project_id (required): The unique ID of the LookML project.
- directory_path (required): The path to the directory within the project.
Output:
A confirmation message upon successful directory deletion.
validate_project:
kind: looker-validate-project
source: looker-source
@@ -1129,9 +1087,6 @@ toolsets:
- create_project_file
- update_project_file
- delete_project_file
- get_project_directories
- create_project_directory
- delete_project_directory
- validate_project
- get_connections
- get_connection_schemas

View File

@@ -1,4 +1,4 @@
// Copyright 2026 Google LLC
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -301,34 +301,3 @@ func UpdateProjectFile(l *v4.LookerSDK, projectId string, fileContent FileConten
err := l.AuthSession.Do(nil, "PUT", "/4.0", path, nil, fileContent, options)
return err
}
func GetProjectDirectories(l *v4.LookerSDK, projectId string, options *rtl.ApiSettings) (string, error) {
var result string
path := fmt.Sprintf("/projects/%s/directories", url.PathEscape(projectId))
err := l.AuthSession.Do(&result, "GET", "/4.0", path, nil, nil, options)
return result, err
}
type Directory struct {
Path string `json:"path"`
}
func CreateProjectDirectory(l *v4.LookerSDK, projectId string, directoryPath string, options *rtl.ApiSettings) (string, error) {
d := Directory{
Path: directoryPath,
}
var result string
path := fmt.Sprintf("/projects/%s/directories", url.PathEscape(projectId))
err := l.AuthSession.Do(&result, "POST", "/4.0", path, nil, d, options)
return result, err
}
func DeleteProjectDirectory(l *v4.LookerSDK, projectId string, directoryPath string, options *rtl.ApiSettings) (string, error) {
var query = map[string]any{
"path": directoryPath,
}
var result string
path := fmt.Sprintf("/projects/%s/directories", url.PathEscape(projectId))
err := l.AuthSession.Do(&result, "DELETE", "/4.0", path, query, nil, options)
return result, err
}

View File

@@ -1,175 +0,0 @@
// 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 lookercreateprojectdirectory
import (
"context"
"fmt"
yaml "github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon"
"github.com/googleapis/genai-toolbox/internal/util/parameters"
"github.com/looker-open-source/sdk-codegen/go/rtl"
v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
)
const resourceType string = "looker-create-project-directory"
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 {
UseClientAuthorization() bool
GetAuthTokenHeaderName() string
LookerApiSettings() *rtl.ApiSettings
GetLookerSDK(string) (*v4.LookerSDK, 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"`
Annotations *tools.ToolAnnotations `yaml:"annotations,omitempty"`
}
// validate interface
var _ tools.ToolConfig = Config{}
func (cfg Config) ToolConfigType() string {
return resourceType
}
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
projectIdParameter := parameters.NewStringParameter("project_id", "The id of the project")
directoryPathParameter := parameters.NewStringParameter("directory_path", "The path to create in the project")
params := parameters.Parameters{projectIdParameter, directoryPathParameter}
annotations := cfg.Annotations
if annotations == nil {
readOnlyHint := true
annotations = &tools.ToolAnnotations{
ReadOnlyHint: &readOnlyHint,
}
}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, params, annotations)
// finish tool setup
return Tool{
Config: cfg,
Parameters: params,
manifest: tools.Manifest{
Description: cfg.Description,
Parameters: params.Manifest(),
AuthRequired: cfg.AuthRequired,
},
mcpManifest: mcpManifest,
}, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Config
Parameters parameters.Parameters `yaml:"parameters"`
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) ToConfig() tools.ToolConfig {
return t.Config
}
func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, error) {
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
if err != nil {
return nil, err
}
sdk, err := source.GetLookerSDK(string(accessToken))
if err != nil {
return nil, fmt.Errorf("error getting sdk: %w", err)
}
mapParams := params.AsMap()
projectId, ok := mapParams["project_id"].(string)
if !ok {
return nil, fmt.Errorf("'project_id' must be a string, got %T", mapParams["project_id"])
}
directoryPath, ok := mapParams["directory_path"].(string)
if !ok {
return nil, fmt.Errorf("'directory_path' must be a string, got %T", mapParams["directory_path"])
}
_, err = lookercommon.CreateProjectDirectory(sdk, projectId, directoryPath, source.LookerApiSettings())
if err != nil {
return nil, fmt.Errorf("error making create_project_directory request: %s", err)
}
return fmt.Sprintf("Created directory %s in project %s", directoryPath, projectId), 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) RequiresClientAuthorization(resourceMgr tools.SourceProvider) (bool, error) {
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
if err != nil {
return false, err
}
return source.UseClientAuthorization(), nil
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}
func (t Tool) GetAuthTokenHeaderName(resourceMgr tools.SourceProvider) (string, error) {
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
if err != nil {
return "", err
}
return source.GetAuthTokenHeaderName(), nil
}
func (t Tool) GetParameters() parameters.Parameters {
return t.Parameters
}

View File

@@ -1,108 +0,0 @@
// 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 lookercreateprojectdirectory_test
import (
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/server"
"github.com/googleapis/genai-toolbox/internal/testutils"
lkr "github.com/googleapis/genai-toolbox/internal/tools/looker/lookercreateprojectdirectory"
)
func TestParseFromYamlLookerCreateProjectDirectory(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 example",
in: `
kind: tools
name: example_tool
type: looker-create-project-directory
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": lkr.Config{
Name: "example_tool",
Type: "looker-create-project-directory",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
// Parse contents
_, _, _, 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)
}
})
}
}
func TestFailParseFromYamlLookerCreateProjectDirectory(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tcs := []struct {
desc string
in string
err string
}{
{
desc: "Invalid method",
in: `
kind: tools
name: example_tool
type: looker-create-project-directory
source: my-instance
method: GOT
description: some description
`,
err: "error unmarshaling tools: unable to parse tool \"example_tool\" as type \"looker-create-project-directory\": [3:1] unknown field \"method\"\n 1 | authRequired: []\n 2 | description: some description\n> 3 | method: GOT\n ^\n 4 | name: example_tool\n 5 | source: my-instance\n 6 | type: looker-create-project-directory",
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
_, _, _, _, _, _, err := server.UnmarshalResourceConfig(ctx, testutils.FormatYaml(tc.in))
if err == nil {
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if !strings.Contains(errStr, tc.err) {
t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
}
})
}
}

View File

@@ -1,175 +0,0 @@
// 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 lookerdeleteprojectdirectory
import (
"context"
"fmt"
yaml "github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon"
"github.com/googleapis/genai-toolbox/internal/util/parameters"
"github.com/looker-open-source/sdk-codegen/go/rtl"
v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
)
const resourceType string = "looker-delete-project-directory"
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 {
UseClientAuthorization() bool
GetAuthTokenHeaderName() string
LookerApiSettings() *rtl.ApiSettings
GetLookerSDK(string) (*v4.LookerSDK, 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"`
Annotations *tools.ToolAnnotations `yaml:"annotations,omitempty"`
}
// validate interface
var _ tools.ToolConfig = Config{}
func (cfg Config) ToolConfigType() string {
return resourceType
}
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
projectIdParameter := parameters.NewStringParameter("project_id", "The id of the project")
directoryPathParameter := parameters.NewStringParameter("directory_path", "The path to delete in the project")
params := parameters.Parameters{projectIdParameter, directoryPathParameter}
annotations := cfg.Annotations
if annotations == nil {
readOnlyHint := true
annotations = &tools.ToolAnnotations{
ReadOnlyHint: &readOnlyHint,
}
}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, params, annotations)
// finish tool setup
return Tool{
Config: cfg,
Parameters: params,
manifest: tools.Manifest{
Description: cfg.Description,
Parameters: params.Manifest(),
AuthRequired: cfg.AuthRequired,
},
mcpManifest: mcpManifest,
}, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Config
Parameters parameters.Parameters `yaml:"parameters"`
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) ToConfig() tools.ToolConfig {
return t.Config
}
func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, error) {
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
if err != nil {
return nil, err
}
sdk, err := source.GetLookerSDK(string(accessToken))
if err != nil {
return nil, fmt.Errorf("error getting sdk: %w", err)
}
mapParams := params.AsMap()
projectId, ok := mapParams["project_id"].(string)
if !ok {
return nil, fmt.Errorf("'project_id' must be a string, got %T", mapParams["project_id"])
}
directoryPath, ok := mapParams["directory_path"].(string)
if !ok {
return nil, fmt.Errorf("'directory_path' must be a string, got %T", mapParams["directory_path"])
}
_, err = lookercommon.DeleteProjectDirectory(sdk, projectId, directoryPath, source.LookerApiSettings())
if err != nil {
return nil, fmt.Errorf("error making delete_project_directory request: %s", err)
}
return fmt.Sprintf("Deleted directory %s in project %s", directoryPath, projectId), 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) RequiresClientAuthorization(resourceMgr tools.SourceProvider) (bool, error) {
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
if err != nil {
return false, err
}
return source.UseClientAuthorization(), nil
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}
func (t Tool) GetAuthTokenHeaderName(resourceMgr tools.SourceProvider) (string, error) {
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
if err != nil {
return "", err
}
return source.GetAuthTokenHeaderName(), nil
}
func (t Tool) GetParameters() parameters.Parameters {
return t.Parameters
}

View File

@@ -1,108 +0,0 @@
// 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 lookerdeleteprojectdirectory_test
import (
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/server"
"github.com/googleapis/genai-toolbox/internal/testutils"
lkr "github.com/googleapis/genai-toolbox/internal/tools/looker/lookerdeleteprojectdirectory"
)
func TestParseFromYamlLookerDeleteProjectDirectory(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 example",
in: `
kind: tools
name: example_tool
type: looker-delete-project-directory
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": lkr.Config{
Name: "example_tool",
Type: "looker-delete-project-directory",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
// Parse contents
_, _, _, 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)
}
})
}
}
func TestFailParseFromYamlLookerDeleteProjectDirectory(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tcs := []struct {
desc string
in string
err string
}{
{
desc: "Invalid method",
in: `
kind: tools
name: example_tool
type: looker-delete-project-directory
source: my-instance
method: GOT
description: some description
`,
err: "error unmarshaling tools: unable to parse tool \"example_tool\" as type \"looker-delete-project-directory\": [3:1] unknown field \"method\"\n 1 | authRequired: []\n 2 | description: some description\n> 3 | method: GOT\n ^\n 4 | name: example_tool\n 5 | source: my-instance\n 6 | type: looker-delete-project-directory",
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
_, _, _, _, _, _, err := server.UnmarshalResourceConfig(ctx, testutils.FormatYaml(tc.in))
if err == nil {
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if !strings.Contains(errStr, tc.err) {
t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
}
})
}
}

View File

@@ -1,178 +0,0 @@
// 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 lookergetprojectdirectories
import (
"context"
"fmt"
yaml "github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon"
"github.com/googleapis/genai-toolbox/internal/util"
"github.com/googleapis/genai-toolbox/internal/util/parameters"
"github.com/looker-open-source/sdk-codegen/go/rtl"
v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
)
const resourceType string = "looker-get-project-directories"
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 {
UseClientAuthorization() bool
GetAuthTokenHeaderName() string
LookerApiSettings() *rtl.ApiSettings
GetLookerSDK(string) (*v4.LookerSDK, 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"`
Annotations *tools.ToolAnnotations `yaml:"annotations,omitempty"`
}
// validate interface
var _ tools.ToolConfig = Config{}
func (cfg Config) ToolConfigType() string {
return resourceType
}
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
projectIdParameter := parameters.NewStringParameter("project_id", "The id of the project")
params := parameters.Parameters{projectIdParameter}
annotations := cfg.Annotations
if annotations == nil {
readOnlyHint := true
annotations = &tools.ToolAnnotations{
ReadOnlyHint: &readOnlyHint,
}
}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, params, annotations)
// finish tool setup
return Tool{
Config: cfg,
Parameters: params,
manifest: tools.Manifest{
Description: cfg.Description,
Parameters: params.Manifest(),
AuthRequired: cfg.AuthRequired,
},
mcpManifest: mcpManifest,
}, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Config
Parameters parameters.Parameters `yaml:"parameters"`
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) ToConfig() tools.ToolConfig {
return t.Config
}
func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, error) {
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
if err != nil {
return nil, err
}
logger, err := util.LoggerFromContext(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get logger from ctx: %s", err)
}
sdk, err := source.GetLookerSDK(string(accessToken))
if err != nil {
return nil, fmt.Errorf("error getting sdk: %w", err)
}
mapParams := params.AsMap()
projectId, ok := mapParams["project_id"].(string)
if !ok {
return nil, fmt.Errorf("'project_id' must be a string, got %T", mapParams["project_id"])
}
resp, err := lookercommon.GetProjectDirectories(sdk, projectId, source.LookerApiSettings())
if err != nil {
return nil, fmt.Errorf("error making get_project_directories request: %s", err)
}
logger.DebugContext(ctx, "Got response of %v\n", resp)
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) RequiresClientAuthorization(resourceMgr tools.SourceProvider) (bool, error) {
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
if err != nil {
return false, err
}
return source.UseClientAuthorization(), nil
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}
func (t Tool) GetAuthTokenHeaderName(resourceMgr tools.SourceProvider) (string, error) {
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
if err != nil {
return "", err
}
return source.GetAuthTokenHeaderName(), nil
}
func (t Tool) GetParameters() parameters.Parameters {
return t.Parameters
}

View File

@@ -1,108 +0,0 @@
// 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 lookergetprojectdirectories_test
import (
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/server"
"github.com/googleapis/genai-toolbox/internal/testutils"
lkr "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetprojectdirectories"
)
func TestParseFromYamlLookerGetProjectDirectories(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 example",
in: `
kind: tools
name: example_tool
type: looker-get-project-directories
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": lkr.Config{
Name: "example_tool",
Type: "looker-get-project-directories",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
// Parse contents
_, _, _, 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)
}
})
}
}
func TestFailParseFromYamlLookerGetProjectDirectories(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tcs := []struct {
desc string
in string
err string
}{
{
desc: "Invalid method",
in: `
kind: tools
name: example_tool
type: looker-get-project-directories
source: my-instance
method: GOT
description: some description
`,
err: "error unmarshaling tools: unable to parse tool \"example_tool\" as type \"looker-get-project-directories\": [3:1] unknown field \"method\"\n 1 | authRequired: []\n 2 | description: some description\n> 3 | method: GOT\n ^\n 4 | name: example_tool\n 5 | source: my-instance\n 6 | type: looker-get-project-directories",
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
_, _, _, _, _, _, err := server.UnmarshalResourceConfig(ctx, testutils.FormatYaml(tc.in))
if err == nil {
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if !strings.Contains(errStr, tc.err) {
t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
}
})
}
}

View File

@@ -14,6 +14,7 @@
package bigtable
import (
"bytes"
"context"

View File

@@ -14,6 +14,7 @@
package clickhouse
import (
"bytes"
"context"

View File

@@ -14,6 +14,7 @@
package cloudmonitoring
import (
"context"
"fmt"

View File

@@ -222,21 +222,6 @@ func TestLooker(t *testing.T) {
"source": "my-instance",
"description": "Simple tool to test end to end functionality.",
},
"get_project_directories": map[string]any{
"type": "looker-get-project-directories",
"source": "my-instance",
"description": "Simple tool to test end to end functionality.",
},
"create_project_directory": map[string]any{
"type": "looker-create-project-directory",
"source": "my-instance",
"description": "Simple tool to test end to end functionality.",
},
"delete_project_directory": map[string]any{
"type": "looker-delete-project-directory",
"source": "my-instance",
"description": "Simple tool to test end to end functionality.",
},
"validate_project": map[string]any{
"type": "looker-validate-project",
"source": "my-instance",
@@ -1466,71 +1451,6 @@ func TestLooker(t *testing.T) {
},
},
)
tests.RunToolGetTestByName(t, "get_project_directories",
map[string]any{
"get_project_directories": map[string]any{
"description": "Simple tool to test end to end functionality.",
"authRequired": []any{},
"parameters": []any{
map[string]any{
"authSources": []any{},
"description": "The id of the project",
"name": "project_id",
"required": true,
"type": "string",
},
},
},
},
)
tests.RunToolGetTestByName(t, "create_project_directory",
map[string]any{
"create_project_directory": map[string]any{
"description": "Simple tool to test end to end functionality.",
"authRequired": []any{},
"parameters": []any{
map[string]any{
"authSources": []any{},
"description": "The id of the project",
"name": "project_id",
"required": true,
"type": "string",
},
map[string]any{
"authSources": []any{},
"description": "The path to create in the project",
"name": "directory_path",
"required": true,
"type": "string",
},
},
},
},
)
tests.RunToolGetTestByName(t, "delete_project_directory",
map[string]any{
"delete_project_directory": map[string]any{
"description": "Simple tool to test end to end functionality.",
"authRequired": []any{},
"parameters": []any{
map[string]any{
"authSources": []any{},
"description": "The id of the project",
"name": "project_id",
"required": true,
"type": "string",
},
map[string]any{
"authSources": []any{},
"description": "The path to delete in the project",
"name": "directory_path",
"required": true,
"type": "string",
},
},
},
},
)
tests.RunToolGetTestByName(t, "validate_project",
map[string]any{
"validate_project": map[string]any{
@@ -1767,15 +1687,6 @@ func TestLooker(t *testing.T) {
wantResult = "deleted"
tests.RunToolInvokeParametersTest(t, "delete_project_file", []byte(`{"project_id": "the_look", "file_path": "foo.view.lkml"}`), wantResult)
wantResult = "created"
tests.RunToolInvokeParametersTest(t, "create_project_directory", []byte(`{"project_id": "the_look", "directory_path": "foo_dir"}`), wantResult)
wantResult = "foo_dir"
tests.RunToolInvokeParametersTest(t, "get_project_directories", []byte(`{"project_id": "the_look"}`), wantResult)
wantResult = "deleted"
tests.RunToolInvokeParametersTest(t, "delete_project_directory", []byte(`{"project_id": "the_look", "directory_path": "foo_dir"}`), wantResult)
wantResult = "\"errors\":[]"
tests.RunToolInvokeParametersTest(t, "validate_project", []byte(`{"project_id": "the_look"}`), wantResult)