From 1052e56ae5bcf587320c011127033a1b9fe4a538 Mon Sep 17 00:00:00 2001 From: Juexin Wang Date: Thu, 27 Aug 2026 14:13:06 -0700 Subject: [PATCH 1/4] feat(plugin): migrate from generated skills to the prebuilt MCP server --- .claude-plugin/plugin.json | 47 +++++++- .codex-plugin/.mcp.json | 20 ++++ .codex-plugin/plugin.json | 4 +- .github/renovate.json5 | 22 +++- .github/workflows/presubmit-tests.yml | 9 +- .github/workflows/skills-generate.yml | 57 ---------- .../workflows/skills-validate-fallback.yml | 37 ------- .github/workflows/skills-validate.yml | 56 ---------- README.md | 97 +++++++++-------- gemini-extension.json | 12 ++ mcp.json | 16 +++ mcp_config.json | 14 +++ plugin.json | 47 +++++++- skills/spanner-data/SKILL.md | 75 ------------- skills/spanner-data/scripts/execute_sql.js | 103 ------------------ .../spanner-data/scripts/execute_sql_dql.js | 103 ------------------ skills/spanner-data/scripts/list_graphs.js | 103 ------------------ skills/spanner-data/scripts/list_tables.js | 103 ------------------ toolbox_version.txt | 1 - 19 files changed, 223 insertions(+), 703 deletions(-) create mode 100644 .codex-plugin/.mcp.json delete mode 100644 .github/workflows/skills-generate.yml delete mode 100644 .github/workflows/skills-validate-fallback.yml delete mode 100644 .github/workflows/skills-validate.yml create mode 100644 mcp.json create mode 100644 mcp_config.json delete mode 100644 skills/spanner-data/SKILL.md delete mode 100755 skills/spanner-data/scripts/execute_sql.js delete mode 100755 skills/spanner-data/scripts/execute_sql_dql.js delete mode 100755 skills/spanner-data/scripts/list_graphs.js delete mode 100755 skills/spanner-data/scripts/list_tables.js delete mode 100644 toolbox_version.txt diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 1cbba7e..58679eb 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -7,7 +7,50 @@ "email": "data-cloud-ai-integrations@google.com" }, "homepage": "https://cloud.google.com/spanner", - "license": "Apache-2.0", "repository": "https://github.com/gemini-cli-extensions/spanner", - "skills": "./skills/" + "license": "Apache-2.0", + "userConfig": { + "spanner_project": { + "title": "Project ID", + "description": "ID of the Google Cloud project", + "type": "string", + "sensitive": false + }, + "spanner_instance": { + "title": "Instance", + "description": "ID of the Spanner instance", + "type": "string", + "sensitive": false + }, + "spanner_database": { + "title": "Database", + "description": "ID of the Spanner database", + "type": "string", + "sensitive": false + }, + "spanner_dialect": { + "title": "Database Dialect", + "description": "(Optional) The SQL dialect of the Spanner Database: 'googlesql' or 'postgresql'. (Default: 'googlesql')", + "type": "string", + "sensitive": false + } + }, + "mcpServers": { + "spanner": { + "command": "npx", + "args": [ + "-y", + "@toolbox-sdk/server@1.9.0", + "--prebuilt", + "spanner", + "--stdio" + ], + "env": { + "SPANNER_PROJECT": "${user_config.spanner_project}", + "SPANNER_INSTANCE": "${user_config.spanner_instance}", + "SPANNER_DATABASE": "${user_config.spanner_database}", + "SPANNER_DIALECT": "${user_config.spanner_dialect}" + } + } + } } diff --git a/.codex-plugin/.mcp.json b/.codex-plugin/.mcp.json new file mode 100644 index 0000000..633d9ec --- /dev/null +++ b/.codex-plugin/.mcp.json @@ -0,0 +1,20 @@ +{ + "mcpServers": { + "spanner": { + "command": "npx", + "args": [ + "-y", + "@toolbox-sdk/server@1.9.0", + "--prebuilt", + "spanner", + "--stdio" + ], + "env_vars": [ + "SPANNER_PROJECT", + "SPANNER_INSTANCE", + "SPANNER_DATABASE", + "SPANNER_DIALECT" + ] + } + } +} diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 3ec46df..b9697dc 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -14,7 +14,6 @@ "google-cloud", "database" ], - "skills": "./skills/", "interface": { "displayName": "Spanner", "shortDescription": "Connect and interact with Spanner data using natural language.", @@ -27,5 +26,6 @@ "defaultPrompt": [ "You are a senior database engineer and administrator specializing in Spanner. Your goal is to help users explore their Spanner databases, manage schemas, and execute queries effectively." ] - } + }, + "mcpServers": "./.codex-plugin/.mcp.json" } diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 0533093..d758c32 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -21,18 +21,28 @@ pinDigests: true, }, { - matchPackageNames: ['googleapis/mcp-toolbox'], + matchPackageNames: ['@toolbox-sdk/server'], + // Group the toolbox update under a stable branch name (renovate/toolbox). + groupName: 'toolbox', 'semanticCommitType': 'feat' } ], customManagers: [ { + // Track the npm toolbox server pinned in the MCP command. The version is + // listed in the source (mcp.json) and repeated in every generated + // manifest, so all of them are bumped in one PR. customType: "regex", - managerFilePatterns: ["/toolbox_version\\.txt$/"], - matchStrings: ["(?[\\d\\.]+)"], - datasourceTemplate: "github-releases", - packageNameTemplate: "googleapis/mcp-toolbox", - extractVersionTemplate: "^v(?.*)$", + managerFilePatterns: [ + "/mcp\.json$/", + "/mcp_config\.json$/", + "/gemini-extension\.json$/", + "/\.claude-plugin/plugin\.json$/", + "/\.codex-plugin/\.mcp\.json$/", + ], + matchStrings: ["@toolbox-sdk/server@(?[\d\.]+)"], + datasourceTemplate: "npm", + depNameTemplate: "@toolbox-sdk/server", } ] } diff --git a/.github/workflows/presubmit-tests.yml b/.github/workflows/presubmit-tests.yml index 417bb6f..c9c2680 100644 --- a/.github/workflows/presubmit-tests.yml +++ b/.github/workflows/presubmit-tests.yml @@ -17,6 +17,9 @@ on: pull_request: branches: [ main ] +permissions: + contents: read + jobs: run-presubmit-tests: runs-on: ubuntu-latest @@ -27,12 +30,6 @@ jobs: - name: Install Gemini CLI run: npm install @google/gemini-cli - - name: Install toolbox binary - run: | - VERSION=$(cat toolbox_version.txt) - curl -L -o toolbox "https://storage.googleapis.com/mcp-toolbox-for-databases/v${VERSION}/linux/amd64/toolbox" - chmod +x toolbox - - name: Install Extension run: npx gemini extensions validate . env: diff --git a/.github/workflows/skills-generate.yml b/.github/workflows/skills-generate.yml deleted file mode 100644 index 96ae70a..0000000 --- a/.github/workflows/skills-generate.yml +++ /dev/null @@ -1,57 +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. - -name: Generate Skills - -on: - pull_request: - paths: - - "toolbox_version.txt" - -jobs: - generate-skills: - # Only run for same-repo PRs (e.g. renovate's toolbox bump), where the - # built-in GITHUB_TOKEN can push back to the PR branch. - if: github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Check out PR branch - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - with: - ref: ${{ github.head_ref }} - - - name: Generate skills - run: | - VERSION="$(tr -d '\n' < toolbox_version.txt)" - echo "Detected toolbox version: $VERSION" - export VERSION - chmod +x ./.github/scripts/generate_skills.sh - ./.github/scripts/generate_skills.sh - - - name: Commit and push regenerated skills - run: | - if [ -z "$(git status --porcelain)" ]; then - echo "No skill changes generated. Nothing to commit." - exit 0 - fi - - echo "Changes detected. Committing regenerated skills..." - git config user.name "release-please[bot]" - git config user.email "55107282+release-please[bot]@users.noreply.github.com" - - git add . - git commit -m "chore: auto-generate skills for toolbox v$(tr -d '\n' < toolbox_version.txt)" - git push diff --git a/.github/workflows/skills-validate-fallback.yml b/.github/workflows/skills-validate-fallback.yml deleted file mode 100644 index b49eb62..0000000 --- a/.github/workflows/skills-validate-fallback.yml +++ /dev/null @@ -1,37 +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. - -name: lint - -on: - push: - paths-ignore: - - "skills/**" - pull_request: - paths-ignore: - - "skills/**" - pull_request_target: - types: [labeled] - paths-ignore: - - "skills/**" - workflow_dispatch: - -jobs: - skills-validate: - runs-on: ubuntu-latest - steps: - - name: Skip Skill Validation - run: | - echo "No changes detected in 'skills/' directory. Skipping validation." - echo "This job ensures the required 'skills-validate' status check passes." diff --git a/.github/workflows/skills-validate.yml b/.github/workflows/skills-validate.yml deleted file mode 100644 index e0b627b..0000000 --- a/.github/workflows/skills-validate.yml +++ /dev/null @@ -1,56 +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. - -name: Validate Skills - -on: - push: - paths: - - "skills/**" - pull_request: - paths: - - "skills/**" - pull_request_target: - types: [labeled] - paths: - - "skills/**" - -jobs: - skills-validate: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 - with: - python-version: "3.11" - - - name: Install skills-ref - run: | - pip install "git+https://github.com/agentskills/agentskills.git#subdirectory=skills-ref" - - - name: Validate Skills - run: | - failed=0 - for skill_dir in skills/*/; do - if [ -d "$skill_dir" ]; then - echo "Validating $skill_dir..." - if ! skills-ref validate "$skill_dir"; then - echo "Validation failed for $skill_dir" - failed=1 - fi - fi - done - exit $failed diff --git a/README.md b/README.md index 3d6d2bd..fd5d6e2 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# Spanner Agent Skills +# Spanner > [!NOTE] > Currently in beta (pre-v1.0), and may see breaking changes until the first stable release (v1.0). -This repository provides a set of agent skills to interact with [Google Cloud Spanner](https://cloud.google.com/spanner/docs) instances. These skills can be used with various AI agents, including [Antigravity](https://antigravity.google/), [Claude Code](https://claude.com/product/claude-code) and [Codex](https://developers.openai.com/codex), to manage your databases, execute queries, explore schemas, and troubleshoot issues using natural language prompts. +This repository packages [MCP Toolbox](https://github.com/googleapis/mcp-toolbox)'s prebuilt `spanner` server as a plugin/extension to interact with [Google Cloud Spanner](https://cloud.google.com/spanner/docs) instances. It can be used with various AI agents, including [Antigravity](https://antigravity.google/), [Claude Code](https://claude.com/product/claude-code) and [Codex](https://developers.openai.com/codex), to manage your databases, execute queries, explore schemas, and troubleshoot issues using natural language prompts. > [!IMPORTANT] > **We Want Your Feedback!** @@ -14,7 +14,7 @@ This repository provides a set of agent skills to interact with [Google Cloud Sp ## Table of Contents -- [Why Use Spanner Agent Skills?](#why-use-spanner-agent-skills) +- [Why Use Spanner?](#why-use-spanner) - [Prerequisites](#prerequisites) - [Getting Started](#getting-started) - [Configuration](#configuration) @@ -22,14 +22,14 @@ This repository provides a set of agent skills to interact with [Google Cloud Sp - [Antigravity](#antigravity) - [Claude Code](#claude-code) - [Codex](#codex) -- [Installing using open agent skills tool](#installing-using-open-agent-skills-tool) - [Installing via a compatible Agent Plugins client](#installing-via-a-compatible-agent-plugins-client) - [Usage Examples](#usage-examples) -- [Supported Skills](#supported-skills) -- [Additional Agent Skills](#additional-agent-skills) +- [Available Tools](#available-tools) +- [Generating Skills Instead](#generating-skills-instead) +- [Additional Extensions](#additional-extensions) - [Troubleshooting](#troubleshooting) -## Why Use Spanner Agent Skills? +## Why Use Spanner? - **Seamless Workflow:** Integrates seamlessly into your AI agent's environment. No need to constantly switch contexts for common database tasks. - **Natural Language Queries:** Stop wrestling with complex commands. Explore schemas and query data by describing what you want in plain English. @@ -46,6 +46,7 @@ Before you begin, ensure you have the following: - [Antigravity 2.0](https://antigravity.google/product/antigravity-2) version **v2.0.0** or higher. - [Claude Code](https://claude.com/product/claude-code) version **v2.1.94** or higher. - [Codex](https://developers.openai.com/codex) **v0.117.0** or higher. +- [Node.js](https://nodejs.org/) — the MCP server runs via `npx`. - A Google Cloud project with the **Spanner API** enabled. - Ensure [Application Default Credentials](https://cloud.google.com/docs/authentication/gcloud) are available in your environment. - IAM Permissions: @@ -69,7 +70,7 @@ Please keep these env vars handy during the installation process: ### Installation & Usage -To start interacting with your database, install the skills for your preferred AI agent, then launch the agent and use natural language to ask questions or perform tasks. +To start interacting with your database, install the extension for your preferred AI agent, then launch the agent and use natural language to ask questions or perform tasks. For the latest version, check the [releases page][releases]. @@ -81,8 +82,8 @@ For the latest version, check the [releases page][releases]. Antigravity You can use either of these two agents for Antigravity: -- [Antigravity CLI](https://github.com/google-gemini/gemini-cli) version **v0.3.5** or higher -- [Antigravity 2.0](https://antigravity.google/product/antigravity-2) version **v0.3.5** or higher. +- [Antigravity CLI](https://github.com/google-gemini/gemini-cli) version **v1.6.0** or higher +- [Antigravity 2.0](https://antigravity.google/product/antigravity-2) version **v2.0.0** or higher.
💡 Tip — Migrating from Gemini CLI?
@@ -98,28 +99,18 @@ See Migrating from Gemi #### Antigravity 2.0 (IDE) -**1. Clone the Repo:** - -```bash -git clone --branch 0.3.5 https://github.com/gemini-cli-extensions/spanner.git -``` - -**2. Install the skills:** - -Choose a location for the skills: -- **Global (all workspaces):** `~/.gemini/antigravity/skills/` -- **Workspace-specific:** `/.agents/skills/` +**1. Install the plugin:** -Copy the skill folders from the cloned repository's `skills/` directory to your chosen location: +Install the plugin directly from the remote GitHub repository: ```bash -cp -R spanner/skills/* ~/.gemini/antigravity/skills/ +agy plugin install https://github.com/gemini-cli-extensions/spanner ``` -**3. Set env vars:** +**2. Set env vars:** Set your environment vars as described in the [configuration section](#configuration). -_(Tip: Antigravity 2.0 automatically discovers skills in these directories at the start of a session. You can verify they are active by running the `/skills` command in your active session.)_ +_(Tip: You can verify the MCP server is active by running the `/mcp` command in your active session.)_ #### Antigravity CLI @@ -154,8 +145,7 @@ claude /plugin install spanner@claude-plugins-official ``` -_(Tip: Run `/plugin list` inside Claude Code to verify the plugin is active, or `/reload-plugins` if you just installed it.)_ - +_(Tip: Run `/plugin list` inside Claude Code to verify the plugin is active, or `/reload-plugins` if you just installed it.)
@@ -183,23 +173,7 @@ codex plugin marketplace upgrade data-agent-kit
-## Installing using [open agent skills tool](https://github.com/vercel-labs/skills) - -You can install skills using the `npx skills` command. - -**1. Install the skills:** - -Run the following command in your terminal to automatically download and register the skills: - -```bash -npx skills add https://github.com/gemini-cli-extensions/spanner/tree/0.3.5 -``` - -For detailed info check out the [Skills npm package](https://www.npmjs.com/package/skills). - -**2. Set env vars:** -Set your environment vars as described in the [configuration section](#configuration). - +## Installing via a compatible Agent Plugins client ## Installing via a compatible Agent Plugins client This repository is a valid [Agent Plugins](https://github.com/agentplugins/agent-plugins-spec) (v1) plugin. Any [Agent Plugins–compatible client](https://agent-plugins.org/compatible-clients) can install it directly using its own built-in plugin command — no extra tooling required — by pointing at this repository: @@ -226,11 +200,38 @@ Interact with Spanner using natural language: - **Generate Code:** - "Generate a Python dataclass to represent the 'customers' table." -## Supported Skills +## Available Tools + +The tools come from MCP Toolbox's prebuilt `spanner` server, grouped into toolsets: + +- **data** - Use these tools when you need to explore the database structure, discover schema objects like tables and graphs, and execute custom SQL queries to interact with your data. + +For the full, up-to-date list, see the [`spanner` prebuilt config](https://github.com/googleapis/mcp-toolbox/blob/main/internal/prebuiltconfigs/tools/spanner.yaml) +in the MCP Toolbox repository. -The following skills are available in this repository: +## Generating Skills Instead + +The tool-backed skills this plugin used to ship were generated from the same prebuilt +toolsets. If your agent lacks deferred tool loading, or you prefer skills, regenerate +them with the script in this repository: + +```bash +VERSION= ./.github/scripts/generate_skills.sh +``` + +Use the toolbox version pinned in [`mcp.json`](./mcp.json). A single toolset, without +the script: + +```bash +npx @toolbox-sdk/server@ --prebuilt spanner skills-generate \ + --name "" \ + --toolset "" \ + --description "" +``` -- [Spanner Data](./skills/spanner-data/SKILL.md) - Use these skills when you need to explore the database structure, discover schema objects like tables and graphs, and execute custom SQL queries to interact with your data. +The generated scripts call the toolbox through `npx`, so no binary download is needed. +See [Generate Agent Skills](https://github.com/googleapis/mcp-toolbox#generate-agent-skills) +in the MCP Toolbox repository. ## Additional Extensions @@ -244,5 +245,5 @@ Common issues: - "failed to find default credentials: google: could not find default credentials.": Ensure [Application Default Credentials](https://cloud.google.com/docs/authentication/gcloud) are available in your environment. See [Set up Application Default Credentials](https://cloud.google.com/docs/authentication/external/set-up-adc) for more information. - "✖ Error during discovery for server: MCP error -32000: Connection closed": The database connection has not been established. Ensure your configuration is set via environment variables. -- "✖ MCP ERROR: Error: spawn .../toolbox ENOENT": The Toolbox binary did not download correctly. Ensure you are using the latest version of your agent. +- "✖ MCP ERROR: Error: spawn npx ENOENT": Node.js is not installed, or `npx` is not on your `PATH`. Install Node.js, which provides `npx`. - "cannot execute binary file": The Toolbox binary did not download correctly. Ensure the correct binary for your OS/Architecture has been downloaded. See [Installing the server](https://mcp-toolbox.dev/documentation/introduction/#install-toolbox) for more information. diff --git a/gemini-extension.json b/gemini-extension.json index 9c9a1fc..0ff4f0c 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -2,6 +2,18 @@ "name": "spanner", "version": "0.3.5", "description": "Connect and interact with Spanner data using natural language.", + "mcpServers": { + "spanner": { + "command": "npx", + "args": [ + "-y", + "@toolbox-sdk/server@1.9.0", + "--prebuilt", + "spanner", + "--stdio" + ] + } + }, "contextFileName": "SPANNER.md", "settings": [ { diff --git a/mcp.json b/mcp.json new file mode 100644 index 0000000..f3c1a49 --- /dev/null +++ b/mcp.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", + "mcpServers": { + "spanner": { + "type": "stdio", + "command": "npx", + "args": [ + "-y", + "@toolbox-sdk/server@1.9.0", + "--prebuilt", + "spanner", + "--stdio" + ] + } + } +} diff --git a/mcp_config.json b/mcp_config.json new file mode 100644 index 0000000..28ddb38 --- /dev/null +++ b/mcp_config.json @@ -0,0 +1,14 @@ +{ + "mcpServers": { + "spanner": { + "command": "npx", + "args": [ + "-y", + "@toolbox-sdk/server@1.9.0", + "--prebuilt", + "spanner", + "--stdio" + ] + } + } +} diff --git a/plugin.json b/plugin.json index 4066a5e..f32d39b 100644 --- a/plugin.json +++ b/plugin.json @@ -14,5 +14,50 @@ "spanner", "google-cloud", "database" - ] + ], + "extensions": { + "com.google.cloud.data.agent-plugins": { + "config": [ + { + "key": "SPANNER_PROJECT", + "title": "Project ID", + "description": "ID of the Google Cloud project" + }, + { + "key": "SPANNER_INSTANCE", + "title": "Instance", + "description": "ID of the Spanner instance" + }, + { + "key": "SPANNER_DATABASE", + "title": "Database", + "description": "ID of the Spanner database" + }, + { + "key": "SPANNER_DIALECT", + "title": "Database Dialect", + "description": "(Optional) The SQL dialect of the Spanner Database: 'googlesql' or 'postgresql'. (Default: 'googlesql')" + } + ], + "gemini": { + "mcpServerName": "spanner", + "contextFileName": "SPANNER.md" + }, + "codex": { + "interface": { + "displayName": "Spanner", + "shortDescription": "Connect and interact with Spanner data using natural language.", + "developerName": "Google LLC", + "category": "Database", + "capabilities": [ + "Read", + "Write" + ], + "defaultPrompt": [ + "You are a senior database engineer and administrator specializing in Spanner. Your goal is to help users explore their Spanner databases, manage schemas, and execute queries effectively." + ] + } + } + } + } } diff --git a/skills/spanner-data/SKILL.md b/skills/spanner-data/SKILL.md deleted file mode 100644 index 11d9024..0000000 --- a/skills/spanner-data/SKILL.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -name: spanner-data -description: Use these skills when you need to explore the database structure, discover schema objects like tables and graphs, and execute custom SQL queries to interact with your data. ---- - -## Usage - -All scripts can be executed using Node.js. Replace `` and `` with actual values. - -**Bash:** -`node /scripts/.js '{"": ""}'` - -**PowerShell:** -`node /scripts/.js '{\"\": \"\"}'` - -Note: The scripts automatically load the environment variables from various .env files. Do not ask the user to set vars unless skill executions fails due to env var absence. - - -## Scripts - - -### execute_sql - -Use this tool to execute DML SQL. Please use the googlesql interface for Spanner. - -#### Parameters - -| Name | Type | Description | Required | Default | -| :--- | :--- | :--- | :--- | :--- | -| sql | string | The sql to execute. | Yes | | - - ---- - -### execute_sql_dql - -Use this tool to execute DQL SQL. Please use the googlesql interface for Spanner. - -#### Parameters - -| Name | Type | Description | Required | Default | -| :--- | :--- | :--- | :--- | :--- | -| sql | string | The sql to execute. | Yes | | - - ---- - -### list_graphs - -Lists detailed graph schema information (node tables, edge tables, labels and property declarations) as JSON for user-created graphs. Filters by a comma-separated list of graph names. If names are omitted, lists all graphs. The output can be 'simple' (graph names only) or 'detailed' (full schema). - -#### Parameters - -| Name | Type | Description | Required | Default | -| :--- | :--- | :--- | :--- | :--- | -| graph_names | string | Optional: A comma-separated list of graph names. If empty, details for all graphs in user-accessible schemas will be listed. | No | `` | -| output_format | string | Optional: Use 'simple' to return graph names only or use 'detailed' to return the full information schema. | No | `detailed` | - - ---- - -### list_tables - -Lists detailed schema information (object type, columns, constraints, indexes) as JSON for user-created tables (ordinary or partitioned). Filters by a comma-separated list of names. If names are omitted, lists all tables in user schemas. The output can be 'simple' (table names only) or 'detailed' (full schema). - -#### Parameters - -| Name | Type | Description | Required | Default | -| :--- | :--- | :--- | :--- | :--- | -| table_names | string | Optional: A comma-separated list of table names. If empty, details for all tables in user-accessible schemas will be listed. | No | `` | -| output_format | string | Optional: Use 'simple' to return table names only or use 'detailed' to return the full information schema. | No | `detailed` | - - ---- - diff --git a/skills/spanner-data/scripts/execute_sql.js b/skills/spanner-data/scripts/execute_sql.js deleted file mode 100755 index 1715eee..0000000 --- a/skills/spanner-data/scripts/execute_sql.js +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env node - -// 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. - -const { spawn, execSync } = require('child_process'); -const path = require('path'); -const fs = require('fs'); -const os = require('os'); - -const toolName = "execute_sql"; -const configArgs = ["--prebuilt", "spanner"]; - -const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ - 'SPANNER_DIALECT', -]; - - -function mergeEnvVars(env) { - if (process.env.GEMINI_CLI === '1') { - const envPath = path.resolve(__dirname, '../../../.env'); - if (fs.existsSync(envPath)) { - const envContent = fs.readFileSync(envPath, 'utf-8'); - envContent.split('\n').forEach(line => { - const trimmed = line.trim(); - if (trimmed && !trimmed.startsWith('#')) { - const splitIdx = trimmed.indexOf('='); - if (splitIdx !== -1) { - const key = trimmed.slice(0, splitIdx).trim(); - let value = trimmed.slice(splitIdx + 1).trim(); - value = value.replace(/(^['"]|['"]$)/g, ''); - if (env[key] === undefined) { - env[key] = value; - } - } - } - }); - } - } else if (process.env.CLAUDECODE === '1') { - const prefix = 'CLAUDE_PLUGIN_OPTION_'; - for (const key in process.env) { - if (key.startsWith(prefix)) { - env[key.substring(prefix.length)] = process.env[key]; - } - } - } -} - -function prepareEnvironment() { - let env = { ...process.env }; - let userAgent = "skills"; - if (process.env.GEMINI_CLI === '1') { - userAgent = "skills-geminicli"; - } else if (process.env.CLAUDECODE === '1') { - userAgent = "skills-claudecode"; - } else if (process.env.CODEX_CI === '1') { - userAgent = "skills-codex"; - } - mergeEnvVars(env); - - OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { - if (env[varName] === '') { - delete env[varName]; - } - }); - - - return { env, userAgent }; -} - -function main() { - const { env, userAgent } = prepareEnvironment(); - const args = process.argv.slice(2); - - const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; - const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; - const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; - - const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); - - - child.on('close', (code) => { - process.exit(code); - }); - - child.on('error', (err) => { - console.error("Error executing toolbox:", err); - process.exit(1); - }); -} - -main(); diff --git a/skills/spanner-data/scripts/execute_sql_dql.js b/skills/spanner-data/scripts/execute_sql_dql.js deleted file mode 100755 index c46068d..0000000 --- a/skills/spanner-data/scripts/execute_sql_dql.js +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env node - -// 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. - -const { spawn, execSync } = require('child_process'); -const path = require('path'); -const fs = require('fs'); -const os = require('os'); - -const toolName = "execute_sql_dql"; -const configArgs = ["--prebuilt", "spanner"]; - -const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ - 'SPANNER_DIALECT', -]; - - -function mergeEnvVars(env) { - if (process.env.GEMINI_CLI === '1') { - const envPath = path.resolve(__dirname, '../../../.env'); - if (fs.existsSync(envPath)) { - const envContent = fs.readFileSync(envPath, 'utf-8'); - envContent.split('\n').forEach(line => { - const trimmed = line.trim(); - if (trimmed && !trimmed.startsWith('#')) { - const splitIdx = trimmed.indexOf('='); - if (splitIdx !== -1) { - const key = trimmed.slice(0, splitIdx).trim(); - let value = trimmed.slice(splitIdx + 1).trim(); - value = value.replace(/(^['"]|['"]$)/g, ''); - if (env[key] === undefined) { - env[key] = value; - } - } - } - }); - } - } else if (process.env.CLAUDECODE === '1') { - const prefix = 'CLAUDE_PLUGIN_OPTION_'; - for (const key in process.env) { - if (key.startsWith(prefix)) { - env[key.substring(prefix.length)] = process.env[key]; - } - } - } -} - -function prepareEnvironment() { - let env = { ...process.env }; - let userAgent = "skills"; - if (process.env.GEMINI_CLI === '1') { - userAgent = "skills-geminicli"; - } else if (process.env.CLAUDECODE === '1') { - userAgent = "skills-claudecode"; - } else if (process.env.CODEX_CI === '1') { - userAgent = "skills-codex"; - } - mergeEnvVars(env); - - OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { - if (env[varName] === '') { - delete env[varName]; - } - }); - - - return { env, userAgent }; -} - -function main() { - const { env, userAgent } = prepareEnvironment(); - const args = process.argv.slice(2); - - const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; - const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; - const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; - - const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); - - - child.on('close', (code) => { - process.exit(code); - }); - - child.on('error', (err) => { - console.error("Error executing toolbox:", err); - process.exit(1); - }); -} - -main(); diff --git a/skills/spanner-data/scripts/list_graphs.js b/skills/spanner-data/scripts/list_graphs.js deleted file mode 100755 index 6b3a072..0000000 --- a/skills/spanner-data/scripts/list_graphs.js +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env node - -// 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. - -const { spawn, execSync } = require('child_process'); -const path = require('path'); -const fs = require('fs'); -const os = require('os'); - -const toolName = "list_graphs"; -const configArgs = ["--prebuilt", "spanner"]; - -const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ - 'SPANNER_DIALECT', -]; - - -function mergeEnvVars(env) { - if (process.env.GEMINI_CLI === '1') { - const envPath = path.resolve(__dirname, '../../../.env'); - if (fs.existsSync(envPath)) { - const envContent = fs.readFileSync(envPath, 'utf-8'); - envContent.split('\n').forEach(line => { - const trimmed = line.trim(); - if (trimmed && !trimmed.startsWith('#')) { - const splitIdx = trimmed.indexOf('='); - if (splitIdx !== -1) { - const key = trimmed.slice(0, splitIdx).trim(); - let value = trimmed.slice(splitIdx + 1).trim(); - value = value.replace(/(^['"]|['"]$)/g, ''); - if (env[key] === undefined) { - env[key] = value; - } - } - } - }); - } - } else if (process.env.CLAUDECODE === '1') { - const prefix = 'CLAUDE_PLUGIN_OPTION_'; - for (const key in process.env) { - if (key.startsWith(prefix)) { - env[key.substring(prefix.length)] = process.env[key]; - } - } - } -} - -function prepareEnvironment() { - let env = { ...process.env }; - let userAgent = "skills"; - if (process.env.GEMINI_CLI === '1') { - userAgent = "skills-geminicli"; - } else if (process.env.CLAUDECODE === '1') { - userAgent = "skills-claudecode"; - } else if (process.env.CODEX_CI === '1') { - userAgent = "skills-codex"; - } - mergeEnvVars(env); - - OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { - if (env[varName] === '') { - delete env[varName]; - } - }); - - - return { env, userAgent }; -} - -function main() { - const { env, userAgent } = prepareEnvironment(); - const args = process.argv.slice(2); - - const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; - const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; - const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; - - const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); - - - child.on('close', (code) => { - process.exit(code); - }); - - child.on('error', (err) => { - console.error("Error executing toolbox:", err); - process.exit(1); - }); -} - -main(); diff --git a/skills/spanner-data/scripts/list_tables.js b/skills/spanner-data/scripts/list_tables.js deleted file mode 100755 index 98067bc..0000000 --- a/skills/spanner-data/scripts/list_tables.js +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env node - -// 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. - -const { spawn, execSync } = require('child_process'); -const path = require('path'); -const fs = require('fs'); -const os = require('os'); - -const toolName = "list_tables"; -const configArgs = ["--prebuilt", "spanner"]; - -const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ - 'SPANNER_DIALECT', -]; - - -function mergeEnvVars(env) { - if (process.env.GEMINI_CLI === '1') { - const envPath = path.resolve(__dirname, '../../../.env'); - if (fs.existsSync(envPath)) { - const envContent = fs.readFileSync(envPath, 'utf-8'); - envContent.split('\n').forEach(line => { - const trimmed = line.trim(); - if (trimmed && !trimmed.startsWith('#')) { - const splitIdx = trimmed.indexOf('='); - if (splitIdx !== -1) { - const key = trimmed.slice(0, splitIdx).trim(); - let value = trimmed.slice(splitIdx + 1).trim(); - value = value.replace(/(^['"]|['"]$)/g, ''); - if (env[key] === undefined) { - env[key] = value; - } - } - } - }); - } - } else if (process.env.CLAUDECODE === '1') { - const prefix = 'CLAUDE_PLUGIN_OPTION_'; - for (const key in process.env) { - if (key.startsWith(prefix)) { - env[key.substring(prefix.length)] = process.env[key]; - } - } - } -} - -function prepareEnvironment() { - let env = { ...process.env }; - let userAgent = "skills"; - if (process.env.GEMINI_CLI === '1') { - userAgent = "skills-geminicli"; - } else if (process.env.CLAUDECODE === '1') { - userAgent = "skills-claudecode"; - } else if (process.env.CODEX_CI === '1') { - userAgent = "skills-codex"; - } - mergeEnvVars(env); - - OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { - if (env[varName] === '') { - delete env[varName]; - } - }); - - - return { env, userAgent }; -} - -function main() { - const { env, userAgent } = prepareEnvironment(); - const args = process.argv.slice(2); - - const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; - const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; - const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; - - const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); - - - child.on('close', (code) => { - process.exit(code); - }); - - child.on('error', (err) => { - console.error("Error executing toolbox:", err); - process.exit(1); - }); -} - -main(); diff --git a/toolbox_version.txt b/toolbox_version.txt deleted file mode 100644 index abb1658..0000000 --- a/toolbox_version.txt +++ /dev/null @@ -1 +0,0 @@ -1.9.0 \ No newline at end of file From 2ef7902a1099c4ff7dfa577390a65ece0b993f1c Mon Sep 17 00:00:00 2001 From: Juexin Wang Date: Fri, 4 Sep 2026 14:53:30 -0700 Subject: [PATCH 2/4] fix(marketplace): add missing owner and drop stale skills wording --- .claude-plugin/marketplace.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 3708c32..65f7df2 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,10 +1,14 @@ { "name": "spanner-marketplace", + "owner": { + "name": "Google LLC", + "email": "data-cloud-ai-integrations@google.com" + }, "interface": { - "displayName": "Spanner Agent Skills" + "displayName": "Spanner" }, "metadata": { - "description": "Agent skills to connect and interact with Spanner data using natural language." + "description": "Connect and interact with Spanner data using natural language." }, "plugins": [ { From 4c1d4030f41fda16c87ace88920e163a5608d5c5 Mon Sep 17 00:00:00 2001 From: Juexin Wang Date: Fri, 4 Sep 2026 14:57:54 -0700 Subject: [PATCH 3/4] ci: name the exact checkout tag in the pin comment --- .github/workflows/presubmit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/presubmit-tests.yml b/.github/workflows/presubmit-tests.yml index c9c2680..d81f3b8 100644 --- a/.github/workflows/presubmit-tests.yml +++ b/.github/workflows/presubmit-tests.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install Gemini CLI run: npm install @google/gemini-cli From 9152d10e1dbd198ce8772b399607a280b40120d1 Mon Sep 17 00:00:00 2001 From: Juexin Wang Date: Fri, 4 Sep 2026 16:24:21 -0700 Subject: [PATCH 4/4] ci: keep the skills-validate workflows, required by branch protection --- .../workflows/skills-validate-fallback.yml | 37 ++++++++++++ .github/workflows/skills-validate.yml | 56 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 .github/workflows/skills-validate-fallback.yml create mode 100644 .github/workflows/skills-validate.yml diff --git a/.github/workflows/skills-validate-fallback.yml b/.github/workflows/skills-validate-fallback.yml new file mode 100644 index 0000000..b49eb62 --- /dev/null +++ b/.github/workflows/skills-validate-fallback.yml @@ -0,0 +1,37 @@ +# 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. + +name: lint + +on: + push: + paths-ignore: + - "skills/**" + pull_request: + paths-ignore: + - "skills/**" + pull_request_target: + types: [labeled] + paths-ignore: + - "skills/**" + workflow_dispatch: + +jobs: + skills-validate: + runs-on: ubuntu-latest + steps: + - name: Skip Skill Validation + run: | + echo "No changes detected in 'skills/' directory. Skipping validation." + echo "This job ensures the required 'skills-validate' status check passes." diff --git a/.github/workflows/skills-validate.yml b/.github/workflows/skills-validate.yml new file mode 100644 index 0000000..e0b627b --- /dev/null +++ b/.github/workflows/skills-validate.yml @@ -0,0 +1,56 @@ +# 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. + +name: Validate Skills + +on: + push: + paths: + - "skills/**" + pull_request: + paths: + - "skills/**" + pull_request_target: + types: [labeled] + paths: + - "skills/**" + +jobs: + skills-validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: + python-version: "3.11" + + - name: Install skills-ref + run: | + pip install "git+https://github.com/agentskills/agentskills.git#subdirectory=skills-ref" + + - name: Validate Skills + run: | + failed=0 + for skill_dir in skills/*/; do + if [ -d "$skill_dir" ]; then + echo "Validating $skill_dir..." + if ! skills-ref validate "$skill_dir"; then + echo "Validation failed for $skill_dir" + failed=1 + fi + fi + done + exit $failed