Asking Claude directly¶
The model explorer speaks MCP, so a Claude session can query the application itself — from any repository, without a copy of this one. Useful when the question is about the running system rather than about the prose: "which live flows can set a booking to CX, and who can run them?"
Connect it once¶
claude mcp add --transport http --scope user ww-model http://localhost:8001/-/mcp
claude mcp list # ww-model ... ✔ Connected
--scope user registers it for every repository on your machine. Use --scope project
inside a specific repo instead to write .mcp.json there, so teammates inherit it when they
clone — worth doing once this stops being localhost.
The model explorer must be running: cd serve && docker compose up -d.
A session that was already open will not see it
A Claude session builds its tool list when it starts, and claude mcp add does not
retro-fit it. This is easy to misread as a broken server, because the shell says
everything is fine: claude mcp list reported ww-model ... ✔ Connected while the
session running in the next window had no execute_sql at all.
Start a new session — a new terminal, or /exit and claude again — and the three
tools are there. Nothing needs re-adding; the registration is already on disk.
Checking the endpoint without a session¶
Useful when you want to know whether the server or the session is the problem. The handshake
is three calls, and curl can do all of them:
SID=$(curl -s -D /dev/stderr -X POST http://localhost:8001/-/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' \
2>&1 >/dev/null | awk '/mcp-session-id/ {print $2}' | tr -d '\r')
curl -s -X POST http://localhost:8001/-/mcp \
-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-H "mcp-session-id: $SID" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"execute_sql","arguments":{"database":"model","sql":"select count(*) as flows from docs where kind = '"'"'Microflow'"'"'"}}}'
A reply naming databases or returning rows means the container, the databases and the
read-only path are all healthy, and the problem is on the client side.
What Claude gets¶
Three tools, and it is told to read the schema before querying:
| Tool | Does |
|---|---|
list_databases |
the three databases — model, docs and source |
get_database_schema |
every table, view and column |
execute_sql |
one read-only SELECT, returning columns and rows |
Writes are refused three ways over: the plugin validates that the statement is a SELECT,
Datasette opens the file immutable, and the container mounts it read-only. CREATE,
INSERT, UPDATE, DELETE, DROP, PRAGMA writable_schema and ATTACH were each tried
and each rejected.
Questions that work well¶
Ask in plain language — Claude writes the SQL:
- "Which live flows write
Booking.Booking.WindowStatusto CX, and which roles can run them?" - "What runs implicitly when a
Booking.VoucherTotalsis committed?" - "If I change
Pricing.Sub_AddPriceToDetailedBookingLine_PCG, what could be affected?" - "Which Pricing flows are unreachable from
Pricing.Sub_PriceBookingAll?" - "What does the documentation say about
DiscountPercentage_ExclManual, and does the model still agree?" - "List the live flows that call flows excluded from deployment."
Three databases, and the questions that cross them¶
| Database | Holds |
|---|---|
model |
the application as facts — 504,833 rows: flows, steps, entities, roles, entry points, coverage |
docs |
every section of every document, with the model names it mentions |
source |
the hand-written half — 2,436 files, every Java and JavaScript action linked to its file, and the microflows that call it |
The interesting questions cross them, which is why they are served from one place:
- "What does the Java behind
DataManagement.jaImportCSV_BookingChangesactually do, and which microflows call it?" - "Which code touches
Booking.Booking?" — the model says which flows do;sourcesays which files do - "Show me Java that talks to the database directly" — 13 files use JDBC, going around the Mendix data layer entirely, so the model cannot describe what they read or write
- "Where does Java build a microflow name or an XPath at runtime?" — 31 files do, and those edges cannot be read from the source at all
That last one is the honest limit, and it is worth being exact about what it does not mean.
Microflow logic is completely available: a microflow is a model, not text — closer to BPMN than
to a file — and every step, branch and expression is in model. What cannot be read is the
other direction: Java calls microflows through variables here
(Core.microflowCall(this.Microflow)), so exactly one literal microflow name appears across
1,286 Java files. source records what is written down and counts the rest as dynamic; an
absent reference means "not named in this file", never "not used". See
when to read the code.
Two things it cannot answer¶
Expressions are stored verbatim, never parsed. A write of if … then CC else CX is one
fact mentioning two values, not a decision table. If the answer hinges on which branch runs,
read the flow — block: How <flow> is started and the deep dives exist for that.
Runtime configuration is invisible. Whether a scheduled event is enabled in a given environment, what a constant holds in production, which feature flags are set — none of that is in the model, so nothing here can tell you.
If you would rather not use Claude¶
Everything the MCP endpoint exposes is on the web too: the
model explorer
autocomplete, and every page answers as JSON or CSV by adding .json or .csv to the URL.