Skip to content

Commit 9d9357b

Browse files
Merge branch 'main' into pradeep-ramola/www-auth-param-boundary
2 parents ffdf974 + 1216c53 commit 9d9357b

23 files changed

Lines changed: 1275 additions & 632 deletions

.github/workflows/deploy-docs.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ on:
1414
- src/mcp/**
1515
- src/mcp-types/**
1616
- scripts/build-docs.sh
17+
- scripts/docs/**
1718
- pyproject.toml
1819
- uv.lock
1920
- .github/workflows/deploy-docs.yml
@@ -49,9 +50,6 @@ jobs:
4950

5051
- name: Build combined docs (v1.x at /, main at /v2/)
5152
run: bash scripts/build-docs.sh site
52-
env:
53-
# Silence mkdocs-material's MkDocs 2.0 warning banner in CI logs.
54-
NO_MKDOCS_2_WARNING: "1"
5553

5654
- name: Configure Pages
5755
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0

.github/workflows/docs-preview.yml

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
name: Docs Preview
22

3-
# Builds the mkdocs site for a PR and deploys it to Cloudflare Pages.
3+
# Builds the docs site for a PR and deploys it to Cloudflare Pages.
44
#
5-
# Security: mkdocs executes Python from the PR (mkdocstrings imports src/mcp,
6-
# `!!python/name:` directives). The build is gated by `authorize` (admin sender
7-
# for auto-preview, admin/maintainer commenter for /preview-docs) and isolated
8-
# from Cloudflare secrets — `build` runs PR code with no secrets and hands the
9-
# static site to `deploy` via an artifact, so PR code never shares a runner
10-
# with the Cloudflare token.
5+
# Security: the build executes Python from the PR (mkdocstrings imports
6+
# src/mcp, `!!python/name:` config directives run, and heads may ship their
7+
# own build scripts). The build is gated by `authorize` (admin sender for
8+
# auto-preview, admin/maintainer commenter for /preview-docs) and isolated
9+
# from Cloudflare secrets — `build` runs PR code with no secrets and hands
10+
# the static site to `deploy` via an artifact, so PR code never shares a
11+
# runner with the Cloudflare token.
1112
#
1213
# Required configuration:
1314
# - secrets.CLOUDFLARE_API_TOKEN (scope: Account → Cloudflare Pages → Edit)
@@ -21,6 +22,7 @@ on:
2122
- docs/**
2223
- docs_src/**
2324
- mkdocs.yml
25+
- scripts/docs/**
2426
- pyproject.toml
2527
issue_comment:
2628
types: [created]
@@ -128,17 +130,30 @@ jobs:
128130
enable-cache: false
129131
version: 0.9.5
130132

131-
- run: uv sync --frozen --group docs
132-
- run: uv run --frozen --no-sync mkdocs build
133-
env:
134-
# Silence mkdocs-material's MkDocs 2.0 warning banner in CI logs.
135-
NO_MKDOCS_2_WARNING: "1"
133+
# pull_request_target runs this workflow file from the base branch, so
134+
# the whole recipe — dependency sync included — must come from the
135+
# checkout itself: heads that ship scripts/docs/build.sh (the Zensical
136+
# toolchain) build with it; older heads, and v1.x heads previewed via
137+
# /preview-docs, still build with MkDocs. Both arms must write the site
138+
# to site/. Keep the detection in sync with build_site() in
139+
# scripts/build-docs.sh.
140+
- run: |
141+
if [ -f scripts/docs/build.sh ]; then
142+
bash scripts/docs/build.sh
143+
else
144+
uv sync --frozen --group docs
145+
# The env var silences mkdocs-material's MkDocs 2.0 warning banner.
146+
NO_MKDOCS_2_WARNING=1 uv run --frozen --no-sync mkdocs build
147+
fi
136148
137149
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
138150
with:
139151
name: site
140152
path: site/
141153
retention-days: 1
154+
# An empty site/ means the build arm broke its output contract; fail
155+
# here instead of surfacing as a confusing download error in deploy.
156+
if-no-files-found: error
142157

143158
deploy:
144159
needs: [authorize, build]

.github/workflows/shared.yml

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,23 @@ jobs:
1717
with:
1818
persist-credentials: false
1919

20-
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
20+
# setup-uv's manifest fetch is a single request with a hard 5s timeout
21+
# (astral-sh/setup-uv#869); retry once. Drop when upstream adds a retry.
22+
- name: Install uv
23+
id: setup-uv
24+
continue-on-error: true
25+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
26+
with:
27+
enable-cache: true
28+
version: 0.9.5
29+
30+
- name: Install uv (retry)
31+
if: steps.setup-uv.outcome == 'failure'
32+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
2133
with:
2234
enable-cache: true
2335
version: 0.9.5
36+
2437
- name: Install dependencies
2538
run: uv sync --frozen --all-extras --python 3.10
2639

@@ -44,8 +57,8 @@ jobs:
4457
name: test (${{ matrix.python-version }}, ${{ matrix.dep-resolution.name }}, ${{ matrix.os }})
4558
runs-on: ${{ matrix.os }}
4659
timeout-minutes: 10
47-
continue-on-error: true
4860
strategy:
61+
fail-fast: false
4962
matrix:
5063
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
5164
dep-resolution:
@@ -60,7 +73,18 @@ jobs:
6073
with:
6174
persist-credentials: false
6275

76+
# setup-uv's manifest fetch is a single request with a hard 5s timeout
77+
# (astral-sh/setup-uv#869); retry once. Drop when upstream adds a retry.
6378
- name: Install uv
79+
id: setup-uv
80+
continue-on-error: true
81+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
82+
with:
83+
enable-cache: true
84+
version: 0.9.5
85+
86+
- name: Install uv (retry)
87+
if: steps.setup-uv.outcome == 'failure'
6488
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
6589
with:
6690
enable-cache: true
@@ -92,7 +116,19 @@ jobs:
92116
with:
93117
persist-credentials: false
94118

95-
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
119+
# setup-uv's manifest fetch is a single request with a hard 5s timeout
120+
# (astral-sh/setup-uv#869); retry once. Drop when upstream adds a retry.
121+
- name: Install uv
122+
id: setup-uv
123+
continue-on-error: true
124+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
125+
with:
126+
enable-cache: true
127+
version: 0.9.5
128+
129+
- name: Install uv (retry)
130+
if: steps.setup-uv.outcome == 'failure'
131+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
96132
with:
97133
enable-cache: true
98134
version: 0.9.5
@@ -103,28 +139,39 @@ jobs:
103139
- name: Check README snippets are up to date
104140
run: uv run --frozen scripts/update_readme_snippets.py --check
105141

106-
# `mkdocs.yml` sets `strict: true` and `pymdownx.snippets: check_paths: true`,
107-
# but until this job existed the docs were only ever built post-merge by
108-
# `deploy-docs.yml`, so a broken link, a missing nav target, or a deleted
109-
# `docs_src/` include went green on the PR and broke the next deploy of main.
110-
# This is the check path; `deploy-docs.yml` stays the deploy path.
142+
# `scripts/docs/build.sh` is the whole gauntlet: build_config.py fails on
143+
# nav entries without a page and pages without a nav entry, `zensical build
144+
# --strict` fails on broken .md links, `pymdownx.snippets: check_paths:
145+
# true` fails on a deleted `docs_src/` include, and the post-build steps
146+
# fail on unresolved cross-references, inventory download failures, and
147+
# broken non-markdown link targets.
148+
# Until this job existed the docs were only ever built post-merge by
149+
# `deploy-docs.yml`, so those failures went green on the PR and broke the next
150+
# deploy of main. This is the check path; `deploy-docs.yml` stays the deploy
151+
# path.
111152
docs:
112153
runs-on: ubuntu-latest
113154
steps:
114155
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
115156
with:
116157
persist-credentials: false
117158

118-
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
159+
# setup-uv's manifest fetch is a single request with a hard 5s timeout
160+
# (astral-sh/setup-uv#869); retry once. Drop when upstream adds a retry.
161+
- name: Install uv
162+
id: setup-uv
163+
continue-on-error: true
164+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
119165
with:
120166
enable-cache: true
121167
version: 0.9.5
122168

123-
- name: Install dependencies
124-
run: uv sync --frozen --all-extras --python 3.10
169+
- name: Install uv (retry)
170+
if: steps.setup-uv.outcome == 'failure'
171+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
172+
with:
173+
enable-cache: true
174+
version: 0.9.5
125175

126176
- name: Build the docs in strict mode
127-
run: uv run --frozen --no-sync mkdocs build --strict
128-
env:
129-
# Silence mkdocs-material's MkDocs 2.0 warning banner in CI logs.
130-
NO_MKDOCS_2_WARNING: "1"
177+
run: bash scripts/docs/build.sh

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,13 @@ venv.bak/
141141
# Rope project settings
142142
.ropeproject
143143

144-
# mkdocs documentation
144+
# documentation
145145
/site
146146
/.worktrees/
147+
# Generated at build time by scripts/docs/ (the API reference tree and the
148+
# concrete Zensical config spliced from mkdocs.yml).
149+
/docs/api/
150+
/mkdocs.gen.yml
147151

148152
# mypy
149153
.mypy_cache/

RELEASE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ the publish job — `skip-existing` makes it skip whatever already landed. The
3939
commit — and therefore the README PyPI publishes — names the version
4040
being released. When entering a new phase (alpha → beta → rc), update
4141
the banner wording too.
42-
2. Check the full test matrix is green on the release commit. The matrix runs
43-
with `continue-on-error`, so a green workflow run does not mean the tests
44-
passed — check the individual jobs.
42+
2. Check the full test matrix is green on the release commit. The publish
43+
workflow re-runs the checks and blocks publishing until they pass, so a
44+
red leg there means re-running the failed jobs on the Publishing run.
4545
3. Create the release as a pre-release, passing the exact commit verified in
4646
step 2 as `--target` (otherwise the tag is created from whatever `main`'s
4747
HEAD is by then). The tagged commit determines everything about the

docs/extra.css

Lines changed: 70 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,42 @@
1-
/* Sidebar hierarchy + density for MkDocs Material 9.7.x.
2-
All rules scoped to the desktop sidebar breakpoint (>= 76.25em), matching
3-
Material's own scoping for navigation.sections, so the mobile drill-down
4-
drawer keeps stock styling. Colors use Material tokens, so the light and
5-
slate schemes both work without extra palette handling. */
1+
/* Sidebar hierarchy + density for Zensical's UI (Material-compatible md-*
2+
DOM, but different stock spacing: nav links are 8px-radius pills with
3+
7px 16px padding). All rules scoped to the desktop sidebar breakpoint
4+
(>= 76.25em) so the mobile drill-down drawer keeps stock styling. Colors
5+
use the md-* tokens, so the light and slate schemes both work without
6+
extra palette handling. */
67

