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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
68 changes: 41 additions & 27 deletions .ci/lint-docs-source-page.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ ALLOWED_ORDER = [
"Troubleshooting",
"Additional Resources"
]
REQUIRED = {"About", "Example", "Reference"}

# "Available Tools" is explicitly required
REQUIRED = {"About", "Available Tools", "Example", "Reference"}

# Regex to catch any variation of the list-tools shortcode
SHORTCODE_PATTERN = r"\{\{<\s*list-tools.*?>\}\}"
# ---------------------

Expand All @@ -53,10 +57,12 @@ if not integration_dir.exists():
sys.exit(0)

has_errors = False
source_pages_found = 0

for filepath in integration_dir.rglob("_index.md"):
if filepath.parent == integration_dir:
continue
# ONLY scan files specifically named "source.md"
for filepath in integration_dir.rglob("source.md"):
source_pages_found += 1
file_errors = False

with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
Expand All @@ -65,30 +71,33 @@ for filepath in integration_dir.rglob("_index.md"):
if match:
frontmatter, body = match.group(1), match.group(2)
else:
frontmatter, body = "", content

if not body.strip():
print(f"[{filepath}] Error: Missing or invalid YAML frontmatter.")
has_errors = True
continue

file_errors = False
# 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

# 1. Frontmatter Title Check
title_match = re.search(r"^title:\s*[\"']?(.*?)[\"']?\s*$", frontmatter if frontmatter else content, re.MULTILINE)
if not title_match or not title_match.group(1).strip().endswith("Source"):
print(f"[{filepath}] Error: Title must end with 'Source'.")
# 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

# 2. Shortcode Placement Check
tools_section = re.search(r"^##\s+Available Tools\s*(.*?)(?=^##\s|\Z)", body, re.MULTILINE | re.DOTALL)
if tools_section:
if not re.search(SHORTCODE_PATTERN, tools_section.group(1)):
print(f"[{filepath}] Error: {{< list-tools >}} must be under '## Available Tools'.")
# 3. Check Shortcode Placement & Available Tools Section
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
elif re.search(SHORTCODE_PATTERN, body):
print(f"[{filepath}] Error: {{< list-tools >}} found, but '## Available Tools' heading is missing.")
else:
print(f"[{filepath}] Error: The '## Available Tools' heading is missing or incorrectly formatted.")
file_errors = True

# 3. Heading Linting (Stripping code blocks first)
# 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):
Expand All @@ -97,9 +106,10 @@ for filepath in integration_dir.rglob("_index.md"):

h2s = [h.strip() for h in re.findall(r"^##\s+(.*)", clean_body, re.MULTILINE)]

# 4. Required & Unauthorized Check
if missing := (REQUIRED - set(h2s)):
print(f"[{filepath}] Error: Missing required H2s: {missing}")
# 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)):
Expand All @@ -113,9 +123,13 @@ for filepath in integration_dir.rglob("_index.md"):

if file_errors: has_errors = True

if has_errors:
print("Linting failed. Fix structure errors above.")
# 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)
print("Success: Source pages validated.")
sys.exit(0)
else:
print(f"Success: {source_pages_found} 'source.md' file(s) passed structure validation.")
EOF
63 changes: 39 additions & 24 deletions .ci/lint-docs-tool-page.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ if not integration_dir.exists():
sys.exit(0)

has_errors = False
tools_pages_found = 0

# Find all .md files, excluding _index.md (which are Source pages)
for filepath in integration_dir.rglob("*.md"):
if filepath.name == "_index.md":
continue

# 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()

Expand All @@ -68,23 +68,34 @@ for filepath in integration_dir.rglob("*.md"):
frontmatter = match.group(1)
body = match.group(2)
else:
frontmatter = ""
body = content

if not body.strip():
print(f"[{filepath}] Error: Missing or invalid YAML frontmatter.")
has_errors = True
continue

file_errors = False

# 1. Check Frontmatter Title
title_source = frontmatter if frontmatter else content
title_match = re.search(r"^title:\s*[\"']?(.*?)[\"']?\s*$", title_source, re.MULTILINE)
if not title_match or not title_match.group(1).strip().endswith("Tool"):
found_title = title_match.group(1) if title_match else "None"
print(f"[{filepath}] Error: Frontmatter title must end with 'Tool'. Found: '{found_title}'")
file_errors = True
# --- 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

# 2. Check Shortcode Placement
# --- 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)):
Expand All @@ -94,16 +105,17 @@ for filepath in integration_dir.rglob("*.md"):
print(f"[{filepath}] Error: Shortcode found, but '## Compatible Sources' heading is missing.")
file_errors = True

# 3. Strip code blocks
clean_body = re.sub(r"^(?:```|~~~).*?^(?:```|~~~)", "", body, flags=re.DOTALL | re.MULTILINE)
# 2. Strip code blocks from body to avoid linting example markdown headings
clean_body = re.sub(r"```.*?```", "", body, flags=re.DOTALL)

# 4. Check H1 Headings
# 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

