deploy/docker-compose.yml is unedited docs-mcp-template boilerplate. It has never been
touched since the initial scaffold commit (9ba615c) — it still carries literal <product>, <registry> and <owner> placeholders, and it describes a deployment that does not exist.
grep -rn "os.environ\|getenv" docs_mcp/ rag/ — MCP_ALLOWED_HOSTS appears nowhere.
The knob that actually governs DNS-rebinding behaviour is MCP_DISABLE_DNS_REBINDING_PROTECTION
(docs_mcp/server.py:725). Anyone who trusted this file and set an allowlist would get a 421
on every request and no clue why.
What production actually runs
crop-chem-docs is not deployed as a standalone stack. It is the chem-mcp service inside
Drawbar's parent compose (Drawbar/drawbar-backend/docker-compose.yml):
Every material line disagrees with what this repo ships:
deploy/docker-compose.yml says
Production does
Shape
standalone stack, own network + watchtower
one service merged into Drawbar's compose
Port
ports: "8000:8000" (published to host)
expose: 8080, MCP_PORT=8080, no host port
Rebind protection
MCP_ALLOWED_HOSTS (not read)
MCP_DISABLE_DNS_REBINDING_PROTECTION=1
Ollama
not set → image default http://ollama:11434
host.docker.internal:11434 + extra_hosts
Rerank
http://<product>-rerank:8080
image default http://llama-rerank:8080
Service name
<product>-docs-mcp
chem-mcp
The OLLAMA_URL row is not cosmetic: Drawbar's ollama service is commented out in the
parent compose, so the image's baked default (http://ollama:11434, Dockerfile:49) does not
resolve. Query-time embedding — i.e. all of search_docs — depends on that override.
deploy/drawbar-compose-snippet.md is closer to reality but also drifted: it names the service crop-chem-docs, maps ports: "8001:8000", and asserts "No environment block needed — the
image's defaults handle it", which is false on both the rebind and Ollama counts above.
The fix
Model it on justin/seed-mcp:deploy/docker-compose.yml, which the sibling repo already got
right: not a standalone stack, but the service block to merge into Drawbar's parent compose,
with a header comment saying exactly that and where the parent lives.
Replace deploy/docker-compose.yml with the real chem-mcp block, copied from Drawbar/drawbar-backend/docker-compose.yml so the two cannot drift silently. Keep the
inline comments explaining why rebind protection is off (internal-network-only, expose
not ports) and why Ollama goes through host.docker.internal.
Delete the MCP_ALLOWED_HOSTS line. It is not a supported knob in this codebase.
(If a real allowlist is wanted instead of an on/off switch, that is a code change — see justin/zerto-docs#12, which made allowed_hosts/origins genuinely configurable. Out of
scope here; file it separately if you want it.)
Reconcile deploy/drawbar-compose-snippet.md with the same source of truth — service
name chem-mcp, expose: 8080, and drop the "no environment block needed" claim. If the
rewritten compose file makes the snippet redundant, delete the snippet and update the
README tree instead of maintaining two half-true copies.
Update the README file tree (README.md:93-96) to match whatever survives step 3.
Also note while you are in there: the snippet's verification block calls corpus_status(),
which does exist (server.py:536) — the five real tools are search_docs, get_page, list_versions, corpus_status, crop_chem_api_lessons. That part is accurate; leave it.
Verification
This is documentation/config only — no code path changes, no eval run needed.
Every env var named in deploy/ is one the code actually reads (cross-check against grep -rn "os.environ" docs_mcp/ rag/).
The service block in deploy/docker-compose.yml is byte-comparable with the chem-mcp
block in Drawbar/drawbar-backend/docker-compose.yml modulo comments.
Related observation — not part of this issue
Production pins chem-mcp to image: ...crop-chem-docs:corpus-2026.05.24while carrying com.centurylinklabs.watchtower.enable: "true". Watchtower only re-pulls the tag a container
is running, and corpus-YYYY.MM.DD is a fresh unique tag per build (refresh.yml:153), so that
tag will never be re-pushed — the monthly corpus refresh has been shipping :latest to a
production container that cannot see it. seed-mcp runs :latest and does move. That is a
Drawbar-side deploy question, not a crop-chem-docs one; file against Drawbar/drawbar-backend
if it is not intentional.
Related: #4 (mcp 2.x migration — this mismatch was found while scoping it).
## The defect
`deploy/docker-compose.yml` is **unedited docs-mcp-template boilerplate**. It has never been
touched since the initial scaffold commit (9ba615c) — it still carries literal `<product>`,
`<registry>` and `<owner>` placeholders, and it describes a deployment that does not exist.
Worse, it sets an env var the code does not read:
```yaml
# deploy/docker-compose.yml:33
MCP_ALLOWED_HOSTS: "<product>-docs-mcp,localhost,127.0.0.1"
```
`grep -rn "os.environ\|getenv" docs_mcp/ rag/` — `MCP_ALLOWED_HOSTS` appears **nowhere**.
The knob that actually governs DNS-rebinding behaviour is `MCP_DISABLE_DNS_REBINDING_PROTECTION`
(`docs_mcp/server.py:725`). Anyone who trusted this file and set an allowlist would get a 421
on every request and no clue why.
## What production actually runs
crop-chem-docs is **not** deployed as a standalone stack. It is the `chem-mcp` service inside
Drawbar's parent compose (`Drawbar/drawbar-backend/docker-compose.yml`):
```yaml
chem-mcp:
image: git.jpaul.io/justin/crop-chem-docs:corpus-2026.05.24
environment:
MCP_TRANSPORT: streamable-http
MCP_HOST: 0.0.0.0
MCP_PORT: "8080"
MCP_DISABLE_DNS_REBINDING_PROTECTION: "1"
OLLAMA_URL: ${CHEM_OLLAMA_URL:-http://host.docker.internal:11434}
EMBED_MODEL: ${CHEM_EMBED_MODEL:-nomic-embed-text}
extra_hosts:
- "host.docker.internal:host-gateway"
expose:
- "8080"
restart: unless-stopped
labels:
com.centurylinklabs.watchtower.enable: "true"
```
Every material line disagrees with what this repo ships:
| | `deploy/docker-compose.yml` says | Production does |
|---|---|---|
| Shape | standalone stack, own network + watchtower | one service merged into Drawbar's compose |
| Port | `ports: "8000:8000"` (published to host) | `expose: 8080`, `MCP_PORT=8080`, no host port |
| Rebind protection | `MCP_ALLOWED_HOSTS` (**not read**) | `MCP_DISABLE_DNS_REBINDING_PROTECTION=1` |
| Ollama | not set → image default `http://ollama:11434` | `host.docker.internal:11434` + `extra_hosts` |
| Rerank | `http://<product>-rerank:8080` | image default `http://llama-rerank:8080` |
| Service name | `<product>-docs-mcp` | `chem-mcp` |
The `OLLAMA_URL` row is not cosmetic: Drawbar's `ollama` service is **commented out** in the
parent compose, so the image's baked default (`http://ollama:11434`, Dockerfile:49) does not
resolve. Query-time embedding — i.e. all of `search_docs` — depends on that override.
`deploy/drawbar-compose-snippet.md` is closer to reality but also drifted: it names the service
`crop-chem-docs`, maps `ports: "8001:8000"`, and asserts *"No environment block needed — the
image's defaults handle it"*, which is false on both the rebind and Ollama counts above.
## The fix
Model it on **`justin/seed-mcp:deploy/docker-compose.yml`**, which the sibling repo already got
right: not a standalone stack, but the service block to *merge* into Drawbar's parent compose,
with a header comment saying exactly that and where the parent lives.
1. **Replace `deploy/docker-compose.yml`** with the real `chem-mcp` block, copied from
`Drawbar/drawbar-backend/docker-compose.yml` so the two cannot drift silently. Keep the
inline comments explaining *why* rebind protection is off (internal-network-only, `expose`
not `ports`) and why Ollama goes through `host.docker.internal`.
2. **Delete the `MCP_ALLOWED_HOSTS` line.** It is not a supported knob in this codebase.
(If a real allowlist is wanted instead of an on/off switch, that is a code change — see
justin/zerto-docs#12, which made allowed_hosts/origins genuinely configurable. Out of
scope here; file it separately if you want it.)
3. **Reconcile `deploy/drawbar-compose-snippet.md`** with the same source of truth — service
name `chem-mcp`, `expose: 8080`, and drop the "no environment block needed" claim. If the
rewritten compose file makes the snippet redundant, delete the snippet and update the
README tree instead of maintaining two half-true copies.
4. **Update the README file tree** (`README.md:93-96`) to match whatever survives step 3.
Also note while you are in there: the snippet's verification block calls `corpus_status()`,
which does exist (`server.py:536`) — the five real tools are `search_docs`, `get_page`,
`list_versions`, `corpus_status`, `crop_chem_api_lessons`. That part is accurate; leave it.
## Verification
This is documentation/config only — no code path changes, no eval run needed.
- `grep -rn "MCP_ALLOWED_HOSTS\|<product>\|<registry>\|<owner>" deploy/ README.md` returns nothing.
- Every env var named in `deploy/` is one the code actually reads (cross-check against
`grep -rn "os.environ" docs_mcp/ rag/`).
- The service block in `deploy/docker-compose.yml` is byte-comparable with the `chem-mcp`
block in `Drawbar/drawbar-backend/docker-compose.yml` modulo comments.
## Related observation — not part of this issue
Production pins `chem-mcp` to `image: ...crop-chem-docs:corpus-2026.05.24` **while carrying
`com.centurylinklabs.watchtower.enable: "true"`**. Watchtower only re-pulls the tag a container
is running, and `corpus-YYYY.MM.DD` is a fresh unique tag per build (`refresh.yml:153`), so that
tag will never be re-pushed — the monthly corpus refresh has been shipping `:latest` to a
production container that cannot see it. seed-mcp runs `:latest` and does move. That is a
Drawbar-side deploy question, not a crop-chem-docs one; file against `Drawbar/drawbar-backend`
if it is not intentional.
Related: #4 (mcp 2.x migration — this mismatch was found while scoping it).
The deploy-pinning observation at the bottom of this issue is now filed as Drawbar/drawbar-backend#339 — confirmed against the registry: corpus-2026.08.11 and corpus-2026.09.01 both exist, so production has silently missed two refreshes. Stays out of scope here.
The deploy-pinning observation at the bottom of this issue is now filed as Drawbar/drawbar-backend#339 — confirmed against the registry: `corpus-2026.08.11` and `corpus-2026.09.01` both exist, so production has silently missed two refreshes. Stays out of scope here.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The defect
deploy/docker-compose.ymlis unedited docs-mcp-template boilerplate. It has never beentouched since the initial scaffold commit (
9ba615c) — it still carries literal<product>,<registry>and<owner>placeholders, and it describes a deployment that does not exist.Worse, it sets an env var the code does not read:
grep -rn "os.environ\|getenv" docs_mcp/ rag/—MCP_ALLOWED_HOSTSappears nowhere.The knob that actually governs DNS-rebinding behaviour is
MCP_DISABLE_DNS_REBINDING_PROTECTION(
docs_mcp/server.py:725). Anyone who trusted this file and set an allowlist would get a 421on every request and no clue why.
What production actually runs
crop-chem-docs is not deployed as a standalone stack. It is the
chem-mcpservice insideDrawbar's parent compose (
Drawbar/drawbar-backend/docker-compose.yml):Every material line disagrees with what this repo ships:
deploy/docker-compose.ymlsaysports: "8000:8000"(published to host)expose: 8080,MCP_PORT=8080, no host portMCP_ALLOWED_HOSTS(not read)MCP_DISABLE_DNS_REBINDING_PROTECTION=1http://ollama:11434host.docker.internal:11434+extra_hostshttp://<product>-rerank:8080http://llama-rerank:8080<product>-docs-mcpchem-mcpThe
OLLAMA_URLrow is not cosmetic: Drawbar'sollamaservice is commented out in theparent compose, so the image's baked default (
http://ollama:11434, Dockerfile:49) does notresolve. Query-time embedding — i.e. all of
search_docs— depends on that override.deploy/drawbar-compose-snippet.mdis closer to reality but also drifted: it names the servicecrop-chem-docs, mapsports: "8001:8000", and asserts "No environment block needed — theimage's defaults handle it", which is false on both the rebind and Ollama counts above.
The fix
Model it on
justin/seed-mcp:deploy/docker-compose.yml, which the sibling repo already gotright: not a standalone stack, but the service block to merge into Drawbar's parent compose,
with a header comment saying exactly that and where the parent lives.
deploy/docker-compose.ymlwith the realchem-mcpblock, copied fromDrawbar/drawbar-backend/docker-compose.ymlso the two cannot drift silently. Keep theinline comments explaining why rebind protection is off (internal-network-only,
exposenot
ports) and why Ollama goes throughhost.docker.internal.MCP_ALLOWED_HOSTSline. It is not a supported knob in this codebase.(If a real allowlist is wanted instead of an on/off switch, that is a code change — see
justin/zerto-docs#12, which made allowed_hosts/origins genuinely configurable. Out of
scope here; file it separately if you want it.)
deploy/drawbar-compose-snippet.mdwith the same source of truth — servicename
chem-mcp,expose: 8080, and drop the "no environment block needed" claim. If therewritten compose file makes the snippet redundant, delete the snippet and update the
README tree instead of maintaining two half-true copies.
README.md:93-96) to match whatever survives step 3.Also note while you are in there: the snippet's verification block calls
corpus_status(),which does exist (
server.py:536) — the five real tools aresearch_docs,get_page,list_versions,corpus_status,crop_chem_api_lessons. That part is accurate; leave it.Verification
This is documentation/config only — no code path changes, no eval run needed.
grep -rn "MCP_ALLOWED_HOSTS\|<product>\|<registry>\|<owner>" deploy/ README.mdreturns nothing.deploy/is one the code actually reads (cross-check againstgrep -rn "os.environ" docs_mcp/ rag/).deploy/docker-compose.ymlis byte-comparable with thechem-mcpblock in
Drawbar/drawbar-backend/docker-compose.ymlmodulo comments.Related observation — not part of this issue
Production pins
chem-mcptoimage: ...crop-chem-docs:corpus-2026.05.24while carryingcom.centurylinklabs.watchtower.enable: "true". Watchtower only re-pulls the tag a containeris running, and
corpus-YYYY.MM.DDis a fresh unique tag per build (refresh.yml:153), so thattag will never be re-pushed — the monthly corpus refresh has been shipping
:latestto aproduction container that cannot see it. seed-mcp runs
:latestand does move. That is aDrawbar-side deploy question, not a crop-chem-docs one; file against
Drawbar/drawbar-backendif it is not intentional.
Related: #4 (mcp 2.x migration — this mismatch was found while scoping it).
The deploy-pinning observation at the bottom of this issue is now filed as Drawbar/drawbar-backend#339 — confirmed against the registry:
corpus-2026.08.11andcorpus-2026.09.01both exist, so production has silently missed two refreshes. Stays out of scope here.