78
@media screen and (min-width: 76.25em) {
8-
/* Section labels: smaller, uppercase, letter-spaced, muted. Covers both the
9-
clickable index-page headers and the bare API Reference label. */
9+
/* The sidebar is one coordinate system derived from the pill inset:
10+
every row — page links, group rows, section labels — is a direct
11+
.md-nav__link child of its item with the same 10px horizontal padding,
12+
so all text shares one column, and hover/active pills always paint
13+
10px of breathing room inside the scroll container (never clipped).
14+
The padding lives on the elements Zensical paints hover/active pills
15+
on (.md-nav__link[href] anchors and [for] labels — leaf links, bare
16+
section labels, and the inner anchor of an .md-nav__container
17+
wrapper); wrappers stay geometry-neutral, as stock. The 10px inset
18+
also stays >= the 0.4rem pill radius, so the corner curve never
19+
crowds the text. Vertical rhythm has a single knob: the nav list's
20+
flex gap (stock 0.2rem reads airy; 2px matches the density the site
21+
shipped with on Material, ~30px row pitch). */
22+
.md-sidebar--primary .md-nav__list {
23+
gap: 2px;
24+
}
25+
.md-sidebar--primary .md-nav__item > .md-nav__link:not(.md-nav__container),
26+
.md-sidebar--primary .md-nav__container > .md-nav__link {
27+
padding: 3px 10px;
28+
margin: 0;
29+
}
30+
.md-sidebar--primary .md-nav__item > .md-nav__container {
31+
padding: 0;
32+
margin: 0;
33+
}
34+
35+
/* Section labels: typography only — geometry comes from the row rule
36+
above, so no specificity coordination is needed. */
37+
.md-sidebar--primary .md-nav__item--section {
38+
margin: 0.8em 0;
39+
}
1040
.md-sidebar--primary .md-nav__item--section > .md-nav__link {
1141
font-size: 0.62rem;
1242
font-weight: 700;
@@ -15,39 +45,19 @@
1545
color: var(--md-default-fg-color--light);
1646
}
1747

18-
/* Indent section children and hang a guide line. Material outdents section
19-
children with [dir=ltr] ... margin-left: -0.6rem, which is why they sit
20-
flush under the header; restoring the margin re-exposes the stock 0.6rem
21-
list padding. The logical property ties Material's physical one at
22-
(0,3,0) specificity and wins on source order (extra_css loads last),
23-
while staying direction-agnostic. */
48+
/* Guide lines: 12px from the item box = 2px right of the label text
49+
(which sits at box + 10px pill inset); children indent past them. */
2450
.md-sidebar--primary .md-nav__item--section > .md-nav {
25-
margin-inline-start: 0.1rem;
51+
margin-inline-start: 12px;
2652
border-inline-start: 0.05rem solid var(--md-default-fg-color--lightest);
2753
}
28-
29-
/* Same guide line for collapsible groups inside the API Reference subtree
30-
(section items also carry --nested, so exclude them). */
3154
.md-sidebar--primary .md-nav__item--nested:not(.md-nav__item--section) > .md-nav {
55+
margin-inline-start: 12px;
3256
border-inline-start: 0.05rem solid var(--md-default-fg-color--lightest);
3357
}
3458

35-
/* Tighten vertical rhythm (stock: 0.625em link margins, 1.25em sections).
36-
The child combinator keeps this off anchors inside md-nav__container,
37-
which carry their own margin-top: 0 stock rule. */
38-
.md-sidebar--primary .md-nav__item > .md-nav__link {
39-
margin-top: 0.45em;
40-
}
41-
.md-sidebar--primary .md-nav__item--section {
42-
margin: 1em 0;
43-
}
44-
.md-sidebar--primary .md-nav__item--section > .md-nav__link {
45-
margin-top: 0;
46-
}
47-
48-
/* The current page stands out from its siblings. 700 because Material only
49-
loads Inter at 300/400/700; a 600 would silently substitute the 700 face
50-
anyway, but render lighter on the system-font fallback stack. */
59+
/* The current page stands out from its siblings (on top of the stock
60+
pill highlight). 700 because only Inter 300/400/700 are loaded. */
5161
.md-sidebar--primary .md-nav__link--active {
5262
font-weight: 700;
5363
}
@@ -60,10 +70,33 @@
6070
}
6171
}
6272

