You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ep...@apache.org on 2026/10/06 18:16:10 UTC

(solr-mcp) branch main updated: docs(events): add Community over Code 2026 hackathon card (#244)

This is an automated email from the ASF dual-hosted git repository.

epugh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-mcp.git


The following commit(s) were added to refs/heads/main by this push:
     new 55c297c6 docs(events): add Community over Code 2026 hackathon card (#244)
55c297c6 is described below

commit 55c297c682f7275e29c944d6693231bf089379bb
Author: Aditya Parikh <ad...@gmail.com>
AuthorDate: Tue Oct 6 14:16:04 2026 -0400

    docs(events): add Community over Code 2026 hackathon card (#244)
    
    Adds the Glasgow hackathon card (site/), its publish and offline-serve
    scripts, and the booth table card. The card walks attendees through
    running the server as a jar or Docker image over stdio or http, and its
    "Going further" section covers building every variant, including the
    native binary and native Docker images.
    
    Signed-off-by: Aditya Parikh <ad...@gmail.com>
    Co-authored-by: Claude Sonnet 5.5 <no...@anthropic.com>
---
 docs/events/coc2026-glasgow/README.md              |   57 +
 docs/events/coc2026-glasgow/publish.sh             |   67 +
 docs/events/coc2026-glasgow/qr-pages.png           |  Bin 0 -> 1091 bytes
 docs/events/coc2026-glasgow/qr-pages.svg           |    2 +
 docs/events/coc2026-glasgow/serve-offline.sh       |   47 +
 docs/events/coc2026-glasgow/site/.nojekyll         |    0
 docs/events/coc2026-glasgow/site/index.html        | 3045 ++++++++++++++++++++
 docs/events/coc2026-glasgow/site/slides.html       |  203 ++
 docs/events/coc2026-glasgow/site/table-card.html   |   85 +
 .../coc2026-glasgow/solr-mcp-table-card.html       |   85 +
 10 files changed, 3591 insertions(+)

diff --git a/docs/events/coc2026-glasgow/README.md b/docs/events/coc2026-glasgow/README.md
new file mode 100644
index 00000000..02a62f4c
--- /dev/null
+++ b/docs/events/coc2026-glasgow/README.md
@@ -0,0 +1,57 @@
+# Solr MCP — Community over Code 2026, Glasgow
+
+Everything for the booth. The live card is what the QR points at:
+
+**https://adityamparikh.github.io/solr-mcp/** · [slides](https://adityamparikh.github.io/solr-mcp/slides.html)
+
+## Editing the card during the session
+
+`site/` is the source of truth. Edit `site/index.html` (or `site/slides.html`), then:
+
+```bash
+./publish.sh
+```
+
+It pushes to the `gh-pages` branch and then **polls the live URL until it serves your
+change** — usually 10–30s. It only reports success once the deployed bytes match, so a
+green message means attendees are really seeing the new version. If it times out it says
+so rather than lying, and points at the Actions tab.
+
+Running it with no changes is a no-op, so it is safe to re-run.
+
+The printed QR never changes — it points at the URL, not at a version — so you can edit
+freely without reprinting anything.
+
+## If the venue wifi cannot reach github.io
+
+```bash
+./serve-offline.sh          # or ./serve-offline.sh 8080
+```
+
+Serves `site/` from this laptop over the LAN and prints a QR for the address in the
+terminal. Attendees must be on the same network.
+
+The LAN address changes per network, so that QR is generated fresh on each run — **do not
+print it in advance**. The printed card's QR is the github.io one and will not work
+offline.
+
+## Files
+
+| | |
+|---|---|
+| `site/` | The card and slides. Edit here. |
+| `publish.sh` | Push `site/` live and verify it deployed. |
+| `serve-offline.sh` | Serve `site/` over the LAN with a scannable QR. |
+| `solr-mcp-table-card.html` | A5 table card. Open and ⌘P. |
+| `qr-pages.png` / `.svg` | The QR alone. Use the SVG for anything printed at size. |
+
+## Notes
+
+Pages are self-contained — the QR, styles and scripts are inlined, so they render with no
+external requests and work from a USB stick.
+
+The QR uses error-correction level H: roughly 30% of it can be scuffed or covered and it
+still scans. Reprint if it gets worse than that.
+
+The container image the card references is a personal pre-release build, not an Apache
+release. The card says so, and that framing should stay.
diff --git a/docs/events/coc2026-glasgow/publish.sh b/docs/events/coc2026-glasgow/publish.sh
new file mode 100755
index 00000000..3dbd90d4
--- /dev/null
+++ b/docs/events/coc2026-glasgow/publish.sh
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+#
+# Publish site/ to the gh-pages branch of adityamparikh/solr-mcp and verify the
+# change is actually live before reporting success.
+#
+#   ./publish.sh
+#
+# The live page is what attendees scan, so this checks the deployed bytes rather
+# than trusting the push. GitHub Pages takes ~10-60s to serve a new commit.
+
+set -euo pipefail
+
+REPO="git@github.com:adityamparikh/solr-mcp.git"
+REPO_HTTPS="https://github.com/adityamparikh/solr-mcp.git"
+BRANCH="gh-pages"
+LIVE="https://adityamparikh.github.io/solr-mcp/"
+HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+SITE="$HERE/site"
+
+[ -d "$SITE" ] || { echo "error: $SITE not found"; exit 1; }
+[ -f "$SITE/index.html" ] || { echo "error: $SITE/index.html not found"; exit 1; }
+
+# A marker that changes whenever the card changes, so the liveness check below
+# is testing *this* deploy rather than any old successful one.
+STAMP="$(shasum -a 256 "$SITE/index.html" | cut -c1-12)"
+echo "==> publishing site/ (index.html sha ${STAMP})"
+
+WORK="$(mktemp -d)"
+trap 'rm -rf "$WORK"' EXIT
+
+# Shallow-clone just the branch. Falls back to HTTPS if no SSH key is loaded.
+git clone --quiet --depth 1 --branch "$BRANCH" "$REPO" "$WORK/repo" 2>/dev/null \
+  || git clone --quiet --depth 1 --branch "$BRANCH" "$REPO_HTTPS" "$WORK/repo"
+
+# Mirror site/ into the checkout: --delete so removing a local file removes it
+# live too, but keep .git.
+rsync -a --delete --exclude '.git' "$SITE/" "$WORK/repo/"
+
+cd "$WORK/repo"
+if git diff --quiet && [ -z "$(git status --porcelain)" ]; then
+  echo "==> no changes; live site already matches site/"
+  exit 0
+fi
+
+git add -A
+git commit --quiet -m "Update hackathon card and slides (${STAMP})"
+git push --quiet origin "$BRANCH"
+echo "==> pushed"
+
+# GitHub Pages serves the old bytes for a while after the push. Poll for the new
+# ones rather than declaring victory immediately.
+echo -n "==> waiting for $LIVE to serve the new build "
+for _ in $(seq 1 40); do
+  LIVE_SHA="$(curl -fsS --max-time 10 "$LIVE" 2>/dev/null | shasum -a 256 | cut -c1-12 || true)"
+  if [ "$LIVE_SHA" = "$STAMP" ]; then
+    echo
+    echo "==> LIVE and verified: $LIVE"
+    exit 0
+  fi
+  echo -n "."
+  sleep 5
+done
+
+echo
+echo "!! pushed, but $LIVE has not served the new build within ~3min."
+echo "   Check https://github.com/adityamparikh/solr-mcp/actions (pages-build-deployment)."
+exit 1
diff --git a/docs/events/coc2026-glasgow/qr-pages.png b/docs/events/coc2026-glasgow/qr-pages.png
new file mode 100644
index 00000000..4d36a74d
Binary files /dev/null and b/docs/events/coc2026-glasgow/qr-pages.png differ
diff --git a/docs/events/coc2026-glasgow/qr-pages.svg b/docs/events/coc2026-glasgow/qr-pages.svg
new file mode 100644
index 00000000..63d5bbb4
--- /dev/null
+++ b/docs/events/coc2026-glasgow/qr-pages.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="900" height="900" class="segno"><g transform="scale(20)"><path fill="#fff" d="M0 0h45v45h-45z"/><path class="qrline" stroke="#15171a" d="M4 4.5h7m3 0h2m1 0h1m1 0h1m3 0h2m2 0h1m2 0h1m3 0h7m-37 1h1m5 0h1m1 0h2m2 0h1m2 0h1m2 0h1m2 0h1m1 0h1m1 0h2m3 0h1m5 0h1m-37 1h1m1 0h3m1 0h1m1 0h1m2 0h5m2 0h1m1 0h1m1 0h1m1 0h3m1 0h1m1 0h1m1 0h3m1 0h1m-37 1h1m1 0h3m1 0h1m1 0h1m2 0h7m1 0h3m1 0h4m3 0h1m1 0h3m1 0h1m-37 1h1m1 0h3m1 0h1m1 0h2m1 0h1m1 0h3m1 0h1m1 0 [...]
diff --git a/docs/events/coc2026-glasgow/serve-offline.sh b/docs/events/coc2026-glasgow/serve-offline.sh
new file mode 100755
index 00000000..e73721a0
--- /dev/null
+++ b/docs/events/coc2026-glasgow/serve-offline.sh
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+#
+# Serve the card from this laptop over the local network, for when the venue
+# wifi cannot reach github.io. Prints a QR for the LAN URL so people can still
+# scan rather than type.
+#
+#   ./serve-offline.sh [port]     (default 8000)
+#
+# The LAN address changes with the network, so this generates the QR fresh each
+# run — do not print it in advance.
+
+set -euo pipefail
+
+PORT="${1:-8000}"
+HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+SITE="$HERE/site"
+
+[ -d "$SITE" ] || { echo "error: $SITE not found"; exit 1; }
+
+# First non-loopback IPv4 on a real interface.
+IP="$(ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null || true)"
+if [ -z "$IP" ]; then
+  IP="$(ifconfig 2>/dev/null | awk '/inet /&&$2!="127.0.0.1"{print $2; exit}')"
+fi
+[ -n "$IP" ] || { echo "error: could not determine a LAN address — are you on wifi?"; exit 1; }
+
+URL="http://${IP}:${PORT}/"
+
+echo "==> serving $SITE at $URL"
+echo "    (attendees must be on the same network; this address changes per network)"
+echo
+
+if python3 -c "import segno" 2>/dev/null; then
+  python3 - "$URL" <<'PY'
+import sys, segno
+segno.make(sys.argv[1], error='m').terminal(compact=True)
+PY
+  echo
+else
+  echo "    (pip3 install segno for a scannable QR here)"
+  echo
+fi
+
+echo "    Ctrl-C to stop."
+echo
+cd "$SITE"
+exec python3 -m http.server "$PORT" --bind 0.0.0.0
diff --git a/docs/events/coc2026-glasgow/site/.nojekyll b/docs/events/coc2026-glasgow/site/.nojekyll
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/events/coc2026-glasgow/site/index.html b/docs/events/coc2026-glasgow/site/index.html
new file mode 100644
index 00000000..d7e905df
--- /dev/null
+++ b/docs/events/coc2026-glasgow/site/index.html
@@ -0,0 +1,3045 @@
+<meta charset="utf-8">
+<title>Solr MCP — Hackathon Card, Community over Code 2026</title>
+<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20650%201000'%3E%3Cpath%20fill='%23dd552c'%20d='M276.7092915,398.1515795c25.5279479-63.1242453,54.0110775-126.1246793,84.5729347-181.9636035-45.6212286-33.8852148-89.4276433-106.9534674-107.4259055-139.3268803-6.4642564,7.3439687-10.6608099,15.8022396-12.563595,22.6835448-16.9556402,61.2214602,43.4023987,135.1498759-5.21311,108.1394499-40.5058645-22.5076023-131.7157397-71.797557-166.506 [...]
+
+<style>
+/* ============================================================
+   Tokens — Glasgow School of Art / Mackintosh palette
+   ============================================================ */
+:root {
+  --ground:   #F1F3F0;
+  --surface:  #FFFFFF;
+  --surface-2:#E8EBE6;
+  --ink:      #15171A;
+  --ink-2:    #585C63;
+  --ink-3:    #83888F;
+  --rule:     #D3D8D1;
+  --rule-2:   #BFC5BD;
+  --accent:   #9E2A4E;
+  --accent-soft: #9E2A4E14;
+  --sage:     #4F6650;
+  --sage-soft:#4F665014;
+  --amber:    #7E5A0D;
+  --amber-soft:#7E5A0D14;
+  --code-bg:  #14161A;
+  --code-ink: #DFE3DC;
+  --shadow:   0 1px 2px #15171A0d, 0 6px 18px #15171A0a;
+
+  --f-display: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
+  --f-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+  --f-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
+
+  --measure: 68ch;
+  --spine: 2.75rem;
+
+  /* Brand plate: the CoC wordmark is deep purple, so on dark grounds the
+     official artwork needs a white card behind it to stay legible. */
+  --brand-chip: transparent;
+  --brand-chip-pad: 0;
+
+  /* Community over Code event lockup. Navy is the brand colour; the dark
+     variants are lightened so the lockup survives on a near-black ground. */
+  --event-ink: #282662;
+  --tag-a: #F79A23;
+  --tag-b: #D22128;
+  --tag-c: #7C297D;
+}
+
+@media (prefers-color-scheme: dark) {
+  :root {
+    --ground:   #131417;
+    --surface:  #1A1C20;
+    --surface-2:#22252A;
+    --ink:      #E9EBE6;
+    --ink-2:    #A0A5AC;
+    --ink-3:    #7C828A;
+    --rule:     #2A2D32;
+    --rule-2:   #3A3E45;
+    --accent:   #E4778F;
+    --accent-soft: #E4778F1a;
+    --sage:     #93AC93;
+    --sage-soft:#93AC931a;
+    --amber:    #C9A24A;
+    --amber-soft:#C9A24A1a;
+    --code-bg:  #0D0E11;
+    --code-ink: #DFE3DC;
+    --shadow:   0 1px 2px #0006, 0 6px 20px #0004;
+
+    --brand-chip: #FFFFFF;
+    --brand-chip-pad: .5rem .7rem;
+
+    --event-ink: #B9B7E4;
+    --tag-a: #F9AE4E;
+    --tag-b: #E8555C;
+    --tag-c: #C071C2;
+  }
+}
+:root[data-theme="dark"] {
+  --ground:#131417; --surface:#1A1C20; --surface-2:#22252A;
+  --ink:#E9EBE6; --ink-2:#A0A5AC; --ink-3:#7C828A;
+  --rule:#2A2D32; --rule-2:#3A3E45;
+  --accent:#E4778F; --accent-soft:#E4778F1a;
+  --sage:#93AC93; --sage-soft:#93AC931a;
+  --amber:#C9A24A; --amber-soft:#C9A24A1a;
+  --code-bg:#0D0E11; --code-ink:#DFE3DC;
+  --shadow:0 1px 2px #0006, 0 6px 20px #0004;
+  --brand-chip:#FFFFFF; --brand-chip-pad:.5rem .7rem;
+  --event-ink:#B9B7E4; --tag-a:#F9AE4E; --tag-b:#E8555C; --tag-c:#C071C2;
+}
+:root[data-theme="light"] {
+  --ground:#F1F3F0; --surface:#FFFFFF; --surface-2:#E8EBE6;
+  --ink:#15171A; --ink-2:#585C63; --ink-3:#83888F;
+  --rule:#D3D8D1; --rule-2:#BFC5BD;
+  --accent:#9E2A4E; --accent-soft:#9E2A4E14;
+  --sage:#4F6650; --sage-soft:#4F665014;
+  --amber:#7E5A0D; --amber-soft:#7E5A0D14;
+  --code-bg:#14161A; --code-ink:#DFE3DC;
+  --shadow:0 1px 2px #15171A0d, 0 6px 18px #15171A0a;
+  --brand-chip:transparent; --brand-chip-pad:0;
+  --event-ink:#282662; --tag-a:#F79A23; --tag-b:#D22128; --tag-c:#7C297D;
+}
+
+* { box-sizing: border-box; }
+
+body {
+  margin: 0;
+  background: var(--ground);
+  color: var(--ink);
+  font-family: var(--f-body);
+  font-size: 16px;
+  line-height: 1.6;
+  -webkit-text-size-adjust: 100%;
+}
+
+.wrap { max-width: 62rem; margin: 0 auto; padding: 0 1.25rem 6rem; }
+
+/* ============================================================
+   Masthead
+   ============================================================ */
+.masthead {
+  padding: 3rem 0 2rem;
+  border-bottom: 1px solid var(--rule);
+  display: grid;
+  gap: 1.25rem;
+}
+.event-lockup { display: grid; gap: .3rem; }
+.event-when {
+  margin: 0;
+  font-size: .8rem; font-weight: 700; font-style: italic;
+  letter-spacing: .085em; text-transform: uppercase;
+  color: var(--event-ink);
+}
+.event-when .dot { padding: 0 1.05em; }
+.event-tag {
+  margin: 0;
+  font-size: .86rem; font-weight: 700; letter-spacing: .005em;
+}
+/* Each word runs its own orange->purple sweep, as in the event artwork. */
+.event-tag span {
+  background-image: linear-gradient(95deg, var(--tag-a) 0%, var(--tag-b) 55%, var(--tag-c) 100%);
+  -webkit-background-clip: text; background-clip: text;
+  color: transparent;
+}
+@supports not (background-clip: text) { .event-tag span { color: var(--tag-b); } }
+
+.eyebrow {
+  font-size: .7rem;
+  letter-spacing: .18em;
+  text-transform: uppercase;
+  color: var(--ink-3);
+  font-weight: 600;
+  margin: 0;
+}
+.masthead h1 {
+  font-family: var(--f-display);
+  font-weight: 400;
+  font-size: clamp(2.1rem, 6vw, 3.4rem);
+  line-height: 1.08;
+  letter-spacing: -.015em;
+  margin: 0;
+  text-wrap: balance;
+}
+.masthead h1 em { font-style: normal; color: var(--accent); }
+.standfirst {
+  margin: 0;
+  max-width: var(--measure);
+  color: var(--ink-2);
+  font-size: 1.06rem;
+}
+.mast-meta {
+  display: flex; flex-wrap: wrap; gap: .5rem 1.5rem;
+  font-size: .8rem; color: var(--ink-3);
+  padding-top: .25rem;
+}
+.mast-meta b { color: var(--ink-2); font-weight: 600; }
+
+/* ------------------------------------------------------------
+   Event branding — Community over Code wordmark + the ASF events
+   banner from apachecon.com/event-images. Both are inlined as data
+   URIs so the card still renders with no network (serve-offline.sh).
+   ------------------------------------------------------------ */
+.brandbar {
+  display: flex; align-items: center; flex-wrap: wrap;
+  gap: .9rem 1.4rem;
+  margin-bottom: 1.6rem;
+}
+.brandbar a {
+  display: inline-flex; line-height: 0;
+  background: var(--brand-chip);
+  padding: var(--brand-chip-pad);
+  border-radius: 4px;
+  transition: opacity .15s ease;
+}
+.brandbar a:hover { opacity: .82; }
+.brandbar img { display: block; width: auto; }
+.coc-mark img  { height: 38px; }
+
+/* The apachecon.com banner ships as opaque-white RGB, so unlike the
+   wordmark it needs a plate in BOTH themes, not just on dark. */
+.ac-banner {
+  background: #FFFFFF !important;
+  padding: .4rem .55rem !important;
+  border: 1px solid var(--rule);
+}
+.ac-banner img { height: 26px; }
+@media (max-width: 30rem) {
+  .coc-mark img  { height: 30px; }
+  .ac-banner img { height: 22px; }
+}
+
+/* Footer events row — the ASF asks project sites to carry a link to
+   upcoming events; this is that link. */
+.footer-events {
+  display: flex; align-items: center; flex-wrap: wrap;
+  gap: .75rem 1.1rem; margin-top: 1.1rem;
+}
+.footer-events .lbl { font-size: .72rem; color: var(--ink-3); }
+
+/* Hashtag chips */
+.tags { display: flex; flex-wrap: wrap; gap: .35rem; }
+.tags span {
+  font-size: .71rem; font-weight: 600; letter-spacing: .005em;
+  color: var(--accent);
+  background: var(--accent-soft);
+  border: 1px solid color-mix(in srgb, var(--accent) 24%, transparent);
+  border-radius: 999px;
+  padding: .17rem .58rem;
+  white-space: nowrap;
+}
+.share-text .tags { margin-top: .3rem; }
+.footer .tags { margin-top: .9rem; }
+
+
+
+/* Share block — the card shows its own QR so a visitor can take it to their phone */
+.share {
+  display: flex; align-items: center; gap: 1rem;
+  border: 1px solid var(--rule); border-radius: 3px;
+  background: var(--surface); padding: .85rem 1rem;
+  margin-top: .25rem; max-width: 34rem;
+}
+.share img {
+  width: 88px; height: 88px; flex: none;
+  background: #fff; border-radius: 2px; padding: 4px;
+}
+.share-text { display: grid; gap: .2rem; }
+.share-text b { font-size: .82rem; }
+.share-text span { font-size: .76rem; color: var(--ink-2); line-height: 1.45; }
+.share-text a { font-size: .72rem; word-break: break-all; }
+
+/* ============================================================
+   Sticky nav
+   ============================================================ */
+.navbar {
+  position: sticky; top: 0; z-index: 40;
+  background: color-mix(in srgb, var(--ground) 92%, transparent);
+  backdrop-filter: blur(10px);
+  border-bottom: 1px solid var(--rule);
+  margin-bottom: 2.5rem;
+}
+.navbar-inner {
+  max-width: 62rem; margin: 0 auto; padding: .55rem 1.25rem;
+  display: flex; align-items: center; gap: .85rem;
+  overflow-x: auto; scrollbar-width: thin;
+}
+.navbar a {
+  font-size: .78rem; color: var(--ink-2); text-decoration: none;
+  white-space: nowrap; padding: .25rem 0;
+  border-bottom: 2px solid transparent;
+  font-variant-numeric: tabular-nums;
+}
+.navbar a:hover { color: var(--accent); border-bottom-color: var(--accent); }
+.navbar a b { color: var(--ink-3); font-weight: 600; margin-right: .3rem; }
+
+/* ============================================================
+   Step spine
+   ============================================================ */
+.step {
+  position: relative;
+  padding-left: var(--spine);
+  padding-bottom: 2.75rem;
+  border-left: 1px solid var(--rule);
+  margin-left: .75rem;
+}
+.step:last-of-type { border-left-color: transparent; }
+.step-num {
+  position: absolute; left: 0; top: .1rem;
+  transform: translateX(-50%);
+  width: 1.7rem; height: 1.7rem; border-radius: 50%;
+  background: var(--surface); border: 1px solid var(--rule-2);
+  color: var(--ink-2);
+  display: grid; place-items: center;
+  font-size: .78rem; font-weight: 700;
+  font-variant-numeric: tabular-nums;
+}
+.step[data-done="1"] .step-num {
+  background: var(--accent); border-color: var(--accent); color: #fff;
+}
+.step-head { display: flex; align-items: baseline; gap: .75rem; flex-wrap: wrap; }
+.step h2 {
+  font-family: var(--f-display); font-weight: 400;
+  font-size: 1.62rem; line-height: 1.2; letter-spacing: -.01em;
+  margin: 0 0 .15rem; text-wrap: balance;
+}
+.step .kicker {
+  font-size: .7rem; letter-spacing: .14em; text-transform: uppercase;
+  color: var(--ink-3); font-weight: 600;
+}
+.step > p { max-width: var(--measure); }
+.done-toggle {
+  margin-left: auto; display: inline-flex; align-items: center; gap: .4rem;
+  font-size: .72rem; color: var(--ink-3); cursor: pointer;
+  user-select: none; white-space: nowrap;
+  border: 1px solid var(--rule); border-radius: 2px;
+  padding: .18rem .5rem; background: transparent;
+  font-family: var(--f-body);
+}
+.done-toggle:hover { border-color: var(--accent); color: var(--accent); }
+.step[data-done="1"] .done-toggle { border-color: var(--accent); color: var(--accent); }
+.step[data-done="1"] > .step-body { opacity: .55; }
+
+.panel-body h4 { font-size: .95rem; margin: 1.1rem 0 .35rem; color: var(--ink); }
+h3 {
+  font-family: var(--f-body);
+  font-size: .82rem; letter-spacing: .1em; text-transform: uppercase;
+  color: var(--ink-2); font-weight: 700;
+  margin: 1.75rem 0 .6rem;
+}
+p { margin: 0 0 .9rem; max-width: var(--measure); }
+a { color: var(--accent); }
+
+/* ============================================================
+   Code
+   ============================================================ */
+.codeblock { position: relative; margin: 0 0 1rem; }
+.codeblock pre {
+  margin: 0; padding: .95rem 3.4rem .95rem 1rem;
+  background: var(--code-bg); color: var(--code-ink);
+  border-radius: 3px; overflow-x: auto;
+  font-family: var(--f-mono); font-size: .82rem; line-height: 1.62;
+  tab-size: 2;
+}
+.codeblock pre code { font: inherit; color: inherit; }
+.tok-c { color: #7E8B93; }
+.tok-s { color: #B9C99A; }
+.tok-k { color: #E4778F; }
+.copy {
+  position: absolute; top: .5rem; right: .5rem;
+  font-family: var(--f-body); font-size: .66rem; letter-spacing: .07em;
+  text-transform: uppercase; font-weight: 600;
+  color: #A9B0AE; background: #FFFFFF14;
+  border: 1px solid #FFFFFF24; border-radius: 2px;
+  padding: .22rem .45rem; cursor: pointer;
+}
+.copy:hover { color: #fff; background: #FFFFFF24; }
+.copy[data-copied="1"] { color: #B9C99A; border-color: #B9C99A66; }
+code.inline {
+  font-family: var(--f-mono); font-size: .87em;
+  background: var(--surface-2); border-radius: 2px;
+  padding: .08em .32em;
+}
+
+/* ============================================================
+   Selectors (OS / runtime / client)
+   ============================================================ */
+.picker {
+  background: var(--surface); border: 1px solid var(--rule);
+  border-radius: 3px; padding: .9rem 1rem; margin: 0 0 1.5rem;
+  display: grid; gap: .85rem; box-shadow: var(--shadow);
+}
+.picker-row { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
+.picker-label {
+  font-size: .68rem; letter-spacing: .13em; text-transform: uppercase;
+  color: var(--ink-3); font-weight: 700; width: 5.2rem; flex: none;
+}
+.seg { display: flex; flex-wrap: wrap; gap: .3rem; }
+.seg button {
+  font-family: var(--f-body); font-size: .78rem;
+  padding: .3rem .7rem; cursor: pointer;
+  background: transparent; color: var(--ink-2);
+  border: 1px solid var(--rule-2); border-radius: 2px;
+}
+.seg button:hover { border-color: var(--accent); color: var(--accent); }
+.seg button[aria-pressed="true"] {
+  background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600;
+}
+
+/* ============================================================
+   Callouts
+   ============================================================ */
+.note {
+  border-left: 3px solid var(--rule-2);
+  background: var(--surface);
+  padding: .8rem 1rem; margin: 0 0 1.15rem;
+  border-radius: 0 3px 3px 0;
+  max-width: var(--measure);
+}
+.note p:last-child { margin-bottom: 0; }
+.note .tag {
+  display: inline-block; font-size: .64rem; letter-spacing: .13em;
+  text-transform: uppercase; font-weight: 700; margin-bottom: .3rem;
+}
+.note.warn { border-left-color: var(--amber); background: var(--amber-soft); }
+.note.warn .tag { color: var(--amber); }
+.note.good { border-left-color: var(--sage); background: var(--sage-soft); }
+.note.good .tag { color: var(--sage); }
+.note.bug  { border-left-color: var(--accent); background: var(--accent-soft); }
+.note.bug .tag { color: var(--accent); }
+
+/* ============================================================
+   Prompt cards — the things people actually say out loud
+   ============================================================ */
+.prompts { display: grid; gap: .55rem; margin-bottom: 1.1rem; }
+.prompt {
+  display: grid; grid-template-columns: 1fr auto; align-items: center;
+  gap: .75rem;
+  background: var(--surface); border: 1px solid var(--rule);
+  border-radius: 3px; padding: .6rem .55rem .6rem .9rem;
+}
+.prompt q {
+  font-family: var(--f-display); font-size: 1.02rem; quotes: '"' '"';
+  line-height: 1.4;
+}
+.prompt .why {
+  display: block; font-family: var(--f-body); font-size: .7rem;
+  color: var(--ink-3); letter-spacing: .04em; margin-top: .18rem;
+}
+.prompt .why b { color: var(--ink-2); font-family: var(--f-mono); font-weight: 400; }
+.copy-sm {
+  font-family: var(--f-body); font-size: .64rem; letter-spacing: .07em;
+  text-transform: uppercase; font-weight: 600;
+  color: var(--ink-3); background: transparent;
+  border: 1px solid var(--rule-2); border-radius: 2px;
+  padding: .28rem .5rem; cursor: pointer; white-space: nowrap;
+}
+.copy-sm:hover { color: var(--accent); border-color: var(--accent); }
+.copy-sm[data-copied="1"] { color: var(--sage); border-color: var(--sage); }
+
+/* ============================================================
+   Tables
+   ============================================================ */
+.tablewrap { overflow-x: auto; margin: 0 0 1.15rem; }
+table { border-collapse: collapse; width: 100%; font-size: .85rem; }
+th, td {
+  text-align: left; padding: .45rem .7rem;
+  border-bottom: 1px solid var(--rule);
+  vertical-align: top;
+}
+th {
+  font-size: .68rem; letter-spacing: .1em; text-transform: uppercase;
+  color: var(--ink-3); font-weight: 700; white-space: nowrap;
+}
+td code { font-family: var(--f-mono); font-size: .8rem; }
+tbody tr:last-child td { border-bottom: none; }
+.num { font-variant-numeric: tabular-nums; text-align: right; }
+
+/* ============================================================
+   Details
+   ============================================================ */
+details.panel {
+  background: var(--surface); border: 1px solid var(--rule);
+  border-radius: 3px; margin: 0 0 1rem; overflow: hidden;
+}
+details.panel > summary {
+  cursor: pointer; padding: .8rem 1rem;
+  font-weight: 600; font-size: .95rem;
+  display: flex; align-items: center; gap: .6rem;
+  list-style: none;
+}
+details.panel > summary::-webkit-details-marker { display: none; }
+details.panel > summary::before {
+  content: "▸"; color: var(--accent); font-size: .8rem;
+  transition: transform .15s ease;
+}
+details.panel[open] > summary::before { transform: rotate(90deg); }
+details.panel > summary .hint {
+  font-weight: 400; font-size: .78rem; color: var(--ink-3); margin-left: auto;
+}
+.panel-body { padding: 0 1rem 1rem; border-top: 1px solid var(--rule); padding-top: 1rem; }
+
+/* ============================================================
+   Footer
+   ============================================================ */
+.footer {
+  border-top: 1px solid var(--rule); margin-top: 2rem;
+  padding-top: 1.5rem; font-size: .8rem; color: var(--ink-3);
+}
+.footer a { color: var(--ink-2); }
+
+.linkrow { display: flex; flex-wrap: wrap; gap: .5rem 1.25rem; margin-bottom: 1rem; }
+
+@media (prefers-reduced-motion: reduce) {
+  * { animation: none !important; transition: none !important; }
+}
+:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
+
+@media (max-width: 620px) {
+  .picker-label { width: auto; }
+  .prompt { grid-template-columns: 1fr; }
+  .prompt .copy-sm { justify-self: start; }
+  .step { padding-left: 1.6rem; margin-left: .6rem; }
+}
+</style>
+
+<div class="navbar">
+  <div class="navbar-inner">
+    <a href="#s0"><b>0</b>Prereqs</a>
+    <a href="#s1"><b>1</b>Solr</a>
+    <a href="#s2"><b>2</b>Connect</a>
+    <a href="#s3"><b>3</b>Schemaless</a>
+    <a href="#s4"><b>4</b>The wall</a>
+    <a href="#s5"><b>5</b>Real schema</a>
+    <a href="#s6"><b>6</b>Queries</a>
+    <a href="#s7"><b>7</b>Introspect</a>
+    <a href="#more">Going further</a>
+  </div>
+</div>
+
+<div class="wrap">
+
+<header class="masthead">
+  <div class="brandbar">
+    <a class="coc-mark" href="https://communityovercode.org/"
+       title="Community over Code">
+      <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAnYAAADNCAYAAAA1xj1mAABy4UlEQVR4XuydCXwcZd3Hg7x4veKNByLN7k6yc21aDLeiFUFREYE2SQERVCyK8EpfsG2yc22SHlCoioKkSVrAu4qviqIcXqCAUJA2V48cvQu9jxy72ST7/v8zs+3kye7OzO7OZtM+38/n/0mbzHPMzHP85jn+T0kJhUKhUCgUCoVCoVAoFAqFQqFQKBQKhUKhUCgUCoVCoVAoFAqFQqFQKBQKhZInTiJ/QaFQKBQKhUIpchKrS06OP8xcEF/JLB1a4X9yaEXgX0NNgefg519jzYGfjTxU9pXog/4yMhyFQqFQKBQKpYiIN5ddOtzM/D3WEhhJPFyWGFvFJMZWHrME/B9/P9LC7IHrftjf7Hs/GQeFQqFQKBQKZRLZ2XT6W+OtgbtHVjIxFG/R5kBiaEV6G2 [...]
+    </a>
+  </div>
+  <div class="event-lockup">
+    <p class="event-when">October 11–14, 2026<span class="dot">·</span>Glasgow, Scotland</p>
+    <p class="event-tag"><span>#Community</span><span>OverCode</span></p>
+  </div>
+  <h1>Talk to <em>Apache Solr</em> in plain English.</h1>
+  <p class="standfirst">
+    Solr MCP is a Model Context Protocol server that hands your AI assistant twelve Solr
+    tools — search, indexing, schema and collection management. This card takes you from
+    nothing installed to running faceted queries against a real index. Ten minutes, no
+    Solr syntax required.
+  </p>
+  <div class="mast-meta">
+    <span><b>Time</b> ~10 min</span>
+    <span><b>Needs</b> Docker, or Java 25</span>
+    <span><b>Dataset</b> 61 streaming shows</span>
+    <span><b>Tools</b> 12 · 2 resources · 6 prompts</span>
+  </div>
+  <div class="share">
+    <img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iOTAwIiBjbGFzcz0ic2Vnbm8iPjxnIHRyYW5zZm9ybT0ic2NhbGUoMjApIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMCAwaDQ1djQ1aC00NXoiLz48cGF0aCBjbGFzcz0icXJsaW5lIiBzdHJva2U9IiMxNTE3MWEiIGQ9Ik00IDQuNWg3bTMgMGgybTEgMGgxbTEgMGgxbTMgMGgybTIgMGgxbTIgMGgxbTMgMGg3bS0zNyAxaDFtNSAwaDFtMSAwaDJtMiAwaDFtMiAwaDFtMiAwaDFtMiAwaDFtMSAwaDFtMSAwaDJtMyAwaDFtNSAw [...]
+    <span class="share-text">
+      <b>Take this with you</b>
+      <span>Scan to open this card on your phone, then work through it beside your laptop.</span>
+      <a href="https://adityamparikh.github.io/solr-mcp/">adityamparikh.github.io/solr-mcp</a>
+      <div class="tags">
+        <span>#CommunityOverCode</span><span>#ASF2026Glasgow</span><span>#OpenSource</span>
+      </div>
+    </span>
+  </div>
+</header>
+
+<div style="height:1.5rem"></div>
+
+<!-- ══════════════════════════════════════════════════════════
+     STEP 0
+     ══════════════════════════════════════════════════════════ -->
+<section class="step" id="s0" data-step="0">
+  <div class="step-num">0</div>
+  <div class="step-body">
+    <div class="step-head">
+      <div>
+        <p class="kicker">Before anything else</p>
+        <h2>Pick your runtime</h2>
+      </div>
+      <button class="done-toggle" type="button">Mark done</button>
+    </div>
+    <p>
+      Two ways to run the server. Docker is the shorter road — the image carries its own
+      Java, so nothing on your machine needs to change.
+    </p>
+
+    <div class="picker">
+      <div class="picker-row">
+        <span class="picker-label">Your OS</span>
+        <div class="seg" data-group="os">
+          <button type="button" data-val="macos" aria-pressed="true">macOS</button>
+          <button type="button" data-val="windows" aria-pressed="false">Windows</button>
+          <button type="button" data-val="linux" aria-pressed="false">Linux</button>
+          <button type="button" data-val="wsl" aria-pressed="false">WSL</button>
+        </div>
+      </div>
+      <div class="picker-row">
+        <span class="picker-label">Run via</span>
+        <div class="seg" data-group="runtime">
+          <button type="button" data-val="docker" aria-pressed="true">Docker</button>
+          <button type="button" data-val="jar" aria-pressed="false">JAR</button>
+        </div>
+      </div>
+      <div class="picker-row">
+        <span class="picker-label">Transport</span>
+        <div class="seg" data-group="transport">
+          <button type="button" data-val="stdio" aria-pressed="true">STDIO</button>
+          <button type="button" data-val="http" aria-pressed="false">HTTP</button>
+        </div>
+      </div>
+      <div class="picker-row">
+        <span class="picker-label">Data format</span>
+        <div class="seg" data-group="format">
+          <button type="button" data-val="json" aria-pressed="true">JSON</button>
+          <button type="button" data-val="csv" aria-pressed="false">CSV</button>
+          <button type="button" data-val="xml" aria-pressed="false">XML</button>
+          <button type="button" data-val="markdown" aria-pressed="false">Markdown</button>
+        </div>
+      </div>
+      <div class="picker-row">
+        <span class="picker-label">Client</span>
+        <div class="seg" data-group="client">
+          <button type="button" data-val="claude-code" aria-pressed="true">Claude Code</button>
+          <button type="button" data-val="codex" aria-pressed="false">Codex</button>
+          <button type="button" data-val="chatgpt" aria-pressed="false">ChatGPT</button>
+          <button type="button" data-val="claude-desktop" aria-pressed="false">Claude Desktop</button>
+          <button type="button" data-val="copilot" aria-pressed="false">VS Code / Copilot</button>
+          <button type="button" data-val="cursor" aria-pressed="false">Cursor</button>
+          <button type="button" data-val="zed" aria-pressed="false">Zed</button>
+          <button type="button" data-val="intellij" aria-pressed="false">IntelliJ</button>
+          <button type="button" data-val="inspector" aria-pressed="false">MCP Inspector</button>
+        </div>
+      </div>
+    </div>
+
+    <p style="font-size:.85rem;color:var(--ink-3)">
+      These five choices drive every command on this page. Set them once. STDIO is the
+      default: your client launches the server as a subprocess, nothing else to run. HTTP starts
+      one server in a terminal that any client connects to over a URL. The data format picks
+      which flavour of the same 61-show dataset step 3 indexes; JSON is the path the rest of the
+      card was written against.
+    </p>
+
+    <div data-runtime="docker">
+      <h3>Check Docker</h3>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>docker --version</code></pre></div>
+      <p>Any recent version works. If this errors, install
+        <a href="https://docs.docker.com/get-docker/">Docker Desktop</a> (macOS/Windows) or
+        Docker Engine (Linux).</p>
+    </div>
+
+    <div data-runtime="jar">
+      <h3>Check your Java</h3>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>java -version</code></pre></div>
+
+      <div class="note warn">
+        <span class="tag">Read this before you file a bug</span>
+        <p>
+          The JAR is built for <b>Java 25</b>. On Java 17 or 21 it fails with
+          <code class="inline">UnsupportedClassVersionError</code> — and because STDIO servers
+          are launched silently by your client, most clients show only
+          <em>"server failed to start"</em> with no reason. If the server won't connect,
+          check this first.
+        </p>
+      </div>
+
+      <h3>Get the JAR</h3>
+      <p>
+        Prebuilt, so you don't need Git or Gradle — 53 MB:
+      </p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>curl -LO https://github.com/adityamparikh/solr-mcp/releases/download/coc2026/solr-mcp-1.0.0-SNAPSHOT.jar</code></pre></div>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        Built from the same commit as the container image, so both paths behave identically.
+        Unofficial pre-release build — not an Apache release.
+      </p>
+
+      <h3>Install Java 25</h3>
+      <div class="codeblock" data-os="macos"><button class="copy" type="button">Copy</button><pre><code><span class="tok-c"># SDKMAN (recommended — no sudo, easy to switch back)</span>
+sdk install java 25-tem
+
+<span class="tok-c"># or Homebrew</span>
+brew install --cask temurin@25</code></pre></div>
+      <div class="codeblock" data-os="windows"><button class="copy" type="button">Copy</button><pre><code>winget install EclipseAdoptium.Temurin.25.JDK</code></pre></div>
+      <div class="codeblock" data-os="linux wsl"><button class="copy" type="button">Copy</button><pre><code><span class="tok-c"># SDKMAN (recommended — no sudo, easy to switch back)</span>
+curl -s "https://get.sdkman.io" | bash
+source "$HOME/.sdkman/bin/sdkman-init.sh"
+sdk install java 25-tem</code></pre></div>
+    </div>
+  </div>
+</section>
+
+<!-- ══════════════════════════════════════════════════════════
+     STEP 1
+     ══════════════════════════════════════════════════════════ -->
+<section class="step" id="s1" data-step="1">
+  <div class="step-num">1</div>
+  <div class="step-body">
+    <div class="step-head">
+      <div>
+        <p class="kicker">One container, no clone</p>
+        <h2>Start Solr</h2>
+      </div>
+      <button class="done-toggle" type="button">Mark done</button>
+    </div>
+    <p>
+      SolrCloud mode with embedded ZooKeeper, which is what the collection tools need.
+      Ready in about seven seconds.
+    </p>
+
+    <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>docker run -d --name solr-mcp-demo -p 8983:8983 solr:9-slim solr start -c -f</code></pre></div>
+
+    <p data-runtime="docker">
+      While that boots, pull the server image too. It is ~155 MB, and several clients time out
+      a first connection at 60 seconds — long enough to look like a broken config when it is
+      really just a download:
+    </p>
+    <div class="codeblock" data-runtime="docker"><button class="copy" type="button">Copy</button><pre><code>docker pull ghcr.io/adityamparikh/solr-mcp:coc2026</code></pre></div>
+
+    <p>Confirm Solr is up — this should return an empty collection list:</p>
+    <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>curl "http://localhost:8983/solr/admin/collections?action=LIST"</code></pre></div>
+
+    <p>
+      The admin UI is at <a href="http://localhost:8983/solr/">localhost:8983/solr</a>.
+      Keep it open on a second screen if you have one — watching collections appear as you
+      talk to the assistant is half the fun.
+    </p>
+
+    <div class="note good">
+      <span class="tag">Why not docker compose</span>
+      <p>
+        The repo ships a <code class="inline">compose.yaml</code>, but it exists to preload the
+        <em>films</em> and <em>books</em> samples. This card builds its own collection from
+        scratch, so a single container is all you need — no clone, no Gradle, no build.
+      </p>
+    </div>
+  </div>
+</section>
+
+<!-- ══════════════════════════════════════════════════════════
+     STEP 2
+     ══════════════════════════════════════════════════════════ -->
+<section class="step" id="s2" data-step="2">
+  <div class="step-num">2</div>
+  <div class="step-body">
+    <div class="step-head">
+      <div>
+        <p class="kicker">Wire it to your assistant</p>
+        <h2>Connect the server</h2>
+      </div>
+      <button class="done-toggle" type="button">Mark done</button>
+    </div>
+
+    <p>
+      Commands below follow the choices you made in step 0. Change them there and
+      everything on this page updates.
+    </p>
+
+    <div class="note warn" data-runtime="docker">
+      <span class="tag">Unofficial build</span>
+      <p>
+        <code class="inline">ghcr.io/adityamparikh/solr-mcp:coc2026</code> is a personal
+        pre-release build for this event — <b>not</b> an Apache release. Apache Solr MCP is an
+        incubating project and has not cut a voted release yet, so no official image exists.
+        Building it yourself is covered under <a href="#build-local">Build it yourself</a>.
+      </p>
+    </div>
+
+    <div class="note warn" data-runtime="jar" data-os="windows wsl" data-transport="stdio">
+      <span class="tag">Windows paths in JSON</span>
+      <p>
+        Backslashes are escape characters in JSON, so a Windows path must double them —
+        <code class="inline">"C:\\\\Users\\\\you\\\\solr-mcp-1.0.0-SNAPSHOT.jar"</code> — or just
+        use forward slashes, which Java accepts on Windows:
+        <code class="inline">"C:/Users/you/solr-mcp-1.0.0-SNAPSHOT.jar"</code>. Forward slashes
+        are the safer habit.
+      </p>
+    </div>
+
+    <!-- ── HTTP: start the server once ── -->
+    <div data-transport="http">
+      <h3>Start the server</h3>
+      <p>
+        In HTTP mode the server is a process you start yourself, in a terminal you keep open.
+        Every client then connects to <code class="inline">http://localhost:8080/mcp</code>.
+      </p>
+      <div class="codeblock" data-runtime="docker"><button class="copy" type="button">Copy</button><pre><code>docker run -p 8080:8080 --rm \
+  --add-host=host.docker.internal:host-gateway \
+  -e PROFILES=http \
+  -e HTTP_SECURITY_ENABLED=false \
+  -e SOLR_URL=http://host.docker.internal:8983/solr/ \
+  ghcr.io/adityamparikh/solr-mcp:coc2026</code></pre></div>
+      <div class="codeblock" data-runtime="jar" data-os="macos linux wsl"><button class="copy" type="button">Copy</button><pre><code>PROFILES=http HTTP_SECURITY_ENABLED=false SOLR_URL=http://localhost:8983/solr/ \
+  java -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar</code></pre></div>
+      <div class="codeblock" data-runtime="jar" data-os="windows"><button class="copy" type="button">Copy</button><pre><code><span class="tok-c"># PowerShell</span>
+$env:PROFILES="http"; $env:HTTP_SECURITY_ENABLED="false"; $env:SOLR_URL="http://localhost:8983/solr/"
+java -jar C:/path/to/solr-mcp-1.0.0-SNAPSHOT.jar</code></pre></div>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        Ready when <code class="inline">curl http://localhost:8080/actuator/health</code> answers
+        <code class="inline">{"status":"UP"}</code>. Leave the terminal open; closing it stops the
+        server for every client.
+      </p>
+      <div class="note warn">
+        <span class="tag">Security is off on purpose</span>
+        <p>
+          HTTP mode requires OAuth2 out of the box (<code class="inline">HTTP_SECURITY_ENABLED</code>
+          defaults to <b>true</b>, the issuer is <code class="inline">OAUTH2_ISSUER_URI</code>).
+          <code class="inline">HTTP_SECURITY_ENABLED=false</code> is for a laptop at a hackathon,
+          bound to localhost. Never run it that way on anything other people can reach; the
+          <a href="#secured">Security setup</a> panel below walks through turning it on, start to finish.
+        </p>
+      </div>
+      <h3>Point your client at it</h3>
+    </div>
+
+    <!-- ── Claude Code ── -->
+    <div data-client="claude-code">
+      <h3>Claude Code</h3>
+      <div data-transport="stdio">
+      <div class="codeblock" data-runtime="docker"><button class="copy" type="button">Copy</button><pre><code>claude mcp add solr-mcp -- docker run -i --rm \
+  --add-host=host.docker.internal:host-gateway \
+  -e SOLR_URL=http://host.docker.internal:8983/solr/ \
+  ghcr.io/adityamparikh/solr-mcp:coc2026</code></pre></div>
+      <div class="codeblock" data-runtime="jar"><button class="copy" type="button">Copy</button><pre><code>claude mcp add solr-mcp \
+  -e SOLR_URL=http://localhost:8983/solr/ \
+  -- java -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar</code></pre></div>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        The <code class="inline">--</code> matters: it stops Claude Code reading the server's
+        own flags as its own. Verify with <code class="inline">claude mcp list</code>.
+      </p>
+      </div>
+      <div data-transport="http">
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>claude mcp add --transport http solr-mcp http://localhost:8080/mcp</code></pre></div>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        Verify with <code class="inline">claude mcp list</code>; the server must already be running
+        or the entry shows as failed.
+      </p>
+      </div>
+    </div>
+
+    <!-- ── Codex ── -->
+    <div data-client="codex">
+      <h3>Codex</h3>
+      <div data-transport="stdio">
+      <div class="codeblock" data-runtime="docker"><button class="copy" type="button">Copy</button><pre><code>codex mcp add solr-mcp -- docker run -i --rm \
+  --add-host=host.docker.internal:host-gateway \
+  -e SOLR_URL=http://host.docker.internal:8983/solr/ \
+  ghcr.io/adityamparikh/solr-mcp:coc2026</code></pre></div>
+      <div class="codeblock" data-runtime="jar"><button class="copy" type="button">Copy</button><pre><code>codex mcp add solr-mcp \
+  --env SOLR_URL=http://localhost:8983/solr/ \
+  -- java -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar</code></pre></div>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        The <code class="inline">--</code> separates Codex's flags from the server's launch
+        command. This writes a <code class="inline">[mcp_servers.solr-mcp]</code> table to
+        <code class="inline">~/.codex/config.toml</code>, which you can also edit by hand.
+        Verify with <code class="inline">codex mcp list</code>, or <code class="inline">/mcp</code>
+        inside a Codex session.
+      </p>
+      <p style="font-size:.86rem;color:var(--ink-2)" data-runtime="docker">
+        Codex gives a server about 10 seconds to start. A cold <code class="inline">docker run</code>
+        that has to pull the image will miss that — run
+        <code class="inline">docker pull ghcr.io/adityamparikh/solr-mcp:coc2026</code> once first, or add
+        <code class="inline">startup_timeout_sec = 60</code> to the
+        <code class="inline">[mcp_servers.solr-mcp]</code> table.
+      </p>
+      </div>
+      <div data-transport="http">
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>codex mcp add solr-mcp --url http://localhost:8080/mcp</code></pre></div>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        Codex prints <q>may or may not require login</q> — ignore it, there is no login on an
+        unsecured server. Verify with <code class="inline">codex mcp list</code>; the server must
+        already be running when Codex starts.
+      </p>
+      </div>
+    </div>
+
+    <!-- ── ChatGPT ── -->
+    <div data-client="chatgpt">
+      <h3>ChatGPT</h3>
+      <div data-transport="stdio" class="note warn">
+        <span class="tag">HTTP only</span>
+        <p>
+          ChatGPT connects to a <em>remote</em> MCP server; it cannot launch a local process, so
+          there is no STDIO setup. Switch <b>Transport</b> to <b>HTTP</b> above.
+        </p>
+      </div>
+      <div data-transport="http">
+        <div class="note warn">
+          <span class="tag">Needs a public HTTPS URL</span>
+          <p>
+            <code class="inline">localhost</code> is unreachable from ChatGPT. Put a tunnel in front of
+            the server you started above, for example
+            <code class="inline">ngrok http 8080</code>, and use the <code class="inline">https://…</code>
+            address it prints with <code class="inline">/mcp</code> appended. Because security is off on
+            this server, <b>anyone with that URL can read and change your Solr</b> — demo against a
+            throwaway Solr and stop the tunnel afterwards. ChatGPT supports OAuth 2.1 or no auth only,
+            and this server's secured mode is not compatible yet, so there is no secured variant.
+          </p>
+        </div>
+        <ol style="font-size:.92rem;color:var(--ink-2);line-height:1.6">
+          <li><b>Settings → Security and login</b>: turn on <b>Developer mode</b> (Pro, Plus, Business, Enterprise, Education; workspace admins may need to allow it).</li>
+          <li>Open <b>ChatGPT Plugins</b>, press <b>+</b>, and create a developer-mode app.</li>
+          <li>Name it <code class="inline">solr-mcp</code>; under <b>Connection</b> enter <code class="inline">https://&lt;your-tunnel&gt;/mcp</code>; choose <b>No authentication</b>.</li>
+          <li>Create it and check the discovered tools — there should be twelve. Enable the app in a chat.</li>
+        </ol>
+        <p style="font-size:.86rem;color:var(--ink-2)">
+          ChatGPT asks before running tools it treats as writes, which is most of them. After
+          restarting the server, press <b>Refresh</b> on the app.
+        </p>
+      </div>
+    </div>
+
+    <!-- ── Claude Desktop ── -->
+    <div data-client="claude-desktop">
+      <h3>Claude Desktop</h3>
+      <div data-transport="stdio">
+      <p>Edit the config file, then <b>fully quit and reopen</b> Claude Desktop:</p>
+      <div class="codeblock" data-os="macos"><button class="copy" type="button">Copy</button><pre><code>~/Library/Application Support/Claude/claude_desktop_config.json</code></pre></div>
+      <div class="codeblock" data-os="windows wsl"><button class="copy" type="button">Copy</button><pre><code>%APPDATA%\Claude\claude_desktop_config.json</code></pre></div>
+      <div class="codeblock" data-os="linux"><button class="copy" type="button">Copy</button><pre><code>~/.config/Claude/claude_desktop_config.json</code></pre></div>
+      <div class="note warn" data-os="wsl">
+        <span class="tag">On WSL</span>
+        <p>
+          Claude Desktop runs on the Windows host, not inside WSL — so use the Windows path above
+          and let Docker Desktop's WSL integration handle the container.
+        </p>
+      </div>
+      <div class="note warn" data-os="linux">
+        <span class="tag">On Linux</span>
+        <p>
+          There is no official Claude Desktop build for Linux. That path is where community
+          builds look; if you're on Linux, <b>Claude Code</b> or <b>MCP Inspector</b> is the
+          smoother route.
+        </p>
+      </div>
+
+      <div class="codeblock" data-runtime="docker"><button class="copy" type="button">Copy</button><pre><code>{
+  <span class="tok-s">"mcpServers"</span>: {
+    <span class="tok-s">"solr-mcp"</span>: {
+      <span class="tok-s">"command"</span>: <span class="tok-s">"docker"</span>,
+      <span class="tok-s">"args"</span>: [<span class="tok-s">"run"</span>, <span class="tok-s">"-i"</span>, <span class="tok-s">"--rm"</span>,
+               <span class="tok-s">"--add-host=host.docker.internal:host-gateway"</span>,
+               <span class="tok-s">"-e"</span>, <span class="tok-s">"SOLR_URL=http://host.docker.internal:8983/solr/"</span>,
+               <span class="tok-s">"ghcr.io/adityamparikh/solr-mcp:coc2026"</span>]
+    }
+  }
+}</code></pre></div>
+      <div class="codeblock" data-runtime="jar"><button class="copy" type="button">Copy</button><pre><code>{
+  <span class="tok-s">"mcpServers"</span>: {
+    <span class="tok-s">"solr-mcp"</span>: {
+      <span class="tok-s">"command"</span>: <span class="tok-s">"java"</span>,
+      <span class="tok-s">"args"</span>: [<span class="tok-s">"-jar"</span>, <span class="tok-s">"/absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar"</span>],
+      <span class="tok-s">"env"</span>: { <span class="tok-s">"SOLR_URL"</span>: <span class="tok-s">"http://localhost:8983/solr/"</span> }
+    }
+  }
+}</code></pre></div>
+      </div>
+      <div data-transport="http">
+      <p>
+        Claude Desktop's own <b>Connectors</b> screen cannot reach this server: custom connectors
+        are contacted from Anthropic's cloud, not from your laptop, so a
+        <code class="inline">localhost</code> URL is unreachable there and would need a public
+        HTTPS tunnel <em>and</em> OAuth switched back on. Skip that. Bridge it locally instead with
+        the <code class="inline">mcp-remote</code> shim (needs Node.js), which talks plain HTTP to
+        the server on your machine:
+      </p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>{
+  <span class="tok-s">"mcpServers"</span>: {
+    <span class="tok-s">"solr-mcp"</span>: {
+      <span class="tok-s">"command"</span>: <span class="tok-s">"npx"</span>,
+      <span class="tok-s">"args"</span>: [<span class="tok-s">"-y"</span>, <span class="tok-s">"mcp-remote"</span>, <span class="tok-s">"http://localhost:8080/mcp"</span>]
+    }
+  }
+}</code></pre></div>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        Same config file as the STDIO route (paths above). Then fully quit and reopen Claude
+        Desktop. No ngrok needed.
+      </p>
+      </div>
+    </div>
+
+    <!-- ── VS Code ── -->
+    <div data-client="copilot">
+      <h3>VS Code / GitHub Copilot</h3>
+      <div data-transport="stdio">
+      <p>
+        Needs VS Code 1.99+. Create <code class="inline">.vscode/mcp.json</code> in your
+        workspace, then use Copilot Chat in <b>Agent</b> mode.
+      </p>
+      <div class="codeblock" data-runtime="docker"><button class="copy" type="button">Copy</button><pre><code>{
+  <span class="tok-s">"servers"</span>: {
+    <span class="tok-s">"solr-mcp"</span>: {
+      <span class="tok-s">"type"</span>: <span class="tok-s">"stdio"</span>,
+      <span class="tok-s">"command"</span>: <span class="tok-s">"docker"</span>,
+      <span class="tok-s">"args"</span>: [<span class="tok-s">"run"</span>, <span class="tok-s">"-i"</span>, <span class="tok-s">"--rm"</span>,
+               <span class="tok-s">"--add-host=host.docker.internal:host-gateway"</span>,
+               <span class="tok-s">"-e"</span>, <span class="tok-s">"SOLR_URL=http://host.docker.internal:8983/solr/"</span>,
+               <span class="tok-s">"ghcr.io/adityamparikh/solr-mcp:coc2026"</span>]
+    }
+  }
+}</code></pre></div>
+      <div class="codeblock" data-runtime="jar"><button class="copy" type="button">Copy</button><pre><code>{
+  <span class="tok-s">"servers"</span>: {
+    <span class="tok-s">"solr-mcp"</span>: {
+      <span class="tok-s">"type"</span>: <span class="tok-s">"stdio"</span>,
+      <span class="tok-s">"command"</span>: <span class="tok-s">"java"</span>,
+      <span class="tok-s">"args"</span>: [<span class="tok-s">"-jar"</span>, <span class="tok-s">"/absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar"</span>],
+      <span class="tok-s">"env"</span>: { <span class="tok-s">"SOLR_URL"</span>: <span class="tok-s">"http://localhost:8983/solr/"</span> }
+    }
+  }
+}</code></pre></div>
+      </div>
+      <div data-transport="http">
+      <p>Same file, <code class="inline">.vscode/mcp.json</code>, with a URL instead of a command:</p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>{
+  <span class="tok-s">"servers"</span>: {
+    <span class="tok-s">"solr-mcp"</span>: {
+      <span class="tok-s">"type"</span>: <span class="tok-s">"http"</span>,
+      <span class="tok-s">"url"</span>: <span class="tok-s">"http://localhost:8080/mcp"</span>
+    }
+  }
+}</code></pre></div>
+      </div>
+    </div>
+
+    <!-- ── Cursor ── -->
+    <div data-client="cursor">
+      <h3>Cursor</h3>
+      <div data-transport="stdio">
+      <p>Create <code class="inline">.cursor/mcp.json</code> in your project root:</p>
+      <div class="codeblock" data-runtime="docker"><button class="copy" type="button">Copy</button><pre><code>{
+  <span class="tok-s">"mcpServers"</span>: {
+    <span class="tok-s">"solr-mcp"</span>: {
+      <span class="tok-s">"command"</span>: <span class="tok-s">"docker"</span>,
+      <span class="tok-s">"args"</span>: [<span class="tok-s">"run"</span>, <span class="tok-s">"-i"</span>, <span class="tok-s">"--rm"</span>,
+               <span class="tok-s">"--add-host=host.docker.internal:host-gateway"</span>,
+               <span class="tok-s">"-e"</span>, <span class="tok-s">"SOLR_URL=http://host.docker.internal:8983/solr/"</span>,
+               <span class="tok-s">"ghcr.io/adityamparikh/solr-mcp:coc2026"</span>]
+    }
+  }
+}</code></pre></div>
+      <div class="codeblock" data-runtime="jar"><button class="copy" type="button">Copy</button><pre><code>{
+  <span class="tok-s">"mcpServers"</span>: {
+    <span class="tok-s">"solr-mcp"</span>: {
+      <span class="tok-s">"command"</span>: <span class="tok-s">"java"</span>,
+      <span class="tok-s">"args"</span>: [<span class="tok-s">"-jar"</span>, <span class="tok-s">"/absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar"</span>],
+      <span class="tok-s">"env"</span>: { <span class="tok-s">"SOLR_URL"</span>: <span class="tok-s">"http://localhost:8983/solr/"</span> }
+    }
+  }
+}</code></pre></div>
+      </div>
+      <div data-transport="http">
+      <p>Same file, <code class="inline">.cursor/mcp.json</code>, with a URL instead of a command:</p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>{
+  <span class="tok-s">"mcpServers"</span>: {
+    <span class="tok-s">"solr-mcp"</span>: { <span class="tok-s">"url"</span>: <span class="tok-s">"http://localhost:8080/mcp"</span> }
+  }
+}</code></pre></div>
+      </div>
+    </div>
+
+    <!-- ── Zed ── -->
+    <div data-client="zed">
+      <h3>Zed</h3>
+      <div data-transport="stdio">
+      <p>
+        Zed has a form for this, which is the easier route here — no JSON, and it validates as
+        you type. <b>Settings → AI → General → MCP Servers → Add Server → Add Local MCP
+        Server</b>, then:
+      </p>
+      <div class="tablewrap" data-runtime="docker">
+        <table>
+          <tbody>
+            <tr><th>Server Name</th><td><code>solr-mcp</code></td></tr>
+            <tr><th>Command</th><td><code>docker</code></td></tr>
+            <tr><th>Arguments</th><td><code>run -i --rm --add-host=host.docker.internal:host-gateway -e SOLR_URL=http://host.docker.internal:8983/solr/ ghcr.io/adityamparikh/solr-mcp:coc2026</code></td></tr>
+            <tr><th>Timeout</th><td>60 is fine <em>once the image is pulled</em> — see below</td></tr>
+          </tbody>
+        </table>
+      </div>
+      <div class="tablewrap" data-runtime="jar">
+        <table>
+          <tbody>
+            <tr><th>Server Name</th><td><code>solr-mcp</code></td></tr>
+            <tr><th>Command</th><td><code>java</code></td></tr>
+            <tr><th>Arguments</th><td><code>-jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar</code></td></tr>
+            <tr><th>Environment Variables</th><td><code>SOLR_URL</code> = <code>http://localhost:8983/solr/</code></td></tr>
+          </tbody>
+        </table>
+      </div>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        Arguments go in <b>one field, space-separated</b> — not as a JSON array. The form writes
+        the same settings entry you'd hand-edit below.
+      </p>
+      <div class="note warn" data-runtime="docker">
+        <span class="tag">Pull the image first</span>
+        <p>
+          Zed's default timeout is <b>60 seconds</b>, and a first run has to fetch ~155 MB. On
+          venue wifi that can outrun the timeout, and the failure looks like a broken config
+          rather than a slow download. Run
+          <code class="inline">docker pull ghcr.io/adityamparikh/solr-mcp:coc2026</code> once
+          beforehand and the problem disappears.
+        </p>
+      </div>
+      <p>
+        Prefer JSON? <code class="inline">zed: open settings</code> from the command palette,
+        and add a <code class="inline">context_servers</code> entry:
+      </p>
+      <div class="codeblock" data-runtime="docker"><button class="copy" type="button">Copy</button><pre><code>{
+  <span class="tok-s">"context_servers"</span>: {
+    <span class="tok-s">"solr-mcp"</span>: {
+      <span class="tok-s">"command"</span>: <span class="tok-s">"docker"</span>,
+      <span class="tok-s">"args"</span>: [<span class="tok-s">"run"</span>, <span class="tok-s">"-i"</span>, <span class="tok-s">"--rm"</span>,
+               <span class="tok-s">"--add-host=host.docker.internal:host-gateway"</span>,
+               <span class="tok-s">"-e"</span>, <span class="tok-s">"SOLR_URL=http://host.docker.internal:8983/solr/"</span>,
+               <span class="tok-s">"ghcr.io/adityamparikh/solr-mcp:coc2026"</span>],
+      <span class="tok-s">"env"</span>: {}
+    }
+  }
+}</code></pre></div>
+      <div class="codeblock" data-runtime="jar"><button class="copy" type="button">Copy</button><pre><code>{
+  <span class="tok-s">"context_servers"</span>: {
+    <span class="tok-s">"solr-mcp"</span>: {
+      <span class="tok-s">"command"</span>: <span class="tok-s">"java"</span>,
+      <span class="tok-s">"args"</span>: [<span class="tok-s">"-jar"</span>, <span class="tok-s">"/absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar"</span>],
+      <span class="tok-s">"env"</span>: { <span class="tok-s">"SOLR_URL"</span>: <span class="tok-s">"http://localhost:8983/solr/"</span> }
+    }
+  }
+}</code></pre></div>
+      <div class="note good">
+        <span class="tag">Zed schema — verified 1.16.2</span>
+        <p>
+          Verified against Zed <b>1.16.2</b>. Zed's schema takes <code class="inline">command</code>
+          as a plain string with <code class="inline">args</code> and
+          <code class="inline">env</code> alongside it — there is no
+          <code class="inline">source</code> key, and adding one makes Zed's settings
+          validator reject the entry. If a future release moves this again, check
+          <a href="https://zed.dev/docs/ai/mcp">zed.dev/docs/ai/mcp</a>.
+        </p>
+      </div>
+      </div>
+      <div data-transport="http">
+      <p>
+        <b>Settings → AI → General → MCP Servers → Add Server → Add Remote Server</b>, name it
+        <code class="inline">solr-mcp</code>, URL <code class="inline">http://localhost:8080/mcp</code>.
+        Or in <code class="inline">zed: open settings</code>:
+      </p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>{
+  <span class="tok-s">"context_servers"</span>: {
+    <span class="tok-s">"solr-mcp"</span>: { <span class="tok-s">"url"</span>: <span class="tok-s">"http://localhost:8080/mcp"</span> }
+  }
+}</code></pre></div>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        Zed starts an OAuth flow only if the server answers 401. With security off it will not; if
+        it prompts anyway, add <code class="inline">"headers": { "Authorization": "Bearer x" }</code>
+        to the entry, which the server ignores.
+      </p>
+      </div>
+    </div>
+
+    <!-- ── IntelliJ ── -->
+    <div data-client="intellij">
+      <h3>IntelliJ IDEA / JetBrains</h3>
+      <div data-transport="stdio">
+      <p>Needs the AI Assistant plugin. Either drop a file at
+        <code class="inline">.junie/mcp/mcp.json</code> in your project root:</p>
+      <div class="codeblock" data-runtime="docker"><button class="copy" type="button">Copy</button><pre><code>{
+  <span class="tok-s">"mcpServers"</span>: {
+    <span class="tok-s">"solr-mcp"</span>: {
+      <span class="tok-s">"command"</span>: <span class="tok-s">"docker"</span>,
+      <span class="tok-s">"args"</span>: [<span class="tok-s">"run"</span>, <span class="tok-s">"-i"</span>, <span class="tok-s">"--rm"</span>,
+               <span class="tok-s">"--add-host=host.docker.internal:host-gateway"</span>,
+               <span class="tok-s">"-e"</span>, <span class="tok-s">"SOLR_URL=http://host.docker.internal:8983/solr/"</span>,
+               <span class="tok-s">"ghcr.io/adityamparikh/solr-mcp:coc2026"</span>]
+    }
+  }
+}</code></pre></div>
+      <div class="codeblock" data-runtime="jar"><button class="copy" type="button">Copy</button><pre><code>{
+  <span class="tok-s">"mcpServers"</span>: {
+    <span class="tok-s">"solr-mcp"</span>: {
+      <span class="tok-s">"command"</span>: <span class="tok-s">"java"</span>,
+      <span class="tok-s">"args"</span>: [<span class="tok-s">"-jar"</span>, <span class="tok-s">"/absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar"</span>],
+      <span class="tok-s">"env"</span>: { <span class="tok-s">"SOLR_URL"</span>: <span class="tok-s">"http://localhost:8983/solr/"</span> }
+    }
+  }
+}</code></pre></div>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        …or add it through <b>Settings → Tools → AI Assistant → MCP Servers → Add</b>,
+        transport <b>STDIO</b>.
+      </p>
+      </div>
+      <div data-transport="http">
+      <p>Same file, <code class="inline">.junie/mcp/mcp.json</code>, with a URL instead of a command:</p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>{
+  <span class="tok-s">"mcpServers"</span>: {
+    <span class="tok-s">"solr-mcp"</span>: { <span class="tok-s">"url"</span>: <span class="tok-s">"http://localhost:8080/mcp"</span> }
+  }
+}</code></pre></div>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        …or <b>Settings → Tools → AI Assistant → MCP Servers → Add</b>, transport <b>HTTP</b>.
+      </p>
+      </div>
+    </div>
+
+    <!-- ── Inspector ── -->
+    <div data-client="inspector">
+      <h3>MCP Inspector</h3>
+      <div data-transport="stdio">
+      <p>
+        No editor, no assistant — a browser UI that shows the raw protocol. The best way to
+        <em>see</em> what MCP actually is, and the fastest way to prove the server works.
+      </p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>npx @modelcontextprotocol/inspector</code></pre></div>
+      <p>Opens at <code class="inline">http://localhost:6274</code>. Choose <b>STDIO</b>, then:</p>
+      <div class="tablewrap">
+        <table>
+          <tbody>
+            <tr data-runtime="docker"><th>Command</th><td><code>docker</code></td></tr>
+            <tr data-runtime="docker"><th>Arguments</th><td><code>run -i --rm --add-host=host.docker.internal:host-gateway -e SOLR_URL=http://host.docker.internal:8983/solr/ ghcr.io/adityamparikh/solr-mcp:coc2026</code></td></tr>
+            <tr data-runtime="jar"><th>Command</th><td><code>java</code></td></tr>
+            <tr data-runtime="jar"><th>Arguments</th><td><code>-jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar</code></td></tr>
+          </tbody>
+        </table>
+      </div>
+      <p>
+        Hit <b>Connect</b>, then <b>List Tools</b>. You should see twelve. Click
+        <code class="inline">list-collections</code> and run it — that round-trip is the whole
+        protocol in one click.
+      </p>
+      </div>
+      <div data-transport="http">
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>npx @modelcontextprotocol/inspector</code></pre></div>
+      <p>
+        Opens at <code class="inline">http://localhost:6274</code>. Choose <b>Streamable HTTP</b>,
+        URL <code class="inline">http://localhost:8080/mcp</code>, then <b>Connect</b> and
+        <b>List Tools</b>. Click <code class="inline">list-collections</code> and run it.
+      </p>
+      </div>
+    </div>
+
+    <h3>Confirm the connection</h3>
+    <p>Ask your assistant:</p>
+    <div class="prompts">
+      <div class="prompt">
+        <q>What Solr collections are available?<span class="why">calls <b>list-collections</b> — expect an empty list, which is correct right now</span></q>
+        <button class="copy-sm" type="button" data-copy="What Solr collections are available?">Copy</button>
+      </div>
+    </div>
+    <p style="font-size:.86rem;color:var(--ink-2)">
+      An empty list means everything works. If the assistant says it has no Solr tools, see
+      <a href="#broken">When it breaks</a>.
+    </p>
+  </div>
+</section>
+
+<!-- ══════════════════════════════════════════════════════════
+     STEP 3
+     ══════════════════════════════════════════════════════════ -->
+<section class="step" id="s3" data-step="3">
+  <div class="step-num">3</div>
+  <div class="step-body">
+    <div class="step-head">
+      <div>
+        <p class="kicker">The lazy way, on purpose</p>
+        <h2>Index with no schema at all</h2>
+      </div>
+      <button class="done-toggle" type="button">Mark done</button>
+    </div>
+    <p>
+      Solr's <code class="inline">_default</code> configset is <b>schemaless</b>: throw data at
+      it and it invents field types for you. We're going to do exactly that, watch it work,
+      and then watch it bite. That contrast is the point of this whole exercise.
+    </p>
+
+    <div class="prompts">
+      <div class="prompt">
+        <q>Create a Solr collection called shows-auto.<span class="why">calls <b>create-collection</b></span></q>
+        <button class="copy-sm" type="button" data-copy="Create a Solr collection called shows-auto.">Copy</button>
+      </div>
+    </div>
+
+    <h3>Get the data in</h3>
+    <p>
+      Pick whichever suits your client. File-aware clients (Claude Code, Codex, Cursor, Copilot,
+      Zed, IntelliJ) can read a downloaded file; chat-only clients want a paste.
+    </p>
+
+    <details class="panel">
+      <summary>Option A — 61 shows from a file <span class="hint">Claude Code, Codex, Cursor, Copilot, Zed, IntelliJ</span></summary>
+      <div class="panel-body">
+        <div data-format="json">
+          <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>curl -O https://raw.githubusercontent.com/apache/solr-mcp/main/src/test/resources/shows.json</code></pre></div>
+        <div class="prompts">
+          <div class="prompt">
+            <q>Index the contents of ./shows.json into the shows-auto collection.<span class="why">calls <b>index-json-documents</b> — expect 61 of 61</span></q>
+            <button class="copy-sm" type="button" data-copy="Index the contents of ./shows.json into the shows-auto collection.">Copy</button>
+          </div>
+        </div>
+        </div>
+        <div data-format="csv">
+          <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>curl -O https://raw.githubusercontent.com/adityamparikh/solr-mcp/feat/shows-sample-formats/src/test/resources/shows.csv</code></pre></div>
+        <div class="prompts">
+          <div class="prompt">
+            <q>Index the contents of ./shows.csv into the shows-auto collection.<span class="why">calls <b>index-csv-documents</b> — expect 61 of 61</span></q>
+            <button class="copy-sm" type="button" data-copy="Index the contents of ./shows.csv into the shows-auto collection.">Copy</button>
+          </div>
+        </div>
+          <p style="font-size:.86rem;color:var(--ink-2)">
+            Multi-valued fields (<code class="inline">genres</code>, <code class="inline">cast</code>…)
+            are repeated column headers; every non-empty cell becomes one value. Numbers arrive as
+            text and Solr's parsers still guess numeric types, so the next step plays out the same.
+          </p>
+        </div>
+        <div data-format="xml">
+          <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>curl -O https://raw.githubusercontent.com/adityamparikh/solr-mcp/feat/shows-sample-formats/src/test/resources/shows.xml</code></pre></div>
+        <div class="prompts">
+          <div class="prompt">
+            <q>Index the contents of ./shows.xml into the shows-auto collection.<span class="why">calls <b>index-xml-documents</b> — expect 61 of 61</span></q>
+            <button class="copy-sm" type="button" data-copy="Index the contents of ./shows.xml into the shows-auto collection.">Copy</button>
+          </div>
+        </div>
+          <div class="note warn">
+            <span class="tag">XML field names differ</span>
+            <p>
+              The XML tool flattens nested elements by path, so every field lands prefixed with the
+              record element: <code class="inline">show_platform</code>,
+              <code class="inline">show_imdb_rating</code>, and the show's
+              <code class="inline">id</code> becomes <code class="inline">show_id</code> while Solr
+              assigns its own <code class="inline">id</code>. Later prompts on this card say
+              <code class="inline">platform</code>; with XML, say
+              <code class="inline">show_platform</code>, or pick another format for steps 4–7.
+            </p>
+          </div>
+        </div>
+        <div data-format="markdown">
+          <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>curl -L https://github.com/adityamparikh/solr-mcp/archive/refs/heads/feat/shows-sample-formats.tar.gz \
+  | tar xz --strip-components=4 'solr-mcp-feat-shows-sample-formats/src/test/resources/shows-markdown'</code></pre></div>
+        <div class="prompts">
+          <div class="prompt">
+            <q>Index every file in ./shows-markdown into the shows-auto collection, one document per file.<span class="why">calls <b>index-markdown-documents</b> 61 times — one document per Markdown file</span></q>
+            <button class="copy-sm" type="button" data-copy="Index every file in ./shows-markdown into the shows-auto collection, one document per file.">Copy</button>
+          </div>
+        </div>
+          <p style="font-size:.86rem;color:var(--ink-2)">
+            One Markdown document is one Solr document: the fields live in YAML front matter and
+            the description is the body, which also lands as <code class="inline">content</code>
+            with the title in <code class="inline">headings</code>. Sixty-one files means sixty-one
+            tool calls; watch the assistant work through them.
+          </p>
+        </div>
+      </div>
+    </details>
+
+    <details class="panel">
+      <summary>Option B — a dozen shows, paste anywhere <span class="hint">Claude Desktop, or any chat-only client</span></summary>
+      <div class="panel-body">
+        <p style="font-size:.86rem">
+          Same shape, same lesson, small enough to paste into a chat box. Copy this whole block
+          and send it with the sentence at the top.
+        </p>
+        <div data-format="json">
+        <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>Index this JSON into the shows-auto collection:
+
+[
+  {"id":"hbo-001","title":"Game of Thrones","platform":"HBO Max","genres":["Fantasy","Drama","Action"],"release_year":2011,"end_year":2019,"status":"Ended","seasons":8,"episodes":73,"creators":["David Benioff","D.B. Weiss"],"cast":["Emilia Clarke","Kit Harington","Peter Dinklage"],"country":"USA","language":"English","rating":"TV-MA","imdb_rating":9.2,"description":"Noble families vie for control of the Iron Throne while an ancient threat returns.","tags":["epic","fantasy","adaptation"," [...]
+  {"id":"netflix-001","title":"Stranger Things","platform":"Netflix","genres":["Sci-Fi","Horror","Drama"],"release_year":2016,"end_year":2025,"status":"Ended","seasons":5,"episodes":42,"creators":["Matt Duffer","Ross Duffer"],"cast":["Millie Bobby Brown","Finn Wolfhard","Winona Ryder"],"country":"USA","language":"English","rating":"TV-14","imdb_rating":8.7,"description":"A group of kids in 1980s Indiana uncover supernatural mysteries and government conspiracies.","tags":["80s","supernatu [...]
+  {"id":"hulu-001","title":"The Bear","platform":"Hulu","genres":["Drama","Comedy"],"release_year":2022,"end_year":0,"status":"Ongoing","seasons":3,"episodes":28,"creators":["Christopher Storer"],"cast":["Jeremy Allen White","Ayo Edebiri","Ebon Moss-Bachrach"],"country":"USA","language":"English","rating":"TV-MA","imdb_rating":8.6,"description":"A fine-dining chef returns to Chicago to run his late brother's sandwich shop.","tags":["food","family","anxiety"]},
+  {"id":"apple-001","title":"Ted Lasso","platform":"Apple TV+","genres":["Comedy","Drama","Sport"],"release_year":2020,"end_year":2023,"status":"Ended","seasons":3,"episodes":34,"creators":["Bill Lawrence","Jason Sudeikis"],"cast":["Jason Sudeikis","Hannah Waddingham","Brett Goldstein"],"country":"USA","language":"English","rating":"TV-MA","imdb_rating":8.8,"description":"An American football coach is hired to manage an English Premier League soccer team.","tags":["feel-good","sports","b [...]
+  {"id":"disney-001","title":"The Mandalorian","platform":"Disney+","genres":["Sci-Fi","Action","Adventure"],"release_year":2019,"end_year":0,"status":"Ongoing","seasons":3,"episodes":24,"creators":["Jon Favreau"],"cast":["Pedro Pascal","Carl Weathers","Giancarlo Esposito"],"country":"USA","language":"English","rating":"TV-14","imdb_rating":8.7,"description":"A lone bounty hunter guards a mysterious child across the outer reaches of the galaxy.","tags":["star-wars","space-western","adven [...]
+  {"id":"hbo-002","title":"Succession","platform":"HBO Max","genres":["Drama","Comedy"],"release_year":2018,"end_year":2023,"status":"Ended","seasons":4,"episodes":39,"creators":["Jesse Armstrong"],"cast":["Brian Cox","Jeremy Strong","Sarah Snook"],"country":"USA","language":"English","rating":"TV-MA","imdb_rating":8.9,"description":"A media dynasty's children fight over control of the family empire.","tags":["satire","family","politics","business"]},
+  {"id":"prime-001","title":"The Boys","platform":"Amazon Prime Video","genres":["Action","Comedy","Sci-Fi"],"release_year":2019,"end_year":0,"status":"Ongoing","seasons":4,"episodes":32,"creators":["Eric Kripke"],"cast":["Karl Urban","Jack Quaid","Antony Starr"],"country":"USA","language":"English","rating":"TV-MA","imdb_rating":8.7,"description":"A group of vigilantes set out to take down corrupt superheroes.","tags":["superhero","satire","violent"]},
+  {"id":"netflix-002","title":"Dark","platform":"Netflix","genres":["Sci-Fi","Thriller","Drama"],"release_year":2017,"end_year":2020,"status":"Ended","seasons":3,"episodes":26,"creators":["Baran bo Odar","Jantje Friese"],"cast":["Louis Hofmann","Lisa Vicari","Oliver Masucci"],"country":"Germany","language":"German","rating":"TV-MA","imdb_rating":8.7,"description":"Four families search for a missing child and uncover a time-travel conspiracy.","tags":["time-travel","mystery","german"]},
+  {"id":"prime-002","title":"Fleabag","platform":"Amazon Prime Video","genres":["Comedy","Drama"],"release_year":2016,"end_year":2019,"status":"Ended","seasons":2,"episodes":12,"creators":["Phoebe Waller-Bridge"],"cast":["Phoebe Waller-Bridge","Sian Clifford","Andrew Scott"],"country":"UK","language":"English","rating":"TV-MA","imdb_rating":8.7,"description":"A sharp-tongued woman navigates grief and family in London.","tags":["british","dark-comedy","fourth-wall"]},
+  {"id":"apple-002","title":"Severance","platform":"Apple TV+","genres":["Sci-Fi","Thriller","Drama"],"release_year":2022,"end_year":0,"status":"Ongoing","seasons":2,"episodes":19,"creators":["Dan Erickson"],"cast":["Adam Scott","Britt Lower","Patricia Arquette"],"country":"USA","language":"English","rating":"TV-MA","imdb_rating":8.7,"description":"Office workers surgically divide their memories between work and personal life.","tags":["dystopian","workplace","mystery"]},
+  {"id":"hbo-003","title":"The Last of Us","platform":"HBO Max","genres":["Drama","Horror","Adventure"],"release_year":2023,"end_year":0,"status":"Ongoing","seasons":2,"episodes":16,"creators":["Craig Mazin","Neil Druckmann"],"cast":["Pedro Pascal","Bella Ramsey","Gabriel Luna"],"country":"USA","language":"English","rating":"TV-MA","imdb_rating":8.7,"description":"A smuggler escorts a teenage girl across a post-apocalyptic United States.","tags":["adaptation","post-apocalyptic","survival"]},
+  {"id":"hulu-002","title":"Only Murders in the Building","platform":"Hulu","genres":["Comedy","Mystery","Crime"],"release_year":2021,"end_year":0,"status":"Ongoing","seasons":4,"episodes":40,"creators":["Steve Martin","John Hoffman"],"cast":["Steve Martin","Martin Short","Selena Gomez"],"country":"USA","language":"English","rating":"TV-14","imdb_rating":8.1,"description":"Three true-crime obsessed neighbours investigate a death in their apartment building.","tags":["whodunnit","comedy", [...]
+]</code></pre></div>
+        </div>
+        <div data-format="csv">
+        <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>Index this CSV into the shows-auto collection:
+id,title,platform,genres,genres,genres,release_year,end_year,status,seasons,episodes,creators,creators,creators,cast,cast,cast,cast,country,language,rating,imdb_rating,description,tags,tags,tags,tags
+hbo-001,Game of Thrones,HBO Max,Fantasy,Drama,Adventure,2011,2019,Ended,8,73,David Benioff,D.B. Weiss,,Emilia Clarke,Peter Dinklage,Kit Harington,Lena Headey,USA,English,TV-MA,9.2,&quot;Nine noble families fight for control over the lands of Westeros, while an ancient enemy returns after being dormant for millennia.&quot;,fantasy,dragons,epic,george-rr-martin
+netflix-001,Stranger Things,Netflix,Sci-Fi,Horror,Drama,2016,2025,Ended,5,42,Matt Duffer,Ross Duffer,,Millie Bobby Brown,Finn Wolfhard,Winona Ryder,David Harbour,USA,English,TV-14,8.7,A group of kids in 1980s Indiana uncover supernatural mysteries and government conspiracies tied to a parallel dimension.,80s,supernatural,coming-of-age,monsters
+hulu-001,The Handmaid&#x27;s Tale,Hulu,Drama,Sci-Fi,Dystopian,2017,2025,Ended,6,66,Bruce Miller,,,Elisabeth Moss,Joseph Fiennes,Yvonne Strahovski,Samira Wiley,USA,English,TV-MA,8.4,&quot;Set in a dystopian future, a woman is forced to live as a concubine under a fundamentalist theocratic dictatorship.&quot;,dystopian,feminist,atwood,theocracy
+disney-001,The Mandalorian,Disney+,Sci-Fi,Action,Adventure,2019,,Ongoing,3,24,Jon Favreau,,,Pedro Pascal,Carl Weathers,Giancarlo Esposito,,USA,English,TV-14,8.6,&quot;The travels of a lone bounty hunter in the outer reaches of the galaxy, far from the authority of the New Republic.&quot;,star-wars,space-western,grogu,baby-yoda
+hbo-002,Succession,HBO Max,Drama,Comedy,,2018,2023,Ended,4,39,Jesse Armstrong,,,Brian Cox,Jeremy Strong,Kieran Culkin,Sarah Snook,USA,English,TV-MA,8.9,&quot;The Roy family controls the biggest media and entertainment company in the world, but their patriarch&#x27;s health is failing.&quot;,media,family-drama,satire,wealth
+prime-001,The Boys,Amazon Prime Video,Action,Sci-Fi,Drama,2019,,Ongoing,4,32,Eric Kripke,,,Karl Urban,Jack Quaid,Antony Starr,Erin Moriarty,USA,English,TV-MA,8.7,A group of vigilantes set out to take down corrupt superheroes who abuse their powers.,superheroes,satire,violent,comic-adaptation
+netflix-002,The Crown,Netflix,Drama,Historical,Biography,2016,2023,Ended,6,60,Peter Morgan,,,Claire Foy,Olivia Colman,Imelda Staunton,Matt Smith,UK,English,TV-MA,8.6,The reign of Queen Elizabeth II from her wedding in 1947 through the early 21st century.,royalty,british,period-drama,politics
+prime-002,The Marvelous Mrs. Maisel,Amazon Prime Video,Comedy,Drama,Period,2017,2023,Ended,5,43,Amy Sherman-Palladino,,,Rachel Brosnahan,Alex Borstein,Michael Zegen,Tony Shalhoub,USA,English,TV-MA,8.7,A 1950s New York housewife discovers she has a talent for stand-up comedy.,50s,stand-up,feminist,new-york
+hbo-003,The Last of Us,HBO Max,Drama,Horror,Sci-Fi,2023,,Ongoing,2,16,Craig Mazin,Neil Druckmann,,Pedro Pascal,Bella Ramsey,Anna Torv,,USA,English,TV-MA,8.7,&quot;After a global pandemic destroys civilization, a hardened survivor takes charge of a 14-year-old girl who may be humanity&#x27;s last hope.&quot;,post-apocalyptic,zombies,video-game-adaptation,fungal
+hulu-002,The Bear,Hulu,Comedy,Drama,,2022,,Ongoing,3,28,Christopher Storer,,,Jeremy Allen White,Ebon Moss-Bachrach,Ayo Edebiri,,USA,English,TV-MA,8.6,A young chef from the fine dining world returns to Chicago to run his family&#x27;s sandwich shop after a heartbreaking death.,cooking,chicago,family,anxiety
+netflix-003,Squid Game,Netflix,Thriller,Drama,Survival,2021,2025,Ended,3,22,Hwang Dong-hyuk,,,Lee Jung-jae,Park Hae-soo,Wi Ha-joon,HoYeon Jung,South Korea,Korean,TV-MA,8.0,Hundreds of cash-strapped contestants accept an invitation to compete in deadly children&#x27;s games for a tempting prize.,korean,survival,dystopian,social-commentary
+netflix-004,Wednesday,Netflix,Comedy,Horror,Mystery,2022,,Ongoing,2,16,Alfred Gough,Miles Millar,,Jenna Ortega,Catherine Zeta-Jones,Luis Guzmán,Gwendoline Christie,USA,English,TV-14,8.1,Wednesday Addams navigates a supernatural boarding school while solving a murder mystery.,gothic,teen,supernatural,addams-family</code></pre></div>
+        </div>
+        <div data-format="xml">
+        <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>Index this XML into the shows-auto collection:
+&lt;shows&gt;
+  &lt;show&gt;
+    &lt;id&gt;hbo-001&lt;/id&gt;
+    &lt;title&gt;Game of Thrones&lt;/title&gt;
+    &lt;platform&gt;HBO Max&lt;/platform&gt;
+    &lt;genres&gt;Fantasy&lt;/genres&gt;
+    &lt;genres&gt;Drama&lt;/genres&gt;
+    &lt;genres&gt;Adventure&lt;/genres&gt;
+    &lt;release_year&gt;2011&lt;/release_year&gt;
+    &lt;end_year&gt;2019&lt;/end_year&gt;
+    &lt;status&gt;Ended&lt;/status&gt;
+    &lt;seasons&gt;8&lt;/seasons&gt;
+    &lt;episodes&gt;73&lt;/episodes&gt;
+    &lt;creators&gt;David Benioff&lt;/creators&gt;
+    &lt;creators&gt;D.B. Weiss&lt;/creators&gt;
+    &lt;cast&gt;Emilia Clarke&lt;/cast&gt;
+    &lt;cast&gt;Peter Dinklage&lt;/cast&gt;
+    &lt;cast&gt;Kit Harington&lt;/cast&gt;
+    &lt;cast&gt;Lena Headey&lt;/cast&gt;
+    &lt;country&gt;USA&lt;/country&gt;
+    &lt;language&gt;English&lt;/language&gt;
+    &lt;rating&gt;TV-MA&lt;/rating&gt;
+    &lt;imdb_rating&gt;9.2&lt;/imdb_rating&gt;
+    &lt;description&gt;Nine noble families fight for control over the lands of Westeros, while an ancient enemy returns after being dormant for millennia.&lt;/description&gt;
+    &lt;tags&gt;fantasy&lt;/tags&gt;
+    &lt;tags&gt;dragons&lt;/tags&gt;
+    &lt;tags&gt;epic&lt;/tags&gt;
+    &lt;tags&gt;george-rr-martin&lt;/tags&gt;
+  &lt;/show&gt;
+  &lt;show&gt;
+    &lt;id&gt;netflix-001&lt;/id&gt;
+    &lt;title&gt;Stranger Things&lt;/title&gt;
+    &lt;platform&gt;Netflix&lt;/platform&gt;
+    &lt;genres&gt;Sci-Fi&lt;/genres&gt;
+    &lt;genres&gt;Horror&lt;/genres&gt;
+    &lt;genres&gt;Drama&lt;/genres&gt;
+    &lt;release_year&gt;2016&lt;/release_year&gt;
+    &lt;end_year&gt;2025&lt;/end_year&gt;
+    &lt;status&gt;Ended&lt;/status&gt;
+    &lt;seasons&gt;5&lt;/seasons&gt;
+    &lt;episodes&gt;42&lt;/episodes&gt;
+    &lt;creators&gt;Matt Duffer&lt;/creators&gt;
+    &lt;creators&gt;Ross Duffer&lt;/creators&gt;
+    &lt;cast&gt;Millie Bobby Brown&lt;/cast&gt;
+    &lt;cast&gt;Finn Wolfhard&lt;/cast&gt;
+    &lt;cast&gt;Winona Ryder&lt;/cast&gt;
+    &lt;cast&gt;David Harbour&lt;/cast&gt;
+    &lt;country&gt;USA&lt;/country&gt;
+    &lt;language&gt;English&lt;/language&gt;
+    &lt;rating&gt;TV-14&lt;/rating&gt;
+    &lt;imdb_rating&gt;8.7&lt;/imdb_rating&gt;
+    &lt;description&gt;A group of kids in 1980s Indiana uncover supernatural mysteries and government conspiracies tied to a parallel dimension.&lt;/description&gt;
+    &lt;tags&gt;80s&lt;/tags&gt;
+    &lt;tags&gt;supernatural&lt;/tags&gt;
+    &lt;tags&gt;coming-of-age&lt;/tags&gt;
+    &lt;tags&gt;monsters&lt;/tags&gt;
+  &lt;/show&gt;
+  &lt;show&gt;
+    &lt;id&gt;hulu-001&lt;/id&gt;
+    &lt;title&gt;The Handmaid&#x27;s Tale&lt;/title&gt;
+    &lt;platform&gt;Hulu&lt;/platform&gt;
+    &lt;genres&gt;Drama&lt;/genres&gt;
+    &lt;genres&gt;Sci-Fi&lt;/genres&gt;
+    &lt;genres&gt;Dystopian&lt;/genres&gt;
+    &lt;release_year&gt;2017&lt;/release_year&gt;
+    &lt;end_year&gt;2025&lt;/end_year&gt;
+    &lt;status&gt;Ended&lt;/status&gt;
+    &lt;seasons&gt;6&lt;/seasons&gt;
+    &lt;episodes&gt;66&lt;/episodes&gt;
+    &lt;creators&gt;Bruce Miller&lt;/creators&gt;
+    &lt;cast&gt;Elisabeth Moss&lt;/cast&gt;
+    &lt;cast&gt;Joseph Fiennes&lt;/cast&gt;
+    &lt;cast&gt;Yvonne Strahovski&lt;/cast&gt;
+    &lt;cast&gt;Samira Wiley&lt;/cast&gt;
+    &lt;country&gt;USA&lt;/country&gt;
+    &lt;language&gt;English&lt;/language&gt;
+    &lt;rating&gt;TV-MA&lt;/rating&gt;
+    &lt;imdb_rating&gt;8.4&lt;/imdb_rating&gt;
+    &lt;description&gt;Set in a dystopian future, a woman is forced to live as a concubine under a fundamentalist theocratic dictatorship.&lt;/description&gt;
+    &lt;tags&gt;dystopian&lt;/tags&gt;
+    &lt;tags&gt;feminist&lt;/tags&gt;
+    &lt;tags&gt;atwood&lt;/tags&gt;
+    &lt;tags&gt;theocracy&lt;/tags&gt;
+  &lt;/show&gt;
+  &lt;show&gt;
+    &lt;id&gt;disney-001&lt;/id&gt;
+    &lt;title&gt;The Mandalorian&lt;/title&gt;
+    &lt;platform&gt;Disney+&lt;/platform&gt;
+    &lt;genres&gt;Sci-Fi&lt;/genres&gt;
+    &lt;genres&gt;Action&lt;/genres&gt;
+    &lt;genres&gt;Adventure&lt;/genres&gt;
+    &lt;release_year&gt;2019&lt;/release_year&gt;
+    &lt;status&gt;Ongoing&lt;/status&gt;
+    &lt;seasons&gt;3&lt;/seasons&gt;
+    &lt;episodes&gt;24&lt;/episodes&gt;
+    &lt;creators&gt;Jon Favreau&lt;/creators&gt;
+    &lt;cast&gt;Pedro Pascal&lt;/cast&gt;
+    &lt;cast&gt;Carl Weathers&lt;/cast&gt;
+    &lt;cast&gt;Giancarlo Esposito&lt;/cast&gt;
+    &lt;country&gt;USA&lt;/country&gt;
+    &lt;language&gt;English&lt;/language&gt;
+    &lt;rating&gt;TV-14&lt;/rating&gt;
+    &lt;imdb_rating&gt;8.6&lt;/imdb_rating&gt;
+    &lt;description&gt;The travels of a lone bounty hunter in the outer reaches of the galaxy, far from the authority of the New Republic.&lt;/description&gt;
+    &lt;tags&gt;star-wars&lt;/tags&gt;
+    &lt;tags&gt;space-western&lt;/tags&gt;
+    &lt;tags&gt;grogu&lt;/tags&gt;
+    &lt;tags&gt;baby-yoda&lt;/tags&gt;
+  &lt;/show&gt;
+  &lt;show&gt;
+    &lt;id&gt;hbo-002&lt;/id&gt;
+    &lt;title&gt;Succession&lt;/title&gt;
+    &lt;platform&gt;HBO Max&lt;/platform&gt;
+    &lt;genres&gt;Drama&lt;/genres&gt;
+    &lt;genres&gt;Comedy&lt;/genres&gt;
+    &lt;release_year&gt;2018&lt;/release_year&gt;
+    &lt;end_year&gt;2023&lt;/end_year&gt;
+    &lt;status&gt;Ended&lt;/status&gt;
+    &lt;seasons&gt;4&lt;/seasons&gt;
+    &lt;episodes&gt;39&lt;/episodes&gt;
+    &lt;creators&gt;Jesse Armstrong&lt;/creators&gt;
+    &lt;cast&gt;Brian Cox&lt;/cast&gt;
+    &lt;cast&gt;Jeremy Strong&lt;/cast&gt;
+    &lt;cast&gt;Kieran Culkin&lt;/cast&gt;
+    &lt;cast&gt;Sarah Snook&lt;/cast&gt;
+    &lt;country&gt;USA&lt;/country&gt;
+    &lt;language&gt;English&lt;/language&gt;
+    &lt;rating&gt;TV-MA&lt;/rating&gt;
+    &lt;imdb_rating&gt;8.9&lt;/imdb_rating&gt;
+    &lt;description&gt;The Roy family controls the biggest media and entertainment company in the world, but their patriarch&#x27;s health is failing.&lt;/description&gt;
+    &lt;tags&gt;media&lt;/tags&gt;
+    &lt;tags&gt;family-drama&lt;/tags&gt;
+    &lt;tags&gt;satire&lt;/tags&gt;
+    &lt;tags&gt;wealth&lt;/tags&gt;
+  &lt;/show&gt;
+  &lt;show&gt;
+    &lt;id&gt;prime-001&lt;/id&gt;
+    &lt;title&gt;The Boys&lt;/title&gt;
+    &lt;platform&gt;Amazon Prime Video&lt;/platform&gt;
+    &lt;genres&gt;Action&lt;/genres&gt;
+    &lt;genres&gt;Sci-Fi&lt;/genres&gt;
+    &lt;genres&gt;Drama&lt;/genres&gt;
+    &lt;release_year&gt;2019&lt;/release_year&gt;
+    &lt;status&gt;Ongoing&lt;/status&gt;
+    &lt;seasons&gt;4&lt;/seasons&gt;
+    &lt;episodes&gt;32&lt;/episodes&gt;
+    &lt;creators&gt;Eric Kripke&lt;/creators&gt;
+    &lt;cast&gt;Karl Urban&lt;/cast&gt;
+    &lt;cast&gt;Jack Quaid&lt;/cast&gt;
+    &lt;cast&gt;Antony Starr&lt;/cast&gt;
+    &lt;cast&gt;Erin Moriarty&lt;/cast&gt;
+    &lt;country&gt;USA&lt;/country&gt;
+    &lt;language&gt;English&lt;/language&gt;
+    &lt;rating&gt;TV-MA&lt;/rating&gt;
+    &lt;imdb_rating&gt;8.7&lt;/imdb_rating&gt;
+    &lt;description&gt;A group of vigilantes set out to take down corrupt superheroes who abuse their powers.&lt;/description&gt;
+    &lt;tags&gt;superheroes&lt;/tags&gt;
+    &lt;tags&gt;satire&lt;/tags&gt;
+    &lt;tags&gt;violent&lt;/tags&gt;
+    &lt;tags&gt;comic-adaptation&lt;/tags&gt;
+  &lt;/show&gt;
+  &lt;show&gt;
+    &lt;id&gt;netflix-002&lt;/id&gt;
+    &lt;title&gt;The Crown&lt;/title&gt;
+    &lt;platform&gt;Netflix&lt;/platform&gt;
+    &lt;genres&gt;Drama&lt;/genres&gt;
+    &lt;genres&gt;Historical&lt;/genres&gt;
+    &lt;genres&gt;Biography&lt;/genres&gt;
+    &lt;release_year&gt;2016&lt;/release_year&gt;
+    &lt;end_year&gt;2023&lt;/end_year&gt;
+    &lt;status&gt;Ended&lt;/status&gt;
+    &lt;seasons&gt;6&lt;/seasons&gt;
+    &lt;episodes&gt;60&lt;/episodes&gt;
+    &lt;creators&gt;Peter Morgan&lt;/creators&gt;
+    &lt;cast&gt;Claire Foy&lt;/cast&gt;
+    &lt;cast&gt;Olivia Colman&lt;/cast&gt;
+    &lt;cast&gt;Imelda Staunton&lt;/cast&gt;
+    &lt;cast&gt;Matt Smith&lt;/cast&gt;
+    &lt;country&gt;UK&lt;/country&gt;
+    &lt;language&gt;English&lt;/language&gt;
+    &lt;rating&gt;TV-MA&lt;/rating&gt;
+    &lt;imdb_rating&gt;8.6&lt;/imdb_rating&gt;
+    &lt;description&gt;The reign of Queen Elizabeth II from her wedding in 1947 through the early 21st century.&lt;/description&gt;
+    &lt;tags&gt;royalty&lt;/tags&gt;
+    &lt;tags&gt;british&lt;/tags&gt;
+    &lt;tags&gt;period-drama&lt;/tags&gt;
+    &lt;tags&gt;politics&lt;/tags&gt;
+  &lt;/show&gt;
+  &lt;show&gt;
+    &lt;id&gt;prime-002&lt;/id&gt;
+    &lt;title&gt;The Marvelous Mrs. Maisel&lt;/title&gt;
+    &lt;platform&gt;Amazon Prime Video&lt;/platform&gt;
+    &lt;genres&gt;Comedy&lt;/genres&gt;
+    &lt;genres&gt;Drama&lt;/genres&gt;
+    &lt;genres&gt;Period&lt;/genres&gt;
+    &lt;release_year&gt;2017&lt;/release_year&gt;
+    &lt;end_year&gt;2023&lt;/end_year&gt;
+    &lt;status&gt;Ended&lt;/status&gt;
+    &lt;seasons&gt;5&lt;/seasons&gt;
+    &lt;episodes&gt;43&lt;/episodes&gt;
+    &lt;creators&gt;Amy Sherman-Palladino&lt;/creators&gt;
+    &lt;cast&gt;Rachel Brosnahan&lt;/cast&gt;
+    &lt;cast&gt;Alex Borstein&lt;/cast&gt;
+    &lt;cast&gt;Michael Zegen&lt;/cast&gt;
+    &lt;cast&gt;Tony Shalhoub&lt;/cast&gt;
+    &lt;country&gt;USA&lt;/country&gt;
+    &lt;language&gt;English&lt;/language&gt;
+    &lt;rating&gt;TV-MA&lt;/rating&gt;
+    &lt;imdb_rating&gt;8.7&lt;/imdb_rating&gt;
+    &lt;description&gt;A 1950s New York housewife discovers she has a talent for stand-up comedy.&lt;/description&gt;
+    &lt;tags&gt;50s&lt;/tags&gt;
+    &lt;tags&gt;stand-up&lt;/tags&gt;
+    &lt;tags&gt;feminist&lt;/tags&gt;
+    &lt;tags&gt;new-york&lt;/tags&gt;
+  &lt;/show&gt;
+  &lt;show&gt;
+    &lt;id&gt;hbo-003&lt;/id&gt;
+    &lt;title&gt;The Last of Us&lt;/title&gt;
+    &lt;platform&gt;HBO Max&lt;/platform&gt;
+    &lt;genres&gt;Drama&lt;/genres&gt;
+    &lt;genres&gt;Horror&lt;/genres&gt;
+    &lt;genres&gt;Sci-Fi&lt;/genres&gt;
+    &lt;release_year&gt;2023&lt;/release_year&gt;
+    &lt;status&gt;Ongoing&lt;/status&gt;
+    &lt;seasons&gt;2&lt;/seasons&gt;
+    &lt;episodes&gt;16&lt;/episodes&gt;
+    &lt;creators&gt;Craig Mazin&lt;/creators&gt;
+    &lt;creators&gt;Neil Druckmann&lt;/creators&gt;
+    &lt;cast&gt;Pedro Pascal&lt;/cast&gt;
+    &lt;cast&gt;Bella Ramsey&lt;/cast&gt;
+    &lt;cast&gt;Anna Torv&lt;/cast&gt;
+    &lt;country&gt;USA&lt;/country&gt;
+    &lt;language&gt;English&lt;/language&gt;
+    &lt;rating&gt;TV-MA&lt;/rating&gt;
+    &lt;imdb_rating&gt;8.7&lt;/imdb_rating&gt;
+    &lt;description&gt;After a global pandemic destroys civilization, a hardened survivor takes charge of a 14-year-old girl who may be humanity&#x27;s last hope.&lt;/description&gt;
+    &lt;tags&gt;post-apocalyptic&lt;/tags&gt;
+    &lt;tags&gt;zombies&lt;/tags&gt;
+    &lt;tags&gt;video-game-adaptation&lt;/tags&gt;
+    &lt;tags&gt;fungal&lt;/tags&gt;
+  &lt;/show&gt;
+  &lt;show&gt;
+    &lt;id&gt;hulu-002&lt;/id&gt;
+    &lt;title&gt;The Bear&lt;/title&gt;
+    &lt;platform&gt;Hulu&lt;/platform&gt;
+    &lt;genres&gt;Comedy&lt;/genres&gt;
+    &lt;genres&gt;Drama&lt;/genres&gt;
+    &lt;release_year&gt;2022&lt;/release_year&gt;
+    &lt;status&gt;Ongoing&lt;/status&gt;
+    &lt;seasons&gt;3&lt;/seasons&gt;
+    &lt;episodes&gt;28&lt;/episodes&gt;
+    &lt;creators&gt;Christopher Storer&lt;/creators&gt;
+    &lt;cast&gt;Jeremy Allen White&lt;/cast&gt;
+    &lt;cast&gt;Ebon Moss-Bachrach&lt;/cast&gt;
+    &lt;cast&gt;Ayo Edebiri&lt;/cast&gt;
+    &lt;country&gt;USA&lt;/country&gt;
+    &lt;language&gt;English&lt;/language&gt;
+    &lt;rating&gt;TV-MA&lt;/rating&gt;
+    &lt;imdb_rating&gt;8.6&lt;/imdb_rating&gt;
+    &lt;description&gt;A young chef from the fine dining world returns to Chicago to run his family&#x27;s sandwich shop after a heartbreaking death.&lt;/description&gt;
+    &lt;tags&gt;cooking&lt;/tags&gt;
+    &lt;tags&gt;chicago&lt;/tags&gt;
+    &lt;tags&gt;family&lt;/tags&gt;
+    &lt;tags&gt;anxiety&lt;/tags&gt;
+  &lt;/show&gt;
+  &lt;show&gt;
+    &lt;id&gt;netflix-003&lt;/id&gt;
+    &lt;title&gt;Squid Game&lt;/title&gt;
+    &lt;platform&gt;Netflix&lt;/platform&gt;
+    &lt;genres&gt;Thriller&lt;/genres&gt;
+    &lt;genres&gt;Drama&lt;/genres&gt;
+    &lt;genres&gt;Survival&lt;/genres&gt;
+    &lt;release_year&gt;2021&lt;/release_year&gt;
+    &lt;end_year&gt;2025&lt;/end_year&gt;
+    &lt;status&gt;Ended&lt;/status&gt;
+    &lt;seasons&gt;3&lt;/seasons&gt;
+    &lt;episodes&gt;22&lt;/episodes&gt;
+    &lt;creators&gt;Hwang Dong-hyuk&lt;/creators&gt;
+    &lt;cast&gt;Lee Jung-jae&lt;/cast&gt;
+    &lt;cast&gt;Park Hae-soo&lt;/cast&gt;
+    &lt;cast&gt;Wi Ha-joon&lt;/cast&gt;
+    &lt;cast&gt;HoYeon Jung&lt;/cast&gt;
+    &lt;country&gt;South Korea&lt;/country&gt;
+    &lt;language&gt;Korean&lt;/language&gt;
+    &lt;rating&gt;TV-MA&lt;/rating&gt;
+    &lt;imdb_rating&gt;8.0&lt;/imdb_rating&gt;
+    &lt;description&gt;Hundreds of cash-strapped contestants accept an invitation to compete in deadly children&#x27;s games for a tempting prize.&lt;/description&gt;
+    &lt;tags&gt;korean&lt;/tags&gt;
+    &lt;tags&gt;survival&lt;/tags&gt;
+    &lt;tags&gt;dystopian&lt;/tags&gt;
+    &lt;tags&gt;social-commentary&lt;/tags&gt;
+  &lt;/show&gt;
+  &lt;show&gt;
+    &lt;id&gt;netflix-004&lt;/id&gt;
+    &lt;title&gt;Wednesday&lt;/title&gt;
+    &lt;platform&gt;Netflix&lt;/platform&gt;
+    &lt;genres&gt;Comedy&lt;/genres&gt;
+    &lt;genres&gt;Horror&lt;/genres&gt;
+    &lt;genres&gt;Mystery&lt;/genres&gt;
+    &lt;release_year&gt;2022&lt;/release_year&gt;
+    &lt;status&gt;Ongoing&lt;/status&gt;
+    &lt;seasons&gt;2&lt;/seasons&gt;
+    &lt;episodes&gt;16&lt;/episodes&gt;
+    &lt;creators&gt;Alfred Gough&lt;/creators&gt;
+    &lt;creators&gt;Miles Millar&lt;/creators&gt;
+    &lt;cast&gt;Jenna Ortega&lt;/cast&gt;
+    &lt;cast&gt;Catherine Zeta-Jones&lt;/cast&gt;
+    &lt;cast&gt;Luis Guzmán&lt;/cast&gt;
+    &lt;cast&gt;Gwendoline Christie&lt;/cast&gt;
+    &lt;country&gt;USA&lt;/country&gt;
+    &lt;language&gt;English&lt;/language&gt;
+    &lt;rating&gt;TV-14&lt;/rating&gt;
+    &lt;imdb_rating&gt;8.1&lt;/imdb_rating&gt;
+    &lt;description&gt;Wednesday Addams navigates a supernatural boarding school while solving a murder mystery.&lt;/description&gt;
+    &lt;tags&gt;gothic&lt;/tags&gt;
+    &lt;tags&gt;teen&lt;/tags&gt;
+    &lt;tags&gt;supernatural&lt;/tags&gt;
+    &lt;tags&gt;addams-family&lt;/tags&gt;
+  &lt;/show&gt;
+&lt;/shows&gt;</code></pre></div>
+        </div>
+        <div data-format="markdown">
+        <p style="font-size:.86rem">Three shows, three Markdown documents, three tool calls:</p>
+        <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>Index each of the following three Markdown documents into the shows-auto collection, one at a time:
+
+---
+id: hbo-001
+title: Game of Thrones
+platform: HBO Max
+genres:
+  - Fantasy
+  - Drama
+  - Adventure
+release_year: 2011
+end_year: 2019
+status: Ended
+seasons: 8
+episodes: 73
+creators:
+  - David Benioff
+  - D.B. Weiss
+cast:
+  - Emilia Clarke
+  - Peter Dinklage
+  - Kit Harington
+  - Lena Headey
+country: USA
+language: English
+rating: TV-MA
+imdb_rating: 9.2
+tags:
+  - fantasy
+  - dragons
+  - epic
+  - george-rr-martin
+---
+
+# Game of Thrones
+
+Nine noble families fight for control over the lands of Westeros, while an ancient enemy returns after being dormant for millennia.
+
+---
+id: netflix-001
+title: Stranger Things
+platform: Netflix
+genres:
+  - Sci-Fi
+  - Horror
+  - Drama
+release_year: 2016
+end_year: 2025
+status: Ended
+seasons: 5
+episodes: 42
+creators:
+  - Matt Duffer
+  - Ross Duffer
+cast:
+  - Millie Bobby Brown
+  - Finn Wolfhard
+  - Winona Ryder
+  - David Harbour
+country: USA
+language: English
+rating: TV-14
+imdb_rating: 8.7
+tags:
+  - 80s
+  - supernatural
+  - coming-of-age
+  - monsters
+---
+
+# Stranger Things
+
+A group of kids in 1980s Indiana uncover supernatural mysteries and government conspiracies tied to a parallel dimension.
+
+---
+id: hulu-001
+title: The Handmaid&#x27;s Tale
+platform: Hulu
+genres:
+  - Drama
+  - Sci-Fi
+  - Dystopian
+release_year: 2017
+end_year: 2025
+status: Ended
+seasons: 6
+episodes: 66
+creators:
+  - Bruce Miller
+cast:
+  - Elisabeth Moss
+  - Joseph Fiennes
+  - Yvonne Strahovski
+  - Samira Wiley
+country: USA
+language: English
+rating: TV-MA
+imdb_rating: 8.4
+tags:
+  - dystopian
+  - feminist
+  - atwood
+  - theocracy
+---
+
+# The Handmaid&#x27;s Tale
+
+Set in a dystopian future, a woman is forced to live as a concubine under a fundamentalist theocratic dictatorship.</code></pre></div>
+        </div>
+      </div>
+    </details>
+
+    <div class="note good">
+      <span class="tag">What just happened</span>
+      <p>
+        No schema, no field definitions, no configuration — and every document landed. That is
+        genuinely useful, and it's why schemaless mode exists. Now let's find its edge.
+      </p>
+    </div>
+  </div>
+</section>
+
+<!-- ══════════════════════════════════════════════════════════
+     STEP 4
+     ══════════════════════════════════════════════════════════ -->
+<section class="step" id="s4" data-step="4">
+  <div class="step-num">4</div>
+  <div class="step-body">
+    <div class="step-head">
+      <div>
+        <p class="kicker">Where guessing runs out</p>
+        <h2>Hit the wall</h2>
+      </div>
+      <button class="done-toggle" type="button">Mark done</button>
+    </div>
+    <p>Ask the obvious business question:</p>
+
+    <div class="prompts">
+      <div class="prompt">
+        <q>Show me the breakdown of shows-auto by platform.<span class="why">calls <b>search</b> with a facet — this will not go well</span></q>
+        <button class="copy-sm" type="button" data-copy="Show me the breakdown of shows-auto by platform.">Copy</button>
+      </div>
+      <div class="prompt">
+        <q>Show me the schema for shows-auto.<span class="why">calls <b>get-schema</b> — here's the actual explanation</span></q>
+        <button class="copy-sm" type="button" data-copy="Show me the schema for shows-auto.">Copy</button>
+      </div>
+    </div>
+
+    <h3>What Solr guessed</h3>
+    <p style="font-size:.86rem;color:var(--ink-2)" data-format="csv markdown">
+      The table below was captured from the JSON path. CSV and Markdown deliver the same values as
+      text; Solr's schemaless chain parses numeric-looking text before choosing a type, so expect the
+      same guesses.
+    </p>
+    <p style="font-size:.86rem;color:var(--ink-2)" data-format="xml">
+      With XML every name below carries the <code class="inline">show_</code> prefix
+      (<code class="inline">show_platform</code>, <code class="inline">show_imdb_rating</code>).
+    </p>
+    <div class="tablewrap">
+      <table>
+        <thead>
+          <tr><th>Field</th><th>Guessed type</th><th>Consequence</th></tr>
+        </thead>
+        <tbody>
+          <tr><td><code>platform</code></td><td><code>text_general</code></td><td>Tokenized, so <code>platform:prime</code> now matches "Amazon Prime Video" — nonsense for a category. Faceting it returns <b>nothing at all</b>, not even wrong buckets.</td></tr>
+          <tr><td><code>title</code></td><td><code>text_general</code></td><td>Searchable, but not sortable or exact-matchable.</td></tr>
+          <tr><td><code>imdb_rating</code></td><td><code>pdouble<b>s</b></code></td><td>Plural — <em>multi-valued</em>. Every rating is now a list.</td></tr>
+          <tr><td><code>release_year</code></td><td><code>plong<b>s</b></code></td><td>Multi-valued too. Range filters get awkward.</td></tr>
+        </tbody>
+      </table>
+    </div>
+
+    <p>
+      Look at any returned document and you'll see it: every single field came back wrapped in
+      an array — <code class="inline">"title":["Stranger Things"]</code>. Solr had one sample of
+      each field and no reason to assume it wouldn't repeat, so it hedged.
+    </p>
+
+    <div class="note bug">
+      <span class="tag">You'll see one of two things here</span>
+      <p>
+        Either an empty breakdown — <code class="inline">"facets": { "platform": {} }</code> —
+        or a <code class="inline">ClassCastException: ArrayList cannot be cast to NamedList</code>.
+        Which one depends on whether your image predates
+        <a href="https://github.com/apache/solr-mcp/pull/175">apache/solr-mcp#175</a>. Both make
+        the same point, and neither is your fault.
+      </p>
+      <p>
+        The exception is worth understanding because it isn't about this field type: the trigger
+        is <b>any facet that comes back with zero buckets</b>. Solr serializes an empty facet as
+        <code class="inline">[]</code>, and the response parser hands that to SolrJ as a plain
+        list where a <code class="inline">NamedList</code> is expected — so an ordinary search on
+        a well-typed field whose filter matches nothing hits it too. #175 fixes that by deciding
+        from the response path rather than the array's shape.
+      </p>
+      <p>
+        Either way, the lesson is the one below: <code class="inline">text_general</code> yields
+        no facet buckets at all. Step 5 gives <code class="inline">platform</code> real buckets,
+        and the question starts answering itself.
+      </p>
+    </div>
+
+    <p>
+      <b>The lesson:</b> schemaless is a fast on-ramp, not a destination. Solr guessed from a
+      single document with no idea what you'd want to ask later. Facets, ranges and sorting all
+      need types chosen deliberately.
+    </p>
+  </div>
+</section>
+
+<!-- ══════════════════════════════════════════════════════════
+     STEP 5
+     ══════════════════════════════════════════════════════════ -->
+<section class="step" id="s5" data-step="5">
+  <div class="step-num">5</div>
+  <div class="step-body">
+    <div class="step-head">
+      <div>
+        <p class="kicker">Types chosen on purpose</p>
+        <h2>Build a real schema</h2>
+      </div>
+      <button class="done-toggle" type="button">Mark done</button>
+    </div>
+
+    <div class="note warn">
+      <span class="tag">Reset first — this is not optional</span>
+      <p>
+        Solr field types <b>cannot be changed once created</b>, and every collection here shares
+        the same <code class="inline">_default</code> configset. The guesses from step 3 are
+        already baked in, so a new collection would inherit them. Wipe the slate:
+      </p>
+    </div>
+
+    <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>docker rm -f solr-mcp-demo
+docker run -d --name solr-mcp-demo -p 8983:8983 solr:9-slim solr start -c -f</code></pre></div>
+    <p style="font-size:.86rem;color:var(--ink-2)">
+      Takes about seven seconds. Wait for the collections endpoint to answer before continuing.
+    </p>
+
+    <h3>Create the collection and define fields <em>before</em> indexing</h3>
+    <div class="prompts">
+      <div class="prompt">
+        <q>Create a Solr collection called shows.<span class="why">calls <b>create-collection</b></span></q>
+        <button class="copy-sm" type="button" data-copy="Create a Solr collection called shows.">Copy</button>
+      </div>
+    </div>
+
+    <p>Then paste this. Order matters — fields first, documents second:</p>
+    <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>Add these fields to the shows collection schema:
+
+- title         text_general  single-valued   (full-text search)
+- description   text_general  single-valued   (full-text search)
+- platform      string        single-valued   docValues  (faceting)
+- status        string        single-valued   docValues
+- country       string        single-valued   docValues
+- language      string        single-valued   docValues
+- rating        string        single-valued   docValues
+- genres        string        multi-valued    docValues  (faceting)
+- tags          string        multi-valued    docValues
+- cast          string        multi-valued    docValues
+- creators      string        multi-valued    docValues
+- release_year  pint          single-valued   docValues  (range filters)
+- end_year      pint          single-valued   docValues
+- seasons       pint          single-valued   docValues
+- episodes      pint          single-valued   docValues
+- imdb_rating   pdouble       single-valued   docValues  (sorting)</code></pre></div>
+
+    <p>
+      Now index the same data into <code class="inline">shows</code> — same file or same paste
+      as step 3, just the new collection name.
+    </p>
+
+    <h3>Why these types</h3>
+    <div class="tablewrap">
+      <table>
+        <thead><tr><th>Choice</th><th>Buys you</th></tr></thead>
+        <tbody>
+          <tr><td><code>string</code> not <code>text_general</code></td><td>Exact values. "Amazon Prime Video" stays one facet bucket instead of three tokens.</td></tr>
+          <tr><td><code>docValues: true</code></td><td>The column-oriented structure that makes faceting and sorting fast.</td></tr>
+          <tr><td><code>pint</code> / <code>pdouble</code></td><td>Real numbers — range filters like <code>[2020 TO *]</code> and numeric sort.</td></tr>
+          <tr><td>single-valued where true</td><td>You can sort on it. Multi-valued fields make "highest rated" meaningless.</td></tr>
+          <tr><td><code>text_general</code> kept for prose</td><td>Analysis and tokenizing is exactly what you want in <code>title</code> and <code>description</code>.</td></tr>
+        </tbody>
+      </table>
+    </div>
+
+    <p>Ask the same question that failed in step 4:</p>
+    <div class="prompts">
+      <div class="prompt">
+        <q>Show me the breakdown of shows by platform.<span class="why">now returns clean buckets — Netflix 20, Amazon Prime Video 20, HBO Max 7…</span></q>
+        <button class="copy-sm" type="button" data-copy="Show me the breakdown of shows by platform.">Copy</button>
+      </div>
+    </div>
+  </div>
+</section>
+
+<!-- ══════════════════════════════════════════════════════════
+     STEP 6
+     ══════════════════════════════════════════════════════════ -->
+<section class="step" id="s6" data-step="6">
+  <div class="step-num">6</div>
+  <div class="step-body">
+    <div class="step-head">
+      <div>
+        <p class="kicker">Eight questions, eight Solr features</p>
+        <h2>Actually search</h2>
+      </div>
+      <button class="done-toggle" type="button">Mark done</button>
+    </div>
+    <p>
+      Every prompt below is plain English. The note under each one names the Solr parameter it
+      exercises, and the result you should get from the full 61-show set.
+    </p>
+
+    <div class="prompts">
+      <div class="prompt">
+        <q>Which shows have "dragon" in the title?<span class="why"><b>q</b> · full-text on an analyzed field → 1 hit, House of the Dragon</span></q>
+        <button class="copy-sm" type="button" data-copy="Which shows have &quot;dragon&quot; in the title?">Copy</button>
+      </div>
+      <div class="prompt">
+        <q>Find shows whose description contains the exact phrase "a group of".<span class="why"><b>q</b> · phrase query → 2 hits, The Boys and Stranger Things</span></q>
+        <button class="copy-sm" type="button" data-copy="Find shows whose description contains the exact phrase &quot;a group of&quot;.">Copy</button>
+      </div>
+      <div class="prompt">
+        <q>Show me everything on Netflix.<span class="why"><b>fq</b> · exact string filter → 20 hits</span></q>
+        <button class="copy-sm" type="button" data-copy="Show me everything on Netflix.">Copy</button>
+      </div>
+      <div class="prompt">
+        <q>Which shows were released in 2020 or later?<span class="why"><b>fq</b> · numeric range <code>release_year:[2020 TO *]</code> → 31 hits</span></q>
+        <button class="copy-sm" type="button" data-copy="Which shows were released in 2020 or later?">Copy</button>
+      </div>
+      <div class="prompt">
+        <q>Find all the comedies.<span class="why"><b>fq</b> · membership in a multi-valued field → 15 hits</span></q>
+        <button class="copy-sm" type="button" data-copy="Find all the comedies.">Copy</button>
+      </div>
+      <div class="prompt">
+        <q>Break the collection down by genre and by status.<span class="why"><b>facet</b> · two fields at once → Ended 33, Ongoing 28</span></q>
+        <button class="copy-sm" type="button" data-copy="Break the collection down by genre and by status.">Copy</button>
+      </div>
+      <div class="prompt">
+        <q>What are the five highest rated shows?<span class="why"><b>sort</b> + <b>rows</b> → Game of Thrones 9.2, Succession 8.9, True Detective 8.9…</span></q>
+        <button class="copy-sm" type="button" data-copy="What are the five highest rated shows?">Copy</button>
+      </div>
+      <div class="prompt">
+        <q>Show me shows from 2015 onward rated 8 or above, sorted by rating, with a platform breakdown.<span class="why">everything at once — <b>fq</b> × 2, <b>sort</b>, <b>facet</b>, <b>rows</b> → 39 hits</span></q>
+        <button class="copy-sm" type="button" data-copy="Show me shows from 2015 onward rated 8 or above, sorted by rating, with a platform breakdown.">Copy</button>
+      </div>
+      <div class="prompt">
+        <q>Which shows star Pedro Pascal?<span class="why"><b>fq</b> · exact match inside a multi-valued field → 3 hits</span></q>
+        <button class="copy-sm" type="button" data-copy="Which shows star Pedro Pascal?">Copy</button>
+      </div>
+    </div>
+
+    <div class="note good">
+      <span class="tag">Worth noticing</span>
+      <p>
+        You never wrote <code class="inline">fq=release_year:[2020 TO *]&amp;facet.field=platform</code>.
+        The assistant did — because the tool descriptions told it how. That translation layer is
+        the entire product.
+      </p>
+    </div>
+  </div>
+</section>
+
+<!-- ══════════════════════════════════════════════════════════
+     STEP 7
+     ══════════════════════════════════════════════════════════ -->
+<section class="step" id="s7" data-step="7">
+  <div class="step-num">7</div>
+  <div class="step-body">
+    <div class="step-head">
+      <div>
+        <p class="kicker">Beyond search</p>
+        <h2>Ask about the index itself</h2>
+      </div>
+      <button class="done-toggle" type="button">Mark done</button>
+    </div>
+    <p>
+      Search is the headline, but the operational tools are where this earns its place in a
+      real workflow.
+    </p>
+
+    <div class="prompts">
+      <div class="prompt">
+        <q>Is the shows collection healthy?<span class="why"><b>check-health</b> — ping, response time, doc count</span></q>
+        <button class="copy-sm" type="button" data-copy="Is the shows collection healthy?">Copy</button>
+      </div>
+      <div class="prompt">
+        <q>Give me the stats for the shows collection.<span class="why"><b>get-collection-stats</b> — segments, cache hit ratios, handler timings</span></q>
+        <button class="copy-sm" type="button" data-copy="Give me the stats for the shows collection.">Copy</button>
+      </div>
+      <div class="prompt">
+        <q>What's my query result cache hit ratio, and what does that tell me?<span class="why">the interesting one — real metrics, interpreted</span></q>
+        <button class="copy-sm" type="button" data-copy="What's my query result cache hit ratio, and what does that tell me?">Copy</button>
+      </div>
+      <div class="prompt">
+        <q>Explain the shows schema — which fields can I facet on, and which can I sort by?<span class="why"><b>get-schema</b> — and a genuinely useful answer</span></q>
+        <button class="copy-sm" type="button" data-copy="Explain the shows schema — which fields can I facet on, and which can I sort by?">Copy</button>
+      </div>
+    </div>
+
+    <h3>The full toolbox</h3>
+    <div class="tablewrap">
+      <table>
+        <thead><tr><th>Tool</th><th>Does</th></tr></thead>
+        <tbody>
+          <tr><td><code>search</code></td><td>Query, filter, facet, sort, paginate</td></tr>
+          <tr><td><code>index-json-documents</code></td><td>Index from a JSON string</td></tr>
+          <tr><td><code>index-csv-documents</code></td><td>Index from CSV</td></tr>
+          <tr><td><code>index-xml-documents</code></td><td>Index from XML</td></tr>
+          <tr><td><code>index-markdown-documents</code></td><td>Index from Markdown — YAML front matter plus body text</td></tr>
+          <tr><td><code>create-collection</code></td><td>New collection, optional configset / shards / replicas</td></tr>
+          <tr><td><code>list-collections</code></td><td>Everything in the cluster</td></tr>
+          <tr><td><code>get-collection-stats</code></td><td>Index, query, cache and handler metrics</td></tr>
+          <tr><td><code>check-health</code></td><td>Ping and document count</td></tr>
+          <tr><td><code>get-schema</code></td><td>Fields, types, dynamic fields, copy fields</td></tr>
+          <tr><td><code>add-fields</code></td><td>Add fields — additive only</td></tr>
+          <tr><td><code>add-field-types</code></td><td>Custom analyzers, <code>DenseVectorField</code> for vector search</td></tr>
+        </tbody>
+      </table>
+    </div>
+    <p style="font-size:.86rem;color:var(--ink-2)">
+      Plus 2 resources (<code class="inline">solr://collections</code>,
+      <code class="inline">solr://{collection}/schema</code>) and 6 guided prompts your client
+      may surface as slash commands.
+    </p>
+  </div>
+</section>
+
+<!-- ══════════════════════════════════════════════════════════
+     GOING FURTHER
+     ══════════════════════════════════════════════════════════ -->
+<section id="more" style="padding-top:1rem">
+  <h2 style="font-family:var(--f-display);font-weight:400;font-size:1.9rem;margin:0 0 .3rem">Going further</h2>
+  <p style="color:var(--ink-2);margin-bottom:1.5rem">
+    Nothing below is on the numbered path. <b>When it breaks</b> is where steps 2 and 3 send
+    you if something fails; <b>Build it yourself</b> replaces the prebuilt download; the rest is
+    for after today.
+  </p>
+
+  <details class="panel" id="build-local">
+    <summary>Build it yourself <span class="hint">no image, no download</span></summary>
+    <div class="panel-body">
+      <p>Needs Git, JDK 25 and about three minutes.</p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>git clone https://github.com/apache/solr-mcp.git
+cd solr-mcp
+./gradlew build</code></pre></div>
+      <p>
+        Produces <code class="inline">build/libs/solr-mcp-1.0.0-SNAPSHOT.jar</code>. Use that
+        absolute path anywhere this card says <em>JAR</em>.
+      </p>
+      <h3>…and as a local Docker image</h3>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>./gradlew jibDockerBuild</code></pre></div>
+      <p>
+        Produces <code class="inline">solr-mcp:latest</code> locally. Substitute that for the
+        <code class="inline">ghcr.io/…</code> reference in any config above.
+      </p>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        Want faster startup and a smaller image? See <a href="#build-native">Native builds and
+        every way to run it</a> below.
+      </p>
+    </div>
+  </details>
+
+  <details class="panel" id="build-native">
+    <summary>Native builds and every way to run it <span class="hint">jar · native binary · Docker × stdio · http</span></summary>
+    <div class="panel-body">
+      <p>
+        Six ways to run the server, from one checkout. Each row is a build command and a run
+        command; <b>stdio</b> is launched by your client, <b>http</b> is a process you start
+        yourself (<a href="#s2">step 2</a>). Everything here uses the clone from
+        <a href="#build-local">Build it yourself</a>, with Solr from step 1 running.
+      </p>
+      <table>
+        <thead><tr><th>Form</th><th>stdio</th><th>http</th><th>Needs</th></tr></thead>
+        <tbody>
+          <tr><td>JVM jar</td><td><a href="#bn-jar">one build, both</a></td><td><a href="#bn-jar">same jar</a></td><td>JDK 25</td></tr>
+          <tr><td>JVM Docker image (Jib)</td><td><a href="#bn-docker">one image, both</a></td><td><a href="#bn-docker">same image</a></td><td>JDK 25, Docker</td></tr>
+          <tr><td>Native binary</td><td><a href="#bn-bin">build with stdio</a></td><td><a href="#bn-bin">build with http</a></td><td>GraalVM JDK 25</td></tr>
+          <tr><td>Native Docker image</td><td><a href="#bn-ndocker">…-native-stdio</a></td><td><a href="#bn-ndocker">…-native-http</a></td><td>Docker only</td></tr>
+        </tbody>
+      </table>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        The JVM forms are dual-mode: the <code class="inline">PROFILES</code> environment variable
+        picks the transport at run time. Native forms are <b>pinned to one transport at build
+        time</b> — Spring AOT bakes the web-application type into the binary — so you build one
+        per transport. In the commands below, <code class="inline">SOLR_URL</code> points at the
+        Solr from step 1.
+      </p>
+
+      <h3 id="bn-jar">JVM jar</h3>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>./gradlew build                                  <span class="tok-c"># build/libs/solr-mcp-1.0.0-SNAPSHOT.jar</span>
+
+<span class="tok-c"># stdio: your client runs this</span>
+SOLR_URL=http://localhost:8983/solr/ \
+  java -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar
+
+<span class="tok-c"># http: you run this</span>
+PROFILES=http HTTP_SECURITY_ENABLED=false SOLR_URL=http://localhost:8983/solr/ \
+  java -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar</code></pre></div>
+
+      <h3 id="bn-docker">JVM Docker image (Jib)</h3>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>./gradlew jibDockerBuild                         <span class="tok-c"># solr-mcp:latest</span>
+
+<span class="tok-c"># stdio: your client runs this (-i keeps stdin open)</span>
+docker run -i --rm --add-host=host.docker.internal:host-gateway \
+  -e SOLR_URL=http://host.docker.internal:8983/solr/ \
+  solr-mcp:latest
+
+<span class="tok-c"># http</span>
+docker run -p 8080:8080 --rm --add-host=host.docker.internal:host-gateway \
+  -e PROFILES=http -e HTTP_SECURITY_ENABLED=false \
+  -e SOLR_URL=http://host.docker.internal:8983/solr/ \
+  solr-mcp:latest</code></pre></div>
+
+      <h3 id="bn-bin">Native binary (host OS only)</h3>
+      <p>
+        Needs GraalVM JDK 25 on <code class="inline">JAVA_HOME</code>. The first compile takes
+        several minutes. The binary only runs on the OS and CPU you built it on.
+      </p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code><span class="tok-c"># stdio</span>
+./gradlew nativeCompile -Pnative
+SOLR_URL=http://localhost:8983/solr/ build/native/nativeCompile/solr-mcp
+
+<span class="tok-c"># http (a separate build: the transport is baked in)</span>
+./gradlew nativeCompile -Pnative -Pprofile=http
+PROFILES=http HTTP_SECURITY_ENABLED=false SOLR_URL=http://localhost:8983/solr/ \
+  build/native/nativeCompile/solr-mcp</code></pre></div>
+
+      <h3 id="bn-ndocker">Native Docker image</h3>
+      <p>
+        Built inside a Linux builder container, so it works on macOS, Linux and Windows without
+        installing GraalVM. Needs Docker and a few minutes.
+      </p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code><span class="tok-c"># stdio</span>
+./gradlew bootBuildImage -Pnative                <span class="tok-c"># solr-mcp:latest-native-stdio</span>
+docker run -i --rm --add-host=host.docker.internal:host-gateway \
+  -e SOLR_URL=http://host.docker.internal:8983/solr/ \
+  solr-mcp:latest-native-stdio
+
+<span class="tok-c"># http</span>
+./gradlew bootBuildImage -Pnative -Pprofile=http <span class="tok-c"># solr-mcp:latest-native-http</span>
+docker run -p 8080:8080 --rm --add-host=host.docker.internal:host-gateway \
+  -e PROFILES=http -e HTTP_SECURITY_ENABLED=false \
+  -e SOLR_URL=http://host.docker.internal:8983/solr/ \
+  solr-mcp:latest-native-http</code></pre></div>
+      <div class="note warn">
+        <span class="tag">Mixing them up fails quietly</span>
+        <p>
+          The <code class="inline">-native-stdio</code> image has no web server in it, and the
+          <code class="inline">-native-http</code> image has no stdio transport. Pointing a client
+          at the wrong one gives a timeout, not a helpful error.
+        </p>
+      </div>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        To use any of these with a client, take the matching config from
+        <a href="#s2">step 2</a> and swap in the image tag or binary path. Use
+        <code class="inline">solr-mcp:latest</code> where it says
+        <code class="inline">ghcr.io/…</code>, or the binary's absolute path where it says
+        <em>JAR</em>.
+      </p>
+    </div>
+  </details>
+
+  <details class="panel">
+    <summary>HTTP transport <span class="hint">remote access, multiple clients, one server</span></summary>
+    <div class="panel-body">
+      <p>
+        STDIO has your client launch the server as a subprocess. HTTP mode runs one server that
+        many clients connect to over <code class="inline">http://localhost:8080/mcp</code>. Pick
+        <b>HTTP</b> under <em>Transport</em> in <a href="#s0">step 0</a> and
+        <a href="#s2">step 2</a> shows how to start it and how each client connects.
+      </p>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        The hackathon commands turn security off with
+        <code class="inline">HTTP_SECURITY_ENABLED=false</code>. Anything reachable by other
+        people needs the OAuth2 setup in the <a href="#secured">Security setup</a> panel.
+      </p>
+    </div>
+  </details>
+
+  <details class="panel" id="observability">
+    <summary>Observability — traces and logs in Grafana <span class="hint">HTTP mode, one extra container</span></summary>
+    <div class="panel-body">
+      <p>
+        In HTTP mode the server exports <b>traces</b>, <b>logs</b> and <b>request metrics</b> over
+        OpenTelemetry. Point it at Grafana's all-in-one LGTM container and every tool call shows up
+        as a trace, with a span per tool and any log lines it wrote linked to it. About five minutes. Assumes Solr from step 1 is running. Apart from the server
+        command, commands are bash (macOS, Linux, WSL or Git Bash). STDIO mode exports
+        nothing, so pick <b>HTTP</b> under <em>Transport</em> in <a href="#s0">step 0</a> first.
+      </p>
+
+      <h3>1. Start LGTM</h3>
+      <p style="font-size:.86rem;color:var(--ink-2)">Grafana on 3000, the OTLP receiver on 4317 (gRPC) and 4318 (HTTP). The loop waits for the collector; Grafana answers first, and export only works once the collector is up.</p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>docker run -d --name lgtm -p 3000:3000 -p 4317:4317 -p 4318:4318 grafana/otel-lgtm
+
+until docker logs lgtm 2&gt;&amp;1 | grep -q "up and running"; do sleep 2; done
+echo "LGTM ready"</code></pre></div>
+
+      <h3>2. Restart the server pointed at it</h3>
+      <p style="font-size:.86rem;color:var(--ink-2)" data-runtime="docker">
+        Stop the step 2 server first — this one listens on 8080 too. The server sends to
+        <code class="inline">localhost:4317</code> by default, which inside a container is the
+        container itself, so <code class="inline">OTEL_TRACES_URL</code> sends it to your laptop
+        instead. Despite the name it covers logs as well as traces.
+      </p>
+      <p style="font-size:.86rem;color:var(--ink-2)" data-runtime="jar">
+        Stop the step 2 server first — this one listens on 8080 too. From a plain JAR the default
+        <code class="inline">localhost:4317</code> is already right, so the command is the same as
+        step 2: nothing to add.
+      </p>
+      <div class="codeblock" data-runtime="docker"><button class="copy" type="button">Copy</button><pre><code>docker run -p 8080:8080 --rm \
+  --add-host=host.docker.internal:host-gateway \
+  -e PROFILES=http \
+  -e HTTP_SECURITY_ENABLED=false \
+  -e SOLR_URL=http://host.docker.internal:8983/solr/ \
+  -e OTEL_TRACES_URL=http://host.docker.internal:4317 \
+  ghcr.io/adityamparikh/solr-mcp:coc2026</code></pre></div>
+      <div class="codeblock" data-runtime="jar" data-os="macos linux wsl"><button class="copy" type="button">Copy</button><pre><code>PROFILES=http HTTP_SECURITY_ENABLED=false SOLR_URL=http://localhost:8983/solr/ \
+  java -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar</code></pre></div>
+      <div class="codeblock" data-runtime="jar" data-os="windows"><button class="copy" type="button">Copy</button><pre><code><span class="tok-c"># PowerShell</span>
+$env:PROFILES="http"; $env:HTTP_SECURITY_ENABLED="false"; $env:SOLR_URL="http://localhost:8983/solr/"
+java -jar C:/path/to/solr-mcp-1.0.0-SNAPSHOT.jar</code></pre></div>
+
+      <h3>3. Make some traffic</h3>
+      <p style="font-size:.86rem;color:var(--ink-2)">Any prompt from steps 3–7 in your client works. Or, without a client, a few tool calls with curl — each one is a trace:</p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>for q in "*:*" "title:dragon" "genres:drama"; do
+  curl -s -X POST http://localhost:8080/mcp -H "Content-Type: application/json" \
+    -H "Accept: application/json, text/event-stream" \
+    -d "{\"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\"id\":1,\"params\":{\"name\":\"search\",\"arguments\":{\"collection\":\"shows-auto\",\"query\":\"$q\"}}}" &gt;/dev/null
+done
+
+<span class="tok-c"># one call that fails on purpose, so there is a log line to follow</span>
+curl -s -X POST http://localhost:8080/mcp -H "Content-Type: application/json" \
+  -H "Accept: application/json, text/event-stream" \
+  -d '{"jsonrpc":"2.0","method":"tools/call","id":2,"params":{"name":"check-health","arguments":{"collection":"no-such-collection"}}}' &gt;/dev/null</code></pre></div>
+
+      <h3>4. Look at it</h3>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        Open <a href="http://localhost:3000">localhost:3000</a> (no login) → <b>Explore</b>.
+        Pick <b>Tempo</b>, switch the query type to <b>TraceQL</b>, and run:
+      </p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>{resource.service.name="solr-mcp"}</code></pre></div>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        Open an <code class="inline">http post /mcp</code> trace. Inside it sit the security filter
+        chain and one span named for the tool — <code class="inline">search-service#search</code>,
+        <code class="inline">indexing-service#…</code> — with its duration. Traces take up to a minute to
+        become searchable — an empty result right after the calls is normal, so give it thirty
+        seconds and run the query again.
+      </p>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        Now open the <code class="inline">collection-service#check-health</code> trace from the
+        failing call and click <b>Logs for this span</b>: Loki opens on its
+        <code class="inline">WARN Health check failed</code> line, matched by trace ID. Each
+        log line links back the same way. Request metrics land in <b>Prometheus</b> once a
+        minute; pick it in Explore and run:
+      </p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>http_server_request_duration_seconds_count{job="solr-mcp"}</code></pre></div>
+
+      <div class="note warn">
+        <span class="tag">What you won't see</span>
+        <p>
+          <b>Logs on successful calls</b> — the services only log when something fails, so for
+          the searches <em>Logs for this span</em> comes up empty. That's expected, not broken.
+          <b>JVM metrics</b> aren't exported either; they're served for scraping:
+          <code class="inline">curl -s localhost:8080/actuator/prometheus | grep jvm_memory_used</code>.
+          And the call to Solr has no span of its own; its time is inside the tool span.
+        </p>
+      </div>
+
+      <h3>Tear down</h3>
+      <p style="font-size:.86rem;color:var(--ink-2)">LGTM keeps everything in memory, so removing the container discards all of it.</p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>docker rm -f lgtm</code></pre></div>
+    </div>
+  </details>
+
+  <details class="panel" id="secured">
+    <summary>Security setup — HTTP mode with OAuth2 on <span class="hint">Keycloak on your laptop, start to finish</span></summary>
+    <div class="panel-body">
+      <p>
+        Everything above runs with <code class="inline">HTTP_SECURITY_ENABLED=false</code>. This
+        panel turns security on and proves it, with Keycloak as the identity provider. About ten
+        minutes. It assumes Solr from step 1 is running, the JAR from step 0 is downloaded with
+        Java 25 on the PATH, and you have <code class="inline">docker</code>,
+        <code class="inline">curl</code> and <code class="inline">jq</code>. Commands are bash
+        (macOS, Linux, WSL or Git Bash). Stop any unsecured server from step 2 first — both
+        listen on 8080.
+      </p>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        How it fits together: the server is a <b>resource server</b>. Keycloak issues a JWT, the
+        server checks its signature against Keycloak's keys, the issuer, the expiry, and that the
+        <code class="inline">aud</code> claim contains the URL the client dials —
+        <code class="inline">http://localhost:8080/mcp</code>. Every step below exists to make one
+        of those checks pass.
+      </p>
+
+      <h3>1. Start Keycloak</h3>
+      <p style="font-size:.86rem;color:var(--ink-2)">Port 8180, so it does not collide with the MCP server. The loop waits until Keycloak actually answers; the container is "up" long before that.</p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>docker run -d --name keycloak -p 8180:8080 \
+  -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
+  quay.io/keycloak/keycloak:26.0 start-dev
+
+until curl -sf http://localhost:8180/realms/master/.well-known/openid-configuration >/dev/null; do sleep 2; done
+echo "Keycloak ready"</code></pre></div>
+
+      <h3>2. Create the realm, client and test user</h3>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        One paste. It logs in as admin, creates a realm <code class="inline">solr-mcp</code>, a
+        public client <code class="inline">solr-mcp-client</code> with the <b>audience mapper</b>
+        that puts <code class="inline">http://localhost:8080/mcp</code> into every token (Keycloak
+        does not honour the standard <code class="inline">resource=</code> parameter, so without
+        this mapper every token is rejected), and a user
+        <code class="inline">testuser</code> / <code class="inline">testpassword</code>. First and
+        last name are not decoration — Keycloak refuses to issue a token to a user missing either.
+        Expect three <code class="inline">201</code>s.
+      </p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>KC=http://localhost:8180
+ADMIN_TOKEN=$(curl -s -X POST "$KC/realms/master/protocol/openid-connect/token" \
+  -d client_id=admin-cli -d username=admin -d password=admin -d grant_type=password | jq -r .access_token)
+
+curl -s -o /dev/null -w "realm %{http_code}\n" -X POST "$KC/admin/realms" \
+  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
+  -d '{"realm":"solr-mcp","enabled":true}'
+
+curl -s -o /dev/null -w "client %{http_code}\n" -X POST "$KC/admin/realms/solr-mcp/clients" \
+  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
+  -d '{"clientId":"solr-mcp-client","publicClient":true,"directAccessGrantsEnabled":true,
+       "redirectUris":["http://localhost:6274/*"],"webOrigins":["http://localhost:6274"],
+       "protocolMappers":[{"name":"mcp-audience","protocol":"openid-connect",
+         "protocolMapper":"oidc-audience-mapper",
+         "config":{"included.custom.audience":"http://localhost:8080/mcp",
+                   "access.token.claim":"true","id.token.claim":"false"}}]}'
+
+curl -s -o /dev/null -w "user %{http_code}\n" -X POST "$KC/admin/realms/solr-mcp/users" \
+  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
+  -d '{"username":"testuser","email":"test@example.com","firstName":"Test","lastName":"User",
+       "enabled":true,"emailVerified":true,
+       "credentials":[{"type":"password","value":"testpassword","temporary":false}]}'
+
+# Tokens last 5 minutes by default; one hour is kinder for a trial. Expect 204.
+curl -s -o /dev/null -w "lifetime %{http_code}\n" -X PUT "$KC/admin/realms/solr-mcp" \
+  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
+  -d '{"accessTokenLifespan":3600}'
+
+curl -sf "$KC/realms/solr-mcp/.well-known/openid-configuration" >/dev/null && echo "realm ready"</code></pre></div>
+
+      <h3>3. Start the server with security on</h3>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        The realm must answer <b>before</b> this: the server fetches the issuer's configuration
+        while starting and exits with code 1 if it cannot. That is the "realm ready" line above.
+        Use the JAR here rather than the Docker image — inside a container
+        <code class="inline">localhost:8180</code> is not your laptop, and the issuer name has
+        to be identical in the container and in the tokens, which takes more setup than a card.
+      </p>
+      <div class="codeblock" data-os="macos linux wsl"><button class="copy" type="button">Copy</button><pre><code>PROFILES=http HTTP_SECURITY_ENABLED=true \
+OAUTH2_ISSUER_URI=http://localhost:8180/realms/solr-mcp \
+SOLR_URL=http://localhost:8983/solr/ \
+java -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar</code></pre></div>
+      <div class="codeblock" data-os="windows"><button class="copy" type="button">Copy</button><pre><code><span class="tok-c"># PowerShell</span>
+$env:PROFILES="http"; $env:HTTP_SECURITY_ENABLED="true"; $env:OAUTH2_ISSUER_URI="http://localhost:8180/realms/solr-mcp"; $env:SOLR_URL="http://localhost:8983/solr/"
+java -jar C:/path/to/solr-mcp-1.0.0-SNAPSHOT.jar</code></pre></div>
+      <p style="font-size:.86rem;color:var(--ink-2)">Leave that terminal open. In another one, wait for
+        <code class="inline">curl http://localhost:8080/actuator/health</code> to answer
+        <code class="inline">{"status":"UP"}</code>, then read back the audience the server expects.
+        This is the value the mapper in step 2 has to match, character for character:</p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>curl -s http://localhost:8080/.well-known/oauth-protected-resource | jq -r .resource
+# http://localhost:8080/mcp</code></pre></div>
+
+      <h3>4. Get a token</h3>
+      <p style="font-size:.86rem;color:var(--ink-2)">The password grant, which is what the client in step 2 allows. The second command decodes the token and prints its audience — it must list <code class="inline">http://localhost:8080/mcp</code>.</p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>TOKEN=$(curl -s -X POST http://localhost:8180/realms/solr-mcp/protocol/openid-connect/token \
+  -d client_id=solr-mcp-client -d username=testuser -d password=testpassword \
+  -d grant_type=password | jq -r .access_token)
+
+python3 - "$TOKEN" &lt;&lt;'EOF'
+import sys, base64, json
+p = sys.argv[1].split('.')[1]; p += '=' * (-len(p) % 4)
+print(json.loads(base64.urlsafe_b64decode(p))['aud'])
+EOF
+# ['http://localhost:8080/mcp', 'account']</code></pre></div>
+
+      <h3>5. Prove the gate with curl</h3>
+      <p style="font-size:.86rem;color:var(--ink-2)">The same tool call twice. Without the header the server answers HTTP 200 with an <code class="inline">Access Denied</code> result; with it you get the collection names. Both lines are the test — one alone proves nothing.</p>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>CALL='{"jsonrpc":"2.0","method":"tools/call","id":1,"params":{"name":"list-collections","arguments":{}}}'
+
+curl -s -X POST http://localhost:8080/mcp -H "Content-Type: application/json" \
+  -H "Accept: application/json, text/event-stream" -d "$CALL"
+# ... "text":"Access Denied" ... "isError":true
+
+curl -s -X POST http://localhost:8080/mcp -H "Content-Type: application/json" \
+  -H "Accept: application/json, text/event-stream" -H "Authorization: Bearer $TOKEN" -d "$CALL"
+# ... "text":"[\"shows-auto\", ...]" ... "isError":false   (your collections from steps 3 and 5)</code></pre></div>
+
+      <h3>6. Connect your client — with the token in a header</h3>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        The header is the whole point of this step. The server never answers an anonymous
+        <code class="inline">/mcp</code> request with 401, and MCP clients start their OAuth
+        login only when they see a 401 — so a client given just the URL reports
+        <b>connected</b>, lists every tool, and gets <code class="inline">Access Denied</code>
+        from every call, with no browser opening. Nothing looks broken. Pass
+        <code class="inline">$TOKEN</code> from step 4 explicitly.
+      </p>
+
+      <div data-transport="stdio" class="note warn">
+        <span class="tag">HTTP transport only</span>
+        <p>
+          OAuth2 bearer token security applies only to the <b>HTTP</b> transport. In <b>STDIO</b> mode, your client launches the server directly as a local subprocess over standard input/output without network exposure or bearer authentication headers. Switch <b>Transport</b> to <b>HTTP</b> in <a href="#s0">step 0</a> to see client connection snippets with the token in a header.
+        </p>
+      </div>
+
+      <div data-transport="http">
+        <p style="font-size:.86rem;color:var(--ink-2)" data-runtime="docker">
+          Make sure your Solr MCP Docker container is running and port 8080 is published (<code class="inline">-p 8080:8080</code>) so your client can reach <code class="inline">http://localhost:8080/mcp</code>.
+        </p>
+        <p style="font-size:.86rem;color:var(--ink-2)" data-runtime="jar">
+          Make sure your Solr MCP JAR process from step 3 is running and listening on port 8080 before connecting your client.
+        </p>
+        <p style="font-size:.86rem;color:var(--ink-2)" data-format="json">
+          Dataset: <code class="inline">shows-auto</code> was indexed from JSON in step 3. The verification query checks that collection.
+        </p>
+        <p style="font-size:.86rem;color:var(--ink-2)" data-format="csv">
+          Dataset: <code class="inline">shows-auto</code> was indexed from CSV in step 3. The verification query checks that collection.
+        </p>
+        <p style="font-size:.86rem;color:var(--ink-2)" data-format="xml">
+          Dataset: <code class="inline">shows-auto</code> was indexed from XML in step 3 (fields carry the <code class="inline">show_</code> prefix). The verification query checks that collection.
+        </p>
+        <p style="font-size:.86rem;color:var(--ink-2)" data-format="markdown">
+          Dataset: <code class="inline">shows-auto</code> was indexed from Markdown in step 3. The verification query checks that collection.
+        </p>
+
+        <!-- ── Claude Code ── -->
+        <div data-client="claude-code">
+          <h4>Claude Code</h4>
+          <div class="codeblock" data-os="macos linux wsl"><button class="copy" type="button">Copy</button><pre><code>claude mcp add --transport http solr-mcp http://localhost:8080/mcp \
+  --header "Authorization: Bearer $TOKEN"
+claude mcp list      # ✔ Connected — true with or without the header, so also run:
+claude -p "Call the list-collections tool on the solr-mcp MCP server and reply with its raw result"
+# ["shows-auto", ...]   ← the proof. "Access Denied" here means the header did not reach the server.</code></pre></div>
+          <div class="codeblock" data-os="windows"><button class="copy" type="button">Copy</button><pre><code><span class="tok-c"># PowerShell</span>
+claude mcp add --transport http solr-mcp http://localhost:8080/mcp `
+  --header "Authorization: Bearer $TOKEN"
+claude mcp list      <span class="tok-c"># ✔ Connected — true with or without the header, so also run:</span>
+claude -p "Call the list-collections tool on the solr-mcp MCP server and reply with its raw result"
+<span class="tok-c"># ["shows-auto", ...]   ← the proof. "Access Denied" here means the header did not reach the server.</span></code></pre></div>
+        </div>
+
+        <!-- ── Codex ── -->
+        <div data-client="codex">
+          <h4>Codex</h4>
+          <p style="font-size:.86rem;color:var(--ink-2)">
+            Codex reads the token from an environment variable you name, so the token never lands in
+            <code class="inline">config.toml</code>. Export it in the shell that launches Codex; re-running
+            <code class="inline">codex mcp add</code> replaces an earlier unsecured entry.
+          </p>
+          <div class="codeblock" data-os="macos linux wsl"><button class="copy" type="button">Copy</button><pre><code>export SOLR_MCP_TOKEN="$TOKEN"
+codex mcp add solr-mcp --url http://localhost:8080/mcp --bearer-token-env-var SOLR_MCP_TOKEN
+codex mcp list       # Auth: Bearer token — true with or without a valid token, so also run:
+codex exec "Call the list-collections tool on the solr-mcp MCP server and reply with its raw result"
+# ["shows-auto", ...]   ← the proof. "Access Denied" here means the token did not reach the server.</code></pre></div>
+          <div class="codeblock" data-os="windows"><button class="copy" type="button">Copy</button><pre><code><span class="tok-c"># PowerShell</span>
+$env:SOLR_MCP_TOKEN = $TOKEN
+codex mcp add solr-mcp --url http://localhost:8080/mcp --bearer-token-env-var SOLR_MCP_TOKEN
+codex mcp list       <span class="tok-c"># Auth: Bearer token — true with or without a valid token, so also run:</span>
+codex exec "Call the list-collections tool on the solr-mcp MCP server and reply with its raw result"
+<span class="tok-c"># ["shows-auto", ...]   ← the proof. "Access Denied" here means the token did not reach the server.</span></code></pre></div>
+          <p style="font-size:.86rem;color:var(--ink-2)">
+            The token expires after about five minutes; restart Codex after refreshing it so it re-reads the variable.
+          </p>
+        </div>
+
+        <!-- ── ChatGPT ── -->
+        <div data-client="chatgpt">
+          <h4>ChatGPT</h4>
+          <div class="note warn">
+            <span class="tag">Not supported</span>
+            <p>ChatGPT supports OAuth 2.1 or no authentication — not a static bearer token — and it has no way to start a login against this server's secured mode, which never answers an anonymous request with 401. Use another client for this step.</p>
+          </div>
+        </div>
+
+        <!-- ── Claude Desktop ── -->
+        <div data-client="claude-desktop">
+          <h4>Claude Desktop (via mcp-remote)</h4>
+          <p style="font-size:.86rem;color:var(--ink-2)">Paste the token into <code class="inline">env</code>; <code class="inline">mcp-remote</code> forwards it on every request. Edit your config file, then <b>fully quit and reopen</b> Claude Desktop:</p>
+          <div class="codeblock" data-os="macos"><button class="copy" type="button">Copy</button><pre><code>~/Library/Application Support/Claude/claude_desktop_config.json</code></pre></div>
+          <div class="codeblock" data-os="windows wsl"><button class="copy" type="button">Copy</button><pre><code>%APPDATA%\Claude\claude_desktop_config.json</code></pre></div>
+          <div class="codeblock" data-os="linux"><button class="copy" type="button">Copy</button><pre><code>~/.config/Claude/claude_desktop_config.json</code></pre></div>
+          <div class="note warn" data-os="wsl">
+            <span class="tag">On WSL</span>
+            <p>
+              Claude Desktop runs on the Windows host, not inside WSL — so use the Windows path above
+              and let Docker Desktop's WSL integration handle the container.
+            </p>
+          </div>
+          <div class="note warn" data-os="linux">
+            <span class="tag">On Linux</span>
+            <p>
+              There is no official Claude Desktop build for Linux. That path is where community
+              builds look; if you're on Linux, <b>Claude Code</b> or <b>MCP Inspector</b> is the
+              smoother route.
+            </p>
+          </div>
+          <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>{
+  "mcpServers": {
+    "solr-mcp": {
+      "command": "npx",
+      "args": ["-y", "mcp-remote", "http://localhost:8080/mcp", "--allow-http",
+               "--header", "Authorization: Bearer ${TOKEN}"],
+      "env": { "TOKEN": "paste-the-token-here" }
+    }
+  }
+}</code></pre></div>
+        </div>
+
+        <!-- ── VS Code / Copilot ── -->
+        <div data-client="copilot">
+          <h4>VS Code / GitHub Copilot</h4>
+          <p style="font-size:.86rem;color:var(--ink-2)">In <code class="inline">.vscode/mcp.json</code>, add the <code class="inline">headers</code> object to the server entry from step 2. Using an <code class="inline">inputs</code> prompt keeps the token out of source control:</p>
+          <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>{
+  "inputs": [
+    { "type": "promptString", "id": "solr-token", "description": "Solr MCP token", "password": true }
+  ],
+  "servers": {
+    "solr-mcp": {
+      "type": "http",
+      "url": "http://localhost:8080/mcp",
+      "headers": { "Authorization": "Bearer ${input:solr-token}" }
+    }
+  }
+}</code></pre></div>
+        </div>
+
+        <!-- ── Cursor ── -->
+        <div data-client="cursor">
+          <h4>Cursor</h4>
+          <p style="font-size:.86rem;color:var(--ink-2)">In <code class="inline">.cursor/mcp.json</code>, add the <code class="inline">headers</code> object to your server entry. Cursor expands <code class="inline">${env:SOLR_MCP_TOKEN}</code> from your environment:</p>
+          <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>{
+  "mcpServers": {
+    "solr-mcp": {
+      "url": "http://localhost:8080/mcp",
+      "headers": { "Authorization": "Bearer ${env:SOLR_MCP_TOKEN}" }
+    }
+  }
+}</code></pre></div>
+        </div>
+
+        <!-- ── Zed ── -->
+        <div data-client="zed">
+          <h4>Zed</h4>
+          <p style="font-size:.86rem;color:var(--ink-2)">In <code class="inline">zed: open settings</code> (JSON), add the <code class="inline">headers</code> object to your remote server entry:</p>
+          <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>{
+  "context_servers": {
+    "solr-mcp": {
+      "url": "http://localhost:8080/mcp",
+      "headers": { "Authorization": "Bearer paste-the-token-here" }
+    }
+  }
+}</code></pre></div>
+        </div>
+
+        <!-- ── IntelliJ ── -->
+        <div data-client="intellij">
+          <h4>IntelliJ IDEA / JetBrains (via mcp-remote)</h4>
+          <p style="font-size:.86rem;color:var(--ink-2)">In <code class="inline">.junie/mcp/mcp.json</code> or IDE Settings, proxy through <code class="inline">mcp-remote</code> to forward the bearer header on each request:</p>
+          <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>{
+  "mcpServers": {
+    "solr-mcp": {
+      "command": "npx",
+      "args": ["-y", "mcp-remote", "http://localhost:8080/mcp", "--allow-http",
+               "--header", "Authorization: Bearer ${TOKEN}"],
+      "env": { "TOKEN": "paste-the-token-here" }
+    }
+  }
+}</code></pre></div>
+        </div>
+
+        <!-- ── Inspector ── -->
+        <div data-client="inspector">
+          <h4>MCP Inspector</h4>
+          <div class="codeblock" data-os="macos linux wsl"><button class="copy" type="button">Copy</button><pre><code># web UI
+npx @modelcontextprotocol/inspector --server-url http://localhost:8080/mcp --transport http \
+  --header "Authorization: Bearer $TOKEN"
+# or one call from the CLI, no browser
+npx @modelcontextprotocol/inspector --cli http://localhost:8080/mcp --transport http \
+  --header "Authorization: Bearer $TOKEN" --method tools/call --tool-name list-collections</code></pre></div>
+          <div class="codeblock" data-os="windows"><button class="copy" type="button">Copy</button><pre><code><span class="tok-c"># PowerShell web UI</span>
+npx @modelcontextprotocol/inspector --server-url http://localhost:8080/mcp --transport http `
+  --header "Authorization: Bearer $TOKEN"
+<span class="tok-c"># or one call from the CLI, no browser</span>
+npx @modelcontextprotocol/inspector --cli http://localhost:8080/mcp --transport http `
+  --header "Authorization: Bearer $TOKEN" --method tools/call --tool-name list-collections</code></pre></div>
+        </div>
+      </div>
+
+      <h3>When it breaks</h3>
+      <div class="tablewrap">
+        <table>
+          <thead><tr><th>Symptom</th><th>Cause &amp; fix</th></tr></thead>
+          <tbody>
+            <tr>
+              <td>Server exits with code 1 right after starting</td>
+              <td>The realm was not answering when the server resolved the issuer. Re-run the last line of step 2 until it prints <code>realm ready</code>, then start the server again.</td>
+            </tr>
+            <tr>
+              <td><code>401</code> with <code>The aud claim is not valid</code></td>
+              <td>The mapper's audience and the URL the client dials differ — <code>127.0.0.1</code> vs <code>localhost</code> is enough. Compare step 3's <code>.resource</code> with step 4's <code>aud</code>, and use the same spelling in the client.</td>
+            </tr>
+            <tr>
+              <td><code>401</code> with <code>Jwt expired</code></td>
+              <td>Re-run step 4 and re-add the server in the client. A client with a configured header reports a failed connection rather than logging in again.</td>
+            </tr>
+            <tr>
+              <td><code>Access Denied</code> <i>with</i> the header</td>
+              <td><code>$TOKEN</code> is empty or <code>null</code>: the token request in step 4 failed. Run it without <code>jq</code> and read the error. <code>Account is not fully set up</code> means the user is missing first/last name or has a temporary password.</td>
+            </tr>
+            <tr>
+              <td>Client shows connected, calls denied, no browser opens</td>
+              <td>Expected without the header — see step 6. This is not an OAuth-capable-client problem; the server never sends the 401 that would start the flow.</td>
+            </tr>
+            <tr>
+              <td>Want to see <i>why</i> a token was rejected</td>
+              <td><code>curl -si -H "Authorization: Bearer $TOKEN" localhost:8080/actuator/metrics | grep -i www-authenticate</code> — a protected actuator endpoint returns a plain 401 with the reason.</td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+
+      <h3>Tear down</h3>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>docker rm -f keycloak
+claude mcp remove solr-mcp</code></pre></div>
+
+      <h3>Using Auth0 instead of Keycloak</h3>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        Same server, same checks; only steps 1–4 change. You need your own Auth0 tenant
+        (the free tier works) and administrative access.
+      </p>
+
+      <ol style="max-width:var(--measure);color:var(--ink-2);padding-left:1.1rem;font-size:.86rem">
+        <li>
+          <b>Applications → APIs → Create API.</b>
+          Name it <code class="inline">Solr MCP API</code>. Set the <b>Identifier</b> to exactly
+          <code class="inline">http://localhost:8080/mcp</code> with signing algorithm <b>RS256</b>.
+          The identifier becomes the token's <code class="inline">aud</code> (audience) claim,
+          which the server matches character-for-character against its incoming resource URI.
+          It cannot be edited after creation.
+        </li>
+        <li>
+          <b>Authorize your Machine to Machine application.</b>
+          Auth0 creates a default test application named <i>Solr MCP API (Test Application)</i>.
+          Go to <b>Applications → APIs → Solr MCP API → Machine to Machine Applications</b>,
+          and ensure your app is toggled to <b>Authorized</b>. On the application's <b>Settings</b> tab
+          (under <b>Applications → Applications</b>), copy your <b>Domain</b>, <b>Client ID</b>, and <b>Client Secret</b>.
+        </li>
+        <li>
+          <b>Start the server with security on.</b>
+          Because Auth0 is a public HTTPS provider, both Docker and JAR run directly without host-networking hurdles.
+          The trailing slash in <code class="inline">OAUTH2_ISSUER_URI</code> is mandatory — Auth0 writes its
+          <code class="inline">iss</code> claim with a trailing slash:
+        </li>
+      </ol>
+
+      <div class="codeblock" data-runtime="docker"><button class="copy" type="button">Copy</button><pre><code>docker run -p 8080:8080 --rm \
+  -e PROFILES=http \
+  -e HTTP_SECURITY_ENABLED=true \
+  -e OAUTH2_ISSUER_URI=https://YOUR-TENANT.us.auth0.com/ \
+  -e SOLR_URL=http://host.docker.internal:8983/solr/ \
+  ghcr.io/adityamparikh/solr-mcp:coc2026</code></pre></div>
+
+      <div class="codeblock" data-runtime="jar" data-os="macos linux wsl"><button class="copy" type="button">Copy</button><pre><code>PROFILES=http HTTP_SECURITY_ENABLED=true \
+OAUTH2_ISSUER_URI=https://YOUR-TENANT.us.auth0.com/ \
+SOLR_URL=http://localhost:8983/solr/ \
+java -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar</code></pre></div>
+      <div class="codeblock" data-runtime="jar" data-os="windows"><button class="copy" type="button">Copy</button><pre><code><span class="tok-c"># PowerShell</span>
+$env:PROFILES="http"; $env:HTTP_SECURITY_ENABLED="true"; $env:OAUTH2_ISSUER_URI="https://YOUR-TENANT.us.auth0.com/"; $env:SOLR_URL="http://localhost:8983/solr/"
+java -jar C:/path/to/solr-mcp-1.0.0-SNAPSHOT.jar</code></pre></div>
+
+      <ol start="4" style="max-width:var(--measure);color:var(--ink-2);padding-left:1.1rem;font-size:.86rem">
+        <li>
+          <b>Get a token using the Client Credentials grant.</b>
+          Auth0 M2M tokens default to 24 hours (86,400s), so unlike Keycloak no lifetime adjustment is needed.
+          Mint the token with <code class="inline">curl</code>:
+        </li>
+      </ol>
+
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>TOKEN=$(curl -s -X POST https://YOUR-TENANT.us.auth0.com/oauth/token \
+  -H 'content-type: application/json' \
+  -d '{"client_id":"YOUR_CLIENT_ID","client_secret":"YOUR_CLIENT_SECRET",
+       "audience":"http://localhost:8080/mcp","grant_type":"client_credentials"}' | jq -r .access_token)</code></pre></div>
+
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        Or use the repository helper script with your <code class="inline">.env</code> file:
+      </p>
+
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>TOKEN=$(./scripts/get-auth0-token.sh --quiet)</code></pre></div>
+
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        Confirm the token audience:
+      </p>
+
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>python3 - "$TOKEN" &lt;&lt;'EOF'
+import sys, base64, json
+p = sys.argv[1].split('.')[1]; p += '=' * (-len(p) % 4)
+print(json.loads(base64.urlsafe_b64decode(p))['aud'])
+EOF
+# http://localhost:8080/mcp</code></pre></div>
+
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        Now continue to <b>step 5</b> above to test the gate with curl, then <b>step 6</b> to connect your client.
+      </p>
+
+      <h3>Beyond the laptop</h3>
+      <p style="font-size:.86rem;color:var(--ink-2)">
+        For a team deployment — TLS, a public issuer, the public URL as the audience, an
+        application that refreshes its own token — the full guides are the reference:
+      </p>
+      <div class="linkrow">
+        <a href="https://github.com/apache/solr-mcp/blob/main/docs/security/keycloak.md">Keycloak guide</a>
+        <a href="https://github.com/apache/solr-mcp/blob/main/docs/security/auth0.md">Auth0 guide</a>
+        <a href="https://github.com/apache/solr-mcp/blob/main/docs/security/http.md">HTTP security model</a>
+        <a href="https://github.com/apache/solr-mcp/blob/main/docs/security/deployment-model.md">Deployment model (single-tenant)</a>
+        <a href="https://github.com/apache/solr-mcp/blob/main/docs/security/stdio.md">STDIO security model</a>
+      </div>
+    </div>
+  </details>
+
+  <details class="panel" id="broken">
+    <summary>When it breaks <span class="hint">the five things that actually go wrong</span></summary>
+    <div class="panel-body">
+      <div class="tablewrap">
+        <table>
+          <thead><tr><th>Symptom</th><th>Cause &amp; fix</th></tr></thead>
+          <tbody>
+            <tr>
+              <td>Client says the server failed to start, no detail</td>
+              <td>Almost always <b>Java version</b>. Run <code>java -version</code> — the JAR needs 25. STDIO swallows the error. Switch to the Docker path if you'd rather not install a JDK.</td>
+            </tr>
+            <tr>
+              <td>Connects, but every Solr call fails</td>
+              <td><code>SOLR_URL</code> is wrong for the context. From <b>inside</b> a container use <code>host.docker.internal</code>; from a plain JAR use <code>localhost</code>. The <code>--add-host</code> flag in every Docker command here is what makes that name resolve on Linux — it needs Docker 20.10+.</td>
+            </tr>
+            <tr>
+              <td><code>Solr instance is not running in SolrCloud mode</code></td>
+              <td>The container was started without <code>-c</code>. Collections need SolrCloud. Re-run the step 1 command exactly.</td>
+            </tr>
+            <tr>
+              <td>New collection already has fields you didn't add</td>
+              <td>Every collection shares the <code>_default</code> configset, so the first collection's field types leak into later ones — and types can't be changed once set. Restart the container to reset (step 5). Filed as <a href="https://github.com/apache/solr-mcp/issues/183">#183</a>.</td>
+            </tr>
+            <tr>
+              <td>Results ignore your sort</td>
+              <td>The <code>search</code> tool takes <code>sortClauses</code> as objects shaped <code>{"item":"imdb_rating","order":"desc"}</code>. Unknown parameters are silently dropped rather than rejected, so a wrong key looks like it worked. Ask the assistant to retry and name the field explicitly.</td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+      <h3>Start completely over</h3>
+      <div class="codeblock"><button class="copy" type="button">Copy</button><pre><code>docker rm -f solr-mcp-demo
+docker run -d --name solr-mcp-demo -p 8983:8983 solr:9-slim solr start -c -f</code></pre></div>
+    </div>
+  </details>
+
+  <details class="panel">
+    <summary>Take it home <span class="hint">contribute, or just keep poking</span></summary>
+    <div class="panel-body">
+      <div class="linkrow">
+        <a href="https://github.com/apache/solr-mcp">Repository</a>
+        <a href="https://github.com/apache/solr-mcp/issues">Issues</a>
+        <a href="https://lists.apache.org/list.html?dev@solr.apache.org">dev@solr.apache.org</a>
+        <a href="https://github.com/apache/solr-mcp/blob/main/CONTRIBUTING.md">Contributing</a>
+        <a href="https://the-asf.slack.com/archives/C09TVG3BM1P">#solr-mcp on ASF Slack</a>
+      </div>
+      <p>
+        Good first issues tend to be client guides, tool descriptions and error messages —
+        all of which directly affect how well an assistant uses the server. If something on
+        this card confused you, that's a bug worth filing.
+      </p>
+      <h3>Ideas worth trying while you're here</h3>
+      <ul style="max-width:var(--measure);color:var(--ink-2);padding-left:1.1rem">
+        <li>Index your own JSON, CSV or XML and see what the schema guesser makes of it.</li>
+        <li>Add a <code class="inline">DenseVectorField</code> with <code class="inline">add-field-types</code> and try vector search.</li>
+        <li>Ask the assistant to design a schema for a dataset you describe in words.</li>
+        <li>Point it at a Solr you already run — <code class="inline">SOLR_URL</code> is all it takes.</li>
+      </ul>
+    </div>
+  </details>
+</section>
+
+<footer class="footer">
+  <p>
+    <b>Apache Solr MCP Server</b> — incubating, Apache License 2.0. Solr, Apache and the feather
+    logo are trademarks of the Apache Software Foundation. This card was prepared for a
+    Community over Code 2026 hackathon session; the container image it references is a personal
+    pre-release build, not an Apache release.
+  </p>
+  <p>Verified against Solr 9 (SolrCloud), Spring Boot 3.5.14, solr-mcp 1.0.0-SNAPSHOT.</p>
+  <div class="footer-events">
+    <a class="ac-banner" href="https://events.apache.org/" title="Apache Events Calendar">
+      <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAAA8CAIAAADnkbErAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TpUVaFOwgopChOtlFRRxrFYpQIdQKrTqYXPoFTRqSFBdHwbXg4Mdi1cHFWVcHV0EQ/ABxdXFSdJES/5cWWsR4cNyPd/ced+8AoVFhmtUTBzTdNtPJhJjNrYqBVwQxgDBGIcjMMuYkKQXP8XUPH1/vYjzL+9yfI6zmLQb4ROI4M0ybeIN4ZtM2OO8TR1hJVonPiSdMuiDxI9eVFr9xLros8MyImUnPE0eIxWIXK13MSqZGPE0cVTWd8oVsi1XOW5y1So2178lfGMrrK8tcpzmCJBaxBAkiFNRQRgU2YrTqpFhI037Cwz/s+iVyKeQqg5FjAVVokF0/+B/87tYqTE22kkIJoPfFcT7GgMAu0Kw7zvex4z [...]
+    </a>
+    <span class="lbl">Upcoming Apache events</span>
+  </div>
+  <div class="tags">
+    <span>#CommunityOverCode</span><span>#ASF2026Glasgow</span><span>#OpenSource</span>
+  </div>
+</footer>
+
+</div>
+
+<script>
+(function () {
+  "use strict";
+
+  var STATE_KEY = "solrmcp-coc2026-state";
+  var DONE_KEY = "solrmcp-coc2026-done";
+
+  var state = { os: "macos", runtime: "docker", transport: "stdio", format: "json", client: "claude-code" };
+  try {
+    var saved = JSON.parse(localStorage.getItem(STATE_KEY) || "{}");
+    ["os", "runtime", "transport", "format", "client"].forEach(function (k) {
+      if (saved[k]) state[k] = saved[k];
+    });
+  } catch (e) { /* first visit, or storage blocked */ }
+
+  /* ---- show/hide blocks matching the current selection ---- */
+  function matches(el, attr, value) {
+    var spec = el.getAttribute(attr);
+    if (spec === null) return true;
+    return spec.split(/\s+/).indexOf(value) !== -1;
+  }
+
+  function apply() {
+    document.querySelectorAll("[data-os],[data-runtime],[data-transport],[data-format],[data-client]").forEach(function (el) {
+      var ok = matches(el, "data-os", state.os)
+            && matches(el, "data-runtime", state.runtime)
+            && matches(el, "data-transport", state.transport)
+            && matches(el, "data-format", state.format)
+            && matches(el, "data-client", state.client);
+      el.hidden = !ok;
+    });
+    document.querySelectorAll(".seg").forEach(function (seg) {
+      var group = seg.getAttribute("data-group");
+      seg.querySelectorAll("button").forEach(function (b) {
+        b.setAttribute("aria-pressed", String(b.getAttribute("data-val") === state[group]));
+      });
+    });
+    try { localStorage.setItem(STATE_KEY, JSON.stringify(state)); } catch (e) {}
+  }
+
+  document.querySelectorAll(".seg").forEach(function (seg) {
+    var group = seg.getAttribute("data-group");
+    seg.addEventListener("click", function (ev) {
+      var btn = ev.target.closest("button[data-val]");
+      if (!btn) return;
+      state[group] = btn.getAttribute("data-val");
+      apply();
+    });
+  });
+
+  /* ---- open a panel when linked to by hash ---- */
+  function openHashPanel() {
+    var el = location.hash && document.getElementById(location.hash.slice(1));
+    if (el && el.tagName === "DETAILS") { el.open = true; }
+  }
+  window.addEventListener("hashchange", openHashPanel);
+  openHashPanel();
+
+  /* ---- copy buttons ---- */
+  function flash(btn, label) {
+    var original = btn.textContent;
+    btn.textContent = label;
+    btn.setAttribute("data-copied", "1");
+    setTimeout(function () {
+      btn.textContent = original;
+      btn.removeAttribute("data-copied");
+    }, 1400);
+  }
+
+  function writeClipboard(text, btn) {
+    if (navigator.clipboard && navigator.clipboard.writeText) {
+      navigator.clipboard.writeText(text).then(
+        function () { flash(btn, "Copied"); },
+        function () { flash(btn, "Press ⌘C"); }
+      );
+      return;
+    }
+    var ta = document.createElement("textarea");
+    ta.value = text;
+    ta.setAttribute("readonly", "");
+    ta.style.position = "fixed";
+    ta.style.opacity = "0";
+    document.body.appendChild(ta);
+    ta.select();
+    try { document.execCommand("copy"); flash(btn, "Copied"); }
+    catch (e) { flash(btn, "Press ⌘C"); }
+    document.body.removeChild(ta);
+  }
+
+  document.querySelectorAll(".codeblock .copy").forEach(function (btn) {
+    btn.addEventListener("click", function () {
+      var pre = btn.parentElement.querySelector("pre");
+      writeClipboard(pre.innerText, btn);
+    });
+  });
+
+  document.querySelectorAll(".copy-sm[data-copy]").forEach(function (btn) {
+    btn.addEventListener("click", function () {
+      writeClipboard(btn.getAttribute("data-copy"), btn);
+    });
+  });
+
+  /* ---- step check-off, so people can leave and come back ---- */
+  var done = {};
+  try { done = JSON.parse(localStorage.getItem(DONE_KEY) || "{}"); } catch (e) {}
+
+  document.querySelectorAll(".step").forEach(function (step) {
+    var id = step.getAttribute("data-step");
+    var btn = step.querySelector(".done-toggle");
+    if (!btn) return;
+
+    function render() {
+      var isDone = !!done[id];
+      step.setAttribute("data-done", isDone ? "1" : "0");
+      btn.textContent = isDone ? "Done ✓" : "Mark done";
+      btn.setAttribute("aria-pressed", String(isDone));
+    }
+
+    btn.addEventListener("click", function () {
+      done[id] = !done[id];
+      try { localStorage.setItem(DONE_KEY, JSON.stringify(done)); } catch (e) {}
+      render();
+    });
+
+    render();
+  });
+
+  apply();
+})();
+</script>
diff --git a/docs/events/coc2026-glasgow/site/slides.html b/docs/events/coc2026-glasgow/site/slides.html
new file mode 100644
index 00000000..2fa36c86
--- /dev/null
+++ b/docs/events/coc2026-glasgow/site/slides.html
@@ -0,0 +1,203 @@
+<title>Solr MCP — Community over Code 2026</title>
+<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20650%201000'%3E%3Cpath%20fill='%23dd552c'%20d='M276.7092915,398.1515795c25.5279479-63.1242453,54.0110775-126.1246793,84.5729347-181.9636035-45.6212286-33.8852148-89.4276433-106.9534674-107.4259055-139.3268803-6.4642564,7.3439687-10.6608099,15.8022396-12.563595,22.6835448-16.9556402,61.2214602,43.4023987,135.1498759-5.21311,108.1394499-40.5058645-22.5076023-131.7157397-71.797557-166.506 [...]
+
+<style>
+:root {
+  --ground:#F1F3F0; --surface:#FFFFFF; --ink:#15171A; --ink-2:#585C63; --ink-3:#83888F;
+  --rule:#D3D8D1; --accent:#9E2A4E; --sage:#4F6650; --code-bg:#14161A; --code-ink:#DFE3DC;
+  --f-display:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
+  --f-body:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
+  --f-mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
+  --brand-chip:transparent; --brand-chip-pad:0;
+}
+@media (prefers-color-scheme: dark){
+  :root{--ground:#131417;--surface:#1A1C20;--ink:#E9EBE6;--ink-2:#A0A5AC;--ink-3:#7C828A;
+  --rule:#2A2D32;--accent:#E4778F;--sage:#93AC93;--code-bg:#0D0E11;
+  --brand-chip:#FFFFFF;--brand-chip-pad:.45rem .6rem;}
+}
+:root[data-theme="dark"]{--ground:#131417;--surface:#1A1C20;--ink:#E9EBE6;--ink-2:#A0A5AC;
+  --ink-3:#7C828A;--rule:#2A2D32;--accent:#E4778F;--sage:#93AC93;--code-bg:#0D0E11;
+  --brand-chip:#FFFFFF;--brand-chip-pad:.45rem .6rem;}
+:root[data-theme="light"]{--ground:#F1F3F0;--surface:#FFFFFF;--ink:#15171A;--ink-2:#585C63;
+  --ink-3:#83888F;--rule:#D3D8D1;--accent:#9E2A4E;--sage:#4F6650;--code-bg:#14161A;
+  --brand-chip:transparent;--brand-chip-pad:0;}
+
+.brandbar{display:flex;align-items:center;flex-wrap:wrap;gap:.8rem 1.3rem;margin:0 0 1.4rem}
+.brandbar a{display:inline-flex;line-height:0;background:var(--brand-chip);
+  padding:var(--brand-chip-pad);border-radius:4px}
+.brandbar img{display:block;width:auto}
+.brandbar .coc-mark img{height:36px}
+.ac-banner{background:#FFFFFF !important;padding:.35rem .5rem !important;
+  border:1px solid var(--rule)}
+.ac-banner img{height:24px;display:block;width:auto}
+.slide-foot{display:flex;align-items:center;flex-wrap:wrap;gap:.7rem 1rem;margin-top:1.2rem}
+.slide-foot .lbl{font-size:.72rem;color:var(--ink-3)}
+.tags{display:flex;flex-wrap:wrap;gap:.35rem;margin:1.1rem 0 0}
+.tags span{font-size:.72rem;font-weight:600;color:var(--accent);
+  background:color-mix(in srgb,var(--accent) 9%,transparent);
+  border:1px solid color-mix(in srgb,var(--accent) 24%,transparent);
+  border-radius:999px;padding:.18rem .6rem;white-space:nowrap}
+
+*{box-sizing:border-box}
+body{margin:0;background:var(--ground);color:var(--ink);font-family:var(--f-body);}
+.deck{max-width:60rem;margin:0 auto;padding:1.5rem 1.25rem 5rem;}
+.slide{
+  background:var(--surface);border:1px solid var(--rule);border-radius:4px;
+  padding:2.5rem 2.25rem;margin-bottom:1.25rem;min-height:19rem;
+  display:flex;flex-direction:column;justify-content:center;
+  page-break-after:always;
+}
+.num{font-size:.68rem;letter-spacing:.2em;text-transform:uppercase;color:var(--ink-3);
+  font-weight:700;margin:0 0 1rem;font-variant-numeric:tabular-nums;}
+h1{font-family:var(--f-display);font-weight:400;font-size:clamp(1.9rem,4.5vw,3rem);
+  line-height:1.08;margin:0 0 1rem;text-wrap:balance;}
+h1 em{font-style:normal;color:var(--accent)}
+h2{font-family:var(--f-display);font-weight:400;font-size:clamp(1.5rem,3.2vw,2.1rem);
+  line-height:1.15;margin:0 0 1rem;text-wrap:balance;}
+p{margin:0 0 .9rem;max-width:46rem;line-height:1.6;color:var(--ink-2);font-size:1.02rem}
+p.lead{color:var(--ink);font-size:1.12rem}
+ul{margin:.2rem 0 .9rem;padding-left:1.15rem;color:var(--ink-2);line-height:1.7;max-width:46rem}
+li{margin-bottom:.3rem}
+li b{color:var(--ink)}
+code{font-family:var(--f-mono);font-size:.86em;background:var(--ground);
+  padding:.1em .35em;border-radius:2px;border:1px solid var(--rule)}
+pre{background:var(--code-bg);color:var(--code-ink);padding:1rem 1.1rem;border-radius:3px;
+  overflow-x:auto;font-family:var(--f-mono);font-size:.82rem;line-height:1.65;margin:0 0 .9rem}
+pre code{background:none;border:none;padding:0;color:inherit;font-size:inherit}
+.tok-c{color:#7E8B93}
+table{border-collapse:collapse;width:100%;font-size:.92rem;margin:.3rem 0 .9rem}
+th,td{text-align:left;padding:.5rem .7rem;border-bottom:1px solid var(--rule);vertical-align:top}
+th{font-size:.68rem;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);font-weight:700}
+tbody tr:last-child td{border-bottom:none}
+.two{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem}
+.qr-slide{align-items:center;text-align:center}
+.qr-slide > img{width:230px;height:230px;background:#fff;padding:10px;border-radius:3px;margin:.4rem 0 1rem}
+.q{font-family:var(--f-display);font-size:1.18rem;color:var(--ink);margin:.15rem 0}
+.tag{display:inline-block;font-size:.64rem;letter-spacing:.13em;text-transform:uppercase;
+  font-weight:700;color:var(--accent);margin-bottom:.5rem}
+.foot{font-size:.78rem;color:var(--ink-3);margin-top:1rem}
+@media print{
+  body{background:#fff}
+  .deck{max-width:none;padding:0}
+  .slide{border:none;box-shadow:none;margin:0;min-height:100vh}
+}
+@media (max-width:640px){ .two{grid-template-columns:1fr} }
+</style>
+
+<div class="deck">
+
+<section class="slide qr-slide">
+  <div class="brandbar">
+    <a class="coc-mark" href="https://communityovercode.org/"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAnYAAADNCAYAAAA1xj1mAABy4UlEQVR4XuydCXwcZd3Hg7x4veKNByLN7k6yc21aDLeiFUFREYE2SQERVCyK8EpfsG2yc22SHlCoioKkSVrAu4qviqIcXqCAUJA2V48cvQu9jxy72ST7/v8zs+3kye7OzO7OZtM+38/n/0mbzHPMzHP85jn+T0kJhUKhUCgUCoVCoVAoFAqFQqFQKBQKhUKhUCgUCoVCoVAoFAqFQqFQKBQKhZInTiJ/QaFQKBQKhUIpchKrS06OP8xcEF/JLB1a4X9yaEXgX0NNgefg519jzYGfjTxU9pXog/4yMhyFQqFQKBQKpYiIN5ddOtzM/D3WEhhJPFyWGFvFJMZWHrME/B9/P9LC7I [...]
+  </div>
+  <p class="num">Glasgow 2026</p>
+  <h1>Talk to <em>Apache Solr</em><br>in plain English</h1>
+  <p class="lead">Twelve Solr tools, handed to your AI assistant over MCP.</p>
+  <img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iOTAwIiBjbGFzcz0ic2Vnbm8iPjxnIHRyYW5zZm9ybT0ic2NhbGUoMjApIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMCAwaDQ1djQ1aC00NXoiLz48cGF0aCBjbGFzcz0icXJsaW5lIiBzdHJva2U9IiMxNTE3MWEiIGQ9Ik00IDQuNWg3bTMgMGgybTEgMGgxbTEgMGgxbTMgMGgybTIgMGgxbTIgMGgxbTMgMGg3bS0zNyAxaDFtNSAwaDFtMSAwaDJtMiAwaDFtMiAwaDFtMiAwaDFtMiAwaDFtMSAwaDFtMSAwaDJtMyAwaDFtNSAwaD [...]
+  <p class="foot">Scan to follow along — everything in this deck is on that page.</p>
+  <div class="tags">
+    <span>#CommunityOverCode</span><span>#ASF2026Glasgow</span><span>#OpenSource</span>
+  </div>
+  <div class="slide-foot">
+    <a class="ac-banner" href="https://events.apache.org/"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAAA8CAIAAADnkbErAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TpUVaFOwgopChOtlFRRxrFYpQIdQKrTqYXPoFTRqSFBdHwbXg4Mdi1cHFWVcHV0EQ/ABxdXFSdJES/5cWWsR4cNyPd/ced+8AoVFhmtUTBzTdNtPJhJjNrYqBVwQxgDBGIcjMMuYkKQXP8XUPH1/vYjzL+9yfI6zmLQb4ROI4M0ybeIN4ZtM2OO8TR1hJVonPiSdMuiDxI9eVFr9xLros8MyImUnPE0eIxWIXK13MSqZGPE0cVTWd8oVsi1XOW5y1So2178lfGMrrK8tcpzmCJBaxBAkiFNRQRgU2YrTqpFhI037Cwz/s+iVyK [...]
+    <span class="lbl">Upcoming Apache events</span>
+  </div>
+</section>
+
+<section class="slide">
+  <p class="num">01 — The problem</p>
+  <h2>Solr's query syntax is the barrier, not Solr</h2>
+  <p>To ask a straightforward question you first have to know this:</p>
+  <pre><code>q=title:"star wars" AND genre_s:"sci-fi"&amp;fq=year_i:[2000 TO *]
+&amp;facet=true&amp;facet.field=genre_s&amp;sort=score desc&amp;rows=10</code></pre>
+  <p>Every parameter is learnable. All of them at once, while you are trying to answer a
+  question, is where people give up.</p>
+</section>
+
+<section class="slide">
+  <p class="num">02 — What MCP changes</p>
+  <h2>You describe the question. The assistant writes the query.</h2>
+  <p class="q">“Find sci-fi shows released after 2015 rated 8 or above, sorted by rating,
+  with a platform breakdown.”</p>
+  <p>The assistant produces the parameters above because the tool descriptions told it how.
+  That translation layer is the entire product — the server exposes Solr operations as MCP
+  tools, and any MCP client can call them.</p>
+  <ul>
+    <li><b>12 tools</b> — search, index (JSON/CSV/XML/Markdown), collections, schema</li>
+    <li><b>2 resources</b> — live collection list, live schema</li>
+    <li><b>6 prompts</b> — guided workflows your client may surface as slash commands</li>
+  </ul>
+</section>
+
+<section class="slide">
+  <p class="num">03 — Ten minutes, start to query</p>
+  <h2>No clone, no build</h2>
+  <pre><code><span class="tok-c"># 1. Solr — SolrCloud in one container</span>
+docker run -d --name solr-mcp-demo -p 8983:8983 \
+  solr:9-slim solr start -c -f
+
+<span class="tok-c"># 2. The server — Claude Code shown; every client is on the card</span>
+claude mcp add solr-mcp -- docker run -i --rm \
+  --add-host=host.docker.internal:host-gateway \
+  -e SOLR_URL=http://host.docker.internal:8983/solr/ \
+  ghcr.io/adityamparikh/solr-mcp:coc2026</code></pre>
+  <p>Then talk to it. Claude Desktop, Claude Code, Codex, ChatGPT, VS Code / Copilot, Cursor, Zed, IntelliJ,
+  or MCP Inspector — the card has the exact block for each.</p>
+</section>
+
+<section class="slide">
+  <p class="num">04 — The interesting part</p>
+  <h2>Why field types decide what you can ask</h2>
+  <p>Index 61 shows with no schema at all. It works — every document lands. Then ask for a
+  breakdown by platform:</p>
+  <pre><code>{ "numFound": 61, "facets": { "platform": {} } }</code></pre>
+  <p>Sixty-one matched. The query succeeded. There is simply no answer — and nothing tells
+  you the <b>data</b> is fine and the <b>field type</b> is the problem.</p>
+  <table>
+    <thead><tr><th></th><th>Schemaless</th><th>Designed</th></tr></thead>
+    <tbody>
+      <tr><td><code>platform</code></td><td><code>text_general</code></td><td><code>string</code> + docValues</td></tr>
+      <tr><td><code>imdb_rating</code></td><td><code>pdoubles</code> — multi-valued</td><td><code>pdouble</code></td></tr>
+      <tr><td>Breakdown</td><td>empty</td><td>Netflix 20, Prime 20, HBO 7…</td></tr>
+    </tbody>
+  </table>
+  <p>Same data, same question, same tool. The only change is that someone decided what the
+  fields meant.</p>
+</section>
+
+<section class="slide">
+  <p class="num">05 — Beyond search</p>
+  <h2>Ask about the index itself</h2>
+  <div class="two">
+    <div>
+      <p class="q">“Is the shows collection healthy?”</p>
+      <p class="q">“What's my query result cache hit ratio, and what does that tell me?”</p>
+      <p class="q">“Which fields can I facet on, and which can I sort by?”</p>
+    </div>
+    <div>
+      <p>That last one is the one to try. The assistant has the schema <em>and</em> knows
+      Solr's type semantics, so it answers a question that would otherwise mean reading
+      <code>managed-schema</code> and knowing what <code>docValues</code> implies.</p>
+    </div>
+  </div>
+</section>
+
+<section class="slide">
+  <p class="num">06 — Take it further</p>
+  <h2>It's an incubating ASF project. Come break it.</h2>
+  <ul>
+    <li>Index your own JSON, CSV or XML — see what the guesser makes of it</li>
+    <li>Add a <code>DenseVectorField</code> and try vector search</li>
+    <li>Describe a dataset in words, ask it to design the schema</li>
+    <li>Point it at a Solr you already run — <code>SOLR_URL</code> is the only setting</li>
+  </ul>
+  <p><b>github.com/apache/solr-mcp</b> · <code>#solr-mcp</code> on ASF Slack</p>
+  <p>Good first issues are client guides, tool descriptions and error messages — all of which
+  directly change how well an assistant uses the server. If something confused you today,
+  that's a bug worth filing.</p>
+  <p class="foot">Apache Solr MCP Server — incubating, Apache License 2.0. Solr, Apache and the
+  feather logo are trademarks of the Apache Software Foundation. The container image shown is a
+  personal pre-release build, not an Apache release.</p>
+</section>
+
+</div>
diff --git a/docs/events/coc2026-glasgow/site/table-card.html b/docs/events/coc2026-glasgow/site/table-card.html
new file mode 100644
index 00000000..1640dc36
--- /dev/null
+++ b/docs/events/coc2026-glasgow/site/table-card.html
@@ -0,0 +1,85 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Solr MCP — table card</title>
+<style>
+  @page { size: A5; margin: 0; }
+  * { box-sizing: border-box; }
+  html, body { margin: 0; padding: 0; background: #fff; }
+  .card {
+    width: 148mm; height: 210mm; margin: 0 auto; padding: 8mm 13mm;
+    display: flex; flex-direction: column; align-items: center; text-align: center;
+    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
+    color: #15171A;
+  }
+  /* Community over Code wordmark. Print card is always white, so the
+     official purple artwork needs no backing plate here. */
+  .coc-mark { width: 26mm; height: auto; display: block; margin: 0 auto 3mm; }
+  .tags {
+    display: flex; flex-wrap: wrap; justify-content: center; gap: 1.5mm;
+    margin: 2.5mm 0 0;
+  }
+  .tags span {
+    font-size: 7.5pt; font-weight: 700; color: #9E2A4E;
+    border: .3mm solid #9E2A4E4d; border-radius: 3mm;
+    padding: .8mm 2.2mm; white-space: nowrap;
+  }
+  /* Footer sign-off mark. Deliberately smaller and quieter than the
+     masthead one - it closes the card rather than competing with it. */
+  .coc-foot { width: 20mm; height: auto; display: block; margin: 3mm auto 0; opacity: .75; }
+  .eyebrow {
+    font-size: 8pt; letter-spacing: .22em; text-transform: uppercase;
+    color: #83888F; font-weight: 700; margin: 0 0 4mm;
+  }
+  h1 {
+    font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
+    font-weight: 400; font-size: 25pt; line-height: 1.1; margin: 0 0 3mm;
+  }
+  h1 em { font-style: normal; color: #9E2A4E; }
+  .sub { font-size: 10.5pt; color: #585C63; margin: 0 0 5mm; max-width: 105mm; line-height: 1.45; }
+  .qr { width: 57mm; height: 57mm; display: block; }
+  .scan { font-size: 9pt; letter-spacing: .16em; text-transform: uppercase; color: #83888F; font-weight: 700; margin: 3mm 0 4mm; }
+  .steps {
+    width: 100%; border-top: 1px solid #D3D8D1; padding-top: 4mm;
+    display: grid; gap: 3mm; text-align: left;
+  }
+  .step { display: grid; grid-template-columns: 7mm 1fr; gap: 3mm; align-items: baseline; }
+  .n { font-size: 8.5pt; font-weight: 700; color: #9E2A4E; font-variant-numeric: tabular-nums; }
+  .t { font-size: 9.5pt; color: #15171A; line-height: 1.4; }
+  .t code {
+    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
+    font-size: 8.5pt; background: #F1F3F0; padding: .5mm 1.2mm; border-radius: 1mm;
+  }
+  .foot {
+    margin-top: auto; padding-top: 4mm; font-size: 7.5pt; color: #83888F;
+    line-height: 1.5; border-top: 1px solid #D3D8D1; width: 100%;
+  }
+  @media screen {
+    body { background: #E8EBE6; padding: 10mm 0; }
+    .card { box-shadow: 0 2mm 8mm rgba(0,0,0,.14); background: #fff; }
+  }
+</style>
+<div class="card">
+  <img class="coc-mark" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAnYAAADNCAYAAAA1xj1mAABy4UlEQVR4XuydCXwcZd3Hg7x4veKNByLN7k6yc21aDLeiFUFREYE2SQERVCyK8EpfsG2yc22SHlCoioKkSVrAu4qviqIcXqCAUJA2V48cvQu9jxy72ST7/v8zs+3kye7OzO7OZtM+38/n/0mbzHPMzHP85jn+T0kJhUKhUCgUCoVCoVAoFAqFQqFQKBQKhUKhUCgUCoVCoVAoFAqFQqFQKBQKhZInTiJ/QaFQKBQKhUIpchKrS06OP8xcEF/JLB1a4X9yaEXgX0NNgefg519jzYGfjTxU9pXog/4yMhyFQqFQKBQKpYiIN5ddOtzM/D3WEhhJPFyWGFvFJMZWHrME/B9/P9LC7IHrftjf7Hs/GQeFQqFQKBQKZRLZ2XT6W+OtgbtHVjIxF [...]
+  <p class="eyebrow">Community over Code 2026 · Glasgow</p>
+  <h1>Talk to <em>Apache Solr</em><br>in plain English</h1>
+  <p class="sub">An MCP server that hands your AI assistant twelve Solr tools. Ten minutes from nothing installed to faceted queries.</p>
+
+  <img class="qr" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iOTAwIiBjbGFzcz0ic2Vnbm8iPjxnIHRyYW5zZm9ybT0ic2NhbGUoMjApIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMCAwaDQ1djQ1aC00NXoiLz48cGF0aCBjbGFzcz0icXJsaW5lIiBzdHJva2U9IiMxNTE3MWEiIGQ9Ik00IDQuNWg3bTMgMGgybTEgMGgxbTEgMGgxbTMgMGgybTIgMGgxbTIgMGgxbTMgMGg3bS0zNyAxaDFtNSAwaDFtMSAwaDJtMiAwaDFtMiAwaDFtMiAwaDFtMiAwaDFtMSAwaDFtMSAwaDJtMyA [...]
+  <p class="scan">Scan to get started</p>
+
+  <div class="steps">
+    <div class="step"><span class="n">1</span><span class="t">Start Solr — one <code>docker run</code>, no clone</span></div>
+    <div class="step"><span class="n">2</span><span class="t">Connect your assistant — Claude, Codex, Copilot, Cursor, Zed, IntelliJ or MCP Inspector</span></div>
+    <div class="step"><span class="n">3</span><span class="t">Index 61 shows and ask it questions</span></div>
+  </div>
+
+  <p class="foot">
+    Apache Solr MCP Server — incubating, Apache License 2.0. Solr, Apache and the feather logo
+    are trademarks of the Apache Software Foundation. The container image referenced is a
+    personal pre-release build, not an Apache release.
+  </p>
+  <div class="tags">
+    <span>#CommunityOverCode</span><span>#ASF2026Glasgow</span><span>#OpenSource</span>
+  </div>
+  <img class="coc-foot" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAnYAAADNCAYAAAA1xj1mAABy4UlEQVR4XuydCXwcZd3Hg7x4veKNByLN7k6yc21aDLeiFUFREYE2SQERVCyK8EpfsG2yc22SHlCoioKkSVrAu4qviqIcXqCAUJA2V48cvQu9jxy72ST7/v8zs+3kye7OzO7OZtM+38/n/0mbzHPMzHP85jn+T0kJhUKhUCgUCoVCoVAoFAqFQqFQKBQKhUKhUCgUCoVCoVAoFAqFQqFQKBQKhZInTiJ/QaFQKBQKhUIpchKrS06OP8xcEF/JLB1a4X9yaEXgX0NNgefg519jzYGfjTxU9pXog/4yMhyFQqFQKBQKpYiIN5ddOtzM/D3WEhhJPFyWGFvFJMZWHrME/B9/P9LC7IHrftjf7Hs/GQeFQqFQKBQKZRLZ2XT6W+OtgbtHVjIxF [...]
+</div>
diff --git a/docs/events/coc2026-glasgow/solr-mcp-table-card.html b/docs/events/coc2026-glasgow/solr-mcp-table-card.html
new file mode 100644
index 00000000..1640dc36
--- /dev/null
+++ b/docs/events/coc2026-glasgow/solr-mcp-table-card.html
@@ -0,0 +1,85 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Solr MCP — table card</title>
+<style>
+  @page { size: A5; margin: 0; }
+  * { box-sizing: border-box; }
+  html, body { margin: 0; padding: 0; background: #fff; }
+  .card {
+    width: 148mm; height: 210mm; margin: 0 auto; padding: 8mm 13mm;
+    display: flex; flex-direction: column; align-items: center; text-align: center;
+    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
+    color: #15171A;
+  }
+  /* Community over Code wordmark. Print card is always white, so the
+     official purple artwork needs no backing plate here. */
+  .coc-mark { width: 26mm; height: auto; display: block; margin: 0 auto 3mm; }
+  .tags {
+    display: flex; flex-wrap: wrap; justify-content: center; gap: 1.5mm;
+    margin: 2.5mm 0 0;
+  }
+  .tags span {
+    font-size: 7.5pt; font-weight: 700; color: #9E2A4E;
+    border: .3mm solid #9E2A4E4d; border-radius: 3mm;
+    padding: .8mm 2.2mm; white-space: nowrap;
+  }
+  /* Footer sign-off mark. Deliberately smaller and quieter than the
+     masthead one - it closes the card rather than competing with it. */
+  .coc-foot { width: 20mm; height: auto; display: block; margin: 3mm auto 0; opacity: .75; }
+  .eyebrow {
+    font-size: 8pt; letter-spacing: .22em; text-transform: uppercase;
+    color: #83888F; font-weight: 700; margin: 0 0 4mm;
+  }
+  h1 {
+    font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
+    font-weight: 400; font-size: 25pt; line-height: 1.1; margin: 0 0 3mm;
+  }
+  h1 em { font-style: normal; color: #9E2A4E; }
+  .sub { font-size: 10.5pt; color: #585C63; margin: 0 0 5mm; max-width: 105mm; line-height: 1.45; }
+  .qr { width: 57mm; height: 57mm; display: block; }
+  .scan { font-size: 9pt; letter-spacing: .16em; text-transform: uppercase; color: #83888F; font-weight: 700; margin: 3mm 0 4mm; }
+  .steps {
+    width: 100%; border-top: 1px solid #D3D8D1; padding-top: 4mm;
+    display: grid; gap: 3mm; text-align: left;
+  }
+  .step { display: grid; grid-template-columns: 7mm 1fr; gap: 3mm; align-items: baseline; }
+  .n { font-size: 8.5pt; font-weight: 700; color: #9E2A4E; font-variant-numeric: tabular-nums; }
+  .t { font-size: 9.5pt; color: #15171A; line-height: 1.4; }
+  .t code {
+    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
+    font-size: 8.5pt; background: #F1F3F0; padding: .5mm 1.2mm; border-radius: 1mm;
+  }
+  .foot {
+    margin-top: auto; padding-top: 4mm; font-size: 7.5pt; color: #83888F;
+    line-height: 1.5; border-top: 1px solid #D3D8D1; width: 100%;
+  }
+  @media screen {
+    body { background: #E8EBE6; padding: 10mm 0; }
+    .card { box-shadow: 0 2mm 8mm rgba(0,0,0,.14); background: #fff; }
+  }
+</style>
+<div class="card">
+  <img class="coc-mark" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAnYAAADNCAYAAAA1xj1mAABy4UlEQVR4XuydCXwcZd3Hg7x4veKNByLN7k6yc21aDLeiFUFREYE2SQERVCyK8EpfsG2yc22SHlCoioKkSVrAu4qviqIcXqCAUJA2V48cvQu9jxy72ST7/v8zs+3kye7OzO7OZtM+38/n/0mbzHPMzHP85jn+T0kJhUKhUCgUCoVCoVAoFAqFQqFQKBQKhUKhUCgUCoVCoVAoFAqFQqFQKBQKhZInTiJ/QaFQKBQKhUIpchKrS06OP8xcEF/JLB1a4X9yaEXgX0NNgefg519jzYGfjTxU9pXog/4yMhyFQqFQKBQKpYiIN5ddOtzM/D3WEhhJPFyWGFvFJMZWHrME/B9/P9LC7IHrftjf7Hs/GQeFQqFQKBQKZRLZ2XT6W+OtgbtHVjIxF [...]
+  <p class="eyebrow">Community over Code 2026 · Glasgow</p>
+  <h1>Talk to <em>Apache Solr</em><br>in plain English</h1>
+  <p class="sub">An MCP server that hands your AI assistant twelve Solr tools. Ten minutes from nothing installed to faceted queries.</p>
+
+  <img class="qr" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iOTAwIiBjbGFzcz0ic2Vnbm8iPjxnIHRyYW5zZm9ybT0ic2NhbGUoMjApIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMCAwaDQ1djQ1aC00NXoiLz48cGF0aCBjbGFzcz0icXJsaW5lIiBzdHJva2U9IiMxNTE3MWEiIGQ9Ik00IDQuNWg3bTMgMGgybTEgMGgxbTEgMGgxbTMgMGgybTIgMGgxbTIgMGgxbTMgMGg3bS0zNyAxaDFtNSAwaDFtMSAwaDJtMiAwaDFtMiAwaDFtMiAwaDFtMiAwaDFtMSAwaDFtMSAwaDJtMyA [...]
+  <p class="scan">Scan to get started</p>
+
+  <div class="steps">
+    <div class="step"><span class="n">1</span><span class="t">Start Solr — one <code>docker run</code>, no clone</span></div>
+    <div class="step"><span class="n">2</span><span class="t">Connect your assistant — Claude, Codex, Copilot, Cursor, Zed, IntelliJ or MCP Inspector</span></div>
+    <div class="step"><span class="n">3</span><span class="t">Index 61 shows and ask it questions</span></div>
+  </div>
+
+  <p class="foot">
+    Apache Solr MCP Server — incubating, Apache License 2.0. Solr, Apache and the feather logo
+    are trademarks of the Apache Software Foundation. The container image referenced is a
+    personal pre-release build, not an Apache release.
+  </p>
+  <div class="tags">
+    <span>#CommunityOverCode</span><span>#ASF2026Glasgow</span><span>#OpenSource</span>
+  </div>
+  <img class="coc-foot" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAnYAAADNCAYAAAA1xj1mAABy4UlEQVR4XuydCXwcZd3Hg7x4veKNByLN7k6yc21aDLeiFUFREYE2SQERVCyK8EpfsG2yc22SHlCoioKkSVrAu4qviqIcXqCAUJA2V48cvQu9jxy72ST7/v8zs+3kye7OzO7OZtM+38/n/0mbzHPMzHP85jn+T0kJhUKhUCgUCoVCoVAoFAqFQqFQKBQKhUKhUCgUCoVCoVAoFAqFQqFQKBQKhZInTiJ/QaFQKBQKhUIpchKrS06OP8xcEF/JLB1a4X9yaEXgX0NNgefg519jzYGfjTxU9pXog/4yMhyFQqFQKBQKpYiIN5ddOtzM/D3WEhhJPFyWGFvFJMZWHrME/B9/P9LC7IHrftjf7Hs/GQeFQqFQKBQKZRLZ2XT6W+OtgbtHVjIxF [...]
+</div>