Skip to content

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 websiteId and returns only that website's data (WHERE website_id = $1 in every query). Every tool is read-only; none mutates the graph.

Tools

ToolArgumentsReturns
search_website_knowledgewebsiteId, query, limit? (1 to 50, default 10)Ranked entity, fact, and chunk hits for the website.
get_entity_factswebsiteId, entityIdThe verified, merge-resolved facts for one entity (hidden facts excluded).
list_node_fileswebsiteId*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:

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):

bash
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.js

If 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)

bash
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.js

Implementation 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.

Owner-controlled structured data for AI.