63-
/* Headings: Material's 300-weight light-gray defaults read washed out; use
64-
the full foreground color and a solid weight instead. 700, not 600: the
65-
Google Fonts request only carries Inter 300/400/700 (see the nav__link
66-
note above). */
73+
/* Dark scheme: Zensical's slate canvas is near-black (hsla(225,15%,5%)),
74+
harsher than the Material slate this site shipped with; restore that
75+
blue-grey. Code blocks and other surfaces keep Zensical's own tokens. */
76+
@media screen {
77+
[data-md-color-scheme="slate"] {
78+
--md-default-bg-color: #1e2129;
79+
}
80+
}
81+
82+
/* Inline code inside admonitions: the chip token is an absolute dark
83+
surface designed for the page canvas, so on a tinted admonition panel it
84+
sits as an opaque slab (Zensical's own docs share this bug). Re-tint it
85+
tone-on-tone instead — translucent foreground, composited over whatever
86+
the panel color is — the same pattern Starlight and Docusaurus ship for
87+
code inside callouts. Prose chips keep the block-matching dark surface;
88+
block code inside admonitions keeps its own surface too. The first
89+
declaration is the fallback where color-mix is unsupported. */
90+
.md-typeset .admonition :not(pre) > code,
91+
.md-typeset details :not(pre) > code {
92+
background-color: var(--md-default-fg-color--lightest);
93+
background-color: color-mix(in srgb, currentcolor 11%, transparent);
94+
color: inherit;
95+
}
96+
97+
/* Headings: the 300-weight light-gray defaults read washed out; use the
98+
full foreground color and a solid weight instead. 700, not 600: only
99+
Inter 300/400/700 are loaded (see the nav__link note above). */
67100
.md-typeset h1,
68101
.md-typeset h2 {
69102
font-weight: 700;

0 commit comments

Comments
 (0)