chore: import upstream snapshot with attribution
CF: Deploy Dev Docs / deploy (push) Waiting to run
Sync Labels / build (push) Waiting to run
tests / unit tests (macos-latest) (push) Waiting to run
tests / unit tests (ubuntu-latest) (push) Waiting to run
tests / unit tests (windows-latest) (push) Waiting to run

This commit is contained in:
wehub-resource-sync
2026-07-13 13:32:45 +08:00
commit e04ed9c211
1798 changed files with 307905 additions and 0 deletions
+360
View File
@@ -0,0 +1,360 @@
# Copyright 2024 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.
steps:
- id: "build-docker"
name: "gcr.io/cloud-builders/docker"
waitFor: ['-']
script: |
#!/usr/bin/env bash
docker buildx create --name container-builder --driver docker-container --bootstrap --use
export TAGS="-t ${_DOCKER_URI}:$SHORT_SHA -t ${_DOCKER_URI}:$REF_NAME"
docker buildx build --platform linux/amd64,linux/arm64 --build-arg COMMIT_SHA=$(git rev-parse --short HEAD) $TAGS --push .
- id: "install-dependencies"
name: golang:1
waitFor: ['-']
env:
- 'GOPATH=/gopath'
volumes:
- name: 'go'
path: '/gopath'
script: |
go get -d ./...
- id: "install-zig"
name: golang:1
waitFor: ['-']
volumes:
- name: 'zig'
path: '/zig-tools'
script: |
#!/usr/bin/env bash
set -e
apt-get update && apt-get install -y xz-utils
curl -fL "https://ziglang.org/download/0.15.2/zig-x86_64-linux-0.15.2.tar.xz" -o zig.tar.xz
tar -xf zig.tar.xz -C /zig-tools --strip-components=1
- id: "install-macos-sdk"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor: ['-']
volumes:
- name: 'macos-sdk'
path: '/macos-sdk'
script: |
#!/usr/bin/env bash
set -e
apt-get update && apt-get install -y xz-utils
echo "Downloading macOS 14.5 SDK..."
gcloud storage cp gs://${_ASSETS_BUCKET}/MacOSX14.5.tar.xz sdk.tar.xz
mkdir -p /macos-sdk/MacOSX14.5.sdk
echo "Unpacking macOS 14.5 SDK..."
tar -xf sdk.tar.xz -C /macos-sdk/MacOSX14.5.sdk --strip-components=1
- id: "build-linux-amd64"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
env:
- 'GOPATH=/gopath'
- 'CGO_ENABLED=1'
- 'GOOS=linux'
- 'GOARCH=amd64'
- 'CC=/zig-tools/zig cc -target x86_64-linux-gnu'
- 'CXX=/zig-tools/zig c++ -target x86_64-linux-gnu'
volumes:
- name: 'go'
path: '/gopath'
- name: 'zig'
path: '/zig-tools'
script: |
#!/usr/bin/env bash
go build -ldflags "-X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.linux.amd64
- id: "store-linux-amd64"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "build-linux-amd64"
script: |
#!/usr/bin/env bash
gcloud storage cp toolbox.linux.amd64 gs://$_BUCKET_NAME/$REF_NAME/linux/amd64/toolbox
- id: "build-linux-amd64-geminicli"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
env:
- 'GOPATH=/gopath'
- 'CGO_ENABLED=1'
- 'GOOS=linux'
- 'GOARCH=amd64'
- 'CC=/zig-tools/zig cc -target x86_64-linux-gnu'
- 'CXX=/zig-tools/zig c++ -target x86_64-linux-gnu'
volumes:
- name: 'go'
path: '/gopath'
- name: 'zig'
path: '/zig-tools'
script: |
#!/usr/bin/env bash
export VERSION=$(cat ./cmd/version.txt)
go build -ldflags "-X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.geminicli.linux.amd64
- id: "build-darwin-arm64"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
- "install-macos-sdk"
env:
- 'GOPATH=/gopath'
- 'CGO_ENABLED=1'
- 'GOOS=darwin'
- 'GOARCH=arm64'
- 'SDK_PATH=/macos-sdk/MacOSX14.5.sdk'
- 'MACOS_MIN_VER=10.14'
- 'CGO_LDFLAGS=-mmacosx-version-min=10.14 --sysroot /macos-sdk/MacOSX14.5.sdk -F/macos-sdk/MacOSX14.5.sdk/System/Library/Frameworks -L/usr/lib'
- 'COMMON_FLAGS=-mmacosx-version-min=10.14 -target aarch64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks'
- 'CC=/zig-tools/zig cc -mmacosx-version-min=10.14 -target aarch64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks'
- 'CXX=/zig-tools/zig c++ -mmacosx-version-min=10.14 -target aarch64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks'
volumes:
- name: 'go'
path: '/gopath'
- name: 'zig'
path: '/zig-tools'
- name: 'macos-sdk'
path: '/macos-sdk'
script: |
#!/usr/bin/env bash
go build -trimpath -buildmode=pie -ldflags "-s -w -X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.darwin.arm64
- id: "store-darwin-arm64"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "build-darwin-arm64"
script: |
#!/usr/bin/env bash
gcloud storage cp toolbox.darwin.arm64 gs://$_BUCKET_NAME/$REF_NAME/darwin/arm64/toolbox
- id: "build-darwin-arm64-geminicli"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
- "install-macos-sdk"
env:
- 'GOPATH=/gopath'
- 'CGO_ENABLED=1'
- 'GOOS=darwin'
- 'GOARCH=arm64'
- 'SDK_PATH=/macos-sdk/MacOSX14.5.sdk'
- 'MACOS_MIN_VER=10.14'
- 'CGO_LDFLAGS=-mmacosx-version-min=10.14 --sysroot /macos-sdk/MacOSX14.5.sdk -F/macos-sdk/MacOSX14.5.sdk/System/Library/Frameworks -L/usr/lib'
- 'COMMON_FLAGS=-mmacosx-version-min=10.14 -target aarch64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks'
- 'CC=/zig-tools/zig cc -mmacosx-version-min=10.14 -target aarch64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks'
- 'CXX=/zig-tools/zig c++ -mmacosx-version-min=10.14 -target aarch64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks'
volumes:
- name: 'go'
path: '/gopath'
- name: 'zig'
path: '/zig-tools'
- name: 'macos-sdk'
path: '/macos-sdk'
script: |
#!/usr/bin/env bash
go build -trimpath -buildmode=pie -ldflags "-s -w -X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.geminicli.darwin.arm64
- id: "build-darwin-amd64"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
- "install-macos-sdk"
env:
- 'GOPATH=/gopath'
- 'CGO_ENABLED=1'
- 'GOOS=darwin'
- 'GOARCH=amd64'
- 'SDK_PATH=/macos-sdk/MacOSX14.5.sdk'
- 'MACOS_MIN_VER=10.14'
- 'CGO_LDFLAGS=-mmacosx-version-min=10.14 --sysroot /macos-sdk/MacOSX14.5.sdk -F/macos-sdk/MacOSX14.5.sdk/System/Library/Frameworks -L/usr/lib'
- 'COMMON_FLAGS=-mmacosx-version-min=10.14 -target x86_64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks'
- 'CC=/zig-tools/zig cc -mmacosx-version-min=10.14 -target x86_64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks'
- 'CXX=/zig-tools/zig c++ -mmacosx-version-min=10.14 -target x86_64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks'
volumes:
- name: 'go'
path: '/gopath'
- name: 'zig'
path: '/zig-tools'
- name: 'macos-sdk'
path: '/macos-sdk'
script: |
#!/usr/bin/env bash
go build -trimpath -buildmode=pie -ldflags "-s -w -X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.darwin.amd64
- id: "store-darwin-amd64"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "build-darwin-amd64"
script: |
#!/usr/bin/env bash
gcloud storage cp toolbox.darwin.amd64 gs://$_BUCKET_NAME/$REF_NAME/darwin/amd64/toolbox
- id: "build-darwin-amd64-geminicli"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
- "install-macos-sdk"
env:
- 'GOPATH=/gopath'
- 'CGO_ENABLED=1'
- 'GOOS=darwin'
- 'GOARCH=amd64'
- 'SDK_PATH=/macos-sdk/MacOSX14.5.sdk'
- 'MACOS_MIN_VER=10.14'
- 'CGO_LDFLAGS=-mmacosx-version-min=10.14 --sysroot /macos-sdk/MacOSX14.5.sdk -F/macos-sdk/MacOSX14.5.sdk/System/Library/Frameworks -L/usr/lib'
- 'COMMON_FLAGS=-mmacosx-version-min=10.14 -target x86_64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks'
- 'CC=/zig-tools/zig cc -mmacosx-version-min=10.14 -target x86_64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks'
- 'CXX=/zig-tools/zig c++ -mmacosx-version-min=10.14 -target x86_64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks'
volumes:
- name: 'go'
path: '/gopath'
- name: 'zig'
path: '/zig-tools'
- name: 'macos-sdk'
path: '/macos-sdk'
script: |
#!/usr/bin/env bash
go build -trimpath -buildmode=pie -ldflags "-s -w -X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.geminicli.darwin.amd64
- id: "build-windows-amd64"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
env:
- 'GOPATH=/gopath'
- 'CGO_ENABLED=1'
- 'GOOS=windows'
- 'GOARCH=amd64'
- 'CC=/zig-tools/zig cc -target x86_64-windows-gnu'
- 'CXX=/zig-tools/zig c++ -target x86_64-windows-gnu'
volumes:
- name: 'go'
path: '/gopath'
- name: 'zig'
path: '/zig-tools'
script: |
#!/usr/bin/env bash
go build -ldflags "-X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.windows.amd64
- id: "store-windows-amd64"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "build-windows-amd64"
script: |
#!/usr/bin/env bash
gcloud storage cp toolbox.windows.amd64 gs://$_BUCKET_NAME/$REF_NAME/windows/amd64/toolbox.exe
- id: "build-windows-amd64-geminicli"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
env:
- 'GOPATH=/gopath'
- 'CGO_ENABLED=1'
- 'GOOS=windows'
- 'GOARCH=amd64'
- 'CC=/zig-tools/zig cc -target x86_64-windows-gnu'
- 'CXX=/zig-tools/zig c++ -target x86_64-windows-gnu'
volumes:
- name: 'go'
path: '/gopath'
- name: 'zig'
path: '/zig-tools'
script: |
#!/usr/bin/env bash
export VERSION=$(cat ./cmd/version.txt)
go build -ldflags "-X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.geminicli.windows.amd64
- id: "build-windows-arm64"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
env:
- 'GOPATH=/gopath'
- 'CGO_ENABLED=1'
- 'GOOS=windows'
- 'GOARCH=arm64'
- 'CC=/zig-tools/zig cc -target aarch64-windows-gnu'
- 'CXX=/zig-tools/zig c++ -target aarch64-windows-gnu'
volumes:
- name: 'go'
path: '/gopath'
- name: 'zig'
path: '/zig-tools'
script: |
#!/usr/bin/env bash
go build -ldflags "-X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.windows.arm64
- id: "store-windows-arm64"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "build-windows-arm64"
script: |
#!/usr/bin/env bash
gcloud storage cp toolbox.windows.arm64 gs://$_BUCKET_NAME/$REF_NAME/windows/arm64/toolbox.exe
- id: "build-windows-arm64-geminicli"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
env:
- 'GOPATH=/gopath'
- 'CGO_ENABLED=1'
- 'GOOS=windows'
- 'GOARCH=arm64'
- 'CC=/zig-tools/zig cc -target aarch64-windows-gnu'
- 'CXX=/zig-tools/zig c++ -target aarch64-windows-gnu'
volumes:
- name: 'go'
path: '/gopath'
- name: 'zig'
path: '/zig-tools'
script: |
#!/usr/bin/env bash
export VERSION=$(cat ./cmd/version.txt)
go build -ldflags "-X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.geminicli.windows.arm64
options:
automapSubstitutions: true
dynamicSubstitutions: true
logging: CLOUD_LOGGING_ONLY # Necessary for custom service account
pool:
name: projects/$PROJECT_ID/locations/us-central1/workerPools/run-release # to increase resource for running releases
substitutions:
_REGION: us-central1
_AR_HOSTNAME: ${_REGION}-docker.pkg.dev
_AR_REPO_NAME: toolbox-dev
_BUCKET_NAME: mcp-toolbox-for-databases-dev
_ASSETS_BUCKET: toolbox-build-assets
_DOCKER_URI: ${_AR_HOSTNAME}/${PROJECT_ID}/${_AR_REPO_NAME}/toolbox
+1
View File
@@ -0,0 +1 @@
(^|/)internal/server/|(^|/)internal/util/|internal/tools/[^/]+\.go|internal/sources/[^/]+\.go|(^|/)internal/auth/|(^|/)internal/telemetry/|(^|/)internal/log/|(^|/)internal/testutils/|(^|/)internal/embeddingmodels/|(^|/)internal/sources/sqlcommenter/|(^|/)internal/prebuiltconfigs/|(^|/)internal/prompts/|tests/[^/]+\.go|\.ci/|go\.mod|go\.sum|main\.go|(^|/)cmd/
@@ -0,0 +1,42 @@
# 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.
# NOTE: The _CLUSTER_NAME substitution variable defined here must match the
# DATAPROC_LIST_JOBS_CLUSTER parameter defined in .ci/integration.cloudbuild.yaml
# (default: dataproc-testing-cluster).
steps:
- id: "recreate-dataproc-cluster"
name: "gcr.io/cloud-builders/gcloud:latest"
env:
- "PROJECT_ID=$PROJECT_ID"
- "CLUSTER_NAME=$_CLUSTER_NAME"
- "REGION=$_REGION"
- "IMAGE_VERSION=$_IMAGE_VERSION"
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
script: |
#!/usr/bin/env bash
bash .ci/dataproc/recreate_dataproc_cluster.sh "$${PROJECT_ID}" "$${REGION}" "$${IMAGE_VERSION}" "$${CLUSTER_NAME}" "$${SERVICE_ACCOUNT_EMAIL}"
options:
automapSubstitutions: true
dynamicSubstitutions: true
logging: CLOUD_LOGGING_ONLY
pool:
name: projects/$PROJECT_ID/locations/us-central1/workerPools/integration-testing
substitutions:
_CLUSTER_NAME: "dataproc-testing-cluster"
_REGION: "us-central1"
_IMAGE_VERSION: "2.3-debian12"
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env bash
# 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.
set -eo pipefail
if [ $# -lt 5 ]; then
echo "Error: Missing required arguments." >&2
echo "Usage: $0 <project_id> <region> <image_version> <cluster_name> <service_account>" >&2
exit 1
fi
PROJECT_ID="$1"
REGION="$2"
IMAGE_VERSION="$3"
CLUSTER_NAME="$4"
SERVICE_ACCOUNT="$5"
echo "=========================================================="
echo "Recreating Dataproc cluster in project: ${PROJECT_ID}"
echo "Region: ${REGION}"
echo "Image Version: ${IMAGE_VERSION}"
echo "Cluster Name: ${CLUSTER_NAME}"
echo "Service Account: ${SERVICE_ACCOUNT}"
echo "=========================================================="
# Check if the cluster exists and get its status, capturing output to handle NOT_FOUND
echo "Checking if cluster '${CLUSTER_NAME}' exists..."
set +e
STATE=$(gcloud dataproc clusters describe "${CLUSTER_NAME}" --region="${REGION}" --project="${PROJECT_ID}" --format="value(status.state)" 2>&1)
DESCRIBE_STATUS=$?
set -e
if [ ${DESCRIBE_STATUS} -eq 0 ]; then
if [ "${STATE}" = "DELETING" ]; then
echo "Cluster '${CLUSTER_NAME}' is currently in DELETING state. Waiting for deletion to complete..."
while gcloud dataproc clusters describe "${CLUSTER_NAME}" --region="${REGION}" --project="${PROJECT_ID}" >/dev/null 2>&1; do
echo "Waiting 10 seconds for cluster deletion..."
sleep 10
done
echo "Cluster '${CLUSTER_NAME}' has been successfully deleted."
else
echo "Cluster '${CLUSTER_NAME}' exists (state: ${STATE}). Deleting it..."
gcloud dataproc clusters delete "${CLUSTER_NAME}" \
--region="${REGION}" \
--project="${PROJECT_ID}" \
--quiet
echo "Cluster '${CLUSTER_NAME}' deleted successfully."
fi
elif echo "${STATE}" | grep -q "NOT_FOUND"; then
echo "Cluster '${CLUSTER_NAME}' does not exist. Skipping deletion."
else
echo "Error querying cluster existence: ${STATE}" >&2
exit ${DESCRIBE_STATUS}
fi
# Create the cluster
echo "Creating Dataproc cluster '${CLUSTER_NAME}'..."
gcloud dataproc clusters create "${CLUSTER_NAME}" \
--region="${REGION}" \
--project="${PROJECT_ID}" \
--image-version="${IMAGE_VERSION}" \
--service-account="${SERVICE_ACCOUNT}" \
--scopes=cloud-platform \
--no-address \
--network=default \
--master-machine-type=n4-standard-2 \
--worker-machine-type=n4-standard-2 \
--num-workers=2
echo "Cluster '${CLUSTER_NAME}' created successfully."
# Integration tests require at least one job to exist on the test cluster
echo "Submitting a test Spark job to cluster '${CLUSTER_NAME}'..."
gcloud dataproc jobs submit spark \
--project="${PROJECT_ID}" \
--region="${REGION}" \
--cluster="${CLUSTER_NAME}" \
--class=org.apache.spark.examples.SparkPi \
--jars=file:///usr/lib/spark/examples/jars/spark-examples.jar \
-- 100
+94
View File
@@ -0,0 +1,94 @@
#! /bin/bash
# Check if VERSION has been set
if [ -z "${VERSION}" ]; then
echo "Error: VERSION env var is not set" >&2 # Print to stderr
exit 1 # Exit with a non-zero status to indicate an error
fi
FILES=("linux.amd64" "darwin.arm64" "darwin.amd64" "windows.amd64" "windows.arm64")
echo "Waiting for Kokoro to sign and upload binaries to gs://mcp-toolbox-for-databases..."
# We wait for each of the main binaries
for file_key in "${FILES[@]}"; do
OS=$(echo "$file_key" | cut -d '.' -f 1)
ARCH=$(echo "$file_key" | cut -d '.' -f 2)
if [ "$OS" = 'windows' ]; then
URL="https://storage.googleapis.com/mcp-toolbox-for-databases/$VERSION/$OS/$ARCH/toolbox.exe"
else
URL="https://storage.googleapis.com/mcp-toolbox-for-databases/$VERSION/$OS/$ARCH/toolbox"
fi
until curl --fail --silent --head "${URL}" > /dev/null 2>&1; do
echo "Waiting for signed binary: ${URL}..."
sleep 30
done
echo "Found signed binary: ${URL}!"
done
# Wait for the Linux GPG signature
LINUX_SIG_URL="https://storage.googleapis.com/mcp-toolbox-for-databases/$VERSION/linux/amd64/toolbox.asc"
until curl --fail --silent --head "${LINUX_SIG_URL}" > /dev/null 2>&1; do
echo "Waiting for Linux GPG signature: ${LINUX_SIG_URL}..."
sleep 30
done
echo "Found Linux GPG signature: ${LINUX_SIG_URL}!"
output_string=""
# Define the descriptions - ensure this array's order matches FILES
DESCRIPTIONS=(
"For **Linux** systems running on **Intel/AMD 64-bit processors**."
"For **macOS** systems running on **Apple Silicon** (M1, M2, M3, etc.) processors."
"For **macOS** systems running on **Intel processors**."
"For **Windows** systems running on **Intel/AMD 64-bit processors**."
"For **Windows** systems running on **ARM 64-bit processors**."
)
# Write the table header
ROW_FMT="| %-105s | %-120s | %-67s |\n"
output_string+=$(printf "$ROW_FMT" "**OS/Architecture**" "**Description**" "**SHA256 Hash**")$'\n'
output_string+=$(printf "$ROW_FMT" "$(printf -- '-%0.s' {1..105})" "$(printf -- '-%0.s' {1..120})" "$(printf -- '-%0.s' {1..67})")$'\n'
# Loop through all files matching the pattern "toolbox.*.*"
for i in "${!FILES[@]}"
do
file_key="${FILES[$i]}" # e.g., "linux.amd64"
description_text="${DESCRIPTIONS[$i]}"
# Extract OS and ARCH from the filename
OS=$(echo "$file_key" | cut -d '.' -f 1)
ARCH=$(echo "$file_key" | cut -d '.' -f 2)
# Get release URL
if [ "$OS" = 'windows' ];
then
URL="https://storage.googleapis.com/mcp-toolbox-for-databases/$VERSION/$OS/$ARCH/toolbox.exe"
else
URL="https://storage.googleapis.com/mcp-toolbox-for-databases/$VERSION/$OS/$ARCH/toolbox"
fi
curl "$URL" --fail --output toolbox || exit 1
# Calculate the SHA256 checksum of the file
SHA256=$(shasum -a 256 toolbox | awk '{print $1}')
# Write the table row
if [ "$OS" = 'linux' ]; then
SIG_URL="https://storage.googleapis.com/mcp-toolbox-for-databases/$VERSION/linux/amd64/toolbox.asc"
output_string+=$(printf "$ROW_FMT" "[$OS/$ARCH]($URL) ([Signature]($SIG_URL))" "$description_text" "$SHA256")$'\n'
else
output_string+=$(printf "$ROW_FMT" "[$OS/$ARCH]($URL)" "$description_text" "$SHA256")$'\n'
fi
rm toolbox
done
printf "$output_string\n"
File diff suppressed because it is too large Load Diff
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# ==============================================================================
# Script: lint-sample-filters.sh
# Description: Checks markdown files to ensure 'sample_filters' match the
# allowed tags in filters.yaml.
# ==============================================================================
FILTERS_FILE=".hugo/data/filters.yaml"
DOCS_DIR="docs/en/"
FAILED=0
# Load valid filters from the YAML file (remove dashes and quotes)
VALID_FILTERS=$(grep -Eo "^\s*-\s*.*" "$FILTERS_FILE" | sed -E 's/^\s*-\s*["\047]?//; s/["\047]?\s*$//')
echo "Scanning $DOCS_DIR for invalid sample filters..."
# Find and check each markdown file
while IFS= read -r file; do
# CASE A: Inline Array (e.g., sample_filters: ["Tag 1", "Tag 2"])
if grep -q "^sample_filters:\s*\[" "$file"; then
TAGS=$(grep "^sample_filters:\s*\[" "$file" | grep -Eo '"[^"]+"|\x27[^\x27]+\x27' | sed "s/['\"]//g")
# CASE B: Vertical List (e.g., - Tag 1 \n - Tag 2)
elif grep -q "^sample_filters:" "$file"; then
TAGS=$(awk '/^sample_filters:/ {flag=1; next} /^[a-zA-Z]/ {flag=0} flag && /-/ {sub(/^[ \t]*-[ \t]*["\x27]?/, ""); sub(/["\x27]?\s*$/, ""); print}' "$file")
# Skip file if no sample_filters exist
else
continue
fi
# Validate extracted tags against the allowed list
while IFS= read -r tag; do
# Skip empty lines
[[ -z "$tag" ]] && continue
# Check if the exact tag exists in our valid list
if ! grep -Fxq "$tag" <<< "$VALID_FILTERS"; then
echo "Invalid filter found: '$tag' in $file"
FAILED=1
fi
done <<< "$TAGS"
done < <(find "$DOCS_DIR" -name "*.md")
# Final Output
if [[ $FAILED -eq 1 ]]; then
echo "------------------------------------------------------"
echo "Build failed: Unapproved sample_filter detected."
echo "Check your spelling/spaces or add it to $FILTERS_FILE."
exit 1
else
echo "All sample filters are valid!"
exit 0
fi
+136
View File
@@ -0,0 +1,136 @@
#!/bin/bash
set -e
python3 - << 'EOF'
"""
MCP TOOLBOX: SOURCE PAGE LINTER
===============================
This script enforces a standardized structure for integration Source pages
(source.md files). It ensures users can predictably find connection details
and configurations across all database integrations.
Note: The structural _index.md folder wrappers are intentionally ignored
by this script as they should only contain YAML frontmatter.
MAINTENANCE GUIDE:
------------------
1. TO ADD A NEW HEADING:
Add the exact heading text to the 'ALLOWED_ORDER' list in the desired
sequence.
2. TO MAKE A HEADING MANDATORY/OPTIONAL:
Add or remove the heading text in the 'REQUIRED' set.
3. TO IGNORE NEW CONTENT TYPES:
Update the regex in the 'clean_body' variable to strip out
Markdown before linting.
4. SCOPE:
This script only targets docs/en/integrations/**/source.md.
"""
import os
import re
import sys
from pathlib import Path
# --- CONFIGURATION ---
ALLOWED_ORDER = [
"About",
"Available Tools",
"Requirements",
"Example",
"Reference",
"Advanced Usage",
"Troubleshooting",
"Additional Resources"
]
REQUIRED = {"About", "Example", "Reference"}
# Regex to catch any variation of the list-tools shortcode
SHORTCODE_PATTERN = r"\{\{<\s*list-tools.*?>\}\}"
# ---------------------
integration_dir = Path("./docs/en/integrations")
if not integration_dir.exists():
print("Info: Directory './docs/en/integrations' not found. Skipping linting.")
sys.exit(0)
has_errors = False
source_pages_found = 0
# ONLY scan files specifically named "source.md"
for filepath in integration_dir.rglob("source.md"):
source_pages_found += 1
file_errors = False
if filepath.parent.parent != integration_dir:
continue
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
match = re.match(r'^\s*---\s*\n(.*?)\n---\s*(.*)', content, re.DOTALL)
if match:
frontmatter, body = match.group(1), match.group(2)
else:
print(f"[{filepath}] Error: Missing or invalid YAML frontmatter.")
has_errors = True
continue
# 1. Check for linkTitle: "Source" in frontmatter
link_title_match = re.search(r"^linkTitle:\s*[\"']?(.*?)[\"']?\s*$", frontmatter, re.MULTILINE)
if not link_title_match or link_title_match.group(1).strip() != "Source":
print(f"[{filepath}] Error: Frontmatter must contain exactly linkTitle: \"Source\".")
file_errors = True
# 2. Check for weight: 1 in frontmatter
weight_match = re.search(r"^weight:\s*[\"']?(\d+)[\"']?\s*$", frontmatter, re.MULTILINE)
if not weight_match or weight_match.group(1).strip() != "1":
print(f"[{filepath}] Error: Frontmatter must contain exactly weight: 1.")
file_errors = True
# 3. Check Shortcode Placement & Available Tools Section (Only if present)
tools_section_match = re.search(r"^##\s+Available Tools\s*(.*?)(?=^##\s|\Z)", body, re.MULTILINE | re.DOTALL)
if tools_section_match:
if not re.search(SHORTCODE_PATTERN, tools_section_match.group(1)):
print(f"[{filepath}] Error: The list-tools shortcode must be placed under the '## Available Tools' heading.")
file_errors = True
# Strip code blocks from body to avoid linting example markdown headings
clean_body = re.sub(r"```.*?```", "", body, flags=re.DOTALL)
if re.search(r"^#\s+\w+", clean_body, re.MULTILINE):
print(f"[{filepath}] Error: H1 (#) headings are forbidden in the body.")
file_errors = True
h2s = [h.strip() for h in re.findall(r"^##\s+(.*)", clean_body, re.MULTILINE)]
# Missing Required Headings
missing = REQUIRED - set(h2s)
if missing:
print(f"[{filepath}] Error: Missing required H2 headings: {missing}")
file_errors = True
if unauthorized := (set(h2s) - set(ALLOWED_ORDER)):
print(f"[{filepath}] Error: Unauthorized H2s found: {unauthorized}")
file_errors = True
# 5. Order Check
if [h for h in h2s if h in ALLOWED_ORDER] != [h for h in ALLOWED_ORDER if h in h2s]:
print(f"[{filepath}] Error: Headings out of order. Reference: {ALLOWED_ORDER}")
file_errors = True
if file_errors: has_errors = True
# Handle final output based on what was found
if source_pages_found == 0:
print("Info: No 'source.md' files found in integrations. Passing gracefully.")
sys.exit(0)
elif has_errors:
print(f"\nLinting failed. Please fix the structure errors in the {source_pages_found} 'source.md' file(s) above.")
sys.exit(1)
else:
print(f"Success: {source_pages_found} 'source.md' file(s) passed structure validation.")
EOF
+155
View File
@@ -0,0 +1,155 @@
#!/bin/bash
set -e
python3 - << 'EOF'
"""
MCP TOOLBOX: TOOL PAGE LINTER
=============================
This script enforces a standardized structure for individual Tool pages
and their parent directory wrappers. It ensures LLM agents can parse
tool capabilities and parameter definitions reliably.
MAINTENANCE GUIDE:
------------------
1. TO ADD A NEW HEADING:
Add the exact heading text to the 'ALLOWED_ORDER' list in the desired
sequence.
2. TO MAKE A HEADING MANDATORY/OPTIONAL:
Add or remove the heading text in the 'REQUIRED' set.
3. TO UPDATE SHORTCODE LOGIC:
If the shortcode name changes, update the 'SHORTCODE_PATTERN' variable.
4. SCOPE & BEHAVIOR:
This script targets all .md files in docs/en/integrations/**/tools/.
- For `_index.md` files: It only validates the frontmatter (requiring
`title: "Tools"` and `weight: 2`) and ignores the body.
- For regular tool files: It validates H1/H2 hierarchy, checks for
required headings ("About", "Example"), and enforces that the
`{{< compatible-sources >}}` shortcode is paired with the
"## Compatible Sources" heading.
"""
import os
import re
import sys
from pathlib import Path
# --- CONFIGURATION ---
ALLOWED_ORDER = [
"About",
"Compatible Sources",
"Requirements",
"Parameters",
"Example",
"Output Format",
"Reference",
"Advanced Usage",
"Troubleshooting",
"Additional Resources"
]
REQUIRED = {"About", "Example"}
SHORTCODE_PATTERN = r"\{\{<\s*compatible-sources.*?>\}\}"
# ---------------------
integration_dir = Path("./docs/en/integrations")
if not integration_dir.exists():
print("Info: Directory './docs/en/integrations' not found. Skipping linting.")
sys.exit(0)
has_errors = False
tools_pages_found = 0
# Specifically target the tools directories
for filepath in integration_dir.rglob("tools/*.md"):
tools_pages_found += 1
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
# Separate YAML frontmatter from the markdown body
match = re.match(r'^\s*---\s*\n(.*?)\n---\s*(.*)', content, re.DOTALL)
if match:
frontmatter = match.group(1)
body = match.group(2)
else:
print(f"[{filepath}] Error: Missing or invalid YAML frontmatter.")
has_errors = True
continue
file_errors = False
# --- SPECIAL VALIDATION FOR tools/_index.md ---
if filepath.name == "_index.md":
title_match = re.search(r"^title:\s*[\"']?(.*?)[\"']?\s*$", frontmatter, re.MULTILINE)
if not title_match or title_match.group(1).strip() != "Tools":
print(f"[{filepath}] Error: tools/_index.md must have exactly title: \"Tools\"")
file_errors = True
weight_match = re.search(r"^weight:\s*(\d+)\s*$", frontmatter, re.MULTILINE)
if not weight_match or weight_match.group(1).strip() != "2":
print(f"[{filepath}] Error: tools/_index.md must have exactly weight: 2")
file_errors = True
if file_errors:
has_errors = True
continue # Skip the rest of the body linting for this structural file
# --- VALIDATION FOR REGULAR TOOL PAGES ---
# If the file has no markdown content (metadata placeholder only), skip it entirely
if not body.strip():
continue
# 1. Check Shortcode Placement
sources_section_match = re.search(r"^##\s+Compatible Sources\s*(.*?)(?=^##\s|\Z)", body, re.MULTILINE | re.DOTALL)
if sources_section_match:
if not re.search(SHORTCODE_PATTERN, sources_section_match.group(1)):
print(f"[{filepath}] Error: The compatible-sources shortcode must be placed under '## Compatible Sources'.")
file_errors = True
elif re.search(SHORTCODE_PATTERN, body):
print(f"[{filepath}] Error: Shortcode found, but '## Compatible Sources' heading is missing.")
file_errors = True
# 2. Strip code blocks from body to avoid linting example markdown headings
clean_body = re.sub(r"```.*?```", "", body, flags=re.DOTALL)
# 3. Check H1 Headings
if re.search(r"^#\s+\w+", clean_body, re.MULTILINE):
print(f"[{filepath}] Error: H1 headings (#) are forbidden in the body.")
file_errors = True
# 4. Check H2 Headings
h2s = re.findall(r"^##\s+(.*)", clean_body, re.MULTILINE)
h2s = [h2.strip() for h2 in h2s]
# Missing Required
if missing := (REQUIRED - set(h2s)):
print(f"[{filepath}] Error: Missing required H2 headings: {missing}")
file_errors = True
# Unauthorized Headings
if unauthorized := (set(h2s) - set(ALLOWED_ORDER)):
print(f"[{filepath}] Error: Unauthorized H2 headings found: {unauthorized}")
file_errors = True
# Strict Ordering
filtered_h2s = [h for h in h2s if h in ALLOWED_ORDER]
expected_order = [h for h in ALLOWED_ORDER if h in h2s]
if filtered_h2s != expected_order:
print(f"[{filepath}] Error: Headings are out of order.")
print(f" Expected: {expected_order}")
print(f" Found: {filtered_h2s}")
file_errors = True
if file_errors:
has_errors = True
if tools_pages_found == 0:
print("Info: No tool directories found. Passing gracefully.")
sys.exit(0)
elif has_errors:
print("Linting failed for Tool pages. Please fix the structure errors above.")
sys.exit(1)
else:
print(f"Success: All {tools_pages_found} Tool page(s) passed structure validation.")
EOF
+51
View File
@@ -0,0 +1,51 @@
# 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.
#
# Retry-only pipeline for the OSS Exit Gate npm publish steps. Use when the
# npm portion of the versioned release fails after Go binaries are already in
# GCS. Each platform package's prepack script downloads its binary from GCS by
# version, and publish_npm_to_ar.sh's idempotency check skips packages already
# in AR.
#
# Invoke after checking out the version's tag locally:
# git checkout v<X.Y.Z>
# gcloud builds submit \
# --config=.ci/npm_retry.cloudbuild.yaml \
# --region=us-central1 \
# --service-account=projects/database-toolbox/serviceAccounts/release@database-toolbox.iam.gserviceaccount.com \
# .
steps:
- id: "publish-npm-to-ar"
name: node:20
script: |
#!/usr/bin/env bash
bash .ci/publish_npm_to_ar.sh
- id: "trigger-exit-gate"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "publish-npm-to-ar"
env:
- "BUILD_ID=$BUILD_ID"
script: |
#!/usr/bin/env bash
bash .ci/trigger_exit_gate.sh npm
options:
automapSubstitutions: true
dynamicSubstitutions: true
logging: CLOUD_LOGGING_ONLY
pool:
name: projects/$PROJECT_ID/locations/us-central1/workerPools/run-release
+90
View File
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
# 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.
#
# Publishes the six @toolbox-sdk npm packages (5 platform-specific + 1 wrapper)
# to the OSS Exit Gate internal Artifact Registry. The Exit Gate then ships
# them externally to npmjs.org once trigger_exit_gate.sh uploads the manifest.
#
# Required CWD: repo root (so npm/server-*/ paths resolve).
#
# Binaries (toolbox.<os>.<arch>) are picked up from the workspace if present
# (versioned-release pipeline) and otherwise downloaded by each package's own
# prepack script from GCS by version (retry pipeline).
#
# Each publish is gated by an "already in AR?" check so the script is safe to
# re-run after a partial failure.
set -eo pipefail
# OSS Exit Gate constants — owned by Exit Gate, not build configuration.
readonly AR_REGISTRY="https://us-npm.pkg.dev/oss-exit-gate-prod/mcp-toolbox--npm/"
readonly AR_HOST="us-npm.pkg.dev"
# Write to $HOME so npm finds it as user config from inside any package dir.
# A .npmrc at /workspace would be ignored — npm's per-project .npmrc must sit
# next to package.json, and each platform package has its own package.json.
cat > "$HOME/.npmrc" <<EOF
@toolbox-sdk:registry=${AR_REGISTRY}
//${AR_HOST}/:always-auth=true
EOF
# Run from $HOME so google-artifactregistry-auth finds the .npmrc above and
# writes the short-lived AR auth token into it.
(cd "$HOME" && npx --yes google-artifactregistry-auth)
# Publishes the npm package at npm/${pkg} to the Exit Gate AR, unless that
# exact version is already there (idempotency check makes retries safe).
# --registry is passed explicitly so we don't depend on scope-config
# resolution at publish time.
publish_pkg() {
local pkg="$1"
local npm_name="@toolbox-sdk/${pkg}"
local version
version=$(cd "npm/${pkg}" && node -p "require('./package.json').version")
if npm view "${npm_name}@${version}" version --registry "${AR_REGISTRY}" 2>/dev/null | grep -q .; then
echo "Skipping ${npm_name}@${version}: already in AR"
return
fi
(cd "npm/${pkg}" && npm publish --registry "${AR_REGISTRY}")
}
# Stages a Cloud-Build-produced binary into a platform npm package and publishes
# it. Copying the binary into bin/ short-circuits the package's prepack
# download script (it skips when the binary already exists). When the binary
# isn't in the workspace (retry pipeline), prepack downloads it from GCS.
#
# Args:
# pkg: npm folder under npm/ (e.g., server-linux-x64)
# src: go build output in the workspace (e.g., toolbox.linux.amd64)
# dest: binary name inside the package's bin/ (toolbox or toolbox.exe)
publish_platform() {
local pkg="$1" src="$2" dest="$3"
if [[ -f "${src}" ]]; then
mkdir -p "npm/${pkg}/bin"
cp "${src}" "npm/${pkg}/bin/${dest}"
chmod +x "npm/${pkg}/bin/${dest}"
fi
publish_pkg "${pkg}"
}
publish_platform "server-linux-x64" "toolbox.linux.amd64" "toolbox"
publish_platform "server-darwin-arm64" "toolbox.darwin.arm64" "toolbox"
publish_platform "server-darwin-x64" "toolbox.darwin.amd64" "toolbox"
publish_platform "server-win32-x64" "toolbox.windows.amd64" "toolbox.exe"
publish_platform "server-win32-arm64" "toolbox.windows.arm64" "toolbox.exe"
publish_pkg "server"
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env bash
# 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.
#
# Builds one toolbox-server wheel per supported platform and uploads them all
# to the OSS Exit Gate internal Artifact Registry. Exit Gate ships them
# externally to pypi.org once trigger_exit_gate.sh uploads the manifest.
#
# Required CWD: repo root.
#
# Binaries (toolbox.<os>.<arch>) must be pre-staged in the workspace by an
# earlier Cloud Build step. The retry pipeline downloads them from GCS first.
#
# AR doesn't support twine's `--skip-existing` flag. If this version already
# has wheels in AR (e.g., from a prior run where Exit Gate failed to externally
# publish and left the AR contents in place), twine will error. Manually clean
# the AR version before re-running:
#
# gcloud artifacts versions delete <VERSION> \
# --package=toolbox-server \
# --repository=mcp-toolbox--pypi \
# --location=us \
# --project=oss-exit-gate-prod
set -eo pipefail
# OSS Exit Gate constants — owned by Exit Gate, not build configuration.
readonly AR_URL="https://us-python.pkg.dev/oss-exit-gate-prod/mcp-toolbox--pypi/"
readonly PYPI_DIR="pypi"
readonly BIN_DIR="${PYPI_DIR}/src/toolbox_server/bin"
readonly DIST_DIR="${PYPI_DIR}/dist"
# Install build + upload tooling. keyrings.google-artifactregistry-auth is the
# keyring backend that twine uses to obtain a short-lived AR token from ADC.
pip install --quiet --upgrade build twine keyring keyrings.google-artifactregistry-auth
# Start each build run with empty dist/ to make the final upload deterministic.
rm -rf "${DIST_DIR}"
# Builds one wheel.
#
# Args:
# src: go build output staged in the workspace (e.g., toolbox.linux.amd64)
# plat: PEP 425 platform tag for the wheel (e.g., manylinux2014_x86_64)
# dest: binary name inside the wheel's bin/ (toolbox or toolbox.exe)
build_wheel() {
local src="$1" plat="$2" dest="$3"
if [[ ! -f "${src}" ]]; then
echo "ERROR: ${src} not found in workspace. Earlier build step must run first." >&2
exit 1
fi
# Replace bin/ so a previous iteration's binary doesn't leak into this wheel.
rm -rf "${BIN_DIR}"
mkdir -p "${BIN_DIR}"
cp "${src}" "${BIN_DIR}/${dest}"
chmod +x "${BIN_DIR}/${dest}"
# Also wipe setuptools' build/ staging tree. It caches the previously-copied
# binary at build/lib/toolbox_server/bin/, which would otherwise be packaged
# into this iteration's wheel alongside the new binary (e.g., the Windows
# wheel would end up shipping both toolbox AND toolbox.exe).
rm -rf "${PYPI_DIR}/build"
(cd "${PYPI_DIR}" && TOOLBOX_PLATFORM="${plat}" python -m build --wheel)
}
build_wheel "toolbox.linux.amd64" "manylinux2014_x86_64" "toolbox"
build_wheel "toolbox.darwin.arm64" "macosx_11_0_arm64" "toolbox"
build_wheel "toolbox.darwin.amd64" "macosx_10_14_x86_64" "toolbox"
build_wheel "toolbox.windows.amd64" "win_amd64" "toolbox.exe"
build_wheel "toolbox.windows.arm64" "win_arm64" "toolbox.exe"
twine upload \
--repository-url "${AR_URL}" \
"${DIST_DIR}"/*.whl
+67
View File
@@ -0,0 +1,67 @@
# 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.
#
# Retry-only pipeline for the OSS Exit Gate PyPI publish steps. Use when the
# PyPI portion of the versioned release fails after Go binaries are already in
# GCS. The fetch-binaries step downloads the five platform binaries from GCS
# by version into the workspace, then publish_pypi_to_ar.sh builds and
# uploads the wheels (twine's --skip-existing makes uploads idempotent).
#
# Invoke after checking out the version's tag locally:
# git checkout v<X.Y.Z>
# gcloud builds submit \
# --config=.ci/pypi_retry.cloudbuild.yaml \
# --region=us-central1 \
# --service-account=projects/database-toolbox/serviceAccounts/release@database-toolbox.iam.gserviceaccount.com \
# .
steps:
- id: "fetch-binaries"
name: "gcr.io/cloud-builders/gcloud:latest"
script: |
#!/usr/bin/env bash
set -eo pipefail
VERSION="v$(cat ./cmd/version.txt)"
BUCKET="gs://mcp-toolbox-for-databases/${VERSION}"
gcloud storage cp "${BUCKET}/linux/amd64/toolbox" toolbox.linux.amd64
gcloud storage cp "${BUCKET}/darwin/arm64/toolbox" toolbox.darwin.arm64
gcloud storage cp "${BUCKET}/darwin/amd64/toolbox" toolbox.darwin.amd64
gcloud storage cp "${BUCKET}/windows/amd64/toolbox.exe" toolbox.windows.amd64
gcloud storage cp "${BUCKET}/windows/arm64/toolbox.exe" toolbox.windows.arm64
- id: "publish-pypi-to-ar"
name: python:3.12
waitFor:
- "fetch-binaries"
script: |
#!/usr/bin/env bash
bash .ci/publish_pypi_to_ar.sh
- id: "trigger-exit-gate-pypi"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "publish-pypi-to-ar"
env:
- "BUILD_ID=$BUILD_ID"
script: |
#!/usr/bin/env bash
bash .ci/trigger_exit_gate.sh pypi
options:
automapSubstitutions: true
dynamicSubstitutions: true
logging: CLOUD_LOGGING_ONLY
pool:
name: projects/$PROJECT_ID/locations/us-central1/workerPools/run-release
@@ -0,0 +1,57 @@
# 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.
steps:
- name: "${_IMAGE}"
id: "go-pre-post-processing-test"
entrypoint: "bash"
args:
- -c
- |
set -ex
chmod +x .ci/sample_tests/run_tests.sh
.ci/sample_tests/run_tests.sh
env:
- "CLOUD_SQL_INSTANCE=${_CLOUD_SQL_INSTANCE}"
- "GCP_PROJECT=${_GCP_PROJECT}"
- "DATABASE_NAME=${_DATABASE_NAME}"
- "DB_USER=${_DB_USER}"
- "TARGET_ROOT=${_TARGET_ROOT}"
- "TARGET_LANG=${_TARGET_LANG}"
- "TABLE_NAME=${_TABLE_NAME}"
- "SQL_FILE=${_SQL_FILE}"
- "AGENT_FILE_PATTERN=${_AGENT_FILE_PATTERN}"
secretEnv: ["TOOLS_YAML_CONTENT", "GOOGLE_API_KEY", "DB_PASSWORD"]
availableSecrets:
secretManager:
- versionName: projects/${_GCP_PROJECT}/secrets/${_TOOLS_YAML_SECRET}/versions/8
env: "TOOLS_YAML_CONTENT"
- versionName: projects/${_GCP_PROJECT_NUMBER}/secrets/${_API_KEY_SECRET}/versions/latest
env: "GOOGLE_API_KEY"
- versionName: projects/${_GCP_PROJECT}/secrets/${_DB_PASS_SECRET}/versions/latest
env: "DB_PASSWORD"
timeout: 1200s
substitutions:
_TARGET_LANG: "go"
_IMAGE: "golang:1.25.7"
_TARGET_ROOT: "docs/en/documentation/configuration/pre-post-processing/go"
_TABLE_NAME: "hotels_go_pre_post_processing"
_SQL_FILE: ".ci/sample_tests/setup_hotels.sql"
_AGENT_FILE_PATTERN: "agent.go"
options:
logging: CLOUD_LOGGING_ONLY
@@ -0,0 +1,57 @@
# 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.
steps:
- name: "${_IMAGE}"
id: "js-pre-post-processing-test"
entrypoint: "bash"
args:
- -c
- |
set -ex
chmod +x .ci/sample_tests/run_tests.sh
.ci/sample_tests/run_tests.sh
env:
- "CLOUD_SQL_INSTANCE=${_CLOUD_SQL_INSTANCE}"
- "GCP_PROJECT=${_GCP_PROJECT}"
- "DATABASE_NAME=${_DATABASE_NAME}"
- "DB_USER=${_DB_USER}"
- "TARGET_ROOT=${_TARGET_ROOT}"
- "TARGET_LANG=${_TARGET_LANG}"
- "TABLE_NAME=${_TABLE_NAME}"
- "SQL_FILE=${_SQL_FILE}"
- "AGENT_FILE_PATTERN=${_AGENT_FILE_PATTERN}"
secretEnv: ["TOOLS_YAML_CONTENT", "GOOGLE_API_KEY", "DB_PASSWORD"]
availableSecrets:
secretManager:
- versionName: projects/${_GCP_PROJECT}/secrets/${_TOOLS_YAML_SECRET}/versions/8
env: "TOOLS_YAML_CONTENT"
- versionName: projects/${_GCP_PROJECT_NUMBER}/secrets/${_API_KEY_SECRET}/versions/latest
env: "GOOGLE_API_KEY"
- versionName: projects/${_GCP_PROJECT}/secrets/${_DB_PASS_SECRET}/versions/latest
env: "DB_PASSWORD"
timeout: 1200s
substitutions:
_TARGET_LANG: "js"
_IMAGE: "node:22"
_TARGET_ROOT: "docs/en/documentation/configuration/pre-post-processing/js"
_TABLE_NAME: "hotels_js_pre_post_processing"
_SQL_FILE: ".ci/sample_tests/setup_hotels.sql"
_AGENT_FILE_PATTERN: "agent.js"
options:
logging: CLOUD_LOGGING_ONLY
@@ -0,0 +1,57 @@
# 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.
steps:
- name: "${_IMAGE}"
id: "py-pre-post-processing-test"
entrypoint: "bash"
args:
- -c
- |
set -ex
chmod +x .ci/sample_tests/run_tests.sh
.ci/sample_tests/run_tests.sh
env:
- "CLOUD_SQL_INSTANCE=${_CLOUD_SQL_INSTANCE}"
- "GCP_PROJECT=${_GCP_PROJECT}"
- "DATABASE_NAME=${_DATABASE_NAME}"
- "DB_USER=${_DB_USER}"
- "TARGET_ROOT=${_TARGET_ROOT}"
- "TARGET_LANG=${_TARGET_LANG}"
- "TABLE_NAME=${_TABLE_NAME}"
- "SQL_FILE=${_SQL_FILE}"
- "AGENT_FILE_PATTERN=${_AGENT_FILE_PATTERN}"
secretEnv: ["TOOLS_YAML_CONTENT", "GOOGLE_API_KEY", "DB_PASSWORD"]
availableSecrets:
secretManager:
- versionName: projects/${_GCP_PROJECT}/secrets/${_TOOLS_YAML_SECRET}/versions/8
env: "TOOLS_YAML_CONTENT"
- versionName: projects/${_GCP_PROJECT_NUMBER}/secrets/${_API_KEY_SECRET}/versions/latest
env: "GOOGLE_API_KEY"
- versionName: projects/${_GCP_PROJECT}/secrets/${_DB_PASS_SECRET}/versions/latest
env: "DB_PASSWORD"
timeout: 1200s
substitutions:
_TARGET_LANG: "python"
_IMAGE: "gcr.io/google.com/cloudsdktool/cloud-sdk:537.0.0"
_TARGET_ROOT: "docs/en/documentation/configuration/pre-post-processing/python"
_TABLE_NAME: "hotels_py_pre_post_processing"
_SQL_FILE: ".ci/sample_tests/setup_hotels.sql"
_AGENT_FILE_PATTERN: "agent.py"
options:
logging: CLOUD_LOGGING_ONLY
@@ -0,0 +1,52 @@
# 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.
# 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.
steps:
- name: 'golang:1.25.7'
id: 'go-quickstart-test'
entrypoint: 'bash'
args:
# The '-c' flag tells bash to execute the following string as a command.
# The 'set -ex' enables debug output and exits on error for easier troubleshooting.
- -c
- |
set -ex
export VERSION=$(cat ./cmd/version.txt)
chmod +x .ci/sample_tests/run_tests.sh
.ci/sample_tests/run_tests.sh
env:
- 'CLOUD_SQL_INSTANCE=${_CLOUD_SQL_INSTANCE}'
- 'GCP_PROJECT=${_GCP_PROJECT}'
- 'DATABASE_NAME=${_DATABASE_NAME}'
- 'DB_USER=${_DB_USER}'
- 'TARGET_ROOT=docs/en/documentation/getting-started/quickstart/go'
- 'TARGET_LANG=go'
- 'TABLE_NAME=hotels_go'
- 'SQL_FILE=.ci/sample_tests/setup_hotels.sql'
- 'AGENT_FILE_PATTERN=quickstart.go'
secretEnv: ['TOOLS_YAML_CONTENT', 'GOOGLE_API_KEY', 'DB_PASSWORD']
availableSecrets:
secretManager:
- versionName: projects/${_GCP_PROJECT}/secrets/${_TOOLS_YAML_SECRET}/versions/10
env: 'TOOLS_YAML_CONTENT'
- versionName: projects/${_GCP_PROJECT_NUMBER}/secrets/${_API_KEY_SECRET}/versions/latest
env: 'GOOGLE_API_KEY'
- versionName: projects/${_GCP_PROJECT}/secrets/${_DB_PASS_SECRET}/versions/latest
env: 'DB_PASSWORD'
timeout: 1000s
options:
logging: CLOUD_LOGGING_ONLY
@@ -0,0 +1,52 @@
# 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.
# 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.
steps:
- name: 'node:22'
id: 'js-quickstart-test'
entrypoint: 'bash'
args:
# The '-c' flag tells bash to execute the following string as a command.
# The 'set -ex' enables debug output and exits on error for easier troubleshooting.
- -c
- |
set -ex
export VERSION=$(cat ./cmd/version.txt)
chmod +x .ci/sample_tests/run_tests.sh
.ci/sample_tests/run_tests.sh
env:
- 'CLOUD_SQL_INSTANCE=${_CLOUD_SQL_INSTANCE}'
- 'GCP_PROJECT=${_GCP_PROJECT}'
- 'DATABASE_NAME=${_DATABASE_NAME}'
- 'DB_USER=${_DB_USER}'
- 'TARGET_ROOT=docs/en/documentation/getting-started/quickstart/js'
- 'TARGET_LANG=js'
- 'TABLE_NAME=hotels_js'
- 'SQL_FILE=.ci/sample_tests/setup_hotels.sql'
- 'AGENT_FILE_PATTERN=quickstart.js'
secretEnv: ['TOOLS_YAML_CONTENT', 'GOOGLE_API_KEY', 'DB_PASSWORD']
availableSecrets:
secretManager:
- versionName: projects/${_GCP_PROJECT}/secrets/${_TOOLS_YAML_SECRET}/versions/9
env: 'TOOLS_YAML_CONTENT'
- versionName: projects/${_GCP_PROJECT_NUMBER}/secrets/${_API_KEY_SECRET}/versions/latest
env: 'GOOGLE_API_KEY'
- versionName: projects/${_GCP_PROJECT}/secrets/${_DB_PASS_SECRET}/versions/latest
env: 'DB_PASSWORD'
timeout: 1000s
options:
logging: CLOUD_LOGGING_ONLY
@@ -0,0 +1,52 @@
# 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.
# 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.
steps:
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk:537.0.0'
id: 'python-quickstart-test'
entrypoint: 'bash'
args:
# The '-c' flag tells bash to execute the following string as a command.
# The 'set -ex' enables debug output and exits on error for easier troubleshooting.
- -c
- |
set -ex
export VERSION=$(cat ./cmd/version.txt)
chmod +x .ci/sample_tests/run_tests.sh
.ci/sample_tests/run_tests.sh
env:
- 'CLOUD_SQL_INSTANCE=${_CLOUD_SQL_INSTANCE}'
- 'GCP_PROJECT=${_GCP_PROJECT}'
- 'DATABASE_NAME=${_DATABASE_NAME}'
- 'DB_USER=${_DB_USER}'
- 'TARGET_ROOT=docs/en/documentation/getting-started/quickstart/python'
- 'TARGET_LANG=python'
- 'TABLE_NAME=hotels_python'
- 'SQL_FILE=.ci/sample_tests/setup_hotels.sql'
- 'AGENT_FILE_PATTERN=quickstart.py'
secretEnv: ['TOOLS_YAML_CONTENT', 'GOOGLE_API_KEY', 'DB_PASSWORD']
availableSecrets:
secretManager:
- versionName: projects/${_GCP_PROJECT}/secrets/${_TOOLS_YAML_SECRET}/versions/8
env: 'TOOLS_YAML_CONTENT'
- versionName: projects/${_GCP_PROJECT_NUMBER}/secrets/${_API_KEY_SECRET}/versions/latest
env: 'GOOGLE_API_KEY'
- versionName: projects/${_GCP_PROJECT}/secrets/${_DB_PASS_SECRET}/versions/latest
env: 'DB_PASSWORD'
timeout: 1000s
options:
logging: CLOUD_LOGGING_ONLY
+202
View File
@@ -0,0 +1,202 @@
# 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.
set -e
# --- Configuration (from Environment Variables) ---
# TARGET_ROOT: The directory to search for tests (e.g., docs/en/getting-started/quickstart/js)
# TARGET_LANG: python, js, go
# TABLE_NAME: Database table name to use
# SQL_FILE: Path to the SQL setup file
# AGENT_FILE_PATTERN: Filename to look for (e.g., quickstart.js or agent.py)
VERSION=$(cat ./cmd/version.txt)
# Process IDs & Logs
PROXY_PID=""
TOOLBOX_PID=""
PROXY_LOG="cloud_sql_proxy.log"
TOOLBOX_LOG="toolbox_server.log"
install_system_packages() {
echo "Installing system packages..."
apt-get update && apt-get install -y \
postgresql-client \
wget \
gettext-base \
netcat-openbsd
if [[ "$TARGET_LANG" == "python" ]]; then
apt-get install -y python3-venv
fi
}
start_cloud_sql_proxy() {
echo "Starting Cloud SQL Proxy..."
wget -q "https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.10.0/cloud-sql-proxy.linux.amd64" -O /usr/local/bin/cloud-sql-proxy
chmod +x /usr/local/bin/cloud-sql-proxy
cloud-sql-proxy "${CLOUD_SQL_INSTANCE}" > "$PROXY_LOG" 2>&1 &
PROXY_PID=$!
# Health Check
for i in {1..30}; do
if nc -z 127.0.0.1 5432; then
echo "Cloud SQL Proxy is up and running."
return
fi
sleep 1
done
echo "ERROR: Cloud SQL Proxy failed to start. Logs:"
cat "$PROXY_LOG"
exit 1
}
setup_toolbox() {
echo "Setting up Toolbox server..."
TOOLBOX_YAML="/tools.yaml"
echo "${TOOLS_YAML_CONTENT}" > "$TOOLBOX_YAML"
wget -q "https://storage.googleapis.com/mcp-toolbox-for-databases/v${VERSION}/linux/amd64/toolbox" -O "/toolbox"
chmod +x "/toolbox"
/toolbox --tools-file "$TOOLBOX_YAML" > "$TOOLBOX_LOG" 2>&1 &
TOOLBOX_PID=$!
# Health Check
for i in {1..15}; do
if nc -z 127.0.0.1 5000; then
echo "Toolbox server is up and running."
return
fi
sleep 1
done
echo "ERROR: Toolbox server failed to start. Logs:"
cat "$TOOLBOX_LOG"
exit 1
}
setup_db_table() {
echo "Setting up database table $TABLE_NAME using $SQL_FILE..."
export TABLE_NAME
envsubst < "$SQL_FILE" | psql -h 127.0.0.1 -p 5432 -U "$DB_USER" -d "$DATABASE_NAME"
}
run_python_test() {
local dir=$1
local name=$(basename "$dir")
echo "--- Running Python Test: $name ---"
(
cd "$dir"
python3 -m venv .venv
source .venv/bin/activate
pip install -q -r requirements.txt pytest
cd ..
local test_file=$(find . -maxdepth 1 -name "*test.py" | head -n 1)
if [ -n "$test_file" ]; then
echo "Found native test: $test_file. Running pytest..."
export ORCH_NAME="$name"
export PYTHONPATH="../"
pytest "$test_file"
else
echo "No native test found. running agent directly..."
export PYTHONPATH="../"
python3 "${name}/${AGENT_FILE_PATTERN}"
fi
rm -rf "${name}/.venv"
)
}
run_js_test() {
local dir=$1
local name=$(basename "$dir")
echo "--- Running JS Test: $name ---"
(
cd "$dir"
if [ -f "package-lock.json" ]; then npm ci -q; else npm install -q; fi
cd ..
# Looking for a JS test file in the parent directory
local test_file=$(find . -maxdepth 1 -name "*test.js" | head -n 1)
if [ -n "$test_file" ]; then
echo "Found native test: $test_file. Running node --test..."
export ORCH_NAME="$name"
node --test "$test_file"
else
echo "No native test found. running agent directly..."
node "${name}/${AGENT_FILE_PATTERN}"
fi
rm -rf "${name}/node_modules"
)
}
run_go_test() {
local dir=$1
local name=$(basename "$dir")
if [ "$name" == "openAI" ]; then
echo -e "\nSkipping framework '${name}': Temporarily excluded."
return
fi
echo "--- Running Go Test: $name ---"
(
cd "$dir"
if [ -f "go.mod" ]; then
go mod tidy
fi
cd ..
local test_file=$(find . -maxdepth 1 -name "*test.go" | head -n 1)
if [ -n "$test_file" ]; then
echo "Found native test: $test_file. Running go test..."
export ORCH_NAME="$name"
go test -v ./...
else
echo "No native test found. running agent directly..."
cd "$name"
go run "."
fi
)
}
cleanup() {
echo "Cleaning up background processes..."
[ -n "$TOOLBOX_PID" ] && kill "$TOOLBOX_PID" || true
[ -n "$PROXY_PID" ] && kill "$PROXY_PID" || true
}
trap cleanup EXIT
# --- Execution ---
install_system_packages
start_cloud_sql_proxy
export PGHOST=127.0.0.1
export PGPORT=5432
export PGPASSWORD="$DB_PASSWORD"
export GOOGLE_API_KEY="$GOOGLE_API_KEY"
setup_toolbox
setup_db_table
echo "Scanning $TARGET_ROOT for tests with pattern $AGENT_FILE_PATTERN..."
find "$TARGET_ROOT" -name "$AGENT_FILE_PATTERN" | while read -r agent_file; do
sample_dir=$(dirname "$agent_file")
if [[ "$TARGET_LANG" == "python" ]]; then
run_python_test "$sample_dir"
elif [[ "$TARGET_LANG" == "js" ]]; then
run_js_test "$sample_dir"
elif [[ "$TARGET_LANG" == "go" ]]; then
run_go_test "$sample_dir"
fi
done
+28
View File
@@ -0,0 +1,28 @@
-- 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.
-- 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.
TRUNCATE TABLE $TABLE_NAME;
INSERT INTO $TABLE_NAME (id, name, location, price_tier, checkin_date, checkout_date, booked)
VALUES
(1, 'Hilton Basel', 'Basel', 'Luxury', '2024-04-22', '2024-04-20', B'0'),
(2, 'Marriott Zurich', 'Zurich', 'Upscale', '2024-04-14', '2024-04-21', B'0'),
(3, 'Hyatt Regency Basel', 'Basel', 'Upper Upscale', '2024-04-02', '2024-04-20', B'0'),
(4, 'Radisson Blu Lucerne', 'Lucerne', 'Midscale', '2024-04-24', '2024-04-05', B'0'),
(5, 'Best Western Bern', 'Bern', 'Upper Midscale', '2024-04-23', '2024-04-01', B'0'),
(6, 'InterContinental Geneva', 'Geneva', 'Luxury', '2024-04-23', '2024-04-28', B'0'),
(7, 'Sheraton Zurich', 'Zurich', 'Upper Upscale', '2024-04-27', '2024-04-02', B'0'),
(8, 'Holiday Inn Basel', 'Basel', 'Upper Midscale', '2024-04-24', '2024-04-09', B'0'),
(9, 'Courtyard Zurich', 'Zurich', 'Upscale', '2024-04-03', '2024-04-13', B'0'),
(10, 'Comfort Inn Bern', 'Bern', 'Midscale', '2024-04-04', '2024-04-16', B'0');
+75
View File
@@ -0,0 +1,75 @@
#!/bin/bash
# .ci/test_prompts_with_coverage.sh
#
# This script runs a specific prompt integration test, calculates its
# code coverage, and checks if it meets a minimum threshold.
#
# It is called with one argument: the type of the prompt.
# Example usage: .ci/test_prompts_with_coverage.sh "custom"
# Exit immediately if a command fails.
set -e
# --- 1. Define Variables ---
# The first argument is the prompt type (e.g., "custom").
PROMPT_TYPE=$1
COVERAGE_THRESHOLD=80 # Minimum coverage percentage required.
if [ -z "$PROMPT_TYPE" ]; then
echo "Error: No prompt type provided. Please call this script with an argument."
echo "Usage: .ci/test_prompts_with_coverage.sh <prompt_type>"
exit 1
fi
# Construct names based on the prompt type.
TEST_BINARY="./prompt.${PROMPT_TYPE}.test"
TEST_NAME="$(tr '[:lower:]' '[:upper:]' <<< ${PROMPT_TYPE:0:1})${PROMPT_TYPE:1} Prompts"
COVERAGE_FILE="coverage.prompts-${PROMPT_TYPE}.out"
# --- 2. Run Integration Tests ---
echo "--- Running integration tests for ${TEST_NAME} ---"
# Safety check for the binary's existence.
if [ ! -f "$TEST_BINARY" ]; then
echo "Error: Test binary not found at ${TEST_BINARY}. Aborting."
exit 1
fi
# Execute the test binary and generate the coverage file.
# If the tests fail, the 'set -e' command will cause the script to exit here.
if ! ./"${TEST_BINARY}" -test.v -test.coverprofile="${COVERAGE_FILE}"; then
echo "Error: Tests for ${TEST_NAME} failed. Exiting."
exit 1
fi
echo "--- Tests for ${TEST_NAME} passed successfully ---"
# --- 3. Calculate and Check Coverage ---
echo "Calculating coverage for ${TEST_NAME}..."
# Calculate the total coverage percentage from the generated file.
# The '2>/dev/null' suppresses warnings if the coverage file is empty.
total_coverage=$(go tool cover -func="${COVERAGE_FILE}" 2>/dev/null | grep "total:" | awk '{print $3}')
if [ -z "$total_coverage" ]; then
echo "Warning: Could not calculate coverage for ${TEST_NAME}. The coverage report might be empty."
total_coverage="0%"
fi
echo "${TEST_NAME} total coverage: $total_coverage"
# Remove the '%' sign for numerical comparison.
coverage_numeric=$(echo "$total_coverage" | sed 's/%//')
# Check if the coverage is below the defined threshold.
if awk -v coverage="$coverage_numeric" -v threshold="$COVERAGE_THRESHOLD" 'BEGIN {exit !(coverage < threshold)}'; then
echo "Coverage failure: ${TEST_NAME} total coverage (${total_coverage}) is below the ${COVERAGE_THRESHOLD}% threshold."
exit 1
else
echo "Coverage for ${TEST_NAME} is sufficient."
fi
+62
View File
@@ -0,0 +1,62 @@
#!/bin/bash
# Arguments:
# $1: Display name for logs (e.g., "Cloud SQL Postgres")
# $2: Integration test's package name (e.g., cloudsqlpg)
# $3, $4, ...: Tool package names for grep (e.g., postgressql), if the
# integration test specifically check a separate package inside a folder, please
# specify the full path instead (e.g., postgressql/postgresexecutesql)
DISPLAY_NAME="$1"
SOURCE_PACKAGE_NAME="$2"
# Construct the test binary name
TEST_BINARY="${SOURCE_PACKAGE_NAME}.test"
# Construct the full source path
SOURCE_PATH="sources/${SOURCE_PACKAGE_NAME}/"
# Shift arguments so that $3 and onwards become the list of tool package names
shift 2
TOOL_PACKAGE_NAMES=("$@")
COVERAGE_FILE="${TEST_BINARY%.test}_coverage.out"
FILTERED_COVERAGE_FILE="${TEST_BINARY%.test}_filtered_coverage.out"
export path="github.com/googleapis/mcp-toolbox/internal/"
GREP_PATTERN="^mode:|${path}${SOURCE_PATH}"
# Add each tool package path to the grep pattern
for tool_name in "${TOOL_PACKAGE_NAMES[@]}"; do
if [ -n "$tool_name" ]; then
full_tool_path="tools/${tool_name}/"
GREP_PATTERN="${GREP_PATTERN}|${path}${full_tool_path}"
fi
done
# Run integration test
if ! ./"${TEST_BINARY}" -test.v ${EXTRA_TEST_ARGS:-} -test.coverprofile="${COVERAGE_FILE}"; then
echo "Error: Tests for ${DISPLAY_NAME} failed. Exiting."
exit 1
fi
# Filter source/tool packages
if ! grep -E "${GREP_PATTERN}" "${COVERAGE_FILE}" > "${FILTERED_COVERAGE_FILE}"; then
echo "Warning: Could not filter coverage for ${DISPLAY_NAME}. Filtered file might be empty or invalid."
fi
# Calculate coverage
echo "Calculating coverage for ${DISPLAY_NAME}..."
total_coverage=$(go tool cover -func="${FILTERED_COVERAGE_FILE}" 2>/dev/null | grep "total:" | awk '{print $3}')
echo "${DISPLAY_NAME} total coverage: $total_coverage"
coverage_numeric=$(echo "$total_coverage" | sed 's/%//')
# Check coverage threshold
if awk -v coverage="$coverage_numeric" 'BEGIN {exit !(coverage < 50)}'; then
echo "Coverage failure: ${DISPLAY_NAME} total coverage($total_coverage) is below 50%."
exit 1
else
echo "Coverage for ${DISPLAY_NAME} is sufficient."
fi
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# 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.
#
# Uploads the OSS Exit Gate publishing manifest, which triggers external
# publication of whichever package set was just pushed to the internal
# Artifact Registry.
#
# Usage:
# bash .ci/trigger_exit_gate.sh <registry>
# registry: "npm" or "pypi" — determines the GCS subpath Exit Gate watches
#
# Required env vars (supplied by Cloud Build):
# BUILD_ID Cloud Build's auto-provided unique build ID
set -eo pipefail
REGISTRY="${1:?registry argument required: npm or pypi}"
case "${REGISTRY}" in
npm|pypi) ;;
*)
echo "ERROR: unsupported registry '${REGISTRY}' (expected: npm or pypi)" >&2
exit 1
;;
esac
# OSS Exit Gate constants — owned by Exit Gate, not build configuration.
readonly EXIT_GATE_PROJECT="mcp-toolbox"
readonly MANIFEST_BUCKET="oss-exit-gate-prod-projects-bucket"
VERSION="v$(cat ./cmd/version.txt)"
MANIFEST="${VERSION}-${BUILD_ID}.json"
MANIFEST_PATH="gs://${MANIFEST_BUCKET}/${EXIT_GATE_PROJECT}/${REGISTRY}/manifests/${MANIFEST}"
echo '{"publish_all": true}' > "${MANIFEST}"
gcloud storage cp "${MANIFEST}" "${MANIFEST_PATH}"
echo "Manifest uploaded: ${MANIFEST_PATH}"
echo "Exit Gate will email a confirmation when the external publish completes."
+473
View File
@@ -0,0 +1,473 @@
# Copyright 2024 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.
steps:
- id: "build-docker"
name: "gcr.io/cloud-builders/docker"
waitFor: ["-"]
script: |
#!/usr/bin/env bash
set -e
export VERSION=$(cat ./cmd/version.txt)
docker buildx create --name container-builder --driver docker-container --bootstrap --use
export TAGS="-t ${_DOCKER_URI}:$VERSION"
if [[ "$_PUSH_LATEST" == "true" ]]; then
export TAGS="$TAGS -t ${_DOCKER_URI}:latest"
fi
# Build and push
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg BUILD_TYPE=container.release \
--build-arg COMMIT_SHA=$(git rev-parse --short HEAD) \
$TAGS \
--push .
# Pull the image to ensure it's available for provenance generation.
docker pull ${_DOCKER_URI}:${VERSION}
docker tag ${_DOCKER_URI}:${VERSION} ${_DOCKER_URI}:latest
- id: "install-dependencies"
name: golang:1
waitFor: ["-"]
env:
- "GOPATH=/gopath"
volumes:
- name: "go"
path: "/gopath"
script: |
go get -d ./...
- id: "install-zig"
name: golang:1
waitFor: ["-"]
volumes:
- name: "zig"
path: "/zig-tools"
script: |
#!/usr/bin/env bash
set -e
apt-get update && apt-get install -y xz-utils
curl -fL "https://ziglang.org/download/0.15.2/zig-x86_64-linux-0.15.2.tar.xz" -o zig.tar.xz
tar -xf zig.tar.xz -C /zig-tools --strip-components=1
- id: "install-macos-sdk"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor: ["-"]
volumes:
- name: "macos-sdk"
path: "/macos-sdk"
script: |
#!/usr/bin/env bash
set -e
apt-get update && apt-get install -y xz-utils
echo "Downloading macOS 14.5 SDK..."
gcloud storage cp gs://${_ASSETS_BUCKET}/MacOSX14.5.tar.xz sdk.tar.xz
mkdir -p /macos-sdk/MacOSX14.5.sdk
echo "Unpacking macOS 14.5 SDK..."
tar -xf sdk.tar.xz -C /macos-sdk/MacOSX14.5.sdk --strip-components=1
- id: "build-linux-amd64"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
env:
- "GOPATH=/gopath"
- "CGO_ENABLED=1"
- "GOOS=linux"
- "GOARCH=amd64"
- "CC=/zig-tools/zig cc -target x86_64-linux-gnu"
- "CXX=/zig-tools/zig c++ -target x86_64-linux-gnu"
volumes:
- name: "go"
path: "/gopath"
- name: "zig"
path: "/zig-tools"
script: |
#!/usr/bin/env bash
go build -ldflags "-X github.com/googleapis/mcp-toolbox/cmd.buildType=binary -X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.linux.amd64
- id: "store-linux-amd64"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "build-linux-amd64"
script: |
#!/usr/bin/env bash
export VERSION=v$(cat ./cmd/version.txt)
gcloud storage cp toolbox.linux.amd64 gs://$_UNSIGNED_BUCKET_NAME/$VERSION/linux/amd64/toolbox
- id: "build-linux-amd64-geminicli"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
env:
- "GOPATH=/gopath"
- "CGO_ENABLED=1"
- "GOOS=linux"
- "GOARCH=amd64"
- "CC=/zig-tools/zig cc -target x86_64-linux-gnu"
- "CXX=/zig-tools/zig c++ -target x86_64-linux-gnu"
volumes:
- name: "go"
path: "/gopath"
- name: "zig"
path: "/zig-tools"
script: |
#!/usr/bin/env bash
go build -ldflags "-X github.com/googleapis/mcp-toolbox/cmd.buildType=geminicli.binary -X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.geminicli.linux.amd64
- id: "store-linux-amd64-geminicli"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "build-linux-amd64-geminicli"
script: |
#!/usr/bin/env bash
export VERSION=v$(cat ./cmd/version.txt)
gcloud storage cp toolbox.geminicli.linux.amd64 gs://$_BUCKET_NAME/geminicli/$VERSION/linux/amd64/toolbox
- id: "build-darwin-arm64"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
- "install-macos-sdk"
env:
- "GOPATH=/gopath"
- "CGO_ENABLED=1"
- "GOOS=darwin"
- "GOARCH=arm64"
- "SDK_PATH=/macos-sdk/MacOSX14.5.sdk"
- "MACOS_MIN_VER=10.14"
- "CGO_LDFLAGS=-mmacosx-version-min=10.14 --sysroot /macos-sdk/MacOSX14.5.sdk -F/macos-sdk/MacOSX14.5.sdk/System/Library/Frameworks -L/usr/lib"
- "COMMON_FLAGS=-mmacosx-version-min=10.14 -target aarch64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks"
- "CC=/zig-tools/zig cc -mmacosx-version-min=10.14 -target aarch64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks"
- "CXX=/zig-tools/zig c++ -mmacosx-version-min=10.14 -target aarch64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks"
volumes:
- name: "go"
path: "/gopath"
- name: "zig"
path: "/zig-tools"
- name: "macos-sdk"
path: "/macos-sdk"
script: |
#!/usr/bin/env bash
go build -trimpath -buildmode=pie -ldflags "-s -w -X github.com/googleapis/mcp-toolbox/cmd.buildType=binary -X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.darwin.arm64
- id: "store-darwin-arm64"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "build-darwin-arm64"
script: |
#!/usr/bin/env bash
export VERSION=v$(cat ./cmd/version.txt)
gcloud storage cp toolbox.darwin.arm64 gs://$_UNSIGNED_BUCKET_NAME/$VERSION/darwin/arm64/toolbox
- id: "build-darwin-arm64-geminicli"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
- "install-macos-sdk"
env:
- "GOPATH=/gopath"
- "CGO_ENABLED=1"
- "GOOS=darwin"
- "GOARCH=arm64"
- "SDK_PATH=/macos-sdk/MacOSX14.5.sdk"
- "MACOS_MIN_VER=10.14"
- "CGO_LDFLAGS=-mmacosx-version-min=10.14 --sysroot /macos-sdk/MacOSX14.5.sdk -F/macos-sdk/MacOSX14.5.sdk/System/Library/Frameworks -L/usr/lib"
- "COMMON_FLAGS=-mmacosx-version-min=10.14 -target aarch64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks"
- "CC=/zig-tools/zig cc -mmacosx-version-min=10.14 -target aarch64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks"
- "CXX=/zig-tools/zig c++ -mmacosx-version-min=10.14 -target aarch64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks"
volumes:
- name: "go"
path: "/gopath"
- name: "zig"
path: "/zig-tools"
- name: "macos-sdk"
path: "/macos-sdk"
script: |
#!/usr/bin/env bash
go build -trimpath -buildmode=pie -ldflags "-s -w -X github.com/googleapis/mcp-toolbox/cmd.buildType=geminicli.binary -X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.geminicli.darwin.arm64
- id: "store-darwin-arm64-geminicli"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "build-darwin-arm64-geminicli"
script: |
#!/usr/bin/env bash
export VERSION=v$(cat ./cmd/version.txt)
gcloud storage cp toolbox.geminicli.darwin.arm64 gs://$_BUCKET_NAME/geminicli/$VERSION/darwin/arm64/toolbox
- id: "build-darwin-amd64"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
- "install-macos-sdk"
env:
- "GOPATH=/gopath"
- "CGO_ENABLED=1"
- "GOOS=darwin"
- "GOARCH=amd64"
- "SDK_PATH=/macos-sdk/MacOSX14.5.sdk"
- "MACOS_MIN_VER=10.14"
- "CGO_LDFLAGS=-mmacosx-version-min=10.14 --sysroot /macos-sdk/MacOSX14.5.sdk -F/macos-sdk/MacOSX14.5.sdk/System/Library/Frameworks -L/usr/lib"
- "COMMON_FLAGS=-mmacosx-version-min=10.14 -target x86_64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks"
- "CC=/zig-tools/zig cc -mmacosx-version-min=10.14 -target x86_64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks"
- "CXX=/zig-tools/zig c++ -mmacosx-version-min=10.14 -target x86_64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks"
volumes:
- name: "go"
path: "/gopath"
- name: "zig"
path: "/zig-tools"
- name: "macos-sdk"
path: "/macos-sdk"
script: |
#!/usr/bin/env bash
go build -trimpath -buildmode=pie -ldflags "-s -w -X github.com/googleapis/mcp-toolbox/cmd.buildType=binary -X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.darwin.amd64
- id: "store-darwin-amd64"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "build-darwin-amd64"
script: |
#!/usr/bin/env bash
export VERSION=v$(cat ./cmd/version.txt)
gcloud storage cp toolbox.darwin.amd64 gs://$_UNSIGNED_BUCKET_NAME/$VERSION/darwin/amd64/toolbox
- id: "build-darwin-amd64-geminicli"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
- "install-macos-sdk"
env:
- "GOPATH=/gopath"
- "CGO_ENABLED=1"
- "GOOS=darwin"
- "GOARCH=amd64"
- "SDK_PATH=/macos-sdk/MacOSX14.5.sdk"
- "MACOS_MIN_VER=10.14"
- "CGO_LDFLAGS=-mmacosx-version-min=10.14 --sysroot /macos-sdk/MacOSX14.5.sdk -F/macos-sdk/MacOSX14.5.sdk/System/Library/Frameworks -L/usr/lib"
- "COMMON_FLAGS=-mmacosx-version-min=10.14 -target x86_64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks"
- "CC=/zig-tools/zig cc -mmacosx-version-min=10.14 -target x86_64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks"
- "CXX=/zig-tools/zig c++ -mmacosx-version-min=10.14 -target x86_64-macos.11.0.0-none -isysroot /macos-sdk/MacOSX14.5.sdk -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks"
volumes:
- name: "go"
path: "/gopath"
- name: "zig"
path: "/zig-tools"
- name: "macos-sdk"
path: "/macos-sdk"
script: |
#!/usr/bin/env bash
go build -trimpath -buildmode=pie -ldflags "-s -w -X github.com/googleapis/mcp-toolbox/cmd.buildType=geminicli.binary -X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.geminicli.darwin.amd64
- id: "store-darwin-amd64-geminicli"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "build-darwin-amd64-geminicli"
script: |
#!/usr/bin/env bash
export VERSION=v$(cat ./cmd/version.txt)
gcloud storage cp toolbox.geminicli.darwin.amd64 gs://$_BUCKET_NAME/geminicli/$VERSION/darwin/amd64/toolbox
- id: "build-windows-amd64"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
env:
- "GOPATH=/gopath"
- "CGO_ENABLED=1"
- "GOOS=windows"
- "GOARCH=amd64"
- "CC=/zig-tools/zig cc -target x86_64-windows-gnu"
- "CXX=/zig-tools/zig c++ -target x86_64-windows-gnu"
volumes:
- name: "go"
path: "/gopath"
- name: "zig"
path: "/zig-tools"
script: |
#!/usr/bin/env bash
go build -ldflags "-X github.com/googleapis/mcp-toolbox/cmd.buildType=binary -X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.windows.amd64
- id: "store-windows-amd64"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "build-windows-amd64"
script: |
#!/usr/bin/env bash
export VERSION=v$(cat ./cmd/version.txt)
gcloud storage cp toolbox.windows.amd64 gs://$_UNSIGNED_BUCKET_NAME/$VERSION/windows/amd64/toolbox.exe
- id: "build-windows-amd64-geminicli"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
env:
- "GOPATH=/gopath"
- "CGO_ENABLED=1"
- "GOOS=windows"
- "GOARCH=amd64"
- "CC=/zig-tools/zig cc -target x86_64-windows-gnu"
- "CXX=/zig-tools/zig c++ -target x86_64-windows-gnu"
volumes:
- name: "go"
path: "/gopath"
- name: "zig"
path: "/zig-tools"
script: |
#!/usr/bin/env bash
go build -ldflags "-X github.com/googleapis/mcp-toolbox/cmd.buildType=geminicli.binary -X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.geminicli.windows.amd64
- id: "store-windows-amd64-geminicli"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "build-windows-amd64-geminicli"
script: |
#!/usr/bin/env bash
export VERSION=v$(cat ./cmd/version.txt)
gcloud storage cp toolbox.geminicli.windows.amd64 gs://$_BUCKET_NAME/geminicli/$VERSION/windows/amd64/toolbox.exe
- id: "build-windows-arm64"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
env:
- "GOPATH=/gopath"
- "CGO_ENABLED=1"
- "GOOS=windows"
- "GOARCH=arm64"
- "CC=/zig-tools/zig cc -target aarch64-windows-gnu"
- "CXX=/zig-tools/zig c++ -target aarch64-windows-gnu"
volumes:
- name: "go"
path: "/gopath"
- name: "zig"
path: "/zig-tools"
script: |
#!/usr/bin/env bash
go build -ldflags "-X github.com/googleapis/mcp-toolbox/cmd.buildType=binary -X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.windows.arm64
- id: "store-windows-arm64"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "build-windows-arm64"
script: |
#!/usr/bin/env bash
export VERSION=v$(cat ./cmd/version.txt)
gcloud storage cp toolbox.windows.arm64 gs://$_UNSIGNED_BUCKET_NAME/$VERSION/windows/arm64/toolbox.exe
- id: "build-windows-arm64-geminicli"
name: golang:1
waitFor:
- "install-dependencies"
- "install-zig"
env:
- "GOPATH=/gopath"
- "CGO_ENABLED=1"
- "GOOS=windows"
- "GOARCH=arm64"
- "CC=/zig-tools/zig cc -target aarch64-windows-gnu"
- "CXX=/zig-tools/zig c++ -target aarch64-windows-gnu"
volumes:
- name: "go"
path: "/gopath"
- name: "zig"
path: "/zig-tools"
script: |
#!/usr/bin/env bash
go build -ldflags "-X github.com/googleapis/mcp-toolbox/cmd.buildType=geminicli.binary -X github.com/googleapis/mcp-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.geminicli.windows.arm64
- id: "store-windows-arm64-geminicli"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "build-windows-arm64-geminicli"
script: |
#!/usr/bin/env bash
export VERSION=v$(cat ./cmd/version.txt)
gcloud storage cp toolbox.geminicli.windows.arm64 gs://$_BUCKET_NAME/geminicli/$VERSION/windows/arm64/toolbox.exe
- id: "publish-npm-to-ar"
name: node:20
waitFor:
- "build-linux-amd64"
- "build-darwin-arm64"
- "build-darwin-amd64"
- "build-windows-amd64"
- "build-windows-arm64"
script: |
#!/usr/bin/env bash
bash .ci/publish_npm_to_ar.sh
- id: "trigger-exit-gate-npm"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "publish-npm-to-ar"
env:
- "BUILD_ID=$BUILD_ID"
script: |
#!/usr/bin/env bash
bash .ci/trigger_exit_gate.sh npm
- id: "publish-pypi-to-ar"
name: python:3.12
waitFor:
- "build-linux-amd64"
- "build-darwin-arm64"
- "build-darwin-amd64"
- "build-windows-amd64"
- "build-windows-arm64"
script: |
#!/usr/bin/env bash
bash .ci/publish_pypi_to_ar.sh
- id: "trigger-exit-gate-pypi"
name: "gcr.io/cloud-builders/gcloud:latest"
waitFor:
- "publish-pypi-to-ar"
env:
- "BUILD_ID=$BUILD_ID"
script: |
#!/usr/bin/env bash
bash .ci/trigger_exit_gate.sh pypi
images:
- "${_DOCKER_URI}:latest"
options:
requestedVerifyOption: VERIFIED # This ensures provenance is generated
automapSubstitutions: true
dynamicSubstitutions: true
logging: CLOUD_LOGGING_ONLY # Necessary for custom service account
pool:
name: projects/$PROJECT_ID/locations/us-central1/workerPools/run-release # to increase resource for running releases
substitutions:
_REGION: us-central1
_AR_HOSTNAME: ${_REGION}-docker.pkg.dev
_AR_REPO_NAME: toolbox
_BUCKET_NAME: mcp-toolbox-for-databases
_UNSIGNED_BUCKET_NAME: mcp-toolbox-for-databases-unsigned
_ASSETS_BUCKET: toolbox-build-assets
_DOCKER_URI: ${_AR_HOSTNAME}/${PROJECT_ID}/${_AR_REPO_NAME}/toolbox
_PUSH_LATEST: "false" # Substituted in trigger
+18
View File
@@ -0,0 +1,18 @@
# 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.
# 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.
ignore_patterns:
- "package-lock.json"
- "go.sum"
- "requirements.txt"
+1
View File
@@ -0,0 +1 @@
../GEMINI.md
+17
View File
@@ -0,0 +1,17 @@
# This file controls who is tagged for review for any given pull request.
#
# For syntax help see:
# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax
* @googleapis/senseai-eco-team
# Code & Tests
**/alloydb*/ @googleapis/toolbox-alloydb-team @googleapis/senseai-eco-team
**/bigquery/ @googleapis/toolbox-bigquery-team @googleapis/senseai-eco-team
**/bigtable/ @googleapis/toolbox-bigtable-team @googleapis/senseai-eco-team
**/cloudsqlmssql/ @googleapis/toolbox-cloud-sql-mssql-team @googleapis/senseai-eco-team
**/cloudsqlmysql/ @googleapis/toolbox-cloud-sql-mysql-team @googleapis/senseai-eco-team
**/cloudsqlpg/ @googleapis/toolbox-cloud-sql-postgres-team @googleapis/senseai-eco-team
**/dataplex/ @googleapis/toolbox-dataplex-team @googleapis/senseai-eco-team
**/firestore/ @googleapis/toolbox-firestore-team @googleapis/senseai-eco-team
**/looker/ @googleapis/toolbox-looker-team @googleapis/senseai-eco-team
**/spanner/ @googleapis/toolbox-spanner-team @googleapis/senseai-eco-team
+119
View File
@@ -0,0 +1,119 @@
# 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.
# You may obtain a copy of the License at
#
# https://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: 🐞 Bug Report
description: File a report for unexpected or undesired behavior.
title: "<brief summary of what bug or error was observed>"
labels: ["type: bug"]
type: "bug"
body:
- type: markdown
attributes:
value: |
Thanks for helping us improve! 🙏 Please answer these questions and provide as much information as possible about your problem.
- id: preamble
type: checkboxes
attributes:
label: Prerequisites
description: |
Please run through the following list and make sure you've tried the usual "quick fixes":
- Search the [current open issues](https://github.com/googleapis/mcp-toolbox/issues)
- Update to the [latest version of Toolbox](https://github.com/googleapis/mcp-toolbox/releases)
options:
- label: "I've searched the current open issues"
required: true
- label: "I've updated to the latest version of Toolbox"
- type: input
id: version
attributes:
label: Toolbox version
description: |
What version of Toolbox are you using (`toolbox --version`)? e.g.
- toolbox version 0.3.0
- us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:0.3.0
placeholder: ex. toolbox version 0.3.0
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: "Let us know some details about the environment in which you are seeing the bug!"
value: |
1. OS type and version: (output of `uname -a`)
2. How are you running Toolbox:
- As a downloaded binary (e.g. from `curl -O https://storage.googleapis.com/mcp-toolbox-for-databases/v$VERSION/linux/amd64/toolbox`)
- As a container (e.g. from `us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION`)
- Compiled from source (include the command used to build)
- type: textarea
id: client
attributes:
label: Client
description: "How are you connecting to Toolbox?"
value: |
1. Client: <name and link to the client are you using>
2. Version: <what exact version of the client are you using>
3. Example: If possible, please include your code of configuration:
```python
# Code goes here!
```
- id: expected-behavior
type: textarea
attributes:
label: Expected Behavior
description: |
Please enter a detailed description of the behavior you expected, and any information about what behavior you
noticed and why it is defective or unintentional.
validations:
required: true
- id: current-behavior
type: textarea
attributes:
label: Current Behavior
description: "Please enter a detailed description of the behavior you encountered instead."
validations:
required: true
- type: textarea
id: repro
attributes:
label: Steps to reproduce?
description: |
How can we reproduce this bug? Please walk us through it step by step,
with as much relevant detail as possible. A 'minimal' reproduction is
preferred, which means removing as much of the examples as possible so
only the minimum required to run and reproduce the bug is left.
value: |
1. ?
2. ?
3. ?
...
validations:
required: true
- type: textarea
id: additional-details
attributes:
label: Additional Details
description: |
Any other information you want us to know? Things such as tools config,
server logs, etc. can be included here.
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Google Cloud Support
url: https://cloud.google.com/support/
about: If you have a support contract with Google, please both open an issue here and open Google Cloud Support portal with a link to the issue.
@@ -0,0 +1,60 @@
# 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.
# You may obtain a copy of the License at
#
# https://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: ✨ Feature Request
description: Suggest an idea for new or improved behavior.
title: "<brief summary of the proposed feature>"
labels: ["type: feature request"]
type: feature
body:
- type: markdown
attributes:
value: |
Thanks for helping us improve! 🙏 Please answer these questions and provide as much information as possible about your feature request.
- id: preamble
type: checkboxes
attributes:
label: Prerequisites
description: |
Please run through the following list and make sure you've tried the usual "quick fixes":
options:
- label: "Search the [current open issues](https://github.com/googleapis/mcp-toolbox/issues)"
required: true
- type: textarea
id: use-case
attributes:
label: What are you trying to do that currently feels hard or impossible?
description: "A clear and concise description of what the end goal for the feature should be -- avoid generalizing and try to provide a specific use-case."
validations:
required: true
- type: textarea
id: suggested-solution
attributes:
label: Suggested Solution(s)
description: "If you have a suggestion for how this use-case can be solved, please feel free to include it."
- type: textarea
id: alternatives-considered
attributes:
label: Alternatives Considered
description: "Are there any workaround or third party tools to replicate this behavior? Why would adding this feature be preferred over them?"
- type: textarea
id: additional-details
attributes:
label: Additional Details
description: "Any additional information we should know? Please reference it here (issues, PRs, descriptions, or screenshots)"
+55
View File
@@ -0,0 +1,55 @@
# 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.
# You may obtain a copy of the License at
#
# https://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: 💬 Question
description: Questions on how something works or the best way to do something?
title: "<brief summary of the question>"
labels: ["type: question"]
body:
- type: markdown
attributes:
value: |
Thanks for helping us improve! 🙏 Please provide as much information as possible about your question.
- id: preamble
type: checkboxes
attributes:
label: Prerequisites
description: |
Please run through the following list and make sure you've tried the usual "quick fixes":
options:
- label: "Search the [current open issues](https://github.com/googleapis/mcp-toolbox/issues)"
required: true
- type: textarea
id: question
attributes:
label: Question
description: "What's your question? Please provide as much relevant information as possible to reduce turnaround time. Include information like what environment, language, or framework you are using."
placeholder: "Example: How do I connect using private IP with the AlloyDB source?"
validations:
required: true
- type: textarea
id: code
attributes:
label: Code
description: "Please paste any useful application code that might be relevant to your question. (if your code is in a public repo, feel free to paste a link!)"
- type: textarea
id: additional-details
attributes:
label: Additional Details
description: "Any other information you want us to know that might be helpful in answering your question? (link issues, PRs, descriptions, or screenshots)."
+22
View File
@@ -0,0 +1,22 @@
## Description
> Should include a concise description of the changes (bug or feature), it's
> impact, along with a summary of the solution
## PR Checklist
> Thank you for opening a Pull Request! Before submitting your PR, there are a
> few things you can do to make sure it goes smoothly:
- [ ] Make sure you reviewed
[CONTRIBUTING.md](https://github.com/googleapis/mcp-toolbox/blob/main/CONTRIBUTING.md)
- [ ] Make sure to open an issue as a
[bug/issue](https://github.com/googleapis/mcp-toolbox/issues/new/choose)
before writing your code! That way we can discuss the change, evaluate
designs, and agree on the general idea
- [ ] Ensure the tests and linter pass
- [ ] Code coverage does not decrease (if any source code was changed)
- [ ] Appropriate docs were updated (if necessary)
- [ ] Make sure to add `!` if this involve a breaking change
🛠️ Fixes #<issue_number_goes_here>
+15
View File
@@ -0,0 +1,15 @@
# Copyright 2024 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
#
# https://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.
enabled: false
+106
View File
@@ -0,0 +1,106 @@
# 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.
# You may obtain a copy of the License at
#
# https://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.
assign_issues:
- Yuan325
- duwenxin99
- anubhav756
- twishabansal
- dishaprakash
assign_issues_by:
- labels:
- 'product: alloydb'
to:
- 'googleapis/toolbox-alloydb-team'
- labels:
- 'product: bigquery'
to:
- 'googleapis/toolbox-bigquery-team'
- labels:
- 'product: bigtable'
to:
- 'googleapis/toolbox-bigtable-team'
- labels:
- 'product: mssql'
to:
- 'googleapis/toolbox-cloud-sql-mssql-team'
- labels:
- 'product: mysql'
to:
- 'googleapis/toolbox-cloud-sql-mysql-team'
- labels:
- 'product: postgres'
to:
- 'googleapis/toolbox-cloud-sql-postgres-team'
- labels:
- 'product: dataplex'
to:
- 'googleapis/toolbox-dataplex-team'
- labels:
- 'product: firestore'
to:
- 'googleapis/toolbox-firestore-team'
- labels:
- 'product: looker'
to:
- 'googleapis/toolbox-looker-team'
- labels:
- 'product: spanner'
to:
- 'googleapis/toolbox-spanner-team'
assign_prs:
- Yuan325
- duwenxin99
assign_prs_by:
- labels:
- 'product: alloydb'
to:
- 'googleapis/toolbox-alloydb-team'
- labels:
- 'product: bigquery'
to:
- 'googleapis/toolbox-bigquery-team'
- labels:
- 'product: bigtable'
to:
- 'googleapis/toolbox-bigtable-team'
- labels:
- 'product: mssql'
to:
- 'googleapis/toolbox-cloud-sql-mssql-team'
- labels:
- 'product: mysql'
to:
- 'googleapis/toolbox-cloud-sql-mysql-team'
- labels:
- 'product: postgres'
to:
- 'googleapis/toolbox-cloud-sql-postgres-team'
- labels:
- 'product: dataplex'
to:
- 'googleapis/toolbox-dataplex-team'
- labels:
- 'product: firestore'
to:
- 'googleapis/toolbox-firestore-team'
- labels:
- 'product: looker'
to:
- 'googleapis/toolbox-looker-team'
- labels:
- 'product: spanner'
to:
- 'googleapis/toolbox-spanner-team'
+27
View File
@@ -0,0 +1,27 @@
# Copyright 2024 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
#
# https://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.
allowedCopyrightHolders:
- 'Google LLC'
- 'Oracle'
allowedLicenses:
- 'Apache-2.0'
sourceFileExtensions:
- 'go'
- 'yaml'
- 'yml'
ignoreFiles:
- 'docs/en/documentation/**/*.go'
- 'docs/en/samples/**/*'
- '**/*oracle*'
+2
View File
@@ -0,0 +1,2 @@
---
ignored: true
+194
View File
@@ -0,0 +1,194 @@
# Copyright 2024 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: duplicate
color: ededed
description: ""
- name: wontfix
color: ededed
description: Issues that we are not planning to fix but would like to keep
track of.
- name: 'type: bug'
color: db4437
description: Error or flaw in code with unintended results or allowing sub-optimal
usage patterns.
- name: 'type: cleanup'
color: c5def5
description: An internal cleanup or hygiene concern.
- name: 'type: docs'
color: 0000A0
description: Improvement to the documentation for an API.
- name: 'type: feature request'
color: c5def5
description: Nice-to-have improvement, new feature or different behavior or design.
- name: 'type: process'
color: c5def5
description: A process-related concern. May include testing, release, or the like.
- name: 'type: question'
color: c5def5
description: Request for information or clarification.
- name: 'priority: p0'
color: b60205
description: Highest priority. Critical issue. P0 implies highest priority.
- name: 'priority: p1'
color: ffa03e
description: Important issue which blocks shipping the next release. Will be fixed
prior to next release.
- name: 'priority: p2'
color: fef2c0
description: Moderately-important priority. Fix may not be included in next release.
- name: 'priority: p3'
color: ffffc7
description: Desirable enhancement or fix. May not be included in next release.
- name: 'do not merge'
color: d93f0b
description: Indicates a pull request not ready for merge, due to either quality
or timing.
- name: 'autorelease: pending'
color: ededed
description: Release please needs to do its work on this.
- name: 'autorelease: triggered'
color: ededed
description: Release please has triggered a release for this.
- name: 'autorelease: tagged'
color: ededed
description: Release please has completed a release for this.
- name: 'blunderbuss: assign'
color: 3DED97
description: Have blunderbuss assign this to someone new.
- name: 'tests: run'
color: 3DED97
description: Label to trigger Github Action tests.
- name: 'docs: deploy-preview'
color: BFDADC
description: Label to trigger Github Action docs preview.
- name: 'status: help wanted'
color: 8befd7
description: 'Status: Unplanned work open to contributions from the community.'
- name: 'status: feedback wanted'
color: 8befd7
description: 'Status: waiting for feedback from community or issue author.'
- name: 'good first issue'
color: 7057ff
description: 'Good for newcomers'
- name: 'ready for work'
color: 0e8a16
description: 'Ready to be picked up'
- name: 'status: waiting for response'
color: 8befd7
description: 'Status: reviewer is awaiting feedback or responses from the author before proceeding.'
- name: 'release candidate'
color: 32CD32
description: 'Use label to signal PR should be included in the next release.'
# Product Labels
- name: 'product: alloydb'
color: 5065c7
description: 'AlloyDB'
- name: 'product: bigquery'
color: 5065c7
description: 'BigQuery'
- name: 'product: bigtable'
color: 5065c7
description: 'Bigtable'
- name: 'product: cassandra'
color: 5065c7
description: 'Cassandra'
- name: 'product: clickhouse'
color: 5065c7
description: 'ClickHouse'
- name: 'product: mssql'
color: 5065c7
description: 'SQL Server'
- name: 'product: mysql'
color: 5065c7
description: 'MySQL'
- name: 'product: postgres'
color: 5065c7
description: 'PostgreSQL'
- name: 'product: couchbase'
color: 5065c7
description: 'Couchbase'
- name: 'product: dataplex'
color: 5065c7
description: 'Dataplex'
- name: 'product: dgraph'
color: 5065c7
description: 'Dgraph'
- name: 'product: elasticsearch'
color: 5065c7
description: 'Elasticsearch'
- name: 'product: firebird'
color: 5065c7
description: 'Firebird'
- name: 'product: firestore'
color: 5065c7
description: 'Firestore'
- name: 'product: looker'
color: 5065c7
description: 'Looker'
- name: 'product: mindsdb'
color: 5065c7
description: 'MindsDB'
- name: 'product: mongodb'
color: 5065c7
description: 'MongoDB'
- name: 'product: neo4j'
color: 5065c7
description: 'Neo4j'
- name: 'product: oceanbase'
color: 5065c7
description: 'OceanBase'
- name: 'product: oracle'
color: 5065c7
description: 'Oracle'
- name: 'product: redis'
color: 5065c7
description: 'Redis'
- name: 'product: serverlessspark'
color: 5065c7
description: 'Serverless Spark'
- name: 'product: singlestore'
color: 5065c7
description: 'SingleStore'
- name: 'product: spanner'
color: 5065c7
description: 'Spanner'
- name: 'product: sqlite'
color: 5065c7
description: 'SQLite'
- name: 'product: tidb'
color: 5065c7
description: 'TiDB'
- name: 'product: trino'
color: 5065c7
description: 'Trino'
- name: 'product: valkey'
color: 5065c7
description: 'Valkey'
- name: 'product: yugabytedb'
color: 5065c7
description: 'YugabyteDB'
+105
View File
@@ -0,0 +1,105 @@
# Copyright 2024 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.
handleGHRelease: true
packageName: genai-toolbox
releaseType: simple
versionFile: "cmd/version.txt"
extraFiles:
[
"README.md",
"docs/en/documentation/getting-started/colab_quickstart.ipynb",
"docs/en/documentation/introduction/_index.md",
"docs/en/documentation/getting-started/mcp_quickstart/_index.md",
"docs/en/documentation/getting-started/quickstart/shared/configure_toolbox.md",
"docs/en/integrations/alloydb/_index.md",
"docs/en/integrations/alloydb/mcp_quickstart.md",
"docs/en/integrations/alloydb/ai-nl/alloydb_ai_nl.ipynb",
"docs/en/integrations/bigquery/local_quickstart.md",
"docs/en/integrations/bigquery/mcp_quickstart/_index.md",
"docs/en/integrations/bigquery/colab_quickstart_bigquery.ipynb",
"docs/en/integrations/looker/samples/looker_gemini.md",
"docs/en/integrations/looker/samples/looker_gemini_oauth/_index.md",
"docs/en/integrations/looker/samples/looker_mcp_inspector/_index.md",
"docs/en/documentation/connect-to/ides/looker_mcp.md",
"docs/en/documentation/connect-to/ides/mysql_mcp.md",
"docs/en/documentation/connect-to/ides/mssql_mcp.md",
"docs/en/documentation/connect-to/ides/postgres_mcp.md",
"docs/en/documentation/connect-to/ides/neo4j_mcp.md",
"docs/en/documentation/connect-to/ides/sqlite_mcp.md",
"docs/en/documentation/connect-to/ides/oracle_mcp.md",
"gemini-extension.json",
"pypi/src/toolbox_server/__init__.py",
{ "type": "json", "path": "server.json", "jsonpath": "$.version" },
{
"type": "json",
"path": "server.json",
"jsonpath": "$.packages[0].identifier",
},
{
"type": "json",
"path": "npm/server/package.json",
"jsonpath": "$.version",
},
{
"type": "json",
"path": "npm/server/package.json",
"jsonpath": "$.optionalDependencies['@toolbox-sdk/server-darwin-arm64']",
},
{
"type": "json",
"path": "npm/server/package.json",
"jsonpath": "$.optionalDependencies['@toolbox-sdk/server-darwin-x64']",
},
{
"type": "json",
"path": "npm/server/package.json",
"jsonpath": "$.optionalDependencies['@toolbox-sdk/server-linux-x64']",
},
{
"type": "json",
"path": "npm/server/package.json",
"jsonpath": "$.optionalDependencies['@toolbox-sdk/server-win32-arm64']",
},
{
"type": "json",
"path": "npm/server/package.json",
"jsonpath": "$.optionalDependencies['@toolbox-sdk/server-win32-x64']",
},
{
"type": "json",
"path": "npm/server-darwin-arm64/package.json",
"jsonpath": "$.version",
},
{
"type": "json",
"path": "npm/server-darwin-x64/package.json",
"jsonpath": "$.version",
},
{
"type": "json",
"path": "npm/server-linux-x64/package.json",
"jsonpath": "$.version",
},
{
"type": "json",
"path": "npm/server-win32-arm64/package.json",
"jsonpath": "$.version",
},
{
"type": "json",
"path": "npm/server-win32-x64/package.json",
"jsonpath": "$.version",
},
]
+110
View File
@@ -0,0 +1,110 @@
{
extends: [
'config:recommended',
':semanticCommitTypeAll(chore)',
':ignoreUnstable',
':separateMajorReleases',
':prConcurrentLimitNone',
':prHourlyLimitNone',
':preserveSemverRanges',
],
minimumReleaseAge: '3 days',
rebaseWhen: 'conflicted',
dependencyDashboardLabels: [
'type: process',
],
postUpdateOptions: [
'gomodTidy',
],
customManagers: [
{
customType: 'regex',
managerFilePatterns: [
'/^\\.github/workflows/.*\\.ya?ml$/',
],
matchStrings: [
'hugo-version:\\s*["\']?(?<currentValue>\\d+\\.\\d+\\.\\d+)["\']?',
],
depNameTemplate: 'gohugoio/hugo',
datasourceTemplate: 'github-releases',
extractVersionTemplate: '^v?(?<version>.*)$',
},
],
packageRules: [
{
groupName: 'Hugo',
matchPackageNames: [
'gohugoio/hugo',
],
},
{
groupName: 'GitHub Actions',
matchManagers: [
'github-actions',
],
pinDigests: true,
},
{
groupName: 'Go',
matchManagers: [
'gomod',
],
ignorePaths: [
'docs/**',
'.hugo/**',
],
},
{
groupName: 'Go Samples',
matchManagers: [
'gomod',
],
matchFileNames: [
'docs/**',
'.hugo/**',
],
},
{
groupName: 'Node',
matchManagers: [
'npm',
],
matchUpdateTypes: [
'minor',
'patch',
],
ignorePaths: [
'docs/**',
],
},
{
groupName: 'Node Samples',
matchManagers: [
'npm',
],
// Explicitly match the sample directories
matchFileNames: [
'docs/**',
],
},
{
groupName: 'Pip',
matchManagers: [
'pip_requirements',
],
ignorePaths: [
'docs/**',
],
},
{
groupName: 'Python Samples',
matchManagers: [
'pip_requirements',
],
// Explicitly match the sample directories
matchFileNames: [
'docs/**',
],
},
],
}
+27
View File
@@ -0,0 +1,27 @@
# 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.
# 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.
# Trigger presubmit tests for trusted contributors
# https://github.com/googleapis/repo-automation-bots/tree/main/packages/trusted-contribution
# Install: https://github.com/apps/trusted-contributions-gcf
trustedContributors:
- "dependabot[bot]"
- "renovate-bot"
annotations:
# Trigger Cloud Build tests
- type: comment
text: "/gcbrun"
- type: label
text: "tests: run"
@@ -0,0 +1,181 @@
# 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.
# 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: Cloud Build Failure Reporter
on:
workflow_call:
inputs:
trigger_names:
required: true
type: string
workflow_dispatch:
inputs:
trigger_names:
description: 'Cloud Build trigger names separated by comma.'
required: true
default: ''
jobs:
report:
permissions:
issues: 'write'
checks: 'read'
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3' # v9
env:
TRIGGER_NAMES: ${{ inputs.trigger_names }}
with:
script: |-
// parse test names
const testNameSubstring = process.env.TRIGGER_NAMES;
const testNameFound = new Map(); //keeps track of whether each test is found
testNameSubstring.split(',').forEach(testName => {
testNameFound.set(testName, false);
});
// label for all issues opened by reporter
const periodicLabel = 'periodic-failure';
// check if any reporter opened any issues previously
const prevIssues = await github.paginate(github.rest.issues.listForRepo, {
...context.repo,
state: 'open',
creator: 'github-actions[bot]',
labels: [periodicLabel]
});
// createOrCommentIssue creates a new issue or comments on an existing issue.
const createOrCommentIssue = async function (title, txt) {
if (prevIssues.length < 1) {
console.log('no previous issues found, creating one');
await github.rest.issues.create({
...context.repo,
title: title,
body: txt,
labels: [periodicLabel]
});
return;
}
// only comment on issue related to the current test
for (const prevIssue of prevIssues) {
if (prevIssue.title.includes(title)){
console.log(
`found previous issue ${prevIssue.html_url}, adding comment`
);
await github.rest.issues.createComment({
...context.repo,
issue_number: prevIssue.number,
body: txt
});
return;
}
}
};
// updateIssues comments on any existing issues. No-op if no issue exists.
const updateIssues = async function (checkName, txt) {
if (prevIssues.length < 1) {
console.log('no previous issues found.');
return;
}
// only comment on issue related to the current test
for (const prevIssue of prevIssues) {
if (prevIssue.title.includes(checkName)){
console.log(`found previous issue ${prevIssue.html_url}, adding comment`);
await github.rest.issues.createComment({
...context.repo,
issue_number: prevIssue.number,
body: txt
});
}
}
};
// Find status of check runs.
// We will find check runs for each commit and then filter for the periodic.
// Checks API only allows for ref and if we use main there could be edge cases where
// the check run happened on a SHA that is different from head.
const commits = await github.paginate(github.rest.repos.listCommits, {
...context.repo
});
const relevantChecks = new Map();
for (const commit of commits) {
console.log(
`checking runs at ${commit.html_url}: ${commit.commit.message}`
);
const checks = await github.rest.checks.listForRef({
...context.repo,
ref: commit.sha
});
// Iterate through each check and find matching names
for (const check of checks.data.check_runs) {
console.log(`Handling test name ${check.name}`);
for (const testName of testNameFound.keys()) {
if (testNameFound.get(testName) === true){
//skip if a check is already found for this name
continue;
}
if (check.name.includes(testName)) {
relevantChecks.set(check, commit);
testNameFound.set(testName, true);
}
}
}
// Break out of the loop early if all tests are found
const allTestsFound = Array.from(testNameFound.values()).every(value => value === true);
if (allTestsFound){
break;
}
}
// Handle each relevant check
relevantChecks.forEach((commit, check) => {
if (
check.status === 'completed' &&
check.conclusion === 'success'
) {
updateIssues(
check.name,
`[Tests are passing](${check.html_url}) for commit [${commit.sha}](${commit.html_url}).`
);
} else if (check.status === 'in_progress') {
console.log(
`Check is pending ${check.html_url} for ${commit.html_url}. Retry again later.`
);
} else {
createOrCommentIssue(
`Cloud Build Failure Reporter: ${check.name} failed`,
`Cloud Build Failure Reporter found test failure for [**${check.name}** ](${check.html_url}) at [${commit.sha}](${commit.html_url}). Please fix the error and then close the issue after the **${check.name}** test passes.`
);
}
});
// no periodic checks found across all commits, report it
const noTestFound = Array.from(testNameFound.values()).every(value => value === false);
if (noTestFound){
createOrCommentIssue(
`Missing periodic tests: ${process.env.TRIGGER_NAMES}`,
`No periodic test is found for triggers: ${process.env.TRIGGER_NAMES}. Last checked from ${
commits[0].html_url
} to ${commits[commits.length - 1].html_url}.`
);
}
+51
View File
@@ -0,0 +1,51 @@
# 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: "Sync v1 docsite to Cloudflare"
permissions:
contents: read
concurrency:
group: cloudflare-sync
cancel-in-progress: true
# zizmor flags workflow_run, but this is safe because it only deploys internal branches and is strictly gated to the base repository.
on: # zizmor: ignore[dangerous-triggers]
workflow_dispatch:
workflow_run:
workflows: ["CF: Deploy Dev Docs", "CF: Deploy Versioned Docs", "CF: Deploy Previous Version Docs"]
types: [completed]
jobs:
deploy:
runs-on: ubuntu-latest
# Hardened workflow_run: strictly verifies the triggering run originated from the base repository to prevent execution from forks.
if: >
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_repository.full_name == github.repository)
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: 'cloudflare-pages'
persist-credentials: false
- name: Cleanup
run: |
rm -rf .git
- name: Cloudflare Deploy
uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy . --project-name=toolbox-docs --branch=main
+79
View File
@@ -0,0 +1,79 @@
# 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: conformance
# zizmor flags pull_request_target, but it is safely gated by the 'tests: run' label manually applied by maintainers.
on: # zizmor: ignore[dangerous-triggers]
pull_request:
pull_request_target:
types: [labeled]
permissions: read-all
jobs:
conformance:
if: "${{ github.event.action != 'labeled' || github.event.label.name == 'tests: run' }}"
name: conformance
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: 'read'
issues: 'write'
pull-requests: 'write'
steps:
- name: Remove PR Label
if: "${{ github.event.action == 'labeled' && github.event.label.name == 'tests: run' }}"
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
try {
await github.rest.issues.removeLabel({
name: 'tests: run',
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number
});
} catch (e) {
console.log('Failed to remove label. Another job may have already removed it!');
}
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
token: ${{ secrets.GITHUB_TOKEN }}
persist-credentials: false
- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: 'go.mod'
- name: Start MCP Toolbox Server
run: |
go build -o toolbox
./toolbox --allowed-hosts localhost --config tests/conformance/tools.yaml &
sleep 5 # Give the server a moment to bind to port 5000
- name: Run Conformance Tests
uses: modelcontextprotocol/conformance@21a9a2febd7100d7c17ac1021ee7f2ed9f66a1e0 # v0.1.16
with:
mode: server
url: 'http://localhost:5000/mcp'
suite: active
expected-failures: tests/conformance/conformance-baseline.yml
@@ -0,0 +1,91 @@
# 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: "CF: Deploy Dev Docs"
permissions:
contents: write
on:
push:
branches:
- main
paths:
- 'docs/**'
- '.github/workflows/docs*_cf.yaml'
- '.github/workflows/deploy*_cf.yaml'
- '.hugo/**'
# Allow triggering manually.
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-24.04
defaults:
run:
working-directory: .hugo
# This shared concurrency group ensures only one docs deployment runs at a time.
concurrency:
group: cf-docs-update
cancel-in-progress: true
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0
submodules: recursive
persist-credentials: false
- name: Setup Hugo
uses: peaceiris/actions-hugo@2752ce1d29631191ea3f27c23495fa06139a5b78 # v3
with:
hugo-version: "0.163.3"
extended: true
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: "22"
- name: Cache dependencies
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm ci
- run: hugo --minify --config hugo.cloudflare.toml
env:
HUGO_BASEURL: https://mcp-toolbox.dev/dev/
HUGO_RELATIVEURLS: false
- name: Build Pagefind Search Index
run: npx pagefind --site public
- name: Create Staging Directory
run: |
mkdir staging
mv public staging/dev
mv staging/dev/releases.releases staging/releases.releases
- name: Push to Cloudflare Branch
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./.hugo/staging
publish_branch: cloudflare-pages
keep_files: true
commit_message: "deploy: ${{ github.event.head_commit.message }}"
@@ -0,0 +1,119 @@
# 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: "CF: Deploy Previous Version Docs"
on:
workflow_dispatch:
inputs:
version_tag:
description: 'The old version tag to build docs for (e.g., v0.15.0)'
required: true
type: string
jobs:
build_and_deploy:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout main branch (for latest templates and theme)
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: 'main'
submodules: 'recursive'
fetch-depth: 0
persist-credentials: false
- name: Checkout old content from tag into a temporary directory
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event.inputs.version_tag }}
path: 'old_version_source'
sparse-checkout: |
docs
persist-credentials: false
- name: Replace content with old version
run: |
# Remove the current content directory from the main branch checkout
rm -rf docs/
# Move the old content directory into place
mv ./old_version_source/docs docs
- name: Setup Hugo
uses: peaceiris/actions-hugo@2752ce1d29631191ea3f27c23495fa06139a5b78 # v3
with:
hugo-version: "0.163.3"
extended: true
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: "22"
- name: Install Dependencies
run: npm ci
working-directory: .hugo
- name: Build Hugo Site for Archived Version
run: |
hugo --minify --config hugo.cloudflare.toml
rm -f public/releases.releases
working-directory: .hugo
env:
HUGO_BASEURL: https://mcp-toolbox.dev/${{ github.event.inputs.version_tag }}/
HUGO_RELATIVEURLS: false
HUGO_PARAMS_VERSION: ${{ github.event.inputs.version_tag }}
- name: Build Pagefind Index (Archived Version)
run: npx pagefind --site public
working-directory: .hugo
- name: Deploy to cloudflare-pages
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: .hugo/public
publish_branch: cloudflare-pages
destination_dir: ./${{ github.event.inputs.version_tag }}
keep_files: true
allow_empty_commit: true
commit_message: "docs(backport): deploy docs for ${{ github.event.inputs.version_tag }}"
- name: Clean Build Directory
run: rm -rf .hugo/public
- name: Build Hugo Site
run: hugo --minify --config hugo.cloudflare.toml
working-directory: .hugo
env:
HUGO_BASEURL: https://mcp-toolbox.dev/
HUGO_RELATIVEURLS: false
HUGO_PARAMS_VERSION: ${{ github.event.inputs.version_tag }}
- name: Build Pagefind Index (Root)
run: npx pagefind --site public
working-directory: .hugo
- name: Deploy to root
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: .hugo/public
publish_branch: cloudflare-pages
keep_files: true
allow_empty_commit: true
commit_message: "deploy: docs to root for ${{ github.event.inputs.version_tag }}"
@@ -0,0 +1,108 @@
# 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: "CF: Deploy Versioned Docs"
permissions:
contents: write
on:
release:
types: [published]
jobs:
deploy:
runs-on: ubuntu-24.04
# This shared concurrency group ensures only one docs deployment runs at a time.
concurrency:
group: cf-docs-update
cancel-in-progress: false
steps:
- name: Checkout Code at Tag
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event.release.tag_name }}
persist-credentials: false
- name: Get Version from Release Tag
id: get_version
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: echo "VERSION=${RELEASE_TAG}" >> "$GITHUB_OUTPUT"
- name: Setup Hugo
uses: peaceiris/actions-hugo@2752ce1d29631191ea3f27c23495fa06139a5b78 # v3
with:
hugo-version: "0.163.3"
extended: true
- name: Setup Node
# zizmor flags setup-node for cache-poisoning on releases, but since we don't pass `cache: npm`, no caching occurs. Safe to ignore.
# zizmor: ignore[cache-poisoning]
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: "22"
- name: Install Dependencies
run: npm ci
working-directory: .hugo
- name: Build Hugo Site
run: |
hugo --minify --config hugo.cloudflare.toml
rm -f public/releases.releases
working-directory: .hugo
env:
HUGO_BASEURL: https://mcp-toolbox.dev/${{ steps.get_version.outputs.VERSION }}/
HUGO_RELATIVEURLS: false
HUGO_PARAMS_VERSION: ${{ steps.get_version.outputs.VERSION }}
- name: Build Pagefind Index (Versioned)
run: npx pagefind --site public
working-directory: .hugo
- name: Deploy
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: .hugo/public
publish_branch: cloudflare-pages
destination_dir: ./${{ steps.get_version.outputs.VERSION }}
keep_files: true
commit_message: "deploy: docs for ${{ steps.get_version.outputs.VERSION }}"
- name: Clean Build Directory
run: rm -rf .hugo/public
- name: Build Hugo Site
run: hugo --minify --config hugo.cloudflare.toml
working-directory: .hugo
env:
HUGO_BASEURL: https://mcp-toolbox.dev/
HUGO_RELATIVEURLS: false
HUGO_PARAMS_VERSION: ${{ steps.get_version.outputs.VERSION }}
- name: Build Pagefind Index (Root)
run: npx pagefind --site public
working-directory: .hugo
- name: Deploy to root
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: .hugo/public
publish_branch: cloudflare-pages
keep_files: true
allow_empty_commit: true
commit_message: "deploy: docs to root for ${{ steps.get_version.outputs.VERSION }}"
+62
View File
@@ -0,0 +1,62 @@
# 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 Documentation
permissions:
contents: read
on:
pull_request:
paths:
- 'docs/**'
- '.github/workflows/docs**'
- '.ci/lint-docs-*.sh'
- '.hugo/data/filters.yaml'
jobs:
lint-source-pages:
name: Lint Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.x'
- name: Check for large files (>24MB)
run: |
LARGE_FILES=$(find docs/ -type f -size +24M)
if [ -n "$LARGE_FILES" ]; then
echo "Error: Files exceed 24MB limit: $LARGE_FILES"
exit 1
fi
- name: Make scripts executable
run: chmod +x .ci/lint-docs-*.sh
- name: Run Structure Linter for Source Pages
run: bash .ci/lint-docs-source-page.sh
- name: Run Structure Linter for Tool Pages
run: bash .ci/lint-docs-tool-page.sh
- name: Run Sample Filters Linter
run: bash .ci/lint-docs-sample-filters.sh
@@ -0,0 +1,118 @@
# 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: "CF: Build Docs Preview"
on:
pull_request:
types: [opened, synchronize, reopened, labeled]
paths:
- 'docs/**'
- '.github/workflows/docs*_cf.yaml'
- '.hugo/**'
permissions:
contents: read
jobs:
build-preview:
if: "contains(github.event.pull_request.labels.*.name, 'docs: deploy-preview')"
runs-on: ubuntu-24.04
env:
PR_NUMBER: ${{ github.event.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
concurrency:
group: "cf-preview-${{ github.event.number }}"
cancel-in-progress: true
defaults:
run:
working-directory: .hugo
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ env.HEAD_SHA }}
fetch-depth: 0
persist-credentials: false
- name: Setup Hugo
uses: peaceiris/actions-hugo@2752ce1d29631191ea3f27c23495fa06139a5b78 # v3
with:
hugo-version: "0.163.3"
extended: true
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: "22"
cache: 'npm'
cache-dependency-path: '.hugo/package-lock.json'
- run: npm ci --ignore-scripts
- run: hugo --minify --config hugo.cloudflare.toml
env:
HUGO_BASEURL: "/"
HUGO_ENVIRONMENT: preview
HUGO_RELATIVEURLS: false
- name: Build Pagefind Search Index
run: npx pagefind --site public
- name: Prepare Artifact Payload
run: |
mkdir -p ../artifact-payload
cp -r public ../artifact-payload/public
echo ${PR_NUMBER} > ../artifact-payload/pr_number.txt
- name: Upload Artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: cf-preview-data
path: artifact-payload/
retention-days: 1
- name: Deployment Link
run: |
DEPLOY_URL="https://github.com/${{ github.repository_owner }}/${GITHUB_EVENT_REPOSITORY_NAME}/actions/workflows/docs_deploy_cf.yaml"
echo "### Build Complete" >> $GITHUB_STEP_SUMMARY
echo "The build for PR #${PR_NUMBER} succeeded." >> $GITHUB_STEP_SUMMARY
echo "The Cloudflare deployment workflow is now starting." >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "#### [Track Deployment Progress]($DEPLOY_URL)" >> $GITHUB_STEP_SUMMARY
env:
GITHUB_EVENT_REPOSITORY_NAME: ${{ github.event.repository.name }}
remove-label:
needs: build-preview
if: "always() && contains(github.event.pull_request.labels.*.name, 'docs: deploy-preview')"
runs-on: ubuntu-24.04
permissions:
pull-requests: write
steps:
- name: Remove deploy-preview label
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: 'docs: deploy-preview'
});
console.log("Label 'docs: deploy-preview' removed successfully.");
} catch (error) {
console.log(`Error removing label: ${error}`);
}
@@ -0,0 +1,73 @@
# 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: "CF: Cleanup PR Preview"
permissions:
pull-requests: write
# This Workflow depends on 'github.event.number',
# not compatible with branch or manual triggers.
on:
pull_request:
types:
- closed
jobs:
clean:
# Only run for PRs from the same repository to ensure secret access
if: "${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }}"
runs-on: ubuntu-24.04
concurrency:
# Shared concurrency group with preview staging.
group: "cf-preview-${{ github.event.number }}"
cancel-in-progress: true
steps:
- name: Delete Cloudflare Pages Deployments via API
env:
ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
PROJECT_NAME: toolbox-docs
BRANCH_NAME: pr-${{ github.event.number }}
run: |
echo "Fetching deployments for preview branch: $BRANCH_NAME"
# Fetch the most recent deployments from your Cloudflare project
RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pages/projects/$PROJECT_NAME/deployments" \
-H "Authorization: Bearer $API_TOKEN")
# Use 'jq' to extract all deployment IDs that match this specific PR branch alias
IDS=$(echo "$RESPONSE" | jq -r --arg branch "$BRANCH_NAME" '.result[] | select(.deployment_trigger.metadata.branch? == $branch) | .id')
if [ -z "$IDS" ]; then
echo "No preview deployments found to clean up."
else
for id in $IDS; do
echo "Deleting Cloudflare deployment ID: $id"
curl -s -X DELETE "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pages/projects/$PROJECT_NAME/deployments/$id?force=true" \
-H "Authorization: Bearer $API_TOKEN"
done
echo "Successfully removed preview environment."
fi
- name: Comment
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
github.rest.issues.createComment({
issue_number: context.payload.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "🧨 **Preview deployments removed.**\n\nCloudflare Pages environments for `pr-${{ github.event.number }}` have been deleted."
})
@@ -0,0 +1,118 @@
# 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: "CF: Deploy Docs Preview"
# zizmor flags workflow_run input, but this workflow safely sanitizes the untrusted PR number artifact before use.
on: # zizmor: ignore[dangerous-triggers]
workflow_run:
workflows: ["CF: Build Docs Preview"]
types:
- completed
workflow_dispatch:
inputs:
pr_number:
description: 'PR Number to deploy (Manual override)'
required: true
type: string
build_run_id:
description: 'The Run ID from the successful "CF: Build Docs Preview" workflow'
required: true
type: string
permissions:
contents: read
pull-requests: write
jobs:
deploy-preview:
if: >
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success')
runs-on: ubuntu-24.04
concurrency:
group: "cf-deploy-${{ github.event.inputs.pr_number || github.event.workflow_run.pull_requests[0].number }}"
cancel-in-progress: true
steps:
- name: Checkout base repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
persist-credentials: false
- name: Download Artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: cf-preview-data
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.inputs.build_run_id || github.event.workflow_run.id }}
path: downloaded-artifact
- name: Read PR Number
id: get_pr
run: |
if [ -n "${GITHUB_EVENT_INPUTS_PR_NUMBER}" ]; then
PR_NUMBER="${GITHUB_EVENT_INPUTS_PR_NUMBER}"
else
PR_NUMBER=$(cat downloaded-artifact/pr_number.txt)
fi
if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
echo "Error: PR number [$PR_NUMBER] is invalid."
exit 1
fi
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
env:
GITHUB_EVENT_INPUTS_PR_NUMBER: ${{ github.event.inputs.pr_number }}
- name: Deploy to Cloudflare Pages
id: cf_deploy
uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy downloaded-artifact/public --project-name toolbox-docs --branch pr-${{ steps.get_pr.outputs.pr_number }}
- name: Post Preview URL Comment
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
PR_NUMBER: ${{ steps.get_pr.outputs.pr_number }}
DEPLOY_URL: ${{ steps.cf_deploy.outputs.pages-deployment-alias-url }}
with:
script: |
const prNumber = parseInt(process.env.PR_NUMBER, 10);
const deployUrl = process.env.DEPLOY_URL;
const marker = '<!-- cf-preview-comment-marker -->';
// Fetch all comments on the PR
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
// Look for the invisible HTML marker
const existingComment = comments.find(c => c.body.includes(marker));
// Exit early if we've already posted a comment for this PR to avoid duplicates
if (existingComment) {
console.log("Preview link already posted on this PR. Skipping.");
return;
}
// Create the comment since it's the first deployment for this PR
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: `${marker}\n🚀 **Cloudflare Preview Ready!**\n\n🔎 View Preview: ${deployUrl}\n\n*(Note: Subsequent pushes to this PR will automatically update the preview at this same URL)*`
});
+105
View File
@@ -0,0 +1,105 @@
# 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.
# 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: Link Checker
on:
pull_request:
permissions:
contents: read
jobs:
link-check:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
persist-credentials: false
- 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 -- '*.md')
if [ -z "$CHANGED_FILES" ]; then
echo "No markdown files changed. Skipping checks."
echo "HAS_CHANGES=false" >> "$GITHUB_OUTPUT"
else
echo "--- Changed Files to Scan ---"
echo "$CHANGED_FILES"
echo "-----------------------------"
FILES_QUOTED=$(echo "$CHANGED_FILES" | sed 's/^/"/;s/$/"/' | tr '\n' ' ')
# Use EOF to write multiline or long strings to GITHUB_OUTPUT
echo "HAS_CHANGES=true" >> "$GITHUB_OUTPUT"
echo "CHECK_FILES<<EOF" >> "$GITHUB_OUTPUT"
echo "$FILES_QUOTED" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
fi
- name: Restore lychee cache
if: steps.changed-files.outputs.HAS_CHANGES == 'true'
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: .lycheecache
key: cache-lychee-${{ github.sha }}
restore-keys: cache-lychee-
- name: Link Checker
id: lychee-check
if: steps.changed-files.outputs.HAS_CHANGES == 'true'
uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2
continue-on-error: true
with:
args: >
--quiet
--no-progress
--cache
--max-cache-age 1d
--exclude '^neo4j\+.*' --exclude '^bolt://.*'
--max-retries 10
${{ steps.changed-files.outputs.CHECK_FILES }}
output: lychee-report.md
format: markdown
fail: true
jobSummary: false
debug: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare Report
if: steps.changed-files.outputs.HAS_CHANGES == 'true' && steps.lychee-check.outcome == 'failure'
run: |
echo "## Link Resolution Note" > full-report.md
echo "Local links and directory changes work differently on GitHub than on the docsite. You must ensure fixes pass the **GitHub check** and also work with **\`hugo server\`**." >> full-report.md
echo "See [Link Checking and Fixing with Lychee](https://github.com/googleapis/mcp-toolbox/blob/main/DEVELOPER.md#link-checking-and-fixing-with-lychee) for more details." >> full-report.md
echo "" >> full-report.md
sed -E '/(Redirect|Redirects per input)/d' lychee-report.md >> full-report.md
- name: Display Failure Report
# Run this ONLY if the link checker failed
if: steps.lychee-check.outcome == 'failure'
run: |
# We can now simply output the prepared file to the job summary
cat full-report.md >> $GITHUB_STEP_SUMMARY
# Fail the job
exit 1
@@ -0,0 +1,75 @@
# 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: Link Checks
on:
schedule:
- cron: '0 0 * * 1'
jobs:
linkChecker:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Checkout Repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0
persist-credentials: false
- name: Link Checker
id: lychee-check
uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2
continue-on-error: true
with:
args: >
--quiet
--no-progress
--exclude '^neo4j\+.*' --exclude '^bolt://.*'
README.md
docs/
output: lychee-report.md
format: markdown
fail: true
jobSummary: false
debug: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare Report
run: |
echo "## Link Resolution Note" > full-report.md
echo "Local links and directory changes work differently on GitHub than on the docsite.You must ensure fixes pass the **GitHub check** and also work with **\`hugo server\`**." >> full-report.md
echo "See [Link Checking and Fixing with Lychee](https://github.com/googleapis/mcp-toolbox/blob/main/DEVELOPER.md#link-checking-and-fixing-with-lychee) for more details." >> full-report.md
echo "" >> full-report.md
sed -E '/(Redirect|Redirects per input)/d' lychee-report.md >> full-report.md
- name: Create Issue From File
if: steps.lychee-check.outcome == 'failure'
uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710 # v6
with:
title: Link Checker Report
content-filepath: full-report.md
labels: |
priority: p2
type: process
- name: Display Failure Report
# Run this ONLY if the link checker failed
if: steps.lychee-check.outcome == 'failure'
run: |
# We can now simply output the prepared file to the job summary
cat full-report.md >> $GITHUB_STEP_SUMMARY
# Fail the job
exit 1
+58
View File
@@ -0,0 +1,58 @@
# Copyright 2024 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:
pull_request:
paths:
- "**"
- "!docs/**"
- "!**.md"
- "!.github/**"
- ".github/workflows/lint.yaml"
# Declare default permissions as read only.
permissions: read-all
jobs:
lint:
name: lint
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: 'read'
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: 'go.mod'
- name: >
Verify go mod tidy. If you're reading this and the check has
failed, run `goimports -w . && go mod tidy && golangci-lint run`
run: |
go mod tidy && git diff --exit-code
- name: golangci-lint
uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee # v9.2.1
with:
version: latest
args: --timeout 10m
+42
View File
@@ -0,0 +1,42 @@
# 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
# zizmor flags pull_request_target, but this fallback workflow never checks out code.
on: # zizmor: ignore[dangerous-triggers]
pull_request:
paths:
- "docs/**"
- "**.md"
- ".github/**"
- "!.github/workflows/lint.yaml"
pull_request_target:
paths:
- "docs/**"
- "**.md"
- ".github/**"
- "!.github/workflows/lint.yaml"
permissions: read-all
jobs:
lint:
name: lint
runs-on: ubuntu-latest
steps:
- name: Skip Lint
run: |
echo "Skipping lint for documentation/config-only changes."
echo "This job exists to satisfy the required status check."
+109
View File
@@ -0,0 +1,109 @@
# 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: Weekly Server Tier Assessment
on:
schedule:
- cron: '0 6 * * 0' # Runs at 6 AM every Sunday
workflow_dispatch:
permissions:
contents: read
issues: write
jobs:
tier-check:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
persist-credentials: false
- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: 'go.mod'
- name: Start MCP Toolbox Server
run: |
go build -o toolbox
./toolbox --allowed-hosts localhost --config tests/conformance/tools.yaml &
sleep 5 # Give the server a moment to bind to port 5000
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: "22"
- name: Run Server Tier Assessment
id: assessment
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
npx --yes @modelcontextprotocol/conformance@latest tier-check --repo ${{ github.repository }} --output markdown --conformance-server-url "http://localhost:5000/mcp" > tier_report.txt
continue-on-error: true
- name: Report Tier Status
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const report = fs.readFileSync('tier_report.txt', 'utf8');
const title = '[Dashboard: Conformance] Weekly Server Tier Assessment Failure';
const prevIssues = await github.rest.issues.listForRepo({
...context.repo,
state: 'open'
});
// Find any open issue matching the exact dashboard title
let existingIssue = prevIssues.data.find(issue => issue.title === title);
if (report.includes('Tier Assessment: Tier 1')) {
console.log('Tier 1 achieved! Closing open issue if it exists.');
if (existingIssue) {
await github.rest.issues.createComment({
...context.repo,
issue_number: existingIssue.number,
body: "The weekly Server Tier Assessment confirms we are now **Tier 1**! Closing this issue."
});
await github.rest.issues.update({
...context.repo,
issue_number: existingIssue.number,
state: 'closed'
});
}
return;
}
const body = `The weekly Server Tier Assessment found gaps preventing Tier 1 status:\n\n${report}\n\nPlease fix these operational gaps.`;
if (existingIssue) {
console.log(`Found previous issue ${existingIssue.html_url}, updating body`);
await github.rest.issues.update({
...context.repo,
issue_number: existingIssue.number,
body: body
});
} else {
console.log('No previous issue found, creating one');
await github.rest.issues.create({
...context.repo,
title: title,
body: body
});
}
+78
View File
@@ -0,0 +1,78 @@
# 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.
# 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: Publish to MCP Registry
on:
push:
tags: ["v*"] # Triggers on version tags like v1.0.0
# allow manual triggering with no inputs required
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC authentication
contents: read
steps:
- name: Delay workflow execution
run: sleep 1200
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
persist-credentials: false
- name: Wait for image in Artifact Registry
shell: bash
run: |
MAX_ATTEMPTS=10
VERSION=$(jq -r '.version' server.json)
REGISTRY_URL="https://us-central1-docker.pkg.dev/v2/database-toolbox/toolbox/toolbox/manifests/${VERSION}"
# initially sleep time to wait for the version release
sleep 3m
for i in $(seq 1 ${MAX_ATTEMPTS}); do
echo "Attempt $i: Checking for image ${REGISTRY_URL}..."
# Use curl to check the manifest header
# Using -I to fetch headers only, -s silent, -f fail fast on errors.
curl -Isf "${REGISTRY_URL}" > /dev/null
if [ $? -eq 0 ]; then
echo "✅ Image found! Continuing to next steps."
exit 0
else
echo "❌ Image not found (likely 404 error) on attempt $i."
if [ $i -lt ${MAX_ATTEMPTS} ]; then
echo "Sleeping for 5 minutes before next attempt..."
sleep 2m
else
echo "Maximum attempts reached. Image not found."
exit 1
fi
fi
done
- name: Install MCP Publisher
run: |
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
- name: Login to MCP Registry
run: ./mcp-publisher login github-oidc
- name: Publish to MCP Registry
run: ./mcp-publisher publish
+70
View File
@@ -0,0 +1,70 @@
# 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: pypi
on:
pull_request:
paths:
- "pypi/**"
- ".github/workflows/pypi.yml"
permissions: read-all
jobs:
build-and-verify:
name: build & verify wheel
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
- name: Setup Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
- name: Install build tooling
run: python -m pip install --upgrade pip build pytest
- name: Build toolbox binary and stage it for the wheel
# setup.py now expects the binary pre-staged in src/toolbox_server/bin/
# instead of downloading from GCS at build time.
run: |
go build -o pypi/src/toolbox_server/bin/toolbox .
chmod +x pypi/src/toolbox_server/bin/toolbox
- name: Build wheel
working-directory: pypi
env:
# setup.py requires this explicitly; matches the Linux x86_64 binary
# built above. Release pipeline sets it per-platform in a loop.
TOOLBOX_PLATFORM: manylinux2014_x86_64
run: python -m build --wheel
- name: Install built wheel
run: pip install pypi/dist/*.whl
- name: Verify console script runs
run: toolbox-server --help
- name: Run wrapper tests
run: pytest pypi/tests/ -v
+29
View File
@@ -0,0 +1,29 @@
# 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.
# 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: Schedule Reporter
on:
schedule:
- cron: '0 6 * * *' # Runs at 6 AM every morning
jobs:
run_reporter:
permissions:
issues: 'write'
checks: 'read'
contents: 'read'
uses: ./.github/workflows/cloud_build_failure_reporter.yml
with:
trigger_names: "toolbox-test-nightly,toolbox-test-on-merge,toolbox-continuous-release"
+39
View File
@@ -0,0 +1,39 @@
# Copyright 2024 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: Sync Labels
on:
push:
branches:
- main
# Declare default permissions as read only.
permissions: read-all
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: 'read'
issues: 'write'
pull-requests: 'write'
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: micnncim/action-label-syncer@3abd5ab72fda571e69fffd97bd4e0033dd5f495c # v1.3.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
manifest: .github/labels.yaml
+95
View File
@@ -0,0 +1,95 @@
# Copyright 2024 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: tests
on:
push:
branches:
- "main"
paths:
- "**"
- "!docs/**"
- "!**.md"
- "!.github/**"
- ".github/workflows/tests.yaml"
pull_request:
paths:
- "**"
- "!docs/**"
- "!**.md"
- "!.github/**"
- ".github/workflows/tests.yaml"
# Declare default permissions as read only.
permissions: read-all
jobs:
integration:
name: unit tests
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
fail-fast: false
permissions:
contents: "read"
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: 'go.mod'
- name: Install dependencies
run: go get .
- name: Build
run: go build -v ./...
- name: Run tests with coverage
if: ${{ runner.os == 'Linux' }}
env:
GOTOOLCHAIN: go1.25.0+auto
run: |
source_dir="./internal/sources/*"
tool_dir="./internal/tools/*"
prompt_dir="./internal/prompts/*"
auth_dir="./internal/auth/*"
int_test_dir="./tests/*"
included_packages=$(go list ./... | grep -v -e "$source_dir" -e "$tool_dir" -e "$prompt_dir" -e "$auth_dir" -e "$int_test_dir")
go test -race -cover -coverprofile=coverage.out -v $included_packages
go test -race -v ./internal/sources/... ./internal/tools/... ./internal/prompts/... ./internal/auth/...
- name: Run tests without coverage
if: ${{ runner.os != 'Linux' }}
run: |
go test -race -v ./internal/... ./cmd/...
- name: Check coverage
if: ${{ runner.os == 'Linux' }}
run: |
FILE_TO_EXCLUDE="github.com/googleapis/mcp-toolbox/internal/server/config.go"
ESCAPED_PATH=$(echo "$FILE_TO_EXCLUDE" | sed 's/\//\\\//g; s/\./\\\./g')
sed -i "/^${ESCAPED_PATH}:/d" coverage.out
total_coverage=$(go tool cover -func=coverage.out | grep "total:" | awk '{print $3}')
echo "Total coverage: $total_coverage"
coverage_numeric=$(echo "$total_coverage" | sed 's/%//')
if (( $(echo "$coverage_numeric < 40" | bc -l) )); then
echo "Coverage failure: total coverage($total_coverage) is below 40%."
exit 1
fi
+54
View File
@@ -0,0 +1,54 @@
# 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: tests
# zizmor flags pull_request_target, but this fallback workflow never checks out code.
on: # zizmor: ignore[dangerous-triggers]
push:
branches:
- "main"
paths:
- "docs/**"
- "**.md"
- ".github/**"
- "!.github/workflows/tests.yaml"
pull_request:
paths:
- "docs/**"
- "**.md"
- ".github/**"
- "!.github/workflows/tests.yaml"
pull_request_target:
types: [labeled]
paths:
- "docs/**"
- "**.md"
- ".github/**"
- "!.github/workflows/tests.yaml"
permissions: read-all
jobs:
integration:
name: unit tests
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
steps:
- name: Skip Tests
run: |
echo "Skipping unit tests for documentation/config-only changes."
echo "This job exists to satisfy the required status check."
+29
View File
@@ -0,0 +1,29 @@
# direnv
.envrc
# vscode
.vscode/
# idea
.idea/
# npm
node_modules
# hugo
.hugo/public/
.hugo/resources/_gen
.hugo/static/pagefind/
.hugo_build.lock
# coverage
.coverage
# python
__pycache__/
# executable
mcp-toolbox
toolbox
toolbox.exe
**/test.db
+3
View File
@@ -0,0 +1,3 @@
[submodule "docs2/themes/godocs"]
path = docs2/themes/godocs
url = https://github.com/themefisher/godocs.git
+35
View File
@@ -0,0 +1,35 @@
# Copyright 2024 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.
version: "2"
linters:
enable:
- errcheck
- govet
- ineffassign
- staticcheck
- unused
exclusions:
presets:
- std-error-handling
formatters:
enable:
- goimports
settings:
gofmt:
rewrite-rules:
- pattern: interface{}
replacement: any
- pattern: a[b:len(a)]
replacement: a[b:]
+1
View File
@@ -0,0 +1 @@
defaults
+5
View File
@@ -0,0 +1,5 @@
+++
date = '{{ .Date }}'
draft = true
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
+++
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

