The two MCP servers (don't confuse them)
/api/v1/mcp— always on. A hardcoded set of 10 tools (list_tables,query_rows,insert_row,list_forms,list_bases,list_workflows,create_invoice,create_quote,create_checkout,create_subscription). This is what an external Claude/Cursor config points at. Auth:Bearer jst_*. Verified working live — a minted key resolves and reachestools/call./api/v1/mcp/actions— tenant-gated bymodules.mcp_export(off by default →403 "MCP export is not enabled for this tenant"). Exposes every registereddefineActionwith the full module-gate + approval semantics: service tokens execute immediately (recorded changeset); human/break-glass tokens are forced to propose mode (queued for human review). This is the richer, safer surface — but a tenant must opt in.
On the 401 that stranded the MCP configs: the header-auth path is correct.
The 401 {code:-32000} seen this session came from an empty token env
(MOS_MCP_TOKEN unset → the client sent Bearer with no value). The 401 now
returns a diagnostic message naming the exact cause (fixed in
mos-hq6q4.76). See quickstart § "MCP config".
3. MCP config (Claude / Cursor / any MCP client)
MOS speaks MCP over plain HTTP (JSON-RPC 2.0) at /api/v1/mcp — 10 tools:
list_tables, query_rows, insert_row, list_forms, list_bases,
list_workflows, create_invoice, create_quote, create_checkout,
create_subscription.
{
"mcpServers": {
"mos": {
"type": "http",
"url": "https://acme.marduk.app/api/v1/mcp",
"headers": { "Authorization": "Bearer jst_YOUR_TOKEN_HERE" }
}
}
}
The #1 gotcha: an empty token → 401 {code:-32000}. If you template the
token from an env var ("Bearer ${MOS_MCP_TOKEN}") and the var is unset, the
client sends Bearer with no value and every call 401s. The server now tells
you exactly which case you hit ("empty Bearer token…", "not a MOS team API
key…", "invalid, revoked, or expired"). Sanity-check with curl before wiring a
client:
curl -s -X POST https://acme.marduk.app/api/v1/mcp \
-H "Authorization: Bearer $MOS_MCP_TOKEN" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
# → {"jsonrpc":"2.0","id":1,"result":{"serverInfo":{"name":"jst-client-api",...}}}
The URL must include /api/v1/mcp. A bare host (https://acme.marduk.app)
gives the client's own ENDPOINT_NOT_FOUND.
There is a second, richer MCP server at /api/v1/mcp/actions that exposes every
MOS action — but it is off by default (per-tenant modules.mcp_export) and
returns 403 until enabled. See agentic-rails.md.