# 5. Check H2 Headings
h2s = [h.strip() for h in re.findall(r"^##\s+(.*)", clean_body, re.MULTILINE)]
# 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)):
Expand All @@ -127,9 +139,12 @@ for filepath in integration_dir.rglob("*.md"):
if file_errors:
has_errors = True

if has_errors:
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("Success: All Tool pages passed structure validation.")
print(f"Success: All {tools_pages_found} Tool page(s) passed structure validation.")
EOF
14 changes: 7 additions & 7 deletions .github/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ versionFile: "cmd/version.txt"
extraFiles: [
"README.md",
"docs/en/build-with-mcp-toolbox/colab_quickstart.ipynb",
"docs/en/user-guide/introduction/_index.md",
"docs/en/documentation/introduction/_index.md",
"docs/en/build-with-mcp-toolbox/mcp_quickstart/_index.md",
"docs/en/build-with-mcp-toolbox/quickstart/shared/configure_toolbox.md",
"docs/en/build-with-mcp-toolbox/alloydb/_index.md",
Expand All @@ -31,12 +31,12 @@ extraFiles: [
"docs/en/build-with-mcp-toolbox/looker/looker_gemini.md",
"docs/en/build-with-mcp-toolbox/looker/looker_gemini_oauth/_index.md",
"docs/en/build-with-mcp-toolbox/looker/looker_mcp_inspector/_index.md",
"docs/en/user-guide/connect-to/ides/looker_mcp.md",
"docs/en/user-guide/connect-to/ides/mysql_mcp.md",
"docs/en/user-guide/connect-to/ides/mssql_mcp.md",
"docs/en/user-guide/connect-to/ides/postgres_mcp.md",
"docs/en/user-guide/connect-to/ides/neo4j_mcp.md",
"docs/en/user-guide/connect-to/ides/sqlite_mcp.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",
"gemini-extension.json",
{
"type": "json",
Expand Down
2 changes: 1 addition & 1 deletion .hugo/assets/scss/_styles_project.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
@import 'components/layout';
@import 'components/header';
@import 'components/sidebar';
@import 'components/callouts';
@import 'components/callouts';
2 changes: 1 addition & 1 deletion .hugo/assets/scss/components/_header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ header, .td-navbar {
max-width: 500px !important;
margin: 0 !important;
z-index: 1060 !important;
transition: all 0.15s ease-out !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;
Expand Down
45 changes: 45 additions & 0 deletions .hugo/assets/scss/components/_sidebar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,49 @@ body.dark .td-page-meta a {
html[data-bs-theme="dark"] .td-page-meta,
body.dark .td-page-meta {
border-bottom-color: rgba(255,255,255,0.1);
}

/* ================================================================= */
/* INTEGRATIONS SIDEBAR LOCKS (DB Root, Tools, & Samples) */
/* ================================================================= */

/* LOCK ALL sections in Integrations
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 everything nested inside samples or tools (e.g., /samples/my-quickstart/) */
a.td-sidebar-link__section[href*="/samples/"],
a.td-sidebar-link__section[href*="/tools/"] {
pointer-events: auto !important;
cursor: pointer !important;
}

/* RE-LOCK exactly the structural "tools" and "samples" parent folders */
a.td-sidebar-link__section[href$="/tools/"],
a.td-sidebar-link__section[href$="/samples/"] {
pointer-events: none !important;
cursor: default !important;
}

/* ================================================================= */
/* REMOVE TAGS FROM RIGHT SIDEBAR & PAGE META */
/* ================================================================= */

.td-toc .taxonomy,
.td-toc .td-tags,
.td-toc [class*="taxonomy"],
.td-page-meta .taxonomy,
.td-page-meta .td-tags,
.td-page-meta [class*="taxonomy"],
.td-page-meta h5.taxonomy-tree-header,
.td-page-meta ul.taxonomy-terms {
display: none !important;
}
11 changes: 11 additions & 0 deletions .hugo/layouts/docs/redirect.html
Original file line number Diff line number Diff line change
@@ -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>
24 changes: 23 additions & 1 deletion .hugo/layouts/partials/hooks/body-end.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,29 @@
logoLink.href = "{{ site.Params.global_logo_url | default `/` }}";
}

// Build Navigation HTML
const sidebar = document.querySelector('#td-section-nav') || document.querySelector('.td-sidebar-nav') || document.querySelector('.td-sidebar');

if (sidebar) {
// 2. Find the currently active link
const activeLink = sidebar.querySelector('.active');

if (activeLink) {
// 3. Get exact pixel coordinates on the screen
const sidebarRect = sidebar.getBoundingClientRect();
const activeRect = activeLink.getBoundingClientRect();

// 4. Calculate exactly where the link is relative to the sidebar's current scroll
const linkTop = activeRect.top - sidebarRect.top + sidebar.scrollTop;

// 5. Center it mathematically
const centerPos = linkTop - (sidebar.clientHeight / 2) + (activeRect.height / 2);

// 6. Apply strictly to the sidebar container
sidebar.scrollTop = centerPos;
}
}

// 3. Build Navigation HTML (With 404 Safety Net & Double-Prefix Protection)
let leftNavItems = '';
let rightNavItems = '';

Expand Down
Loading
Loading