No link check runs in CI. Both link checker workflows are disabled at the GitHub Actions level, so a broken link now merges without warning and nobody files the weekly report issue. Run this skill yourself. Nothing else catches these.
The skill drives two tools, and each one misses what the other catches:
.md link to a pretty URL and generates links from shortcodes. It never checks an external URL.The skill covers the gap. It applies safe mechanical fixes to internal links. It reports external and ambiguous failures for a maintainer to decide.
lychee. Install with brew install lychee, or run docker run --rm -v "$PWD:/input" lycheeverse/lychee. Lychee is now the only external URL check anywhere in this repo. Without it, run the grep and build passes, then report that external URLs went unchecked.hugo, Extended v0.146.0 or later, for shortcode and build verification.Default scope is README.md and docs/en/. Sweep the full scope by default, because no CI pass has covered these files since the workflows went dark. Narrow to the changed markdown files only when the maintainer asks to check one PR.
DEVELOPER.md: canonical link rules. Its "Link Checking and Fixing with Lychee" section says the repo uses lychee for link checks. That claim is stale for CI. The link rules and the .lycheeignore guidance in it remain correct.references/link-forms.md: path-to-URL mapping, version leaks, and Hugo traps..lycheeignore: excluded domains and URLs. The local lychee CLI still reads this file, so it still applies. Every entry must carry a comment.link_checker.yaml and link_checker_report.yaml: both disabled. The YAML still declares a pull_request trigger and a weekly schedule, so read the workflow state, not the file. Check with gh api repos/googleapis/mcp-toolbox/actions/workflows. Treat these files as the reference for flags to reuse, not as a check that runs.Run lychee. Keep the two --exclude patterns. neo4j+ and bolt:// are database schemes, and lychee cannot fetch them:
lychee --quiet --no-progress --exclude '^neo4j\+.*' --exclude '^bolt://.*' README.md docs/
# For a PR, limit the scope to changed files:
git diff --name-only --diff-filter=ACMRT origin/main...HEAD -- '*.md'
Then grep for the structural problems lychee cannot see:
# Directory-style links. Hugo resolves these, lychee fails them.
grep -rnE "\]\(\.\.?/[^)]*\)" docs/en --include=*.md | grep -vE "\.md(#[^)]*)?\)"
# Site-absolute links. These leak across versioned deploys.
grep -rnE "\]\(/[^)]*\)" docs/en --include=*.md
# Hardcoded domain URLs.
grep -rn "https://mcp-toolbox.dev/" docs/en --include=*.md
# Section indexes with no `type: docs`. Docsy then renders no child links.
find docs/en -name _index.md \
-not -path '*/tools/*' -not -path '*/samples/*' -not -path '*/prebuilt-configs/*' \
-exec grep -L "^type: docs" {} +
The three excluded paths hold frontmatter-only wrapper files. CLAUDE.md requires them to stay minimal, so they are expected hits and not findings. Without the exclusions this check returns about 58 files instead of 1.
For a deep sweep, build the site and crawl the rendered HTML. This is the only pass that sees shortcode-generated links:
cd .hugo && hugo --minify --config hugo.cloudflare.toml
lychee --offline --base-url public public
Fix only safe, unambiguous internal links. Report everything else.
| Category | Action | Criteria |
|---|---|---|
| Safe to fix | Rewrite in place to a file-relative .md link |
• Directory link • Site-absolute [Text](/path/)• Hardcoded https://mcp-toolbox.dev/...• Moved file with exactly one obvious git successor • Renamed heading anchor |
| Needs decision | Report with a recommendation. Do not apply. | • Target is missing or deleted • Several candidate targets after a split or reorg • Link points into an ignoreFiles path (see hugo.toml)• _index.md has no type: docs |
| External | Report file:line, URL, and status |
• External URL returns 404, 403, or 500 |
| Ignore-worthy | Propose a commented .lycheeignore regex |
• Endpoint is auth-walled, rate-limited, or flaky |
Canonical link rule: use a file-relative path that ends in .md, for example [Example](../folder/file.md). Never use a site-absolute /... path or a directory /.../ path.
Check every modified file against both checkers:
lychee --quiet --no-progress --offline <modified-files>
cd .hugo && hugo --environment development
git checkout -b docs/fix-docsite-links
git commit -am "docs: fix broken docsite links"
Stop there. Print the report, then give the maintainer the push and PR commands to run.
lychee and hugo --environment development..md. Never add an internal link to .lycheeignore.## Docsite link sweep: <scope>, <X> findings
Checked: lychee (<status>) | Hugo build (<status>) | <N> files changed
**Fixed and verified** (<count>)
| file:line | was | now | reason |
**Needs your decision** (<count>)
| file:line | target | problem | recommendation |
**External, report only** (<count>)
| file:line | url | status |
**Proposed .lycheeignore entries** (<count>)
| pattern | reason |
**Structural** (<count>)
- <file>: <issue>
**Apply:**
git push -u origin docs/fix-docsite-links
gh pr create --title "docs: fix broken docsite links"