Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ redeploying it**.
To run and test the whole thing locally:

```bash
npm run mcp:local # builds the site, serves it, starts the Worker on :8787
npm run mcp:local # builds the site, serves it, starts the MCP server on :8787
npm run mcp:test # in another terminal
```

Expand Down
28 changes: 15 additions & 13 deletions bin/mcp-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,15 @@ serve_site() {

port_pids() { lsof -nP -tiTCP:"$1" -sTCP:LISTEN 2>/dev/null || true; }

# Signalling the PIDs we launched is not enough on its own: `npx` sits in front of a
# chain of node processes, so terminating it can leave the real wrangler process alive
# and still holding the port. The contract we actually care about is "both ports are
# free afterwards", so verify that and force it if the polite shutdown stalls.
# Signalling the PIDs we launched is not enough on its own: intermediary processes
# (npm, shells) can leave the real server process alive and still holding the port.
# The contract we actually care about is "both ports are free afterwards", so verify
# that and force it if the polite shutdown stalls.
cleanup() {
trap - EXIT INT TERM
log "Shutting down…"

for pid in "${WRANGLER_PID:-}" "${SITE_PID:-}"; do
for pid in "${SERVER_PID:-}" "${SITE_PID:-}"; do
[ -n "$pid" ] && kill -TERM "$pid" 2>/dev/null || true
done

Expand Down Expand Up @@ -117,7 +117,7 @@ done

serve_site

[ -d "${ROOT}/mcp-server/node_modules" ] || (log "Installing Worker dependencies…" && cd "${ROOT}/mcp-server" && npm install --silent)
[ -d "${ROOT}/mcp-server/node_modules" ] || (log "Installing MCP server dependencies…" && cd "${ROOT}/mcp-server" && npm install --silent)

cat <<EOF

Expand All @@ -131,13 +131,15 @@ cat <<EOF
EOF

cd "${ROOT}/mcp-server"
npm run --silent build

# Deliberately NOT `exec`: that would replace this shell and discard the trap above,
# leaving the static server on $SITE_PORT orphaned when wrangler stops.
# leaving the static server on $SITE_PORT orphaned when the MCP server stops.
#
# Backgrounded, then waited on, rather than run in the foreground: bash defers trap
# handlers until the current foreground command returns, so a foreground wrangler
# would swallow the signal until it felt like exiting — which is exactly the hang
# this is meant to survive. `wait` is interruptible, so the trap fires immediately.
npx wrangler dev --port "$MCP_PORT" --var "DOCS_BASE_URL:http://127.0.0.1:${SITE_PORT}/" &
WRANGLER_PID=$!
wait "$WRANGLER_PID"
# handlers until the current foreground command returns, so a foreground server
# would swallow the signal until it felt like exiting. `wait` is interruptible,
# so the trap fires immediately.
DOCS_BASE_URL="http://127.0.0.1:${SITE_PORT}/" PORT="$MCP_PORT" node dist/server.mjs &
SERVER_PID=$!
wait "$SERVER_PID"
4 changes: 4 additions & 0 deletions mcp-server/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
dist/
test/
README.md
3 changes: 1 addition & 2 deletions mcp-server/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
node_modules/
.wrangler/
.dev.vars
Comment thread
kpodemski marked this conversation as resolved.
dist/
17 changes: 17 additions & 0 deletions mcp-server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Container image for the devdocs MCP server. Host-agnostic: runs anywhere a
# container runs.

FROM node:24-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts
COPY src ./src
RUN npm run build

FROM node:24-slim
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist/server.mjs ./server.mjs
USER node
EXPOSE 8080
CMD ["node", "server.mjs"]
16 changes: 8 additions & 8 deletions mcp-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ with JSON-RPC — so the two halves live in different places:
└─────────────────────────────────────────────────────────────────┘
▲ ▲
│ fetch (cached) │
┌─ mcp-server/ (Cloudflare Worker) ──────────────────────────────┐
┌─ mcp-server/ (stateless HTTP server) ──────────────────────────┐
│ POST /mcp Streamable HTTP, stateless │
│ search_docs ──► Algolia DocSearch (the site's own index) │
│ get_doc ──► /<path>/index.md on GitHub Pages │
Expand All @@ -30,10 +30,10 @@ with JSON-RPC — so the two halves live in different places:
Claude / Cursor / ChatGPT / …
```

The Worker stores nothing. Search relevance comes from the same Algolia DocSearch index
The server stores nothing. Search relevance comes from the same Algolia DocSearch index
that powers the search box on the website, and page content is read from the markdown
files the Hugo build already publishes. **New documentation is live as soon as GitHub
Pages redeploys — the Worker never needs redeploying for content changes.**
Pages redeploys — the server never needs redeploying for content changes.**

## Tools

Expand All @@ -52,7 +52,7 @@ that version.
Once deployed, point any MCP client at the `/mcp` endpoint.

```bash
claude mcp add --transport http prestashop-devdocs https://<worker-host>/mcp
claude mcp add --transport http prestashop-devdocs https://<host>/mcp
```

Or, in a `mcp.json` / `claude_desktop_config.json`:
Expand All @@ -62,7 +62,7 @@ Or, in a `mcp.json` / `claude_desktop_config.json`:
"mcpServers": {
"prestashop-devdocs": {
"type": "http",
"url": "https://<worker-host>/mcp"
"url": "https://<host>/mcp"
}
}
}
Expand All @@ -74,7 +74,7 @@ No authentication — the server is public and read-only, exactly like the websi

One command brings up the whole stack — it builds the site with the Hugo version CI uses
(downloaded to `bin/.hugo/` on first run), serves `src/public` in place of GitHub Pages,
and starts the Worker against it:
and starts the MCP server against it:

```bash
npm run mcp:local # from the repository root
Expand Down Expand Up @@ -123,9 +123,9 @@ curl -s -X POST http://127.0.0.1:8787/mcp \
"params":{"name":"search_docs","arguments":{"query":"actionValidateOrder"}}}'
```

### Working on the Worker alone
### Working on the server alone

`npm run dev` in this directory starts just the Worker, reading the **live**
`npm run dev` in this directory starts just the MCP server on :8787, reading the **live**
`https://devdocs.prestashop-project.org/`. Useful for iterating on search, but `get_doc`
and `list_sections` will 404 until the artifacts from this branch are actually deployed.

Expand Down
Loading
Loading