October 11–14, 2026·Glasgow, Scotland
#CommunityOverCode
Solr MCP is a Model Context Protocol server that hands your AI assistant fifteen Solr tools — search, indexing, schema, collection and alias management. This card takes you from nothing installed to running faceted queries against a real index. Ten minutes, no Solr syntax required.
Before anything else
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.
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.
docker --versionAny recent version works. If this errors, install Docker Desktop (macOS/Windows) or Docker Engine (Linux).
java -version
The JAR is built for Java 25. On Java 17 or 21 it fails with
UnsupportedClassVersionError — and because STDIO servers
are launched silently by your client, most clients show only
"server failed to start" with no reason. If the server won't connect,
check this first.
Prebuilt, so you don't need Git or Gradle — 52 MB:
curl -LO https://github.com/adityamparikh/solr-mcp/releases/download/coc2026/solr-mcp-1.0.0-SNAPSHOT.jarBuilt from the same commit as the container image, so both paths behave identically. Unofficial pre-release build — not an Apache release.
# SDKMAN (recommended — no sudo, easy to switch back)
sdk install java 25-tem
# or Homebrew
brew install --cask temurin@25winget install EclipseAdoptium.Temurin.25.JDK# SDKMAN (recommended — no sudo, easy to switch back)
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk install java 25-temOne container, no clone
SolrCloud mode with embedded ZooKeeper, which is what the collection tools need.
Ready in about seven seconds. The first line creates a private Docker network: the server's
container joins it later and reaches Solr by its name, solr-mcp-demo.
Solr's port is published on 127.0.0.1 only, so it answers on
your laptop and nobody else on the conference Wi-Fi can reach it.
docker network create solr-mcp
docker run -d --name solr-mcp-demo --network=solr-mcp -p 127.0.0.1:8983:8983 solr:9-slim solr start -c -fWhile that boots, pull the server image too. It is ~170 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:
docker pull ghcr.io/adityamparikh/solr-mcp:coc2026Confirm Solr is up — this should return an empty collection list:
curl "http://localhost:8983/solr/admin/collections?action=LIST"The admin UI is at localhost:8983/solr. Keep it open on a second screen if you have one — watching collections appear as you talk to the assistant is half the fun.
The repo ships a compose.yaml, but it exists to preload the
films and books samples. This card builds its own collection from
scratch, so a single container is all you need — no clone, no Gradle, no build.
Wire it to your assistant
Commands below follow the choices you made in step 0. Change them there and everything on this page updates.
ghcr.io/adityamparikh/solr-mcp:coc2026 is a personal
pre-release build for this event — not 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 Build it yourself.
Backslashes are escape characters in JSON, so a Windows path must double them —
"C:\\\\Users\\\\you\\\\solr-mcp-1.0.0-SNAPSHOT.jar" — or just
use forward slashes, which Java accepts on Windows:
"C:/Users/you/solr-mcp-1.0.0-SNAPSHOT.jar". Forward slashes
are the safer habit.
In HTTP mode the server is a process you start yourself, in a terminal you keep open.
Every client then connects to http://localhost:8080/mcp.
docker run -p 127.0.0.1:8080:8080 --rm \
--network=solr-mcp \
-e PROFILES=http \
-e HTTP_SECURITY_ENABLED=false \
-e SOLR_URL=http://solr-mcp-demo:8983/solr/ \
ghcr.io/adityamparikh/solr-mcp:coc2026PROFILES=http SERVER_ADDRESS=127.0.0.1 HTTP_SECURITY_ENABLED=false \
SOLR_URL=http://localhost:8983/solr/ \
java -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar# PowerShell
$env:PROFILES="http"; $env:SERVER_ADDRESS="127.0.0.1"; $env:HTTP_SECURITY_ENABLED="false"; $env:SOLR_URL="http://localhost:8983/solr/"
java -jar C:/path/to/solr-mcp-1.0.0-SNAPSHOT.jar
Ready when curl http://localhost:8080/actuator/health answers
{"status":"UP"}. Leave the terminal open; closing it stops the
server for every client.
HTTP mode requires OAuth2 out of the box (HTTP_SECURITY_ENABLED
defaults to true, the issuer is OAUTH2_ISSUER_URI).
HTTP_SECURITY_ENABLED=false is for a laptop at a hackathon,
bound to localhost: the commands above listen on 127.0.0.1
only (-p 127.0.0.1:8080:8080 for Docker,
SERVER_ADDRESS for the JAR), so nobody else on the
conference Wi-Fi can reach it. Never run it that way on anything other people can reach; the
Security setup panel below walks through turning it on, start to finish.
claude mcp add solr-mcp -- docker run -i --rm \
--network=solr-mcp \
-e SOLR_URL=http://solr-mcp-demo:8983/solr/ \
ghcr.io/adityamparikh/solr-mcp:coc2026claude mcp add solr-mcp \
-e SOLR_URL=http://localhost:8983/solr/ \
-- java -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar
The -- matters: it stops Claude Code reading the server's
own flags as its own. Verify with claude mcp list.
claude mcp add --transport http solr-mcp http://localhost:8080/mcp
Verify with claude mcp list; the server must already be running
or the entry shows as failed.
codex mcp add solr-mcp -- docker run -i --rm \
--network=solr-mcp \
-e SOLR_URL=http://solr-mcp-demo:8983/solr/ \
ghcr.io/adityamparikh/solr-mcp:coc2026codex mcp add solr-mcp \
--env SOLR_URL=http://localhost:8983/solr/ \
-- java -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar
The -- separates Codex's flags from the server's launch
command. This writes a [mcp_servers.solr-mcp] table to
~/.codex/config.toml, which you can also edit by hand.
Verify with codex mcp list, or /mcp
inside a Codex session.
Codex gives a server about 10 seconds to start. A cold docker run
that has to pull the image will miss that — run
docker pull ghcr.io/adityamparikh/solr-mcp:coc2026 once first, or add
startup_timeout_sec = 60 to the
[mcp_servers.solr-mcp] table.
codex mcp add solr-mcp --url http://localhost:8080/mcp
Codex prints may or may not require login
— ignore it, there is no login on an
unsecured server. Verify with codex mcp list; the server must
already be running when Codex starts.
ChatGPT connects to a remote MCP server; it cannot launch a local process, so there is no STDIO setup. Switch Transport to HTTP above.
localhost is unreachable from ChatGPT. Put a tunnel in front of
the server you started above, for example
ngrok http 8080, and use the https://…
address it prints with /mcp appended. Because security is off on
this server, anyone with that URL can read and change your Solr — 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.
solr-mcp; under Connection enter https://<your-tunnel>/mcp; choose No authentication.ChatGPT asks before running tools it treats as writes, which is most of them. After restarting the server, press Refresh on the app.
Edit the config file, then fully quit and reopen Claude Desktop:
~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.json~/.config/Claude/claude_desktop_config.jsonClaude 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.
There is no official Claude Desktop build for Linux. That path is where community builds look; if you're on Linux, Claude Code or MCP Inspector is the smoother route.
{
"mcpServers": {
"solr-mcp": {
"command": "docker",
"args": ["run", "-i", "--rm",
"--network=solr-mcp",
"-e", "SOLR_URL=http://solr-mcp-demo:8983/solr/",
"ghcr.io/adityamparikh/solr-mcp:coc2026"]
}
}
}{
"mcpServers": {
"solr-mcp": {
"command": "java",
"args": ["-jar", "/absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar"],
"env": { "SOLR_URL": "http://localhost:8983/solr/" }
}
}
}
Claude Desktop's own Connectors screen cannot reach this server: custom connectors
are contacted from Anthropic's cloud, not from your laptop, so a
localhost URL is unreachable there and would need a public
HTTPS tunnel and OAuth switched back on. Skip that. Bridge it locally instead with
the mcp-remote shim (needs Node.js), which talks plain HTTP to
the server on your machine:
{
"mcpServers": {
"solr-mcp": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://localhost:8080/mcp"]
}
}
}Same config file as the STDIO route (paths above). Then fully quit and reopen Claude Desktop. No ngrok needed.
Needs VS Code 1.99+. Create .vscode/mcp.json in your
workspace, then use Copilot Chat in Agent mode.
{
"servers": {
"solr-mcp": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm",
"--network=solr-mcp",
"-e", "SOLR_URL=http://solr-mcp-demo:8983/solr/",
"ghcr.io/adityamparikh/solr-mcp:coc2026"]
}
}
}{
"servers": {
"solr-mcp": {
"type": "stdio",
"command": "java",
"args": ["-jar", "/absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar"],
"env": { "SOLR_URL": "http://localhost:8983/solr/" }
}
}
}Same file, .vscode/mcp.json, with a URL instead of a command:
{
"servers": {
"solr-mcp": {
"type": "http",
"url": "http://localhost:8080/mcp"
}
}
}Create .cursor/mcp.json in your project root:
{
"mcpServers": {
"solr-mcp": {
"command": "docker",
"args": ["run", "-i", "--rm",
"--network=solr-mcp",
"-e", "SOLR_URL=http://solr-mcp-demo:8983/solr/",
"ghcr.io/adityamparikh/solr-mcp:coc2026"]
}
}
}{
"mcpServers": {
"solr-mcp": {
"command": "java",
"args": ["-jar", "/absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar"],
"env": { "SOLR_URL": "http://localhost:8983/solr/" }
}
}
}Same file, .cursor/mcp.json, with a URL instead of a command:
{
"mcpServers": {
"solr-mcp": { "url": "http://localhost:8080/mcp" }
}
}Zed has a form for this, which is the easier route here — no JSON, and it validates as you type. Settings → AI → General → MCP Servers → Add Server → Add Local MCP Server, then:
| Server Name | solr-mcp |
|---|---|
| Command | docker |
| Arguments | run -i --rm --network=solr-mcp -e SOLR_URL=http://solr-mcp-demo:8983/solr/ ghcr.io/adityamparikh/solr-mcp:coc2026 |
| Timeout | 60 is fine once the image is pulled — see below |
| Server Name | solr-mcp |
|---|---|
| Command | java |
| Arguments | -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar |
| Environment Variables | SOLR_URL = http://localhost:8983/solr/ |
Arguments go in one field, space-separated — not as a JSON array. The form writes the same settings entry you'd hand-edit below.
Zed's default timeout is 60 seconds, and a first run has to fetch ~170 MB. On
venue wifi that can outrun the timeout, and the failure looks like a broken config
rather than a slow download. Run
docker pull ghcr.io/adityamparikh/solr-mcp:coc2026 once
beforehand and the problem disappears.
Prefer JSON? zed: open settings from the command palette,
and add a context_servers entry:
{
"context_servers": {
"solr-mcp": {
"command": "docker",
"args": ["run", "-i", "--rm",
"--network=solr-mcp",
"-e", "SOLR_URL=http://solr-mcp-demo:8983/solr/",
"ghcr.io/adityamparikh/solr-mcp:coc2026"],
"env": {}
}
}
}{
"context_servers": {
"solr-mcp": {
"command": "java",
"args": ["-jar", "/absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar"],
"env": { "SOLR_URL": "http://localhost:8983/solr/" }
}
}
}
Verified against Zed 1.16.2. Zed's schema takes command
as a plain string with args and
env alongside it — there is no
source key, and adding one makes Zed's settings
validator reject the entry. If a future release moves this again, check
zed.dev/docs/ai/mcp.
Settings → AI → General → MCP Servers → Add Server → Add Remote Server, name it
solr-mcp, URL http://localhost:8080/mcp.
Or in zed: open settings:
{
"context_servers": {
"solr-mcp": { "url": "http://localhost:8080/mcp" }
}
}
Zed starts an OAuth flow only if the server answers 401. With security off it will not; if
it prompts anyway, add "headers": { "Authorization": "Bearer x" }
to the entry, which the server ignores.
Needs the AI Assistant plugin. Either drop a file at
.junie/mcp/mcp.json in your project root:
{
"mcpServers": {
"solr-mcp": {
"command": "docker",
"args": ["run", "-i", "--rm",
"--network=solr-mcp",
"-e", "SOLR_URL=http://solr-mcp-demo:8983/solr/",
"ghcr.io/adityamparikh/solr-mcp:coc2026"]
}
}
}{
"mcpServers": {
"solr-mcp": {
"command": "java",
"args": ["-jar", "/absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar"],
"env": { "SOLR_URL": "http://localhost:8983/solr/" }
}
}
}…or add it through Settings → Tools → AI Assistant → MCP Servers → Add, transport STDIO.
Same file, .junie/mcp/mcp.json, with a URL instead of a command:
{
"mcpServers": {
"solr-mcp": { "url": "http://localhost:8080/mcp" }
}
}…or Settings → Tools → AI Assistant → MCP Servers → Add, transport HTTP.
No editor, no assistant — a browser UI that shows the raw protocol. The best way to see what MCP actually is, and the fastest way to prove the server works.
npx @modelcontextprotocol/inspectorOpens at http://localhost:6274. Choose STDIO, then:
| Command | docker |
|---|---|
| Arguments | run -i --rm --network=solr-mcp -e SOLR_URL=http://solr-mcp-demo:8983/solr/ ghcr.io/adityamparikh/solr-mcp:coc2026 |
| Command | java |
| Arguments | -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar |
Hit Connect, then List Tools. You should see fifteen. Click
list-collections and run it — that round-trip is the whole
protocol in one click.
npx @modelcontextprotocol/inspector
Opens at http://localhost:6274. Choose Streamable HTTP,
URL http://localhost:8080/mcp, then Connect and
List Tools. Click list-collections and run it.
Ask your assistant:
What Solr collections are available?calls list-collections — expect an empty list, which is correct right now
An empty list means everything works. If the assistant says it has no Solr tools, see When it breaks.
The lazy way, on purpose
Solr's _default configset is schemaless: 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.
Create a Solr collection called shows-auto.calls create-collection
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.
curl -O https://raw.githubusercontent.com/apache/solr-mcp/main/src/test/resources/shows.jsonIndex the contents of ./shows.json into the shows-auto collection.calls index-json-documents — expect 61 of 61
curl -O https://raw.githubusercontent.com/apache/solr-mcp/main/src/test/resources/shows.csvIndex the contents of ./shows.csv into the shows-auto collection.calls index-csv-documents — Solr accepts the payload; the collection then holds 61
The server hands the file to Solr's own CSV handler, which reports that it accepted the
payload rather than a document count. Multi-valued fields (genres,
cast…) 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.
curl -O https://raw.githubusercontent.com/apache/solr-mcp/main/src/test/resources/shows.xmlIndex the contents of ./shows.xml into the shows-auto collection.calls index-xml-documents — Solr accepts the payload; the collection then holds 61
The file is Solr's own update format — <add><doc><field name="…">.
The server checks that it is an <add> block (no deletes, no
commits) and passes it to Solr unchanged, so the field names are the same as in JSON.
curl -O https://raw.githubusercontent.com/apache/solr-mcp/main/src/test/resources/shows-markdown.mdIndex the contents of ./shows-markdown.md into the shows-auto collection.calls index-markdown-documents once — expect 61 of 61
The file holds 61 Markdown documents, each opening with its own YAML front matter, and
the server splits it into one Solr document per show. The fields live in the front
matter; the description is the body, which lands in content,
with the title in headings.
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.
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","politics"]},
{"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","supernatural","coming-of-age","monsters"]},
{"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","british"]},
{"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","adventure"]},
{"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","new-york"]}
]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-familyIndex this XML into the shows-auto collection:
<add>
<doc>
<field name="id">hbo-001</field>
<field name="title">Game of Thrones</field>
<field name="platform">HBO Max</field>
<field name="genres">Fantasy</field>
<field name="genres">Drama</field>
<field name="genres">Adventure</field>
<field name="release_year">2011</field>
<field name="end_year">2019</field>
<field name="status">Ended</field>
<field name="seasons">8</field>
<field name="episodes">73</field>
<field name="creators">David Benioff</field>
<field name="creators">D.B. Weiss</field>
<field name="cast">Emilia Clarke</field>
<field name="cast">Peter Dinklage</field>
<field name="cast">Kit Harington</field>
<field name="cast">Lena Headey</field>
<field name="country">USA</field>
<field name="language">English</field>
<field name="rating">TV-MA</field>
<field name="imdb_rating">9.2</field>
<field name="description">Nine noble families fight for control over the lands of Westeros, while an ancient enemy returns after being dormant for millennia.</field>
<field name="tags">fantasy</field>
<field name="tags">dragons</field>
<field name="tags">epic</field>
<field name="tags">george-rr-martin</field>
</doc>
<doc>
<field name="id">netflix-001</field>
<field name="title">Stranger Things</field>
<field name="platform">Netflix</field>
<field name="genres">Sci-Fi</field>
<field name="genres">Horror</field>
<field name="genres">Drama</field>
<field name="release_year">2016</field>
<field name="end_year">2025</field>
<field name="status">Ended</field>
<field name="seasons">5</field>
<field name="episodes">42</field>
<field name="creators">Matt Duffer</field>
<field name="creators">Ross Duffer</field>
<field name="cast">Millie Bobby Brown</field>
<field name="cast">Finn Wolfhard</field>
<field name="cast">Winona Ryder</field>
<field name="cast">David Harbour</field>
<field name="country">USA</field>
<field name="language">English</field>
<field name="rating">TV-14</field>
<field name="imdb_rating">8.7</field>
<field name="description">A group of kids in 1980s Indiana uncover supernatural mysteries and government conspiracies tied to a parallel dimension.</field>
<field name="tags">80s</field>
<field name="tags">supernatural</field>
<field name="tags">coming-of-age</field>
<field name="tags">monsters</field>
</doc>
<doc>
<field name="id">hulu-001</field>
<field name="title">The Handmaid's Tale</field>
<field name="platform">Hulu</field>
<field name="genres">Drama</field>
<field name="genres">Sci-Fi</field>
<field name="genres">Dystopian</field>
<field name="release_year">2017</field>
<field name="end_year">2025</field>
<field name="status">Ended</field>
<field name="seasons">6</field>
<field name="episodes">66</field>
<field name="creators">Bruce Miller</field>
<field name="cast">Elisabeth Moss</field>
<field name="cast">Joseph Fiennes</field>
<field name="cast">Yvonne Strahovski</field>
<field name="cast">Samira Wiley</field>
<field name="country">USA</field>
<field name="language">English</field>
<field name="rating">TV-MA</field>
<field name="imdb_rating">8.4</field>
<field name="description">Set in a dystopian future, a woman is forced to live as a concubine under a fundamentalist theocratic dictatorship.</field>
<field name="tags">dystopian</field>
<field name="tags">feminist</field>
<field name="tags">atwood</field>
<field name="tags">theocracy</field>
</doc>
<doc>
<field name="id">disney-001</field>
<field name="title">The Mandalorian</field>
<field name="platform">Disney+</field>
<field name="genres">Sci-Fi</field>
<field name="genres">Action</field>
<field name="genres">Adventure</field>
<field name="release_year">2019</field>
<field name="status">Ongoing</field>
<field name="seasons">3</field>
<field name="episodes">24</field>
<field name="creators">Jon Favreau</field>
<field name="cast">Pedro Pascal</field>
<field name="cast">Carl Weathers</field>
<field name="cast">Giancarlo Esposito</field>
<field name="country">USA</field>
<field name="language">English</field>
<field name="rating">TV-14</field>
<field name="imdb_rating">8.6</field>
<field name="description">The travels of a lone bounty hunter in the outer reaches of the galaxy, far from the authority of the New Republic.</field>
<field name="tags">star-wars</field>
<field name="tags">space-western</field>
<field name="tags">grogu</field>
<field name="tags">baby-yoda</field>
</doc>
<doc>
<field name="id">hbo-002</field>
<field name="title">Succession</field>
<field name="platform">HBO Max</field>
<field name="genres">Drama</field>
<field name="genres">Comedy</field>
<field name="release_year">2018</field>
<field name="end_year">2023</field>
<field name="status">Ended</field>
<field name="seasons">4</field>
<field name="episodes">39</field>
<field name="creators">Jesse Armstrong</field>
<field name="cast">Brian Cox</field>
<field name="cast">Jeremy Strong</field>
<field name="cast">Kieran Culkin</field>
<field name="cast">Sarah Snook</field>
<field name="country">USA</field>
<field name="language">English</field>
<field name="rating">TV-MA</field>
<field name="imdb_rating">8.9</field>
<field name="description">The Roy family controls the biggest media and entertainment company in the world, but their patriarch's health is failing.</field>
<field name="tags">media</field>
<field name="tags">family-drama</field>
<field name="tags">satire</field>
<field name="tags">wealth</field>
</doc>
<doc>
<field name="id">prime-001</field>
<field name="title">The Boys</field>
<field name="platform">Amazon Prime Video</field>
<field name="genres">Action</field>
<field name="genres">Sci-Fi</field>
<field name="genres">Drama</field>
<field name="release_year">2019</field>
<field name="status">Ongoing</field>
<field name="seasons">4</field>
<field name="episodes">32</field>
<field name="creators">Eric Kripke</field>
<field name="cast">Karl Urban</field>
<field name="cast">Jack Quaid</field>
<field name="cast">Antony Starr</field>
<field name="cast">Erin Moriarty</field>
<field name="country">USA</field>
<field name="language">English</field>
<field name="rating">TV-MA</field>
<field name="imdb_rating">8.7</field>
<field name="description">A group of vigilantes set out to take down corrupt superheroes who abuse their powers.</field>
<field name="tags">superheroes</field>
<field name="tags">satire</field>
<field name="tags">violent</field>
<field name="tags">comic-adaptation</field>
</doc>
<doc>
<field name="id">netflix-002</field>
<field name="title">The Crown</field>
<field name="platform">Netflix</field>
<field name="genres">Drama</field>
<field name="genres">Historical</field>
<field name="genres">Biography</field>
<field name="release_year">2016</field>
<field name="end_year">2023</field>
<field name="status">Ended</field>
<field name="seasons">6</field>
<field name="episodes">60</field>
<field name="creators">Peter Morgan</field>
<field name="cast">Claire Foy</field>
<field name="cast">Olivia Colman</field>
<field name="cast">Imelda Staunton</field>
<field name="cast">Matt Smith</field>
<field name="country">UK</field>
<field name="language">English</field>
<field name="rating">TV-MA</field>
<field name="imdb_rating">8.6</field>
<field name="description">The reign of Queen Elizabeth II from her wedding in 1947 through the early 21st century.</field>
<field name="tags">royalty</field>
<field name="tags">british</field>
<field name="tags">period-drama</field>
<field name="tags">politics</field>
</doc>
<doc>
<field name="id">prime-002</field>
<field name="title">The Marvelous Mrs. Maisel</field>
<field name="platform">Amazon Prime Video</field>
<field name="genres">Comedy</field>
<field name="genres">Drama</field>
<field name="genres">Period</field>
<field name="release_year">2017</field>
<field name="end_year">2023</field>
<field name="status">Ended</field>
<field name="seasons">5</field>
<field name="episodes">43</field>
<field name="creators">Amy Sherman-Palladino</field>
<field name="cast">Rachel Brosnahan</field>
<field name="cast">Alex Borstein</field>
<field name="cast">Michael Zegen</field>
<field name="cast">Tony Shalhoub</field>
<field name="country">USA</field>
<field name="language">English</field>
<field name="rating">TV-MA</field>
<field name="imdb_rating">8.7</field>
<field name="description">A 1950s New York housewife discovers she has a talent for stand-up comedy.</field>
<field name="tags">50s</field>
<field name="tags">stand-up</field>
<field name="tags">feminist</field>
<field name="tags">new-york</field>
</doc>
<doc>
<field name="id">hbo-003</field>
<field name="title">The Last of Us</field>
<field name="platform">HBO Max</field>
<field name="genres">Drama</field>
<field name="genres">Horror</field>
<field name="genres">Sci-Fi</field>
<field name="release_year">2023</field>
<field name="status">Ongoing</field>
<field name="seasons">2</field>
<field name="episodes">16</field>
<field name="creators">Craig Mazin</field>
<field name="creators">Neil Druckmann</field>
<field name="cast">Pedro Pascal</field>
<field name="cast">Bella Ramsey</field>
<field name="cast">Anna Torv</field>
<field name="country">USA</field>
<field name="language">English</field>
<field name="rating">TV-MA</field>
<field name="imdb_rating">8.7</field>
<field name="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.</field>
<field name="tags">post-apocalyptic</field>
<field name="tags">zombies</field>
<field name="tags">video-game-adaptation</field>
<field name="tags">fungal</field>
</doc>
<doc>
<field name="id">hulu-002</field>
<field name="title">The Bear</field>
<field name="platform">Hulu</field>
<field name="genres">Comedy</field>
<field name="genres">Drama</field>
<field name="release_year">2022</field>
<field name="status">Ongoing</field>
<field name="seasons">3</field>
<field name="episodes">28</field>
<field name="creators">Christopher Storer</field>
<field name="cast">Jeremy Allen White</field>
<field name="cast">Ebon Moss-Bachrach</field>
<field name="cast">Ayo Edebiri</field>
<field name="country">USA</field>
<field name="language">English</field>
<field name="rating">TV-MA</field>
<field name="imdb_rating">8.6</field>
<field name="description">A young chef from the fine dining world returns to Chicago to run his family's sandwich shop after a heartbreaking death.</field>
<field name="tags">cooking</field>
<field name="tags">chicago</field>
<field name="tags">family</field>
<field name="tags">anxiety</field>
</doc>
<doc>
<field name="id">netflix-003</field>
<field name="title">Squid Game</field>
<field name="platform">Netflix</field>
<field name="genres">Thriller</field>
<field name="genres">Drama</field>
<field name="genres">Survival</field>
<field name="release_year">2021</field>
<field name="end_year">2025</field>
<field name="status">Ended</field>
<field name="seasons">3</field>
<field name="episodes">22</field>
<field name="creators">Hwang Dong-hyuk</field>
<field name="cast">Lee Jung-jae</field>
<field name="cast">Park Hae-soo</field>
<field name="cast">Wi Ha-joon</field>
<field name="cast">HoYeon Jung</field>
<field name="country">South Korea</field>
<field name="language">Korean</field>
<field name="rating">TV-MA</field>
<field name="imdb_rating">8.0</field>
<field name="description">Hundreds of cash-strapped contestants accept an invitation to compete in deadly children's games for a tempting prize.</field>
<field name="tags">korean</field>
<field name="tags">survival</field>
<field name="tags">dystopian</field>
<field name="tags">social-commentary</field>
</doc>
<doc>
<field name="id">netflix-004</field>
<field name="title">Wednesday</field>
<field name="platform">Netflix</field>
<field name="genres">Comedy</field>
<field name="genres">Horror</field>
<field name="genres">Mystery</field>
<field name="release_year">2022</field>
<field name="status">Ongoing</field>
<field name="seasons">2</field>
<field name="episodes">16</field>
<field name="creators">Alfred Gough</field>
<field name="creators">Miles Millar</field>
<field name="cast">Jenna Ortega</field>
<field name="cast">Catherine Zeta-Jones</field>
<field name="cast">Luis Guzmán</field>
<field name="cast">Gwendoline Christie</field>
<field name="country">USA</field>
<field name="language">English</field>
<field name="rating">TV-14</field>
<field name="imdb_rating">8.1</field>
<field name="description">Wednesday Addams navigates a supernatural boarding school while solving a murder mystery.</field>
<field name="tags">gothic</field>
<field name="tags">teen</field>
<field name="tags">supernatural</field>
<field name="tags">addams-family</field>
</doc>
</add>Three shows, three Markdown documents, one tool call:
Index the following three Markdown documents into the shows-auto collection:
---
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.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.
Where guessing runs out
Ask the obvious business question:
Show me the breakdown of shows-auto by platform.calls search with a facet — this may not go the way you expect
Show me the schema for shows-auto.calls get-schema — here's the actual explanation
The table below was captured from the JSON path. CSV, XML 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.
| Field | Guessed type | Consequence |
|---|---|---|
platform | text_general | Tokenized, so platform:prime now matches "Amazon Prime Video" — nonsense for a category. Faceting it returns nothing at all, not even wrong buckets. |
title | text_general | Searchable, but not sortable or exact-matchable. |
imdb_rating | pdoubles | Plural — multi-valued. Every rating is now a list. |
release_year | plongs | Multi-valued too. Range filters get awkward. |
Look at any returned document and you'll see it: every single field came back wrapped in
an array — "title":["Stranger Things"]. Solr had one sample of
each field and no reason to assume it wouldn't repeat, so it hedged.
Either an empty breakdown — "facets": { "platform": {} } — or,
if your assistant read the schema first, real buckets on
platform_str. Schemaless mode quietly copies every text field
into a *_str string field, and a schema-aware assistant facets
on the copy instead. Ask it to facet on the platform field itself
and you get the
empty result: text_general yields no facet buckets at all.
The copies are a patch, not a design: one per text field, capped at 256 characters, and no
help for the numbers. Step 5 gives platform real buckets, and
the question starts answering itself.
The lesson: 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.
Types chosen on purpose
Solr field types cannot be changed once created, and every collection here shares
the same _default configset. The guesses from step 3 are
already baked in, so a new collection would inherit them. Wipe the slate:
docker rm -f solr-mcp-demo
docker run -d --name solr-mcp-demo --network=solr-mcp -p 127.0.0.1:8983:8983 solr:9-slim solr start -c -fTakes about seven seconds. Wait for the collections endpoint to answer before continuing.
Create a Solr collection called shows.calls create-collection
Then paste this. Order matters — fields first, documents second:
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)
Now index the same data into shows — same file or same paste
as step 3, just the new collection name.
| Choice | Buys you |
|---|---|
string not text_general | Exact values. "Amazon Prime Video" stays one facet bucket instead of three tokens. |
docValues: true | The column-oriented structure that makes faceting and sorting fast. |
pint / pdouble | Real numbers — range filters like [2020 TO *] and numeric sort. |
| single-valued where true | You can sort on it. Multi-valued fields make "highest rated" meaningless. |
text_general kept for prose | Analysis and tokenizing is exactly what you want in title and description. |
Ask the same question that failed in step 4:
Show me the breakdown of shows by platform.now returns clean buckets — Netflix 20, Amazon Prime Video 20, HBO Max 7…
Eight questions, eight Solr features
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.
Which shows have "dragon" in the title?q · full-text on an analyzed field → 1 hit, House of the Dragon
Find shows whose description contains the exact phrase "a group of".q · phrase query → 2 hits, The Boys and Stranger Things
Find shows whose content field contains the exact phrase "a group of".q · phrase query → 2 hits, The Boys and Stranger Things (Markdown puts the description in content)
Show me everything on Netflix.fq · exact string filter → 20 hits
Which shows were released in 2020 or later?fq · numeric range release_year:[2020 TO *] → 31 hits
Find all the comedies.fq · membership in a multi-valued field → 15 hits
Break the collection down by genre and by status.facet · two fields at once → Ended 33, Ongoing 28
What are the five highest rated shows?sort + rows → Game of Thrones 9.2, Succession 8.9, True Detective 8.9…
Show me shows from 2015 onward rated 8 or above, sorted by rating, with a platform breakdown.everything at once — fq × 2, sort, facet, rows → 39 hits
Which shows star Pedro Pascal?fq · exact match inside a multi-valued field → 3 hits
You never wrote fq=release_year:[2020 TO *]&facet.field=platform.
The assistant did — because the tool descriptions told it how. That translation layer is
the entire product.
Beyond search
Search is the headline, but the operational tools are where this earns its place in a real workflow.
Is the shows collection healthy?check-health — ping, response time, doc count
Give me the stats for the shows collection.get-collection-stats — segments, cache hit ratios, handler timings
What's my query result cache hit ratio, and what does that tell me?the interesting one — real metrics, interpreted
Explain the shows schema — which fields can I facet on, and which can I sort by?get-schema — and a genuinely useful answer
| Tool | Does |
|---|---|
search | Query, filter, facet, sort, paginate |
index-json-documents | Index a JSON array of documents |
index-csv-documents | Index CSV through Solr's CSV handler |
index-xml-documents | Index Solr update XML — <add> blocks only |
index-markdown-documents | Index from Markdown — YAML front matter plus body text |
create-collection | New collection, optional configset / shards / replicas |
list-collections | Everything in the cluster |
list-aliases | Aliases and the collections they point to |
create-alias | Point an alias at one or more collections, or repoint it |
delete-alias | Remove an alias — the collections stay |
get-collection-stats | Index, query, cache and handler metrics |
check-health | Ping and document count |
get-schema | Fields, types, dynamic fields, copy fields |
add-fields | Add fields — additive only |
add-field-types | Custom analyzers, DenseVectorField for vector search |
Plus 2 resources (solr://collections,
solr://{collection}/schema) and 6 guided prompts your client
may surface as slash commands.
Nothing below is on the numbered path. When it breaks is where steps 2 and 3 send you if something fails; Build it yourself replaces the prebuilt download; the rest is for after today.
Needs Git, JDK 25 and about three minutes.
git clone https://github.com/apache/solr-mcp.git
cd solr-mcp
./gradlew build
Produces build/libs/solr-mcp-1.0.0-SNAPSHOT.jar. Use that
absolute path anywhere this card says JAR.
./gradlew jibDockerBuild
Produces solr-mcp:latest locally. Substitute that for the
ghcr.io/… reference in any config above.
Want faster startup and a smaller image? See Native builds and every way to run it below.
Each form below has a build command and a run command, for the Run via and Transport you picked in step 0. JAR shows what runs directly on your machine, the JVM jar and the native binary. Docker shows the two images. Everything here uses the clone from Build it yourself, with Solr from step 1 running.
| Form | stdio | http | Needs |
|---|---|---|---|
| JVM jar | one build, both | same jar | JDK 25 |
| Native binary | build with stdio | build with http | GraalVM JDK 25 |
| JVM Docker image (Jib) | one image, both | same image | JDK 25, Docker |
| Native Docker image | …-native-stdio | …-native-http | Docker only |
The JVM forms are dual-mode: the PROFILES environment variable
picks the transport at run time. Native forms are pinned to one transport at build
time — Spring AOT bakes the web-application type into the binary — so you build one
per transport. In the commands below, SOLR_URL points at the
Solr from step 1.
./gradlew build # build/libs/solr-mcp-1.0.0-SNAPSHOT.jar
# stdio: your client runs this
SOLR_URL=http://localhost:8983/solr/ \
java -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar./gradlew build # build/libs/solr-mcp-1.0.0-SNAPSHOT.jar
# http: you run this
PROFILES=http SERVER_ADDRESS=127.0.0.1 HTTP_SECURITY_ENABLED=false \
SOLR_URL=http://localhost:8983/solr/ \
java -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar
Needs GraalVM JDK 25 on JAVA_HOME. The first compile takes
several minutes. The binary only runs on the OS and CPU you built it on.
# stdio: your client runs the binary
./gradlew nativeCompile -Pnative
SOLR_URL=http://localhost:8983/solr/ build/native/nativeCompile/solr-mcp# http (the transport and the security switch are baked in at build time)
HTTP_SECURITY_ENABLED=false ./gradlew nativeCompile -Pnative -Pprofile=http
PROFILES=http SERVER_ADDRESS=127.0.0.1 HTTP_SECURITY_ENABLED=false \
SOLR_URL=http://localhost:8983/solr/ \
build/native/nativeCompile/solr-mcp./gradlew jibDockerBuild # solr-mcp:latest
# stdio: your client runs this (-i keeps stdin open)
docker run -i --rm --network=solr-mcp \
-e SOLR_URL=http://solr-mcp-demo:8983/solr/ \
solr-mcp:latest./gradlew jibDockerBuild # solr-mcp:latest
# http: you run this
docker run -p 127.0.0.1:8080:8080 --rm --network=solr-mcp \
-e PROFILES=http -e HTTP_SECURITY_ENABLED=false \
-e SOLR_URL=http://solr-mcp-demo:8983/solr/ \
solr-mcp:latestPrebuilt for this event, for Intel and Apple-silicon machines alike — nothing to build:
# stdio: your client runs this
docker run -i --rm --network=solr-mcp \
-e SOLR_URL=http://solr-mcp-demo:8983/solr/ \
ghcr.io/adityamparikh/solr-mcp:coc2026-native-stdio# http: you run this
docker run -p 127.0.0.1:8080:8080 --rm --network=solr-mcp \
-e PROFILES=http -e HTTP_SECURITY_ENABLED=false \
-e SOLR_URL=http://solr-mcp-demo:8983/solr/ \
ghcr.io/adityamparikh/solr-mcp:coc2026-native-httpOr build it yourself, inside a Linux builder container, so it works on macOS, Linux and Windows without installing GraalVM. Needs Docker and a few minutes.
./gradlew bootBuildImage -Pnative # solr-mcp:latest-native-stdio
docker run -i --rm --network=solr-mcp \
-e SOLR_URL=http://solr-mcp-demo:8983/solr/ \
solr-mcp:latest-native-stdioHTTP_SECURITY_ENABLED=false ./gradlew bootBuildImage -Pnative -Pprofile=http # solr-mcp:latest-native-http
docker run -p 127.0.0.1:8080:8080 --rm --network=solr-mcp \
-e PROFILES=http -e HTTP_SECURITY_ENABLED=false \
-e SOLR_URL=http://solr-mcp-demo:8983/solr/ \
solr-mcp:latest-native-http
The -native-stdio image has no web server in it, and the
-native-http image has no stdio transport. Pointing a client
at the wrong one gives a timeout, not a helpful error.
A native binary built without -Pprofile=http has no web
server in it, and one built with it has no stdio transport. Pointing a client at the
wrong one gives a timeout, not a helpful error.
Spring AOT fixes the security setup inside the binary, so
HTTP_SECURITY_ENABLED only counts at build time; at run time
a native http server ignores it. That is why the build commands above set it. Built
without it, every tool answers Access Denied
until you configure OAuth2
(Security setup). The prebuilt
coc2026-native-http image is built with security off, for
use on your own machine only.
To use one of these images with a client, take the matching config from
step 2 and put the image tag above where it says
ghcr.io/adityamparikh/solr-mcp:coc2026.
To use these with a client, take the matching config from step 2. For
the native binary, put its absolute path in place of
java -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar.
STDIO has your client launch the server as a subprocess. HTTP mode runs one server that
many clients connect to over http://localhost:8080/mcp. Pick
HTTP under Transport in step 0 and
step 2 shows how to start it and how each client connects.
The hackathon commands turn security off with
HTTP_SECURITY_ENABLED=false. Anything reachable by other
people needs the OAuth2 setup in the Security setup panel.
In HTTP mode the server exports traces, logs and request metrics 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 HTTP under Transport in step 0 first.
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.
docker run -d --name lgtm --network=solr-mcp -p 127.0.0.1:3000:3000 -p 127.0.0.1:4317:4317 -p 127.0.0.1:4318:4318 grafana/otel-lgtm:0.33.0
until docker logs lgtm 2>&1 | grep -q "up and running"; do sleep 2; done
echo "LGTM ready"
Stop the step 2 server first — this one listens on 8080 too. The server exports over
OTLP/HTTP to localhost:4318 by default, which inside a container
is the container itself, so these three variables send traces, metrics and logs to the
lgtm container instead, by name over the
solr-mcp network. Each is the full URL for its signal.
Stop the step 2 server first — this one listens on 8080 too. From a plain JAR the default
localhost:4318 is already right, so the command is the same as
step 2: nothing to add.
docker run -p 127.0.0.1:8080:8080 --rm \
--network=solr-mcp \
-e PROFILES=http \
-e HTTP_SECURITY_ENABLED=false \
-e SOLR_URL=http://solr-mcp-demo:8983/solr/ \
-e OTEL_TRACES_URL=http://lgtm:4318/v1/traces \
-e OTEL_METRICS_URL=http://lgtm:4318/v1/metrics \
-e OTEL_LOGS_URL=http://lgtm:4318/v1/logs \
ghcr.io/adityamparikh/solr-mcp:coc2026PROFILES=http SERVER_ADDRESS=127.0.0.1 HTTP_SECURITY_ENABLED=false \
SOLR_URL=http://localhost:8983/solr/ \
java -jar /absolute/path/to/solr-mcp-1.0.0-SNAPSHOT.jar# PowerShell
$env:PROFILES="http"; $env:SERVER_ADDRESS="127.0.0.1"; $env:HTTP_SECURITY_ENABLED="false"; $env:SOLR_URL="http://localhost:8983/solr/"
java -jar C:/path/to/solr-mcp-1.0.0-SNAPSHOT.jarAny prompt from steps 3–7 in your client works. Or, without a client, a few tool calls with curl — each one is a trace:
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
# one call that fails on purpose, so there is a log line to follow
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/nullOpen localhost:3000 (no login) → Explore. Pick Tempo, switch the query type to TraceQL, and run:
{resource.service.name="solr-mcp"}
Open an http post /mcp trace. Inside it sit the security filter
chain and one span named for the tool — SearchService#search,
IndexingService#… — 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.
Now open the CollectionService#checkHealth trace from the
failing call and click Logs for this span: Loki opens on its
WARN Health check failed line, matched by trace ID. Each
log line links back the same way. Metrics land in Prometheus once a minute, in
milliseconds; pick it in Explore and run:
http_server_requests_milliseconds_count{job="solr-mcp"}
Logs on successful calls — the services only log when something fails, so for
the searches Logs for this span comes up empty. That's expected, not broken.
And the call to Solr has no span of its own; its time is inside the tool span.
JVM, Tomcat and per-tool metrics arrive the same way — try
jvm_memory_used_bytes{job="solr-mcp"} or
method_observed_milliseconds_count{job="solr-mcp"}.
LGTM keeps everything in memory, so removing the container discards all of it.
docker rm -f lgtm
Everything above runs with HTTP_SECURITY_ENABLED=false. 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 docker,
curl and jq. Commands are bash
(macOS, Linux, WSL or Git Bash). Stop any unsecured server from step 2 first — both
listen on 8080.
How it fits together: the server is a resource server. Keycloak issues a JWT, the
server checks its signature against Keycloak's keys, the issuer, the expiry, and that the
aud claim contains the URL the client dials —
http://localhost:8080/mcp. Every step below exists to make one
of those checks pass.
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.
docker run -d --name keycloak -p 127.0.0.1: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"
One paste. It logs in as admin, creates a realm solr-mcp, a
public client solr-mcp-client with the audience mapper
that puts http://localhost:8080/mcp into every token (Keycloak
does not honour the standard resource= parameter, so without
this mapper every token is rejected), and a user
testuser / testpassword. First and
last name are not decoration — Keycloak refuses to issue a token to a user missing either.
Expect three 201s.
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"
The realm must answer before 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
localhost:8180 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.
PROFILES=http SERVER_ADDRESS=127.0.0.1 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# PowerShell
$env:PROFILES="http"; $env:SERVER_ADDRESS="127.0.0.1"; $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.jarLeave that terminal open. In another one, wait for
curl http://localhost:8080/actuator/health to answer
{"status":"UP"}, then read back the audience the server expects.
This is the value the mapper in step 2 has to match, character for character:
curl -s http://localhost:8080/.well-known/oauth-protected-resource/mcp | jq -r .resource
# http://localhost:8080/mcpThe password grant, which is what the client in step 2 allows. The second command decodes the token and prints its audience — it must list http://localhost:8080/mcp.
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']The same tool call twice. Without the header the server answers HTTP 200 with an Access Denied result; with it you get the collection names. Both lines are the test — one alone proves nothing.
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)
The header is the whole point of this step. The server never answers an anonymous
/mcp request with 401, and MCP clients start their OAuth
login only when they see a 401 — so a client given just the URL reports
connected, lists every tool, and gets Access Denied
from every call, with no browser opening. Nothing looks broken. Pass
$TOKEN from step 4 explicitly.
OAuth2 bearer token security applies only to the HTTP transport. In STDIO mode, your client launches the server directly as a local subprocess over standard input/output without network exposure or bearer authentication headers. Switch Transport to HTTP in step 0 to see client connection snippets with the token in a header.
Make sure your Solr MCP Docker container is running and port 8080 is published (-p 127.0.0.1:8080:8080) so your client can reach http://localhost:8080/mcp.
Make sure your Solr MCP JAR process from step 3 is running and listening on port 8080 before connecting your client.
Dataset: shows-auto was indexed from JSON in step 3. The verification query checks that collection.
Dataset: shows-auto was indexed from CSV in step 3. The verification query checks that collection.
Dataset: shows-auto was indexed from XML in step 3. The verification query checks that collection.
Dataset: shows-auto was indexed from Markdown in step 3. The verification query checks that collection. Markdown keeps each description in the body, so it is in content, not description: the phrase question finds its hits there.
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.
# Tools missing, or the server fails to connect: the token reached it and was rejected (expired, or wrong aud) with HTTP 401.# PowerShell
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.
# Tools missing, or the server fails to connect: the token reached it and was rejected (expired, or wrong aud) with HTTP 401.
Codex reads the token from an environment variable you name, so the token never lands in
config.toml. Export it in the shell that launches Codex; re-running
codex mcp add replaces an earlier unsecured entry.
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.
# Tools missing, or the server fails to connect: the token reached it and was rejected (expired, or wrong aud) with HTTP 401.# PowerShell
$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 # 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.
# Tools missing, or the server fails to connect: the token reached it and was rejected (expired, or wrong aud) with HTTP 401.The token expires after about five minutes; restart Codex after refreshing it so it re-reads the variable.
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.
Paste the token into env; mcp-remote forwards it on every request. Edit your config file, then fully quit and reopen Claude Desktop:
~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.json~/.config/Claude/claude_desktop_config.jsonClaude 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.
There is no official Claude Desktop build for Linux. That path is where community builds look; if you're on Linux, Claude Code or MCP Inspector is the smoother route.
{
"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" }
}
}
}In .vscode/mcp.json, add the headers object to the server entry from step 2. Using an inputs prompt keeps the token out of source control:
{
"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}" }
}
}
}In .cursor/mcp.json, add the headers object to your server entry. Cursor expands ${env:SOLR_MCP_TOKEN} from your environment:
{
"mcpServers": {
"solr-mcp": {
"url": "http://localhost:8080/mcp",
"headers": { "Authorization": "Bearer ${env:SOLR_MCP_TOKEN}" }
}
}
}In zed: open settings (JSON), add the headers object to your remote server entry:
{
"context_servers": {
"solr-mcp": {
"url": "http://localhost:8080/mcp",
"headers": { "Authorization": "Bearer paste-the-token-here" }
}
}
}In .junie/mcp/mcp.json or IDE Settings, proxy through mcp-remote to forward the bearer header on each request:
{
"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" }
}
}
}# 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# PowerShell 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| Symptom | Cause & fix |
|---|---|
| Server exits with code 1 right after starting | The realm was not answering when the server resolved the issuer. Re-run the last line of step 2 until it prints realm ready, then start the server again. |
401 with The aud claim is not valid |
The mapper's audience and the URL the client dials differ — 127.0.0.1 vs localhost is enough. Compare step 3's .resource with step 4's aud, and use the same spelling in the client. |
401 with Jwt expired |
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. |
Access Denied with the header |
$TOKEN is empty or null: the token request in step 4 failed. Run it without jq and read the error. Account is not fully set up means the user is missing first/last name or has a temporary password. |
| Client shows connected, calls denied, no browser opens | 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. |
| Want to see why a token was rejected | curl -si -H "Authorization: Bearer $TOKEN" localhost:8080/actuator/metrics | grep -i www-authenticate — a protected actuator endpoint returns a plain 401 with the reason. |
docker rm -f keycloak
claude mcp remove solr-mcpSame server, same checks; only steps 1–4 change. You need your own Auth0 tenant (the free tier works) and administrative access.
Solr MCP API. Set the Identifier to exactly
http://localhost:8080/mcp with signing algorithm RS256.
The identifier becomes the token's aud (audience) claim,
which the server matches character-for-character against its incoming resource URI.
It cannot be edited after creation.
OAUTH2_ISSUER_URI is mandatory — Auth0 writes its
iss claim with a trailing slash:
docker run -p 127.0.0.1:8080:8080 --rm \
--network=solr-mcp \
-e PROFILES=http \
-e HTTP_SECURITY_ENABLED=true \
-e OAUTH2_ISSUER_URI=https://YOUR-TENANT.us.auth0.com/ \
-e SOLR_URL=http://solr-mcp-demo:8983/solr/ \
ghcr.io/adityamparikh/solr-mcp:coc2026PROFILES=http SERVER_ADDRESS=127.0.0.1 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# PowerShell
$env:PROFILES="http"; $env:SERVER_ADDRESS="127.0.0.1"; $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.jarcurl:
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)
Or use the repository helper script with your .env file:
TOKEN=$(./scripts/get-auth0-token.sh --quiet)Confirm the token audience:
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/mcpNow continue to step 5 above to test the gate with curl, then step 6 to connect your client.
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:
| Symptom | Cause & fix |
|---|---|
| Client says the server failed to start, no detail | Almost always Java version. Run java -version — the JAR needs 25. STDIO swallows the error. Switch to the Docker path if you'd rather not install a JDK. |
| Connects, but every Solr call fails | SOLR_URL is wrong for the context. From inside a container use http://solr-mcp-demo:8983/solr/, which resolves because both containers are on the solr-mcp network from step 1; from a plain JAR use localhost. If Docker says network solr-mcp not found, run docker network create solr-mcp and restart Solr with the step 1 command. |
Solr instance is not running in SolrCloud mode |
The container was started without -c. Collections need SolrCloud. Re-run the step 1 command exactly. |
| New collection already has fields you didn't add | Every collection shares the _default 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 #183. |
| Results ignore your sort | The search tool takes sortClauses as objects shaped {"item":"imdb_rating","order":"desc"}. 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. |
docker rm -f solr-mcp-demo
docker run -d --name solr-mcp-demo --network=solr-mcp -p 127.0.0.1:8983:8983 solr:9-slim solr start -c -fGood 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.
DenseVectorField with add-field-types and try vector search.SOLR_URL is all it takes.