+22
View File
@@ -0,0 +1,22 @@
/* ==========================================================================
PROJECT SCSS HUB
Custom theme overrides and UI components.
========================================================================== */
@import 'components/typography';
@import 'components/layout';
@import 'components/header';
@import 'components/sidebar';
@import 'components/callouts';
@import 'components/secondary_nav';
@import 'components/search';
@import 'td/code-dark';
// Make tabs scrollable horizontally instead of wrapping
.nav-tabs {
flex-wrap: nowrap;
white-space: nowrap;
overflow-x: auto;
overflow-y: hidden;
}
@@ -0,0 +1,2 @@
$primary: #80a7e9;
$secondary: #4484f4;
@@ -0,0 +1,26 @@
/* ==========================================================================
CALLOUTS & NOTICES
Styling for alerts, tips, and admonition blocks.
========================================================================== */
.td-content .alert code,
.td-content .notice code,
.td-content .admonition code {
background-color: #ffffff !important;
color: rgba(32, 33, 36, 0.95) !important;
padding: 0.2rem 0.4rem !important;
border-radius: 4px !important;
border: 1px solid rgba(0, 0, 0, 0.05) !important;
font-weight: 500 !important;
}
html[data-bs-theme="dark"] .td-content .alert code,
body.dark .td-content .alert code,
html[data-bs-theme="dark"] .td-content .notice code,
body.dark .td-content .notice code,
html[data-bs-theme="dark"] .td-content .admonition code,
body.dark .td-content .admonition code {
background-color: rgba(255, 255, 255, 0.15) !important;
color: #ffffff !important;
border-color: rgba(255, 255, 255, 0.1) !important;
}
+213
View File
@@ -0,0 +1,213 @@
/* ==========================================================================
HEADER & NAVIGATION
Primary navbar, secondary tabbed nav, utilities, and mobile layouts.
========================================================================== */
/* Main Header Structure */
header { position: sticky !important; top: 0; z-index: 1060; width: 100%; background-color: var(--bs-body-bg, #ffffff); transform: translateZ(0); }
header, .td-navbar { z-index: 1060 !important; }
.td-navbar {
position: relative !important; width: 100% !important;
/* Header Utility Buttons */
.navbar-nav {
gap: 0.35rem; align-items: center;
li.td-light-dark-menu > button,
li.nav-item:has(a[href*="github"]) > a,
li.nav-item:has(a[href*="Releases"], .dropdown-toggle):not(.td-light-dark-menu) > a {
background-color: transparent !important; background-image: none !important; border: 1.5px solid transparent !important;
box-shadow: none !important; outline: none !important; color: rgba(255, 255, 255, 0.85) !important;
font-weight: 500 !important; font-size: 0.95rem !important; text-decoration: none !important; white-space: nowrap;
display: flex !important; align-items: center !important; justify-content: center !important;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
i, svg { font-size: 1.15rem !important; fill: rgba(255, 255, 255, 0.85) !important; transition: all 0.2s ease-in-out !important; }
&:hover {
background-color: #ffffff !important; color: $primary !important;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1), 0 0 0 2px rgba(255, 255, 255, 0.2) !important;
transform: translateY(-1px) !important; opacity: 1;
i, svg { fill: $primary !important; opacity: 1; }
}
&:active { transform: translateY(0px) !important; background-color: rgba(255, 255, 255, 0.15) !important; }
}
li.nav-item:has(a[href*="github"]) > a,
li.nav-item:has(a[href*="Releases"], .dropdown-toggle):not(.td-light-dark-menu) > a {
border-radius: 50px !important; padding: 0.35rem 1.1rem !important;
i, svg { margin-right: 0.4rem !important; }
}
li.nav-item:has(a[href*="Releases"], .dropdown-toggle):not(.td-light-dark-menu) > a.dropdown-toggle::after {
display: inline-block !important; margin-left: 0.5rem !important; vertical-align: middle !important;
border-top: 0.4em solid; border-right: 0.4em solid transparent; border-bottom: 0; border-left: 0.4em solid transparent;
transition: transform 0.2s ease;
}
li.nav-item.show > a.dropdown-toggle::after,
a.dropdown-toggle.show::after { transform: rotate(180deg); }
li.td-light-dark-menu > button {
width: 38px !important; height: 38px !important; padding: 0 !important; border-radius: 50% !important;
&::after { display: none !important; }
i, svg { margin: 0 !important; width: 1.15rem !important; height: 1.15rem !important; }
}
}
/* Universal Dropdowns */
.dropdown-menu {
z-index: 1065 !important; margin-top: 0.6rem !important; background-color: #ffffff !important;
border: 1px solid rgba(0, 0, 0, 0.08) !important; border-radius: 12px !important;
box-shadow: 0 12px 34px rgba(0, 0, 0, 0.15) !important; padding: 0.6rem !important;
min-width: 200px !important; max-height: 60vh !important; overflow-y: auto !important;
scrollbar-width: thin; scrollbar-color: rgba(0, 0, 0, 0.2) rgba(0, 0, 0, 0.05);
animation: dropdownFadeIn 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards !important; transform-origin: top right;
&::-webkit-scrollbar { width: 8px; display: block !important; }
&::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.05) !important; border-radius: 10px; margin: 5px; }
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2) !important; border-radius: 10px; border: 2px solid transparent; background-clip: content-box;
&:hover { background: rgba(0, 0, 0, 0.35) !important; background-clip: content-box; }
}
}
.dropdown-item {
border-radius: 8px !important; padding: 0.6rem 1rem !important; color: #495057 !important;
margin-bottom: 4px !important; transition: all 0.2s ease !important; display: flex !important;
align-items: center !important; font-weight: 500 !important;
i, svg { margin-right: 12px !important; width: 16px !important; text-align: center; }
&:hover, &:focus {
background-color: rgba(68, 132, 244, 0.08) !important; color: #4484f4 !important; transform: translateX(4px) !important;
}
&:not(.active-version):not(.active):not(:has(i)):not(:has(svg)) {
opacity: 0.75 !important; font-weight: 400 !important; transition: opacity 0.2s ease !important;
&:hover { opacity: 1 !important; }
}
&.active-version, &.active {
background-color: rgba(68, 132, 244, 0.08) !important; color: #4484f4 !important; font-weight: 600 !important;
position: relative; padding-left: 2rem !important; border-left: 4px solid #4484f4 !important;
&:hover { background-color: rgba(68, 132, 244, 0.12) !important; transform: translateX(4px) !important; }
}
}
}
/* Tabbed Secondary Navbar */
#secondary-nav {
position: fixed; top: 4rem; left: 0; right: 0; height: 52px; z-index: 1055 !important;
background: linear-gradient(rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.08)), $primary;
box-shadow: inset 0 4px 12px rgba(0, 0, 0, 0.15); border-bottom: 1px solid rgba(0, 0, 0, 0.1);
display: flex; align-items: center; position: relative !important; top: 0 !important;
width: 100%; max-width: 100vw; box-sizing: border-box;
.container-fluid {
display: flex; align-items: center; width: 100%; height: 100%; overflow-x: auto !important; flex-wrap: nowrap !important;
scrollbar-width: none; -ms-overflow-style: none; -webkit-overflow-scrolling: touch;
&::-webkit-scrollbar { display: none; }
}
.sec-nav-list {
display: flex; margin: 0; padding: 0 1.5rem; list-style: none; gap: 28px;
align-items: center; height: 100%; width: auto; flex-shrink: 0 !important; flex-wrap: nowrap; overflow: visible !important;
}
.sec-nav-icons { display: flex; gap: 1.5rem; list-style: none; margin: 0 0 0 auto; padding: 0 1.5rem 0 2rem; align-items: center; flex-shrink: 0 !important; }
li { height: 100%; display: flex; align-items: center; }
a {
color: rgba(255, 255, 255, 0.7) !important; font-weight: 500; text-decoration: none;
font-size: 14.5px; letter-spacing: 0.2px; height: 100%; display: flex; align-items: center;
padding: 0 2px; border-bottom: 2px solid transparent; margin-bottom: 0; transition: color 0.15s ease, border-color 0.15s ease; white-space: nowrap;
&:hover { color: #ffffff !important; }
&.active { color: #ffffff !important; font-weight: 600; border-bottom: 2px solid #ffffff; }
}
}
/* Dark Mode Overrides */
html[data-bs-theme="dark"], body.dark {
.td-navbar .navbar-nav {
li.td-light-dark-menu > button,
li.nav-item:has(a[href*="github"]) > a,
li.nav-item:has(a[href*="Releases"], .dropdown-toggle):not(.td-light-dark-menu) > a {
background-color: transparent !important; box-shadow: none !important; color: #e8eaed !important;
i, svg { fill: #e8eaed !important; }
&:hover {
background-color: #303134 !important; color: #f8f9fa !important; border-color: #8ab4f8 !important;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3), 0 0 0 2px rgba(138, 180, 248, 0.15) !important;
i, svg { fill: #f8f9fa !important; }
}
}
}
.td-navbar .dropdown-menu {
background-color: #202124 !important; border-color: rgba(255, 255, 255, 0.12) !important;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6) !important; scrollbar-color: rgba(255, 255, 255, 0.2) rgba(255, 255, 255, 0.05);
}
.td-navbar .dropdown-item {
color: #e8eaed !important;
&:hover, &:focus { background-color: rgba(138, 180, 248, 0.12) !important; color: #8ab4f8 !important; }
&.active-version, &.active {
background-color: rgba(138, 180, 248, 0.12) !important; color: #8ab4f8 !important; border-left-color: #8ab4f8 !important;
&::before { color: #8ab4f8; }
&:hover { background-color: rgba(138, 180, 248, 0.18) !important; }
}
}
#secondary-nav {
background: linear-gradient(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15)), $primary; box-shadow: inset 0 4px 15px rgba(0, 0, 0, 0.3); border-bottom: 1px solid rgba(255, 255, 255, 0.05);
a { color: rgba(255, 255, 255, 0.7) !important; &:hover { color: #ffffff !important; } &.active { color: #ffffff !important; border-bottom-color: #ffffff; } }
}
}
/* Desktop Adjustments */
@media (min-width: 992px) {
.td-navbar .custom-pagefind-wrapper {
position: relative !important; left: 50% !important; top: 50% !important; transform: translate(-50%, -50%) !important;
width: 100% !important; max-width: 500px !important; margin: 0 !important; z-index: 1060 !important;
transition: opacity 0.15s ease-out, transform 0.15s ease-out, box-shadow 0.15s ease-out !important;
}
.td-navbar .pagefind-ui__drawer { left: 0 !important; right: 0 !important; width: 100% !important; max-width: 100% !important; }
}
/* Mobile Layout & Scaling */
@media (max-width: 991.98px) {
header, .td-navbar { position: relative !important; }
.td-navbar { height: auto !important; padding-bottom: 0.5rem !important; flex-wrap: nowrap !important; justify-content: space-between !important; min-height: 4rem !important; }
header .navbar-toggler { display: inline-flex !important; z-index: 1080 !important; margin-left: auto !important; }
.td-navbar .container-fluid { display: flex !important; flex-wrap: wrap !important; justify-content: space-between !important; align-items: center !important; }
.td-navbar .navbar-nav, .td-navbar .navbar-toggler { flex-direction: row !important; order: 2 !important; width: auto !important; margin-left: auto !important; }
.td-navbar .custom-pagefind-wrapper { display: none !important; }
.td-navbar .dropdown-menu {
background-color: #ffffff !important; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15) !important; border: 1px solid rgba(0, 0, 0, 0.08) !important;
min-width: 180px !important; padding: 0.5rem !important; position: absolute !important; z-index: 1065 !important;
}
.td-navbar .navbar-nav .dropdown-menu { right: 0 !important; left: auto !important; margin-top: 0.5rem !important; }
.td-navbar .dropdown-item { padding: 0.6rem 1rem !important; font-size: 0.95rem !important; }
#secondary-nav { position: relative !important; top: 0 !important; margin-top: 0 !important; height: auto !important; min-height: 52px; padding: 10px 0 !important; z-index: 1050 !important; clear: both !important; }
#secondary-nav .container-fluid { padding: 0 15px !important; }
#secondary-nav .sec-nav-list { padding: 0 !important; gap: 12px !important; }
#secondary-nav .sec-nav-icons { margin-left: 20px !important; padding: 0 !important; }
#secondary-nav li { height: auto !important; }
#secondary-nav a { height: 34px !important; padding: 0 12px !important; background: rgba(0, 0, 0, 0.1); border-radius: 20px; border-bottom: none !important; }
#secondary-nav a.active { background: #ffffff !important; color: $primary !important; }
}
@media (max-width: 767.98px) {
.td-navbar .navbar-nav { gap: 0.25rem !important; }
.td-navbar .navbar-nav li.nav-item:has(a[href*="github"], a[href*="Releases"]) span { display: none !important; }
.td-navbar .navbar-nav li.nav-item:has(a[href*="github"]) > a,
.td-navbar .navbar-nav li.nav-item:has(a[href*="Releases"], .dropdown-toggle):not(.td-light-dark-menu) > a { padding: 0.25rem 0.6rem !important; font-size: 0.85rem !important; }
.td-navbar .navbar-nav li.nav-item:has(a[href*="github"]) > a i,
.td-navbar .navbar-nav li.nav-item:has(a[href*="github"]) > a svg,
.td-navbar .navbar-nav li.nav-item:has(a[href*="Releases"], .dropdown-toggle):not(.td-light-dark-menu) > a i,
.td-navbar .navbar-nav li.nav-item:has(a[href*="Releases"], .dropdown-toggle):not(.td-light-dark-menu) > a svg { font-size: 1rem !important; margin-right: 0 !important; }
.td-navbar .navbar-nav a.dropdown-toggle::after { display: none !important; }
.td-navbar .navbar-nav li.td-light-dark-menu > button { width: 32px !important; height: 32px !important; }
.td-navbar .navbar-nav li.td-light-dark-menu > button i,
.td-navbar .navbar-nav li.td-light-dark-menu > button svg { width: 1rem !important; height: 1rem !important; }
#secondary-nav a { font-size: 13px !important; padding: 0 10px !important; height: 30px !important; }
#secondary-nav .sec-nav-list { gap: 8px !important; }
}
+27
View File
@@ -0,0 +1,27 @@
/* ==========================================================================
LAYOUT ALIGNMENT & STACKING CONTEXTS
Manages global spacing and z-index hierarchy.
========================================================================== */
/*Global Reset */
body {
padding-top: 0 !important;
}
/* Zero-Spacing Elements */
.td-main, .td-outer, .td-sidebar {
margin-top: 0 !important;
padding-top: 0 !important;
}
/* =Elements Requiring Top Padding */
main[role="main"], #td-section-nav, .td-sidebar-toc {
margin-top: 0 !important;
padding-top: 1.5rem !important;
}
/* Z-Index Hierarchy */
.td-main, .td-content, .td-sidebar-toc, .td-page-meta, .td-toc {
position: relative;
z-index: 10 !important;
}
+245
View File
@@ -0,0 +1,245 @@
/* ==========================================================================
PAGEFIND SEARCH STYLES & MODAL
========================================================================== */
:root {
--pagefind-ui-scale: 0.9;
--pagefind-ui-primary: #0d6efd;
}
@keyframes simpleSlideDown {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Base Modal Backdrop & Body State */
#global-search-backdrop {
position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
background-color: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
z-index: 2147483646 !important; /* Max value minus 1 */
opacity: 0; pointer-events: none; transition: none;
transition: opacity 0.1s ease-in-out;
&.active { opacity: 1; pointer-events: auto; }
}
body.global-search-active {
/* Explicitly target Docsy's layout structure to avoid blurring injected modals */
header, main, footer, .td-main, .td-outer, .td-sidebar, .td-sidebar-toc {
filter: blur(6px) !important;
opacity: 0.4 !important;
pointer-events: none !important;
transition: none !important;
}
}
/* Main Component Scoping */
body {
.custom-pagefind-wrapper {
position: relative;
z-index: 9999 !important;
/* Modal Pop-out State */
&.active-modal {
position: fixed !important;
top: 12vh !important;
left: 50% !important;
transform: translateX(-50%) !important;
width: 90vw !important;
max-width: 650px !important;
z-index: 2147483647 !important; /* Absolute max value possible */
margin: 0 !important;
padding: 0 !important;
.pagefind-ui__drawer {
position: absolute !important;
top: 100% !important;
left: 0 !important;
right: 0 !important;
width: 100% !important;
max-width: 100% !important;
max-height: 70vh !important;
z-index: 999999 !important;
animation: simpleSlideDown 0.2s ease-out forwards !important;
overflow-y: auto !important;
overscroll-behavior: contain !important;
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
box-shadow: none !important;
&::-webkit-scrollbar { width: 6px; }
&::-webkit-scrollbar-track { background: transparent; }
&::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.2); border-radius: 10px; }
}
}
}
/* Pagefind Internal Element Overrides */
.pagefind-ui__form {
--pagefind-ui-text: #ffffff !important;
position: relative !important;
box-sizing: border-box !important;
&:focus-within { --pagefind-ui-text: #4484f4 !important; }
&::before {
top: 50% !important; transform: translateY(-50%) !important; margin-top: 0 !important;
width: 18px !important; height: 18px !important; left: 14px !important;
}
}
.pagefind-ui__search-input {
background-color: rgba(0, 0, 0, 0.1) !important;
border: 1.5px solid transparent !important;
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.15) !important;
color: #ffffff !important; border-radius: 50px !important; font-weight: 400 !important;
transition: all 0.2s ease-in-out !important; height: 38px !important; font-size: 0.95rem !important;
padding-left: 40px !important; padding-right: 36px !important; box-sizing: border-box !important;
&::placeholder { color: #ffffff !important; opacity: 0.9 !important; }
&:focus {
background-color: #ffffff !important; border-color: #4484f4 !important; color: #212529 !important; outline: 0 !important;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1), 0 0 0 4px rgba(68, 132, 244, 0.15) !important;
&::placeholder { color: #6c757d !important; opacity: 1 !important; }
}
}
.pagefind-ui__search-clear {
position: absolute !important; top: 50% !important; transform: translateY(-50%) !important; right: 12px !important;
width: 24px !important; height: 24px !important; min-height: 24px !important; padding: 0 !important; margin: 0 !important;
background: transparent !important; border: none !important; outline: none !important; box-shadow: none !important;
font-size: 0 !important; color: transparent !important; display: flex !important; align-items: center !important;
justify-content: center !important; overflow: hidden !important; cursor: pointer !important; z-index: 99 !important;
&::after {
content: "\00d7" !important; font-size: 22px !important; font-weight: 300 !important;
line-height: 1 !important; color: var(--pagefind-ui-text) !important; display: block !important;
}
}
.pagefind-ui__drawer {
position: absolute !important; top: 100% !important; width: 500px !important;
max-width: calc(100vw - 2rem) !important; margin-top: 12px !important; z-index: 99999 !important;
background-color: #ffffff !important; border: 1px solid #dee2e6 !important; border-radius: 12px !important;
padding: 1.5rem !important;
max-height: 80vh !important; overflow-y: auto !important; overscroll-behavior: contain !important;
box-sizing: border-box !important;
overflow-y: auto !important;
overflow-x: hidden !important;
overflow-wrap: break-word !important;
word-wrap: break-word !important;
word-break: break-word !important;
white-space: normal !important;
mark {
background-color: rgba(68, 132, 244, 0.15) !important; color: #1a73e8 !important; padding: 0 3px !important;
border-radius: 3px !important; font-weight: 600 !important; box-shadow: none !important;
}
}
.pagefind-ui__result-title, .pagefind-ui__result-excerpt, .pagefind-ui__message {
color: #212529 !important;
}
.pagefind-ui__button { margin-top: 1rem !important; }
.pagefind-ui__hidden { display: none !important; }
}
/* =======================================================
RESPONSIVE LAYOUTS
======================================================= */
body {
// DESKTOP SEARCH BAR
@media (min-width: 992px) {
.td-navbar .custom-pagefind-wrapper:not(.active-modal) {
position: absolute !important;
left: 50% !important;
top: 50% !important;
transform: translate(-50%, -50%) !important;
width: 100% !important;
max-width: 500px !important;
margin: 0 !important;
z-index: 1060 !important;
}
.td-navbar .pagefind-ui__drawer { left: 0 !important; right: 0 !important; width: 100% !important; max-width: 100% !important; }
.td-sidebar .td-search, .td-sidebar .custom-pagefind-wrapper { display: none !important; }
}
// MOBILE NATIVE STACK
@media (max-width: 991.98px) {
.td-navbar .custom-pagefind-wrapper, .td-navbar .container-fluid > div.d-none.d-lg-block { display: none !important; }
.td-sidebar .td-search, .td-sidebar .custom-pagefind-wrapper:not(.active-modal) {
display: block !important; width: 100% !important; max-width: 100% !important; margin-top: 1rem !important; margin-bottom: 1rem !important;
}
}
// LEFT SIDEBAR TOGGLE ALIGNMENT & MOBILE MODAL RESIZING
@media (max-width: 767.98px) {
.td-sidebar__search {
display: flex !important; flex-direction: row !important; align-items: center !important; justify-content: space-between !important;
width: 100% !important; margin-top: 60px !important; margin-bottom: 1rem !important; padding: 0 15px !important; z-index: 10 !important;
.custom-pagefind-wrapper:not(.active-modal) { width: 75% !important; flex: 0 0 75% !important; margin: 0 !important; }
}
.custom-pagefind-wrapper.active-modal {
top: 15px !important;
width: calc(100vw - 30px) !important;
.pagefind-ui__drawer {
max-height: calc(100dvh - 100px) !important;
}
}
.td-sidebar__toggle {
display: flex !important; align-items: center !important; justify-content: flex-end !important; width: 20% !important;
height: auto !important; background-color: transparent !important; border: none !important; box-shadow: none !important;
color: inherit !important; font-size: 1rem !important; padding: 0 !important;
&::after { content: none !important; }
}
}
}
/* =======================================================
DARK MODE STYLING
======================================================= */
html[data-bs-theme="dark"], body.td-dark {
#global-search-backdrop { background-color: rgba(0, 0, 0, 0.8); }
.pagefind-ui__form { --pagefind-ui-text: #ffffff !important; &:focus-within { --pagefind-ui-text: #8ab4f8 !important; } }
.pagefind-ui__search-input {
background-color: rgba(0, 0, 0, 0.2) !important; border-color: transparent !important;
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.3) !important; color: #ffffff !important;
&::placeholder { color: #ffffff !important; opacity: 0.9 !important; }
&:focus {
background-color: #303134 !important; border-color: #8ab4f8 !important; color: #f8f9fa !important;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3), 0 0 0 4px rgba(138, 180, 248, 0.15) !important;
&::placeholder { color: #e8eaed !important; opacity: 1 !important; }
}
}
.pagefind-ui__drawer {
background-color: #202124 !important; border-color: #3c4043 !important;
box-shadow: none !important;
mark { background-color: rgba(138, 180, 248, 0.2) !important; color: #8ab4f8 !important; }
}
.pagefind-ui__result-title, .pagefind-ui__result-excerpt, .pagefind-ui__message { color: #e8eaed !important; }
.pagefind-ui__button { color: #e8eaed !important; border-color: #5f6368 !important; background-color: #303134 !important; }
// Mobile sidebar toggle dark mode
@media (max-width: 767.98px) {
.td-sidebar__toggle { color: #e8eaed !important; background-color: transparent !important; border: none !important; &:hover { background-color: transparent !important; opacity: 0.8 !important; } }
}
}
@@ -0,0 +1,43 @@
/* ==========================================================================
Secondary Navigation Header Styles
========================================================================== */
/* Base Layout */
.sec-nav-container { display: flex; align-items: center; height: 100%; width: 100%; overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none; }
.sec-nav-container::-webkit-scrollbar { display: none; }
.sec-nav-list { display: flex; align-items: center; list-style: none; padding: 0; margin: 0; gap: 1.25rem; flex-shrink: 0; }
.sec-nav-icons { display: flex; gap: 1.5rem; list-style: none; margin: 0 0 0 auto; padding: 0 1rem 0 2rem; align-items: center; flex-shrink: 0; }
/* Typography & Links */
.sec-nav-item-left { display: flex; align-items: center; gap: 0.4rem; font-size: 0.95rem; font-weight: 500; color: rgba(255, 255, 255, 0.75) !important; text-decoration: none; transition: all 0.2s; white-space: nowrap; height: 100%; border-bottom: 2px solid transparent; padding-bottom: 2px; }
.sec-nav-item-right { display: flex; align-items: center; gap: 0.5rem; font-size: 0.95rem; font-weight: 700; color: rgba(255, 255, 255, 0.85) !important; text-decoration: none; transition: all 0.2s; white-space: nowrap; }
/* Active States */
.sec-nav-list a:hover, .sec-nav-list a.active { color: #ffffff !important; opacity: 1; }
.sec-nav-list a.active { font-weight: 600; border-bottom: 2px solid #ffffff !important; }
.sec-nav-icons a:hover { color: #ffffff !important; opacity: 1; }
/* Icons */
.pop-icon { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 8px; font-size: 1rem; transition: opacity 0.2s ease; }
.pop-icon.fa-discord { background: #5865F2 !important; color: #ffffff !important; box-shadow: 0 4px 10px rgba(88, 101, 242, 0.35) !important; }
.pop-icon.fa-medium { background: #000000 !important; color: #ffffff !important; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25) !important; }
.sec-nav-icons a:hover .pop-icon { opacity: 0.85 !important; }
/* Dark Mode Overrides */
html[data-bs-theme="dark"] .sec-nav-item-left { color: rgba(255, 255, 255, 0.75) !important; }
html[data-bs-theme="dark"] .sec-nav-item-right { color: rgba(255, 255, 255, 0.85) !important; }
html[data-bs-theme="dark"] .sec-nav-list a.active { color: #ffffff !important; background-color: transparent !important; border-bottom: 2px solid #ffffff !important; font-weight: 600 !important; }
html[data-bs-theme="dark"] .pop-icon.fa-discord { background: #5865F2 !important; color: #ffffff !important; box-shadow: 0 4px 10px rgba(88, 101, 242, 0.35) !important; }
html[data-bs-theme="dark"] .pop-icon.fa-medium { background: #f8f9fa !important; color: #202124 !important; box-shadow: 0 4px 10px rgba(255, 255, 255, 0.15) !important; }
html[data-bs-theme="dark"] .dropdown-item.active-version { color: #8ab4f8 !important; background-color: rgba(138, 180, 248, 0.15) !important; border-left-color: #8ab4f8 !important; }
/* Responsive Layout */
@media (max-width: 991.98px) {
.sec-nav-icons { margin-left: 0; padding-left: 1rem; }
.sec-nav-list { gap: 1rem; }
.sec-nav-item-left, .sec-nav-item-right { font-size: 0.9rem; }
html[data-bs-theme="dark"] #secondary-nav .sec-nav-list a.active,
body.dark #secondary-nav .sec-nav-list a.active {
color: #4484f4 !important; background-color: #ffffff !important; border-bottom: none !important;
}
}
+164
View File
@@ -0,0 +1,164 @@
/* ==========================================================================
SIDEBARS (LEFT NAVIGATION & RIGHT TOC)
Handles positioning, tree navigation, and page metadata.
========================================================================== */
/* =================================================================
LAYOUT & SCROLLING
================================================================= */
@media (min-width: 768px) {
.td-sidebar, .td-sidebar-toc {
position: sticky !important;
top: calc(4rem + 52px) !important;
height: calc(100vh - 4rem - 52px) !important;
overflow-y: auto !important;
padding-bottom: 2rem !important;
scrollbar-width: none; -ms-overflow-style: none;
&::-webkit-scrollbar { display: none; }
}
.td-sidebar-toc .td-toc, .td-sidebar-toc .td-page-meta {
position: static !important; top: auto !important;
height: auto !important; overflow-y: visible !important;
}
}
/* =================================================================
LEFT SIDEBAR NAVIGATION
================================================================= */
.td-sidebar__search {
display: none !important; padding: 0 !important; margin: 0 !important;
height: 0 !important; border: none !important;
}
.td-sidebar-nav, #td-section-nav { margin-top: 1.5rem !important; padding-top: 0 !important; }
#td-section-nav {
.ul-1 > li:not(.active-path), > .ul-0 > li > a.tree-root { display: none !important; }
> .ul-0 > li > ul.ul-1 { margin-top: 0 !important; padding-top: 0 !important; padding-left: 0 !important; margin-left: -1rem !important; }
.ul-1 > li.active-path {
> a, > label, > input { display: none !important; }
> ul { padding-left: 0 !important; margin-top: 0 !important; margin-left: -1rem !important; }
> ul > li > a, > ul > li > label > a { padding-left: 0 !important; }
}
}
.td-sidebar .form-control {
font-family: var(--bs-font-sans-serif) !important; border: 1px solid transparent;
background-color: #f1f3f4; border-radius: 8px; padding: 0.5rem 1rem;
transition: all 0.2s ease; font-size: 0.9rem;
&:focus { background-color: #ffffff; border-color: #4484f4; box-shadow: 0 0 0 3px rgba(68, 132, 244, 0.2); }
}
.td-sidebar-nav {
li > label, li > a {
font-family: var(--bs-font-sans-serif) !important; font-size: 0.95rem !important; font-weight: 400 !important;
display: flex !important; align-items: center; justify-content: space-between; width: 100%;
padding: 0.45rem 0.75rem !important; margin-bottom: 2px; border-radius: 6px; color: #5f6368 !important;
transition: all 0.15s ease-in-out; cursor: pointer;
&:hover { background-color: #f1f3f4; color: #202124 !important; text-decoration: none; }
&.active { color: #4484f4 !important; background-color: transparent !important; font-weight: 600 !important; }
}
li.active-path > label, li.active-path > a:not(.active) {
color: #4484f4 !important; background-color: transparent !important; font-weight: 600 !important;
}
li > label > a { padding: 0 !important; margin: 0 !important; color: inherit !important; flex-grow: 1; }
.with-child > label::after { margin-left: 0.5rem; }
}
/* Left Sidebar Dark Mode */
html[data-bs-theme="dark"], body.dark {
.td-sidebar-nav li > label, .td-sidebar-nav li > a {
color: #9aa0a6 !important;
&:hover { background-color: rgba(255, 255, 255, 0.05); color: #e8eaed !important; }
&.active { color: #8ab4f8 !important; }
}
.td-sidebar-nav li.active-path > label, .td-sidebar-nav li.active-path > a:not(.active) { color: #8ab4f8 !important; }
.td-sidebar .form-control {
background-color: #303134; color: #e8eaed;
&:focus { background-color: #202124; border-color: #8ab4f8; box-shadow: 0 0 0 3px rgba(138, 180, 248, 0.2); }
}
}
/* =================================================================
INTEGRATIONS SIDEBAR LOCKS (Tools & Samples Roots Only)
================================================================= */
/* LOCK ALL sections inside the integrations directory */
a.td-sidebar-link__section[href*="/integrations/"] {
pointer-events: none !important;
cursor: default !important;
}
/* UNLOCK the top-level "/integrations/" root folder itself so it can be toggled */
a.td-sidebar-link__section[href$="/integrations/"] {
pointer-events: auto !important;
cursor: pointer !important;
}
/* UNLOCK any nested sub-folders inside tools or samples (if they exist) */
a.td-sidebar-link__section[href*="/integrations/"][href*="/tools/"],
a.td-sidebar-link__section[href*="/integrations/"][href*="/samples/"] {
pointer-events: auto !important;
cursor: pointer !important;
}
/* RE-LOCK exactly the "tools/" and "samples/" parent folders */
a.td-sidebar-link__section[href*="/integrations/"][href$="/tools/"],
a.td-sidebar-link__section[href*="/integrations/"][href$="/samples/"] {
pointer-events: none !important;
cursor: default !important;
}
/* =================================================================
RIGHT SIDEBAR (TABLE OF CONTENTS)
================================================================= */
.td-sidebar-toc, .td-page-meta {
&, a, li, span, #TableOfContents {
font-family: var(--bs-font-sans-serif) !important; font-weight: 400 !important; letter-spacing: -0.01em;
}
a {
font-size: 0.95rem !important; color: #5f6368 !important; text-decoration: none !important;
display: flex !important; align-items: center; padding: 0.45rem 0.75rem !important;
margin-bottom: 2px; border-radius: 6px; transition: all 0.15s ease-in-out;
i, svg { color: inherit !important; fill: currentColor !important; margin-right: 8px; }
&:hover { background-color: #f1f3f4; color: #202124 !important; }
&.active { color: #4484f4 !important; background-color: transparent !important; font-weight: 600 !important; }
}
}
.td-sidebar-toc #TableOfContents {
li a.active { font-weight: 600 !important; }
ul { padding-left: 0; list-style: none; margin: 0; }
ul ul { padding-left: 1rem; margin-left: 0.75rem; border-left: 1px solid rgba(0,0,0,0.05); }
}
.td-page-meta {
margin-bottom: 1.5rem !important; padding-bottom: 1rem; border-bottom: 1px solid rgba(0,0,0,0.05);
}
/* Right Sidebar Dark Mode */
html[data-bs-theme="dark"], body.dark {
.td-sidebar-toc a, .td-page-meta a {
color: #9aa0a6 !important;
&:hover { background-color: rgba(255, 255, 255, 0.05); color: #e8eaed !important; }
&.active { color: #8ab4f8 !important; }
}
.td-page-meta { border-bottom-color: rgba(255,255,255,0.1); }
.td-sidebar-toc #TableOfContents ul ul { border-left-color: rgba(255,255,255,0.1); }
}
/* =================================================================
HIDe ELEMENTS (Tags & Meta)
================================================================= */
.td-toc, .td-page-meta {
.taxonomy, .td-tags, [class*="taxonomy"], h5.taxonomy-tree-header, ul.taxonomy-terms {
display: none !important;
}
}
@@ -0,0 +1,37 @@
/* ==========================================================================
GLOBAL TYPOGRAPHY & SCALING
Defines core fonts, root variables, and base element typography.
========================================================================== */
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Readex+Pro:wght@400;500;600;700&display=swap');
html {
font-size: 16px !important;
scroll-behavior: smooth;
}
:root {
--bs-font-sans-serif: 'Readex Pro', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
--bs-body-font-family: var(--bs-font-sans-serif) !important;
--bs-font-monospace: 'JetBrains Mono', SFMono-Regular, Menlo, Monaco, Consolas, monospace !important;
--header-offset: 50px;
}
body {
font-family: var(--bs-font-sans-serif) !important;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
.td-main, .td-sidebar, .navbar, .td-content {
font-family: var(--bs-font-sans-serif) !important;
}
code, pre, kbd, samp {
font-family: var(--bs-font-monospace) !important;
font-size: 0.9em;
}
h1[id], h2[id], h3[id], h4[id], h5[id], h6[id] {
scroll-margin-top: calc(var(--header-offset) + 10px) !important;
}
+1
View File
@@ -0,0 +1 @@
{}
+45
View File
@@ -0,0 +1,45 @@
# 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.
data_sources:
- AlloyDB
- BigQuery
- Looker
- Neo4j
- Snowflake
languages:
- Go
- JavaScript
- Python
frameworks:
- ADK
- Genkit
- Google GenAI
- LangChain
- LlamaIndex
- OpenAI
categories:
- Agent
- Claude Desktop
- Gemini CLI
- Google Cloud Run
- Local
- MCP Inspector
- OAuth
- Pre & Post Processing
- Prompts
- Quickstart
+8
View File
@@ -0,0 +1,8 @@
module github.com/googleapis/mcp-toolbox
go 1.23.2
require (
github.com/google/docsy v0.12.0 // indirect
github.com/martignoni/hugo-notice v0.0.0-20240707105359-40327ac00cc4 // indirect
)
+6
View File
@@ -0,0 +1,6 @@
github.com/FortAwesome/Font-Awesome v0.0.0-20241216213156-af620534bfc3/go.mod h1:IUgezN/MFpCDIlFezw3L8j83oeiIuYoj28Miwr/KUYo=
github.com/google/docsy v0.12.0 h1:CddZKL39YyJzawr8GTVaakvcUTCJRAAYdz7W0qfZ2P4=
github.com/google/docsy v0.12.0/go.mod h1:1bioDqA493neyFesaTvQ9reV0V2vYy+xUAnlnz7+miM=
github.com/martignoni/hugo-notice v0.0.0-20240707105359-40327ac00cc4 h1:lxS0B1ta9/uW+orrnvsGHMCC0TgN4DymEgdlb0PL/uU=
github.com/martignoni/hugo-notice v0.0.0-20240707105359-40327ac00cc4/go.mod h1:MIQPOMgEcbyRC0gNLzQFSgrS+wIy3RuQ/HbaZYtTOKU=
github.com/twbs/bootstrap v5.3.6+incompatible/go.mod h1:fZTSrkpSf0/HkL0IIJzvVspTt1r9zuf7XlZau8kpcY0=
+147
View File
@@ -0,0 +1,147 @@
# 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.
title = 'MCP Toolbox for Databases'
relativeURLs = false
locale = 'en-us'
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = false
enableGitInfo = true
enableRobotsTXT = true
ignoreFiles = ["quickstart/shared", "quickstart/python", "quickstart/js", "quickstart/go"]
[security.node]
[security.node.permissions]
allowChildProcess = ["tailwindcss", "postcss"]
[languages]
[languages.en]
label = "English"
weight = 1
[module]
proxy = "direct"
[module.hugoVersion]
extended = true
min = "0.146.0"
[[module.mounts]]
source = "../docs/en"
target = 'content'
[[module.imports]]
path = "github.com/google/docsy"
disable = false
[[module.imports]]
path = "github.com/martignoni/hugo-notice"
[params]
description = "MCP Toolbox for Databases is an open source MCP server for databases. It enables you to develop tools easier, faster, and more securely by handling the complexities such as connection pooling, authentication, and more."
copyright = "Google LLC"
github_repo = "https://github.com/googleapis/mcp-toolbox"
github_project_repo = "https://github.com/googleapis/mcp-toolbox"
github_subdir = "docs"
offlineSearch = false
version_menu = "Releases"
releases_url = "/releases.releases"
global_logo_url = "/"
pagefind = true
version = "dev"
[params.ui]
ul_show = 100
showLightDarkModeMenu = true
breadcrumb_disable = false
sidebar_menu_foldable = true
sidebar_menu_compact = false
[params.ui.feedback]
enable = true
yes = 'Glad to hear it! Please <a href="https://github.com/googleapis/mcp-toolbox/issues/new">tell us how we can improve</a>.'
no = 'Sorry to hear that. Please <a href="https://github.com/googleapis/mcp-toolbox/issues/new">tell us how we can improve</a>.'
[params.ui.readingtime]
enable = true
[[params.versions]]
version = "dev"
url = "https://mcp-toolbox.dev/dev/"
# Add a new version block here before every release
# The order of versions in this file is mirrored into the dropdown
[[params.versions]]
version = "v1.6.0"
url = "https://mcp-toolbox.dev/v1.6.0/"
[[params.versions]]
version = "v1.5.0"
url = "https://mcp-toolbox.dev/v1.5.0/"
[[params.versions]]
version = "v1.4.0"
url = "https://mcp-toolbox.dev/v1.4.0/"
[[params.versions]]
version = "v1.3.0"
url = "https://mcp-toolbox.dev/v1.3.0/"
[[params.versions]]
version = "v1.2.0"
url = "https://mcp-toolbox.dev/v1.2.0/"
[[params.versions]]
version = "v1.1.0"
url = "https://mcp-toolbox.dev/v1.1.0/"
[[params.versions]]
version = "v1.0.0"
url = "https://mcp-toolbox.dev/v1.0.0/"
[[menu.main]]
name = "GitHub"
weight = 50
url = "https://github.com/googleapis/mcp-toolbox"
pre = "<i class='fa-brands fa-github'></i>"
[markup.goldmark.renderer]
unsafe= true
[markup.highlight]
noClasses = false
style = "tango"
[outputFormats]
[outputFormats.LLMS]
mediaType = "text/plain"
baseName = "llms"
isPlainText = true
root = true
[outputFormats.LLMS-FULL]
mediaType = "text/plain"
baseName = "llms-full"
isPlainText = true
root = true
[outputFormats.releases]
baseName = 'releases'
isPlainText = true
mediaType = 'text/releases'
[mediaTypes."text/releases"]
suffixes = ["releases"]
[outputs]
home = ["HTML", "RSS", "LLMS", "LLMS-FULL", "releases"]
[services]
[services.googleAnalytics]
id = "G-GLSV9KD8BF"
+233
View File
@@ -0,0 +1,233 @@
title = 'MCP Toolbox for Databases'
relativeURLs = false
languageCode = 'en-us'
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = false
enableGitInfo = true
enableRobotsTXT = true
ignoreFiles = ["quickstart/shared", "quickstart/python", "quickstart/js", "quickstart/go"]
[languages]
[languages.en]
languageName ="English"
weight = 1
[module]
proxy = "direct"
[module.hugoVersion]
extended = true
min = "0.146.0"
[[module.mounts]]
source = "../docs/en"
target = 'content'
[[module.imports]]
path = "github.com/google/docsy"
disable = false
[[module.imports]]
path = "github.com/martignoni/hugo-notice"
[params]
description = "MCP Toolbox for Databases is an open source MCP server for databases. It enables you to develop tools easier, faster, and more securely by handling the complexities such as connection pooling, authentication, and more."
copyright = "Google LLC"
github_repo = "https://github.com/googleapis/mcp-toolbox"
github_project_repo = "https://github.com/googleapis/mcp-toolbox"
github_subdir = "docs"
offlineSearch = false
version_menu = "Releases"
releases_url = "/mcp-toolbox/releases.releases"
global_logo_url = "/mcp-toolbox/"
version = "dev"
pagefind = true
[params.ui]
ul_show = 100
showLightDarkModeMenu = true
breadcrumb_disable = false
sidebar_menu_foldable = true
sidebar_menu_compact = false
[params.ui.feedback]
enable = true
yes = 'Glad to hear it! Please <a href="https://github.com/googleapis/mcp-toolbox/issues/new">tell us how we can improve</a>.'
no = 'Sorry to hear that. Please <a href="https://github.com/googleapis/mcp-toolbox/issues/new">tell us how we can improve</a>.'
[params.ui.readingtime]
enable = true
[params.mermaid]
version = "11.16.0"
[[params.versions]]
version = "dev"
url = "https://mcp-toolbox.dev/dev/"
# Add a new version block here before every release
# The order of versions in this file is mirrored into the dropdown
[[params.versions]]
version = "v1.6.0"
url = "https://mcp-toolbox.dev/v1.6.0/"
[[params.versions]]
version = "v1.5.0"
url = "https://mcp-toolbox.dev/v1.5.0/"
[[params.versions]]
version = "v1.4.0"
url = "https://mcp-toolbox.dev/v1.4.0/"
[[params.versions]]
version = "v1.3.0"
url = "https://mcp-toolbox.dev/v1.3.0/"
[[params.versions]]
version = "v1.2.0"
url = "https://mcp-toolbox.dev/v1.2.0/"
[[params.versions]]
version = "v1.1.0"
url = "https://mcp-toolbox.dev/v1.1.0/"
[[params.versions]]
version = "v1.0.0"
url = "https://mcp-toolbox.dev/v1.0.0/"
[[params.versions]]
version = "v0.32.0"
url = "https://mcp-toolbox.dev/v0.32.0/"
[[params.versions]]
version = "v0.31.0"
url = "https://mcp-toolbox.dev/v0.31.0/"
[[params.versions]]
version = "v0.30.0"
url = "https://mcp-toolbox.dev/v0.30.0/"
[[params.versions]]
version = "v0.29.0"
url = "https://mcp-toolbox.dev/v0.29.0/"
[[params.versions]]
version = "v0.28.0"
url = "https://mcp-toolbox.dev/v0.28.0/"
[[params.versions]]
version = "v0.27.0"
url = "https://mcp-toolbox.dev/v0.27.0/"
[[params.versions]]
version = "v0.26.0"
url = "https://mcp-toolbox.dev/v0.26.0/"
[[params.versions]]
version = "v0.25.0"
url = "https://mcp-toolbox.dev/v0.25.0/"
[[params.versions]]
version = "v0.24.0"
url = "https://mcp-toolbox.dev/v0.24.0/"
[[params.versions]]
version = "v0.23.0"
url = "https://mcp-toolbox.dev/v0.23.0/"
[[params.versions]]
version = "v0.22.0"
url = "https://mcp-toolbox.dev/v0.22.0/"
[[params.versions]]
version = "v0.21.0"
url = "https://mcp-toolbox.dev/v0.21.0/"
[[params.versions]]
version = "v0.20.0"
url = "https://mcp-toolbox.dev/v0.20.0/"
[[params.versions]]
version = "v0.19.1"
url = "https://mcp-toolbox.dev/v0.19.1/"
[[params.versions]]
version = "v0.18.0"
url = "https://mcp-toolbox.dev/v0.18.0/"
[[params.versions]]
version = "v0.17.0"
url = "https://mcp-toolbox.dev/v0.17.0/"
[[params.versions]]
version = "v0.16.0"
url = "https://mcp-toolbox.dev/v0.16.0/"
[[params.versions]]
version = "v0.15.0"
url = "https://mcp-toolbox.dev/v0.15.0/"
[[params.versions]]
version = "v0.14.0"
url = "https://mcp-toolbox.dev/v0.14.0/"
[[params.versions]]
version = "v0.13.0"
url = "https://mcp-toolbox.dev/v0.13.0/"
[[params.versions]]
version = "v0.12.0"
url = "https://mcp-toolbox.dev/v0.12.0/"
[[params.versions]]
version = "v0.11.0"
url = "https://mcp-toolbox.dev/v0.11.0/"
[[params.versions]]
version = "v0.10.0"
url = "https://mcp-toolbox.dev/v0.10.0/"
[[params.versions]]
version = "v0.9.0"
url = "https://mcp-toolbox.dev/v0.9.0/"
[[params.versions]]
version = "v0.8.0"
url = "https://mcp-toolbox.dev/v0.8.0/"
[[menu.main]]
name = "GitHub"
weight = 50
url = "https://github.com/googleapis/mcp-toolbox"
pre = "<i class='fa-brands fa-github'></i>"
[markup.goldmark.renderer]
unsafe= true
[markup.highlight]
noClasses = false
style = "tango"
[outputFormats]
[outputFormats.LLMS]
mediaType = "text/plain"
baseName = "llms"
isPlainText = true
root = true
[outputFormats.LLMS-FULL]
mediaType = "text/plain"
baseName = "llms-full"
isPlainText = true
root = true
[outputFormats.releases]
baseName = 'releases'
isPlainText = true
mediaType = 'text/releases'
[mediaTypes."text/releases"]
suffixes = ["releases"]
[outputs]
home = ["HTML", "RSS", "LLMS", "LLMS-FULL", "releases"]
# Google Analytics ID
[services]
[services.googleAnalytics]
id = "G-GLSV9KD8BF"
@@ -0,0 +1,11 @@
<a class="dropdown-item" href="{{ .Site.Params.global_logo_url | default `/` }}">latest</a>
{{ if .Site.Params.versions -}}
{{ $path := "" -}}
{{ if .Site.Params.version_menu_pagelinks -}}
{{ $path = .Page.RelPermalink -}}
{{ end -}}
{{ range .Site.Params.versions -}}
<a class="dropdown-item" href="{{ .url }}{{ $path }}">{{ .version }}</a>
{{ end -}}
{{ end -}}
+11
View File
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>{{ .Title }}</title>
<link rel="canonical" href="{{ .Params.external_url }}"/>
<meta http-equiv="refresh" content="0;url={{ .Params.external_url }}"/>
</head>
<body>
<p>Redirecting you to <a href="{{ .Params.external_url }}">{{ .Params.external_url }}</a>...</p>
</body>
</html>
+26
View File
@@ -0,0 +1,26 @@
{{ define "main" }}
<div class="td-content">
<h1>{{ .Title }}</h1>
{{ with .Params.description }}<div class="lead">{{ . | markdownify }}</div>{{ end }}
<header class="article-meta">
{{ partial "taxonomy_terms_article_wrapper.html" . }}
{{ if (and (not .Params.hide_readingtime) (.Site.Params.ui.readingtime.enable)) }}
{{ partial "reading-time.html" . }}
{{ end }}
</header>
{{ .Content }}
{{ partial "section-index.html" . }}
{{ partial "pager.html" . }}
{{ if (and (not .Params.hide_feedback) (.Site.Params.ui.feedback.enable) (.Site.Config.Services.GoogleAnalytics.ID)) }}
<hr class="mt-5 mb-4">
<div class="mt-4">
{{ partial "feedback.html" . }}
<br />
{{ end }}
{{ if (.Site.Params.DisqusShortname) }}
<br />
{{ partial "disqus-comment.html" . }}
{{ end }}
{{ partial "page-meta-lastmod.html" . }}
</div>
{{ end }}
+26
View File
@@ -0,0 +1,26 @@
{{ define "main" }}
<div class="td-content">
<h1>{{ .Title }}</h1>
{{ with .Params.description }}<div class="lead">{{ . | markdownify }}</div>{{ end }}
<header class="article-meta">
{{ partial "taxonomy_terms_article_wrapper.html" . }}
{{ if (and (not .Params.hide_readingtime) (.Site.Params.ui.readingtime.enable)) }}
{{ partial "reading-time.html" . }}
{{ end }}
</header>
{{ .Content }}
{{ partial "section-index.html" . }}
{{ partial "pager.html" . }}
{{ if (and (not .Params.hide_feedback) (.Site.Params.ui.feedback.enable) (.Site.Config.Services.GoogleAnalytics.ID)) }}
<hr class="mt-5 mb-4">
<div class="mt-4">
{{ partial "feedback.html" . }}
<br />
{{ end }}
{{ if (.Site.Params.DisqusShortname) }}
<br />
{{ partial "disqus-comment.html" . }}
{{ end }}
{{ partial "page-meta-lastmod.html" . }}
</div>
{{ end }}
+22
View File
@@ -0,0 +1,22 @@
{{- define "print_full_node" }}
========================================================================
## {{ .page.Title }}
========================================================================
**Hierarchy:** {{ if .page.Parent }}{{ range .page.Ancestors.Reverse }}{{ .Title }} > {{ end }}{{ end }}{{ .page.Title }}
**URL:** {{ .page.Permalink }}
**Description:** {{ .page.Description | default "None" }}
{{ .page.RawContent }}
{{ if .page.Pages }}
{{- range .page.Pages.ByWeight }}
{{ template "print_full_node" (dict "page" .) }}
{{- end }}
{{- end }}
{{- end -}}
{{ partial "llms-header.html" (dict "Site" .Site "TitleSuffix" "Complete Documentation") }}
{{- range .Site.Sections.ByWeight }}
{{ template "print_full_node" (dict "page" .) }}
{{- end }}
+17
View File
@@ -0,0 +1,17 @@
{{- define "print_index_node" }}
{{ .indent }}- [{{ .page.Title }}]({{ .page.Permalink }}): {{ .page.Description | default "No description provided." }}
{{- $nextIndent := printf "%s " .indent }}
{{- if .page.Pages }}
{{- range .page.Pages.ByWeight }}
{{ template "print_index_node" (dict "page" . "indent" $nextIndent) }}
{{- end }}
{{- end }}
{{- end -}}
{{ partial "llms-header.html" (dict "Site" .Site "TitleSuffix" "Documentation Index") }}
## Documentation Map
{{ range .Site.Sections.ByWeight -}}
{{ template "print_index_node" (dict "page" . "indent" "") }}
{{- end }}
+245
View File
@@ -0,0 +1,245 @@
{{- $navLinks := slice -}}
{{- $targetSections := slice -}}
{{- /* Resolve root navigation section */ -}}
{{- $docsRoot := site.GetPage "/docs" -}}
{{- if $docsRoot -}}
{{- $targetSections = $docsRoot.Sections.ByWeight -}}
{{- else -}}
{{- $targetSections = site.Home.Sections.ByWeight -}}
{{- end -}}
{{- /* Determine active top-level directory */ -}}
{{- if .FirstSection -}}
{{- $topLevelFolder := .FirstSection.Section -}}
{{- if or (findRE `^v[0-9]+` $topLevelFolder) (eq $topLevelFolder "dev") -}}
{{- $targetSections = .FirstSection.Sections.ByWeight -}}
{{- end -}}
{{- end -}}
{{- /* Construct navigation link dataset */ -}}
{{- range $targetSections -}}
{{- if .Title -}}
{{- $manualLink := .Params.manualLink | default "" -}}
{{- $icon := .Params.icon | default "" -}}
{{- $isRightSide := ne $manualLink "" -}}
{{- $navLinks = $navLinks | append (dict "name" .Title "url" .RelPermalink "redirect" $manualLink "icon" $icon "isRight" $isRightSide) -}}
{{- end -}}
{{- end -}}
{{- /* Compile shared tools data for sidebar injection */ -}}
{{- $dynamicLinks := dict -}}
{{- range .Site.Pages -}}
{{- if .Params.shared_tools -}}
{{- $targetURL := .RelPermalink -}}
{{- $targetData := slice -}}
{{- range .Params.shared_tools -}}
{{- $sourceSection := site.GetPage .source -}}
{{- if $sourceSection -}}
{{- $links := slice -}}
{{- range $sourceSection.Pages -}}
{{- $links = $links | append (dict "title" .Title "url" .RelPermalink) -}}
{{- end -}}
{{- $targetData = $targetData | append (dict "header" .header "links" $links) -}}
{{- end -}}
{{- end -}}
{{- if gt (len $targetData) 0 -}}
{{- $dynamicLinks = merge $dynamicLinks (dict $targetURL $targetData) -}}
{{- end -}}
{{- end -}}
{{- end -}}
<div id="nav-data-container" data-nav="{{ $navLinks | jsonify }}" style="display: none;"></div>
<script>
window.dynamicSidebarLinks = {{ $dynamicLinks | jsonify | safeJS }};
</script>
<script>
document.addEventListener("DOMContentLoaded", function() {
// Add links to GitHub issues for documentation feedback and feature requests
const docIssueLink = document.querySelector('.td-page-meta__issue');
if (docIssueLink) {
const url = new URL(docIssueLink.href);
url.searchParams.set('template', 'feature_request.yml');
docIssueLink.href = url.toString();
}
const projIssueLink = document.querySelector('.td-page-meta__project-issue');
if (projIssueLink) {
const url = new URL(projIssueLink.href);
url.searchParams.set('template', 'bug_report.yml');
projIssueLink.href = url.toString();
}
const container = document.getElementById('nav-data-container');
const currentPath = window.location.pathname;
const header = document.querySelector('header');
if (!container || !header || document.getElementById('secondary-nav')) return;
let links = [];
try {
links = JSON.parse(container.dataset.nav);
} catch (e) {
console.error("Failed to parse nav data", e);
return;
}
// Configure brand logo routing
const logoLink = document.querySelector('.navbar-brand');
if (logoLink) {
logoLink.href = "{{ site.Params.global_logo_url | default `/` }}";
}
// Sidebar link centering logic
const sidebar = document.querySelector('#td-section-nav') || document.querySelector('.td-sidebar-nav') || document.querySelector('.td-sidebar');
if (sidebar) {
const activeLink = sidebar.querySelector('.active');
if (activeLink) {
const sidebarRect = sidebar.getBoundingClientRect();
const activeRect = activeLink.getBoundingClientRect();
sidebar.scrollTop = activeRect.top - sidebarRect.top + sidebar.scrollTop - (sidebar.clientHeight / 2) + (activeRect.height / 2);
}
}
// Build secondary navigation UI
let leftNavItems = '';
let rightNavItems = '';
const versionMatch = currentPath.match(/^\/(v\d+\.\d+\.\d+|dev)\//);
const activePrefix = versionMatch ? versionMatch[0] : '/';
links.forEach(link => {
if (!link.name || !link.url) return;
let rawUrl = link.isRight ? link.redirect : link.url;
// Only strip double slashes if it is an internal relative link
let finalUrl = rawUrl.startsWith('http') ? rawUrl : rawUrl.replace(/\/+/g, '/');
// Apply active version prefix mapping
if (!link.isRight && activePrefix !== '/' && !finalUrl.startsWith(activePrefix)) {
finalUrl = activePrefix + finalUrl.replace(/^\//, '');
}
const iconHtml = link.icon ? `<i class="${link.icon}${link.isRight ? ' pop-icon' : ''}" ${!link.isRight ? 'style="font-size: 1.05rem;"' : ''}></i> ` : '';
if (link.isRight) {
rightNavItems += `
<li>
<a href="${finalUrl}" target="_blank" rel="noopener noreferrer" class="sec-nav-item-right">
${iconHtml}<span class="sec-nav-text">${link.name}</span>
</a>
</li>`;
} else {
const isActive = currentPath.startsWith(finalUrl) ? ' active' : '';
leftNavItems += `
<li>
<a class="sec-nav-item-left${isActive}" href="${finalUrl}">
${iconHtml}<span>${link.name}</span>
</a>
</li>`;
}
});
header.insertAdjacentHTML('beforeend', `
<div id="secondary-nav">
<div class="container-fluid sec-nav-container">
<ul class="sec-nav-list">${leftNavItems}</ul>
<ul class="sec-nav-icons">${rightNavItems}</ul>
</div>
</div>
`);
// ======================================================================
// Configure release version button labeling & Active Highlight
// ======================================================================
try {
const relBtn = Array.from(document.querySelectorAll('.td-navbar a.dropdown-toggle'))
.find(el => el.textContent.includes('Releases') || el.textContent.match(/v\d+\./) || el.textContent.includes('dev'));
if (relBtn) {
let label = currentPath.includes('/dev/') ? "dev" : (versionMatch ? versionMatch[1].replace(/\//g, '') : "Releases");
// Update the text on the dropdown button itself
for (let n of relBtn.childNodes) {
if (n.nodeType === 3 && n.textContent.trim().length > 1) {
n.textContent = ' ' + label;
break;
}
}
if (label !== "Releases") {
const waitForLinks = setInterval(() => {
const dropdownItems = document.querySelectorAll('.dropdown-menu .dropdown-item');
if (dropdownItems.length > 0) {
clearInterval(waitForLinks);
dropdownItems.forEach(item => {
const href = item.getAttribute('href');
if (href && (href.includes(`/${label}/`) || href.endsWith(`/${label}`))) {
item.classList.add('active-version');
}
});
}
}, 50); // Check every 50 milliseconds
// Safety fallback: Stop checking after 3 seconds so it doesn't run forever
setTimeout(() => clearInterval(waitForLinks), 3000);
}
}
} catch (e) { console.warn("Releases Labeler skipped.", e); }
// Inject shared tools into sidebar DOM
const sidebarData = window.dynamicSidebarLinks;
if (sidebarData && Object.keys(sidebarData).length > 0) {
Object.keys(sidebarData).forEach(targetUrl => {
const targetLink = document.querySelector(`.td-sidebar-nav a[href="${targetUrl}"]`) ||
document.querySelector(`.td-sidebar-nav a[href="${targetUrl.replace(/\/$/, '')}"]`);
if (!targetLink) return;
const parentLi = targetLink.closest('li');
if (!parentLi) return;
let innerUl = parentLi.querySelector('ul');
if (!innerUl) {
innerUl = document.createElement('ul');
innerUl.className = "ul-2 list-unstyled pb-2 pl-3";
innerUl.style.marginLeft = "1rem";
parentLi.appendChild(innerUl);
parentLi.classList.add('with-child');
}
sidebarData[targetUrl].forEach(group => {
const groupLi = document.createElement('li');
groupLi.className = "with-child";
const isGroupActive = group.links.some(link => currentPath === link.url);
const subUl = document.createElement('ul');
subUl.className = "ul-3 list-unstyled pb-2 pl-3";
subUl.style.marginLeft = "1rem";
subUl.style.display = isGroupActive ? "block" : "none";
if (group.header) {
const headerLabel = document.createElement('label');
headerLabel.style.cursor = "pointer";
headerLabel.innerHTML = `<span style="font-weight: 600; text-transform: uppercase; font-size: 0.85rem; letter-spacing: 0.5px;">${group.header}</span>`;
headerLabel.addEventListener('click', function(e) {
e.preventDefault();
subUl.style.display = subUl.style.display === "none" ? "block" : "none";
});
groupLi.appendChild(headerLabel);
}
group.links.forEach(link => {
const linkLi = document.createElement('li');
linkLi.innerHTML = `<a href="${link.url}" class="td-sidebar-link${currentPath === link.url ? ' active' : ''}" title="${link.title}">${link.title}</a>`;
subUl.appendChild(linkLi);
});
groupLi.appendChild(subUl);
innerUl.appendChild(groupLi);
});
});
}
});
</script>
@@ -0,0 +1,4 @@
<script src='{{ "js/w3.js" | relURL }}'></script>
{{ if not .Site.Params.disableBanner }}
<script src="{{ "js/custom-layout.js" | relURL }}"></script>
{{ end }}
+58
View File
@@ -0,0 +1,58 @@
# {{ .Site.Title }} - {{ .TitleSuffix }}
> {{ .Site.Params.description }}
**DOCUMENTATION VERSION:** {{ .Site.Params.version | default "Latest" }}
**BASE URL:** {{ .Site.BaseURL }}
**GENERATED ON:** {{ now.Format "2006-01-02T15:04:05Z07:00" }}
---
### System Directives for AI Models
**Role:** You are an expert Developer Advocate and Integration Engineer for the **MCP (Model Context Protocol) Toolbox for Databases**.
**Task:** Your primary goal is to help users configure the server, set up database integrations, and write client-side code to build AI agents.
**Strict Guidelines:**
1. **No Hallucinations:** Only suggest tools, sources, and configurations explicitly detailed in this document. Do not invent arbitrary REST endpoints.
2. **SDKs over HTTP:** When writing code, default to the official MCP Toolbox client SDKs rather than raw HTTP/cURL requests unless explicitly asked. Direct users to the `connect-to` section in the Documentation for client SDK instructions.
3. **Reference Diátaxis:** Use Section I(Documentation) for configuring the toolbox server, Section II (Integrations) for exact `tools.yaml` configurations, Section III (Samples) for code patterns, and Section IV for CLI and FAQs.
### Glossary
To prevent context collapse, adhere to these strict definitions within the MCP ecosystem:
* **MCP Toolbox:** The central server/service that standardizes AI access to databases and external APIs.
* **Source:** A configured backend connection to an external system (e.g., PostgreSQL, BigQuery, HTTP).
* **Tool:** A single, atomic capability exposed to the LLM (e.g., `bigquery-sql-query`), executed against a Source.
* **Toolset:** A logical, grouped collection of Tools.
* **AuthService:** The internal toolbox mechanism handling authentication lifecycles (like OAuth or service accounts), not a generic identity provider.
* **Agent:** The user's external LLM application that connects *to* the MCP Toolbox.
### Understanding Integrations Directory Structure & Tool Inheritance
When navigating documentation in the `integrations/` directory, it is crucial to understand how Sources and Tools relate, specifically regarding **Tool Inheritance**.
* **Source Pages (`source.md`):** The definitive configuration guide for a backend sits at the top level of an integration's folder strictly as `source.md` (e.g., `integrations/alloydb/source.md`). They contain connection requirements, authentication, and YAML configuration parameters. *(Note: `_index.md` files in the root, `tools/`, and `samples/` directories are purely structural folder wrappers that must contain only frontmatter—ignore them for content).*
* **Native Tools:** Specific capabilities built directly for a Source. If a Source has native tools, they are located in a `tools/` sub-directory (e.g., `integrations/alloydb/tools/alloydb-sql.md`).
* **Inherited Tools (Shared Tools):** Managed or compatible databases (e.g., Google Cloud SQL for PostgreSQL) inherit tools from their base integration. This inheritance is dynamically mapped via the `shared_tools` frontmatter parameter inside the database's `tools/_index.md` file (which contains no body content). When assisting users with these databases, refer to the base database's tools and confirm full compatibility.
### Global Environment & Prerequisites
* **Configuration:** `tools.yaml` is the ultimate source of truth for server configuration.
* **Database:** PostgreSQL 16+ and the `psql` client.
* **Language Requirements:**
{{- with .Site.GetPage "/documentation/getting-started/local_quickstart" }}
{{- $match := findRE "(?i)Python\\s+\\(\\d+\\.\\d+\\s+or\\s+higher\\)" .RawContent 1 }}
* Python: {{ if $match }}{{ index $match 0 | replaceRE "[\\[\\]]" "" }}{{ else }}Refer to Python Quickstart{{ end }}
{{- end }}
{{- with .Site.GetPage "/documentation/getting-started/local_quickstart_js" }}
{{- $match := findRE "(?i)Node\\.js \\(v\\d+ or higher\\)" .RawContent 1 }}
* JavaScript/Node: {{ if $match }}{{ index $match 0 }}{{ else }}Refer to JS Quickstart{{ end }}
{{- end }}
{{- with .Site.GetPage "/documentation/getting-started/local_quickstart_go" }}
{{- $match := findRE "(?i)Go \\(v\\d+\\.\\d+\\.\\d+ or higher\\)" .RawContent 1 }}
* Go: {{ if $match }}{{ index $match 0 }}{{ else }}Refer to Go Quickstart{{ end }}
{{- end }}
### The Diátaxis Narrative Framework
This documentation is structured following the Diátaxis framework to assist in clear navigation and understanding:
* **Section I: Documentation:** (Explanation) Theoretical context, high-level understanding, and universal How-To Guides.
* **Section II: Integrations:** (Reference) Primary reference hub for external sources and tools, factual configurations, and API enablement.
* **Section III: Samples:** (Tutorials) Code patterns and examples. **Note for AI:** Sample code is distributed across three main areas:
1. **Quickstarts:** Located in `documentation/getting-started/`.
2. **Integration-Specific Samples:** Nested within their respective `integrations/<source_name>/samples/` directories.
3. **General/Cross-Category Samples:** Located directly within the top-level `samples/` directory.
* **Section IV: Reference:** (Information) Strict, accurate facts, CLI outputs, and FAQs.
---
@@ -0,0 +1,67 @@
{{ if .Site.Params.versions -}}
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ .Site.Params.version_menu }}
</a>
<div class="dropdown-menu" id="version-dropdown-menu" aria-labelledby="navbarDropdown">
<div w3-include-html="{{ .Site.Params.releases_url }}" w3-include-html-default='<a class="dropdown-item" href="{{ .Site.Params.global_logo_url }}dev/">dev</a>'></div>
<script>
w3.includeHTML(function() {
const basePath = "{{ site.BaseURL | relURL }}";
function stripBase(path) {
if (path.startsWith(basePath)) return path.substring(basePath.length);
if (basePath === "/" && path.startsWith("/")) return path.substring(1);
return path.replace(/^\//, '');
}
document.getElementById('version-dropdown-menu').addEventListener('click', function(e) {
const link = e.target.closest('a.dropdown-item');
if (!link) return;
e.preventDefault();
let targetPath = link.pathname;
if (!targetPath.endsWith('/')) targetPath += '/';
let cleanCurrentPath = stripBase(window.location.pathname);
const allLinks = document.querySelectorAll('#version-dropdown-menu a.dropdown-item');
let currentVersionPrefix = "";
allLinks.forEach(a => {
let cleanVPath = stripBase(a.pathname);
if (!cleanVPath.endsWith('/')) cleanVPath += '/';
if (cleanVPath !== "" && cleanVPath !== "/" && cleanCurrentPath.startsWith(cleanVPath)) {
if (cleanVPath.length > currentVersionPrefix.length) {
currentVersionPrefix = cleanVPath;
}
}
});
let deepPath = cleanCurrentPath;
if (currentVersionPrefix !== "") {
deepPath = cleanCurrentPath.substring(currentVersionPrefix.length);
}
deepPath = deepPath.replace(/^\//, '');
const fullTargetPath = targetPath + deepPath;
// Perform a HEAD request to check if the deep route exists
fetch(fullTargetPath, { method: 'HEAD' })
.then(response => {
if (response.ok) {
// Page exists! Redirect to deep path
window.location.href = fullTargetPath;
} else {
// 404 or other error: Fallback to the version root
window.location.href = targetPath;
}
})
.catch(() => {
// If the fetch fails entirely, fallback to root
window.location.href = targetPath;
});
});
});
</script>
</div>
{{ end -}}
@@ -0,0 +1,52 @@
{{/* cSpell:ignore querify subdir */ -}}
{{/* Class names ending with `--KIND` are deprecated in favor of `__KIND`, but we're keeping them for a few releases after 0.9.0 */ -}}
{{ if .File -}}
{{ $path := urls.JoinPath $.Language.Lang $.File.Path -}}
{{ $gh_repo := $.Param "github_repo" -}}
{{ $gh_url := $.Param "github_url" -}}
{{ $gh_subdir := $.Param "github_subdir" | default "" -}}
{{ $gh_project_repo := $.Param "github_project_repo" -}}
{{ $gh_branch := $.Param "github_branch" | default "main" -}}
<div class="td-page-meta ms-2 pb-1 pt-2 mb-0">
{{ if $gh_url -}}
{{ warnf "Warning: use of `github_url` is deprecated. For details, see https://www.docsy.dev/docs/adding-content/repository-links/#github_url-optional" -}}
<a href="{{ $gh_url }}" target="_blank"><i class="fa-solid fa-pen-to-square fa-fw"></i> {{ T "post_edit_this" }}</a>
{{ else if $gh_repo -}}
{{/* Adjust $path based on path_base_for_github_subdir */ -}}
{{ $ghs_base := $.Param "path_base_for_github_subdir" -}}
{{ $ghs_rename := "" -}}
{{ if reflect.IsMap $ghs_base -}}
{{ $ghs_rename = $ghs_base.to -}}
{{ $ghs_base = $ghs_base.from -}}
{{ end -}}
{{ with $ghs_base -}}
{{ $path = replaceRE . $ghs_rename $path -}}
{{ end -}}
{{ $gh_repo_path := printf "%s/%s/%s" $gh_branch $gh_subdir $path -}}
{{ $gh_repo_path = replaceRE "//+" "/" $gh_repo_path -}}
{{ $viewURL := printf "%s/tree/%s" $gh_repo $gh_repo_path -}}
{{ $editURL := printf "%s/edit/%s" $gh_repo $gh_repo_path -}}
{{ $issuesURL := printf "%s/issues/new?title=%s" $gh_repo (safeURL $.Title ) -}}
{{ $newPageStub := resources.Get "stubs/new-page-template.md" -}}
{{ $newPageQS := querify "value" $newPageStub.Content "filename" "change-me.md" | safeURL -}}
{{ $newPageURL := printf "%s/new/%s?%s" $gh_repo (path.Dir $gh_repo_path) $newPageQS -}}
<a href="{{ $viewURL }}" class="td-page-meta--view td-page-meta__view" target="_blank" rel="noopener"><i class="fa-solid fa-file-lines fa-fw"></i> {{ T "post_view_this" }}</a>
<a href="{{ $editURL }}" class="td-page-meta--edit td-page-meta__edit" target="_blank" rel="noopener"><i class="fa-solid fa-pen-to-square fa-fw"></i> {{ T "post_edit_this" }}</a>
<a href="{{ $newPageURL }}" class="td-page-meta--child td-page-meta__child" target="_blank" rel="noopener"><i class="fa-solid fa-pen-to-square fa-fw"></i> {{ T "post_create_child_page" }}</a>
<a href="{{ $issuesURL }}" class="td-page-meta--issue td-page-meta__issue" target="_blank" rel="noopener"><i class="fa-solid fa-list-check fa-fw"></i> {{ T "post_create_issue" }}</a>
{{ with $gh_project_repo -}}
{{ $project_issueURL := printf "%s/issues/new" . -}}
<a href="{{ $project_issueURL }}" class="td-page-meta--project td-page-meta__project-issue" target="_blank" rel="noopener"><i class="fa-solid fa-list-check fa-fw"></i> {{ T "post_create_project_issue" }}</a>
{{ end -}}
{{ end -}}
{{ with .CurrentSection.AlternativeOutputFormats.Get "print" -}}
<a id="print" href="{{ .RelPermalink | safeURL }}"><i class="fa-solid fa-print fa-fw"></i> {{ T "print_entire_section" }}</a>
{{ end }}
</div>
{{ end -}}
+187
View File
@@ -0,0 +1,187 @@
{{ $curr := . }}
{{ $next := "" }}
{{ $prev := "" }}
{{ $max_skip_attempts := 5 }}
{{/* 1. Calculate Previous Page */}}
{{ if .Parent }}
{{ $siblings := .Parent.Pages.ByWeight }}
{{ $currIndex := -1 }}
{{ range $index, $page := $siblings }}
{{ if eq $page.RelPermalink $curr.RelPermalink }}
{{ $currIndex = $index }}
{{ end }}
{{ end }}
{{ if gt $currIndex 0 }}
{{ $prev = index $siblings (sub $currIndex 1) }}
{{ else }}
{{ if ne .Parent.Type "home" }}
{{ $prev = .Parent }}
{{ end }}
{{ end }}
{{ end }}
{{/* 2. Calculate Next Page */}}
{{ if and .IsNode (gt (len .Pages) 0) }}
{{ $next = index .Pages.ByWeight 0 }}
{{ else }}
{{ if .Parent }}
{{ $siblings := .Parent.Pages.ByWeight }}
{{ $currIndex := -1 }}
{{ range $index, $page := $siblings }}
{{ if eq $page.RelPermalink $curr.RelPermalink }}
{{ $currIndex = $index }}
{{ end }}
{{ end }}
{{ if lt $currIndex (sub (len $siblings) 1) }}
{{ $next = index $siblings (add $currIndex 1) }}
{{ else }}
{{ $p := .Parent }}
{{ $foundNext := false }}
{{ range seq 3 }}
{{ if and (not $foundNext) $p }}
{{ if $p.Parent }}
{{ $pSiblings := $p.Parent.Pages.ByWeight }}
{{ $pIndex := -1 }}
{{ range $index, $page := $pSiblings }}
{{ if eq $page.RelPermalink $p.RelPermalink }}
{{ $pIndex = $index }}
{{ end }}
{{ end }}
{{ if and (ge $pIndex 0) (lt $pIndex (sub (len $pSiblings) 1)) }}
{{ $next = index $pSiblings (add $pIndex 1) }}
{{ $foundNext = true }}
{{ else }}
{{ $p = $p.Parent }}
{{ end }}
{{ else }}
{{ $p = false }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{/* 3. Apply Integration Directory Filters */}}
{{ range seq $max_skip_attempts }}
{{ if $prev }}
{{ $isLockedPrev := false }}
{{ if and $prev.IsNode (in $prev.RelPermalink "/integrations/") }}
{{ if or (strings.HasSuffix $prev.RelPermalink "/tools/") (strings.HasSuffix $prev.RelPermalink "/samples/") (and $prev.Parent (strings.HasSuffix $prev.Parent.RelPermalink "/integrations/")) }}
{{ $isLockedPrev = true }}
{{ end }}
{{ end }}
{{ if $isLockedPrev }}
{{ $steppingOut := strings.HasPrefix $curr.RelPermalink $prev.RelPermalink }}
{{ if and (not $steppingOut) (gt (len $prev.Pages) 0) }}
{{ $prev = index $prev.Pages.ByWeight (sub (len $prev.Pages) 1) }}
{{ else }}
{{ if $prev.Parent }}
{{ if eq $prev.Parent.RelPermalink "/integrations/" }}
{{ $prev = $prev.Parent }}
{{ else }}
{{ $sibs := $prev.Parent.Pages.ByWeight }}
{{ $idx := -1 }}
{{ range $i, $p := $sibs }}{{ if eq $p.RelPermalink $prev.RelPermalink }}{{ $idx = $i }}{{ end }}{{ end }}
{{ if gt $idx 0 }}
{{ $prev = index $sibs (sub $idx 1) }}
{{ else }}
{{ $prev = $prev.Parent }}
{{ end }}
{{ end }}
{{ else }}
{{ $prev = "" }}
{{ end }}
{{ end }}
{{ else }}
{{ break }}
{{ end }}
{{ end }}
{{ end }}
{{ range seq $max_skip_attempts }}
{{ if $next }}
{{ $isLockedNext := false }}
{{ if and $next.IsNode (in $next.RelPermalink "/integrations/") }}
{{ if or (strings.HasSuffix $next.RelPermalink "/tools/") (strings.HasSuffix $next.RelPermalink "/samples/") (and $next.Parent (strings.HasSuffix $next.Parent.RelPermalink "/integrations/")) }}
{{ $isLockedNext = true }}
{{ end }}
{{ end }}
{{ if $isLockedNext }}
{{ if gt (len $next.Pages) 0 }}
{{ $next = index $next.Pages.ByWeight 0 }}
{{ else }}
{{ $sibs := $next.Parent.Pages.ByWeight }}
{{ $idx := -1 }}
{{ range $i, $p := $sibs }}{{ if eq $p.RelPermalink $next.RelPermalink }}{{ $idx = $i }}{{ end }}{{ end }}
{{ if lt $idx (sub (len $sibs) 1) }}
{{ $next = index $sibs (add $idx 1) }}
{{ else }}
{{ $p := $next.Parent }}
{{ $foundNextSibling := false }}
{{ range seq 3 }}
{{ if and (not $foundNextSibling) $p }}
{{ if $p.Parent }}
{{ $pSibs := $p.Parent.Pages.ByWeight }}
{{ $pIdx := -1 }}
{{ range $index, $page := $pSibs }}{{ if eq $page.RelPermalink $p.RelPermalink }}{{ $pIdx = $index }}{{ end }}{{ end }}
{{ if and (ge $pIdx 0) (lt $pIdx (sub (len $pSibs) 1)) }}
{{ $next = index $pSibs (add $pIdx 1) }}
{{ $foundNextSibling = true }}
{{ else }}
{{ $p = $p.Parent }}
{{ end }}
{{ else }}
{{ $p = false }}
{{ end }}
{{ end }}
{{ end }}
{{ if not $foundNextSibling }}{{ $next = "" }}{{ end }}
{{ end }}
{{ end }}
{{ else }}
{{ break }}
{{ end }}
{{ end }}
{{ end }}
{{/* 4. Render Navigation */}}
{{ if or $prev $next }}
<nav class="mt-5 pt-4 border-top d-flex justify-content-between" aria-label="Page navigation">
<div>
{{ with $prev }}
<a href="{{ .RelPermalink }}" class="text-decoration-none">
<small class="text-muted d-block mb-1">&laquo; Previous</small>
<span class="text-body">{{ .Title }}</span>
</a>
{{ end }}
</div>
<div class="text-end">
{{ with $next }}
<a href="{{ .RelPermalink }}" class="text-decoration-none">
<small class="text-muted d-block mb-1">Next &raquo;</small>
<span class="text-body">{{ .Title }}</span>
</a>
{{ end }}
</div>
</nav>
{{ end }}
+137
View File
@@ -0,0 +1,137 @@
{{ .Scratch.Set "docsy-search" 0 }}
{{ if .Site.Params.pagefind }}
{{ .Scratch.Add "docsy-search" 1 }}
<div class="td-search custom-pagefind-wrapper">
<div class="pagefind-search-container"></div>
</div>
<link href="{{ relURL "pagefind/pagefind-ui.css" }}" rel="stylesheet">
<script src="{{ relURL "pagefind/pagefind-ui.js" }}"></script>
<script>
if (!window.pagefindScriptLoaded) {
window.pagefindScriptLoaded = true;
window.addEventListener('DOMContentLoaded', () => {
/* Initialize Pagefind UI */
document.querySelectorAll('.pagefind-search-container').forEach(el => {
new PagefindUI({
element: el,
showSubResults: true,
showImages: false,
resetStyles: false,
debounceTimeoutMs: 300,
bundlePath: "{{ relURL "pagefind/" }}",
processResult: function (result) {
const ignoredSegments = ["en", "docs", "dev", "mcp-toolbox"];
let path = result.url.replace(/^https?:\/\/[^\/]+/, "").split('#')[0].replace(/\/$/, "");
let parts = path.split("/").filter(p => p && !ignoredSegments.includes(p));
parts.pop();
if (parts.length > 0) {
let breadcrumb = parts.map(part => part.replace(/[-_]/g, ' ').replace(/\b\w/g, l => l.toUpperCase())).join(' ');
if (!result.rawTitle) result.rawTitle = result.meta.title;
result.meta.title = breadcrumb + ' ' + result.rawTitle;
}
return result;
}
});
});
/* Modal Behavior Logic */
setTimeout(() => {
let backdrop = document.getElementById('global-search-backdrop');
if (!backdrop) {
backdrop = document.createElement('div');
backdrop.id = 'global-search-backdrop';
document.body.appendChild(backdrop);
}
let isClosing = false;
let activeWrapper = null;
let placeholder = document.createElement('div');
placeholder.style.display = 'none';
function openModal(input) {
if (activeWrapper || isClosing) return;
const wrapper = input.closest('.custom-pagefind-wrapper');
if (!wrapper) return;
activeWrapper = wrapper;
wrapper.parentNode.insertBefore(placeholder, wrapper);
document.body.appendChild(wrapper);
backdrop.classList.add('active');
document.body.classList.add('global-search-active');
wrapper.classList.add('active-modal');
setTimeout(() => {
input.focus();
const len = input.value.length;
input.setSelectionRange(len, len);
}, 10);
}
function closeModal(keepFocus = false) {
if (!activeWrapper) return;
isClosing = true;
backdrop.classList.remove('active');
document.body.classList.remove('global-search-active');
activeWrapper.classList.remove('active-modal');
if (placeholder.parentNode) {
placeholder.parentNode.insertBefore(activeWrapper, placeholder);
}
const input = activeWrapper.querySelector('.pagefind-ui__search-input');
if (!keepFocus) {
const clearBtn = activeWrapper.querySelector('.pagefind-ui__search-clear');
if (clearBtn) clearBtn.click();
if (input) input.blur();
} else {
if (input) setTimeout(() => input.focus(), 10);
}
activeWrapper = null;
setTimeout(() => { isClosing = false; }, 50);
}
/* Event Listeners */
document.addEventListener('input', (e) => {
if (e.target.classList.contains('pagefind-ui__search-input')) {
e.target.value.trim() !== '' ? openModal(e.target) : closeModal(true);
}
});
backdrop.addEventListener('click', () => closeModal(false));
document.addEventListener('mousedown', (e) => {
if (!activeWrapper) return;
const clickedX = e.target.closest('.pagefind-ui__search-clear');
const clickedResult = e.target.closest('.pagefind-ui__result-link');
const isClickInside = activeWrapper.contains(e.target);
if (clickedResult) return;
if (clickedX || (!isClickInside && e.target !== backdrop)) closeModal(false);
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeModal(false);
});
}, 500);
});
}
</script>
{{ else if .Site.Params.offlineSearch }}
{{ .Scratch.Set "docsy-search" 1 }}
<div class="td-search custom-pagefind-wrapper flex-grow-1 order-last order-lg-1 w-100 mt-3 mt-lg-0">
<div class="pagefind-search-container w-100"></div>
</div>
{{ end }}
@@ -0,0 +1 @@
{{ template "partials/td/render-heading.html" . }}
+4
View File
@@ -0,0 +1,4 @@
User-agent: *
{{ if eq hugo.Environment "preview" }}
Disallow: /*
{{ end }}
@@ -0,0 +1,47 @@
{{/* Automatically identify the "Native" source (Grandparent source.md, since this is strictly used inside tools/) */}}
{{ $nativeSource := .Page.Parent.Parent.GetPage "source.md" }}
{{ if not $nativeSource }}
{{ $nativeSource = .Page.Parent.Parent.GetPage "source" }}
{{ end }}
<div class="compatibility-section">
<p>This tool can be used with the following database sources:</p>
<table>
<thead>
<tr>
<th>Source Name</th>
</tr>
</thead>
<tbody>
{{/* Display the Native Source automatically */}}
{{ if $nativeSource }}
<tr>
<td><a href="{{ $nativeSource.RelPermalink }}">{{ $nativeSource.Title }}</a></td>
</tr>
{{ end }}
{{/* Process additional sources passed via the "others" parameter */}}
{{ $others := .Get "others" }}
{{ if $others }}
{{ range split $others "," }}
{{ $path := trim . " " }}
{{ $cleanPath := trim $path "/" }}
{{ $remotePage := site.GetPage (printf "%s/source.md" $cleanPath) }}
{{ if $remotePage }}
<tr>
<td><a href="{{ $remotePage.RelPermalink }}">{{ $remotePage.Title }}</a></td>
</tr>
{{ else }}
<tr>
<td style="color: red;">⚠️ Source not found at path: {{ $path }}</td>
</tr>
{{ end }}
{{ end }}
{{ end }}
</tbody>
</table>
</div>
+13
View File
@@ -0,0 +1,13 @@
{{ $file := .Get 0 }}
{{ $lang := .Get 1 }}
{{ $content := (printf "%s%s" .Page.File.Dir $file) | readFile | replaceRE "^---[\\s\\S]+?---" "" | replaceRE "\r\n"
"\n" | strings.TrimRight "\n" }}
{{ if $lang }}
```{{ $lang }}
{{ $content | safeHTML }}
```
{{ else }}
{{ $content | safeHTML }}
{{ end }}

Some files were not shown because too many files have changed in this diff Show More