MCP server
The bAInquet MCP server is a Model Context Protocol server that exposes one paid website's verified knowledge graph to MCP clients (Claude Desktop, IDEs, agents) as read-only tools. It is the read side of bAInquet: connectors push content in, the pipeline builds the graph, and the MCP server lets an agent query it.
Stable The server, its tenant scoping, and the three tools are implemented and tested.
What it is for
A connector and the published node files make a website's knowledge available to any consumer over HTTP. The MCP server is the live, query-time complement: an MCP client connects to one website's graph and asks targeted questions (search, entity facts, the published file list) without fetching and parsing the whole node.
It is a paid capability. Plan gating (the cap.mcp capability) is enforced upstream at the endpoint and host layer; the server process implements the tools and tenant scoping and does not itself decide entitlement.
How a client connects
- Transport: stdio, newline-delimited JSON-RPC 2.0 (the MCP wire protocol:
initialize,tools/list,tools/call,ping). Diagnostics go to stderr. - Scoping: every tool requires a
websiteIdand returns only that website's data (WHERE website_id = $1in every query). Every tool is read-only; none mutates the graph.
Tools
| Tool | Arguments | Returns |
|---|---|---|
search_website_knowledge | websiteId, query, limit? (1 to 50, default 10) | Ranked entity, fact, and chunk hits for the website. |
get_entity_facts | websiteId, entityId | The verified, merge-resolved facts for one entity (hidden facts excluded). |
list_node_files | websiteId* | The file list of the website's latest published node version. |
* marks a required argument. An out-of-range limit is clamped, not rejected.
Configure an MCP client (Claude Desktop)
Add the server to claude_desktop_config.json:
{
"mcpServers": {
"bainquet": {
"command": "node",
"args": ["/absolute/path/to/bainquet/apps/mcp/dist/server.js"],
"env": {
"DATABASE_URL": "postgres://bainquet:bainquet_local_pw@localhost:5432/bainquet"
}
}
}
}In production each paid website gets its own logical endpoint (mcp.<brand>/{domain}); {domain} resolves to a websiteId and the paid cap.mcp gate is checked before any tool runs.
Run it directly
Build the workspace first (the server dynamic-imports the API's compiled knowledge service):
export NODE_OPTIONS="--use-system-ca"
pnpm install
pnpm --filter @bainquet/api build
pnpm --filter @bainquet/mcp build
# Start the stdio server (DATABASE_URL must point at Postgres):
DATABASE_URL=postgres://bainquet:bainquet_local_pw@localhost:5432/bainquet \
node apps/mcp/dist/server.jsIf the API dist is not built, the server falls back to running the same tenant-scoped SQL against its own pool, so it still works in a knowledge-only stack.
Drive it by hand (smoke test)
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_website_knowledge","arguments":{"websiteId":"<uuid>","query":"pizza"}}}' \
| DATABASE_URL=postgres://bainquet:bainquet_local_pw@localhost:5432/bainquet node apps/mcp/dist/server.jsImplementation note
The server is a minimal hand-rolled transport rather than the official @modelcontextprotocol/sdk. For a stdio server the MCP protocol is a small, stable JSON-RPC surface, so the hand-rolled transport avoids a heavy dependency tree and a zod peer-version conflict while staying spec-compatible.