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://<your-tunnel>/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,"Nine noble families fight for control over the lands of Westeros, while an ancient enemy returns after being dormant for millennia.",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'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,"Set in a dystopian future, a woman is forced to live as a concubine under a fundamentalist theocratic dictatorship.",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,"The travels of a lone bounty hunter in the outer reaches of the galaxy, far from the authority of the New Republic.",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,"The Roy family controls the biggest media and entertainment company in the world, but their patriarch's health is failing.",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,"After a global pandemic destroys civilization, a hardened survivor takes charge of a 14-year-old girl who may be humanity's last hope.",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'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'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:
+<shows>
+ <show>
+ <id>hbo-001</id>
+ <title>Game of Thrones</title>
+ <platform>HBO Max</platform>
+ <genres>Fantasy</genres>
+ <genres>Drama</genres>
+ <genres>Adventure</genres>
+ <release_year>2011</release_year>
+ <end_year>2019</end_year>
+ <status>Ended</status>
+ <seasons>8</seasons>
+ <episodes>73</episodes>
+ <creators>David Benioff</creators>
+ <creators>D.B. Weiss</creators>
+ <cast>Emilia Clarke</cast>
+ <cast>Peter Dinklage</cast>
+ <cast>Kit Harington</cast>
+ <cast>Lena Headey</cast>
+ <country>USA</country>
+ <language>English</language>
+ <rating>TV-MA</rating>
+ <imdb_rating>9.2</imdb_rating>
+ <description>Nine noble families fight for control over the lands of Westeros, while an ancient enemy returns after being dormant for millennia.</description>
+ <tags>fantasy</tags>
+ <tags>dragons</tags>
+ <tags>epic</tags>
+ <tags>george-rr-martin</tags>
+ </show>
+ <show>
+ <id>netflix-001</id>
+ <title>Stranger Things</title>
+ <platform>Netflix</platform>
+ <genres>Sci-Fi</genres>
+ <genres>Horror</genres>
+ <genres>Drama</genres>
+ <release_year>2016</release_year>
+ <end_year>2025</end_year>
+ <status>Ended</status>
+ <seasons>5</seasons>
+ <episodes>42</episodes>
+ <creators>Matt Duffer</creators>
+ <creators>Ross Duffer</creators>
+ <cast>Millie Bobby Brown</cast>
+ <cast>Finn Wolfhard</cast>
+ <cast>Winona Ryder</cast>
+ <cast>David Harbour</cast>
+ <country>USA</country>
+ <language>English</language>
+ <rating>TV-14</rating>
+ <imdb_rating>8.7</imdb_rating>
+ <description>A group of kids in 1980s Indiana uncover supernatural mysteries and government conspiracies tied to a parallel dimension.</description>
+ <tags>80s</tags>
+ <tags>supernatural</tags>
+ <tags>coming-of-age</tags>
+ <tags>monsters</tags>
+ </show>
+ <show>
+ <id>hulu-001</id>
+ <title>The Handmaid's Tale</title>
+ <platform>Hulu</platform>
+ <genres>Drama</genres>
+ <genres>Sci-Fi</genres>
+ <genres>Dystopian</genres>
+ <release_year>2017</release_year>
+ <end_year>2025</end_year>
+ <status>Ended</status>
+ <seasons>6</seasons>
+ <episodes>66</episodes>
+ <creators>Bruce Miller</creators>
+ <cast>Elisabeth Moss</cast>
+ <cast>Joseph Fiennes</cast>
+ <cast>Yvonne Strahovski</cast>
+ <cast>Samira Wiley</cast>
+ <country>USA</country>
+ <language>English</language>
+ <rating>TV-MA</rating>
+ <imdb_rating>8.4</imdb_rating>
+ <description>Set in a dystopian future, a woman is forced to live as a concubine under a fundamentalist theocratic dictatorship.</description>
+ <tags>dystopian</tags>
+ <tags>feminist</tags>
+ <tags>atwood</tags>
+ <tags>theocracy</tags>
+ </show>
+ <show>
+ <id>disney-001</id>
+ <title>The Mandalorian</title>
+ <platform>Disney+</platform>
+ <genres>Sci-Fi</genres>
+ <genres>Action</genres>
+ <genres>Adventure</genres>
+ <release_year>2019</release_year>
+ <status>Ongoing</status>
+ <seasons>3</seasons>
+ <episodes>24</episodes>
+ <creators>Jon Favreau</creators>
+ <cast>Pedro Pascal</cast>
+ <cast>Carl Weathers</cast>
+ <cast>Giancarlo Esposito</cast>
+ <country>USA</country>
+ <language>English</language>
+ <rating>TV-14</rating>
+ <imdb_rating>8.6</imdb_rating>
+ <description>The travels of a lone bounty hunter in the outer reaches of the galaxy, far from the authority of the New Republic.</description>
+ <tags>star-wars</tags>
+ <tags>space-western</tags>
+ <tags>grogu</tags>
+ <tags>baby-yoda</tags>
+ </show>
+ <show>
+ <id>hbo-002</id>
+ <title>Succession</title>
+ <platform>HBO Max</platform>
+ <genres>Drama</genres>
+ <genres>Comedy</genres>
+ <release_year>2018</release_year>
+ <end_year>2023</end_year>
+ <status>Ended</status>
+ <seasons>4</seasons>
+ <episodes>39</episodes>
+ <creators>Jesse Armstrong</creators>
+ <cast>Brian Cox</cast>
+ <cast>Jeremy Strong</cast>
+ <cast>Kieran Culkin</cast>
+ <cast>Sarah Snook</cast>
+ <country>USA</country>
+ <language>English</language>
+ <rating>TV-MA</rating>
+ <imdb_rating>8.9</imdb_rating>
+ <description>The Roy family controls the biggest media and entertainment company in the world, but their patriarch's health is failing.</description>
+ <tags>media</tags>
+ <tags>family-drama</tags>
+ <tags>satire</tags>
+ <tags>wealth</tags>
+ </show>
+ <show>
+ <id>prime-001</id>
+ <title>The Boys</title>
+ <platform>Amazon Prime Video</platform>
+ <genres>Action</genres>
+ <genres>Sci-Fi</genres>
+ <genres>Drama</genres>
+ <release_year>2019</release_year>
+ <status>Ongoing</status>
+ <seasons>4</seasons>
+ <episodes>32</episodes>
+ <creators>Eric Kripke</creators>
+ <cast>Karl Urban</cast>
+ <cast>Jack Quaid</cast>
+ <cast>Antony Starr</cast>
+ <cast>Erin Moriarty</cast>
+ <country>USA</country>
+ <language>English</language>
+ <rating>TV-MA</rating>
+ <imdb_rating>8.7</imdb_rating>
+ <description>A group of vigilantes set out to take down corrupt superheroes who abuse their powers.</description>
+ <tags>superheroes</tags>
+ <tags>satire</tags>
+ <tags>violent</tags>
+ <tags>comic-adaptation</tags>
+ </show>
+ <show>
+ <id>netflix-002</id>
+ <title>The Crown</title>
+ <platform>Netflix</platform>
+ <genres>Drama</genres>
+ <genres>Historical</genres>
+ <genres>Biography</genres>
+ <release_year>2016</release_year>
+ <end_year>2023</end_year>
+ <status>Ended</status>
+ <seasons>6</seasons>
+ <episodes>60</episodes>
+ <creators>Peter Morgan</creators>
+ <cast>Claire Foy</cast>
+ <cast>Olivia Colman</cast>
+ <cast>Imelda Staunton</cast>
+ <cast>Matt Smith</cast>
+ <country>UK</country>
+ <language>English</language>
+ <rating>TV-MA</rating>
+ <imdb_rating>8.6</imdb_rating>
+ <description>The reign of Queen Elizabeth II from her wedding in 1947 through the early 21st century.</description>
+ <tags>royalty</tags>
+ <tags>british</tags>
+ <tags>period-drama</tags>
+ <tags>politics</tags>
+ </show>
+ <show>
+ <id>prime-002</id>
+ <title>The Marvelous Mrs. Maisel</title>
+ <platform>Amazon Prime Video</platform>
+ <genres>Comedy</genres>
+ <genres>Drama</genres>
+ <genres>Period</genres>
+ <release_year>2017</release_year>
+ <end_year>2023</end_year>
+ <status>Ended</status>
+ <seasons>5</seasons>
+ <episodes>43</episodes>
+ <creators>Amy Sherman-Palladino</creators>
+ <cast>Rachel Brosnahan</cast>
+ <cast>Alex Borstein</cast>
+ <cast>Michael Zegen</cast>
+ <cast>Tony Shalhoub</cast>
+ <country>USA</country>
+ <language>English</language>
+ <rating>TV-MA</rating>
+ <imdb_rating>8.7</imdb_rating>
+ <description>A 1950s New York housewife discovers she has a talent for stand-up comedy.</description>
+ <tags>50s</tags>
+ <tags>stand-up</tags>
+ <tags>feminist</tags>
+ <tags>new-york</tags>
+ </show>
+ <show>
+ <id>hbo-003</id>
+ <title>The Last of Us</title>
+ <platform>HBO Max</platform>
+ <genres>Drama</genres>
+ <genres>Horror</genres>
+ <genres>Sci-Fi</genres>
+ <release_year>2023</release_year>
+ <status>Ongoing</status>
+ <seasons>2</seasons>
+ <episodes>16</episodes>
+ <creators>Craig Mazin</creators>
+ <creators>Neil Druckmann</creators>
+ <cast>Pedro Pascal</cast>
+ <cast>Bella Ramsey</cast>
+ <cast>Anna Torv</cast>
+ <country>USA</country>
+ <language>English</language>
+ <rating>TV-MA</rating>
+ <imdb_rating>8.7</imdb_rating>
+ <description>After a global pandemic destroys civilization, a hardened survivor takes charge of a 14-year-old girl who may be humanity's last hope.</description>
+ <tags>post-apocalyptic</tags>
+ <tags>zombies</tags>
+ <tags>video-game-adaptation</tags>
+ <tags>fungal</tags>
+ </show>
+ <show>
+ <id>hulu-002</id>
+ <title>The Bear</title>
+ <platform>Hulu</platform>
+ <genres>Comedy</genres>
+ <genres>Drama</genres>
+ <release_year>2022</release_year>
+ <status>Ongoing</status>
+ <seasons>3</seasons>
+ <episodes>28</episodes>
+ <creators>Christopher Storer</creators>
+ <cast>Jeremy Allen White</cast>
+ <cast>Ebon Moss-Bachrach</cast>
+ <cast>Ayo Edebiri</cast>
+ <country>USA</country>
+ <language>English</language>
+ <rating>TV-MA</rating>
+ <imdb_rating>8.6</imdb_rating>
+ <description>A young chef from the fine dining world returns to Chicago to run his family's sandwich shop after a heartbreaking death.</description>
+ <tags>cooking</tags>
+ <tags>chicago</tags>
+ <tags>family</tags>
+ <tags>anxiety</tags>
+ </show>
+ <show>
+ <id>netflix-003</id>
+ <title>Squid Game</title>
+ <platform>Netflix</platform>
+ <genres>Thriller</genres>
+ <genres>Drama</genres>
+ <genres>Survival</genres>
+ <release_year>2021</release_year>
+ <end_year>2025</end_year>
+ <status>Ended</status>
+ <seasons>3</seasons>
+ <episodes>22</episodes>
+ <creators>Hwang Dong-hyuk</creators>
+ <cast>Lee Jung-jae</cast>
+ <cast>Park Hae-soo</cast>
+ <cast>Wi Ha-joon</cast>
+ <cast>HoYeon Jung</cast>
+ <country>South Korea</country>
+ <language>Korean</language>
+ <rating>TV-MA</rating>
+ <imdb_rating>8.0</imdb_rating>
+ <description>Hundreds of cash-strapped contestants accept an invitation to compete in deadly children's games for a tempting prize.</description>
+ <tags>korean</tags>
+ <tags>survival</tags>
+ <tags>dystopian</tags>
+ <tags>social-commentary</tags>
+ </show>
+ <show>
+ <id>netflix-004</id>
+ <title>Wednesday</title>
+ <platform>Netflix</platform>
+ <genres>Comedy</genres>
+ <genres>Horror</genres>
+ <genres>Mystery</genres>
+ <release_year>2022</release_year>
+ <status>Ongoing</status>
+ <seasons>2</seasons>
+ <episodes>16</episodes>
+ <creators>Alfred Gough</creators>
+ <creators>Miles Millar</creators>
+ <cast>Jenna Ortega</cast>
+ <cast>Catherine Zeta-Jones</cast>
+ <cast>Luis Guzmán</cast>
+ <cast>Gwendoline Christie</cast>
+ <country>USA</country>
+ <language>English</language>
+ <rating>TV-14</rating>
+ <imdb_rating>8.1</imdb_rating>
+ <description>Wednesday Addams navigates a supernatural boarding school while solving a murder mystery.</description>
+ <tags>gothic</tags>
+ <tags>teen</tags>
+ <tags>supernatural</tags>
+ <tags>addams-family</tags>
+ </show>
+</shows></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'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'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 "dragon" 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 "a group of".">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 *]&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>&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\"}}}" >/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"}}}' >/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" <<'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 & 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" <<'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 & 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"&fq=year_i:[2000 TO *]
+&facet=true&facet.field=genre_s&sort=score desc&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>