~/gqlith — docs/mcp.md
Early access gqlith is in private early access — the code is not publicly available yet. These docs preview the surface so you know what to expect. Join the list →

Docs / Protocols

MCP surface

Generate an MCP server from Postgres — four governed tools that make the API self-describing to AI agents, permission-projected so an agent only ever learns what the caller can see.

gqlith generates an MCP server from your Postgres schema — a Streamable HTTP endpoint with four permanent, governed tools that make the API self-describing to any MCP-capable agent. The agent doesn’t guess at your schema: it asks, and the answer is projected through the caller’s permissions.

Enabled by setting targets.mcpServer: streamable-http. Default endpoint: POST /mcp (JSON-RPC 2.0). Protocol version: 2025-11-25.

POST /mcp
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2025-11-25
Authorization: Bearer <jwt>

The four tools

ToolPurposeWrites?
describe_schemaSchema guide for one entity, or a compact index of every visible entityNo
filter_helpFiltrQL syntax, operators, and entity-specific examples built from your real schemaNo
graphql_queryExecutes a read-only GraphQL documentNo
graphql_mutationExecutes a write GraphQL documentYes

The read/write split is enforced at the wire: a mutation { … } document sent to graphql_query is rejected before any handler runs, and the two tools take different parameter names (query vs mutation) so a wrong-tool call fails fast as an input-validation error.

Permission-projected discovery

describe_schema and filter_help never reveal entities, fields, relations, example documents or filterable fields the caller’s role cannot access. A generated example is dropped entirely if it would so much as name something the role can’t see.

describe_schema() with no argument returns a conventions block (pagination, filtering, ordering, nested-mutation semantics, aggregate shapes, limits, scalar wire formats) plus an index of every visible entity. describe_schema('recipes') returns:

  • Every field with its type, nullability, enum values, and a semantic annotation — required-on-create, server-assigned, generated, default-backed.
  • Relations with cardinality and the exact nested-mutation operators available on each (nestedOps), so nested writes are discoverable, not guessed.
  • Operations tagged with the tool that runs them (createRecipe → graphql_mutation), matching the real API exactly.
  • Ready-to-run example documents — create, nested create, update, upsert, delete, find, filter + paginate, aggregates — each tagged with the tool to send it to.

Running documents

{
  "method": "tools/call",
  "params": {
    "name": "graphql_query",
    "arguments": {
      "query": "{ recipes(limit: 10, filterql: \"status = 'published' AND servings >= 4\", orderBy: [createdAt_DESC]) { edges { node { id title servings } } } }"
    }
  }
}

filterql is FiltrQL — the same grammar the REST ?filter= parameter uses; filter_help('recipes') teaches it to the agent with copy-pasteable examples from your actual columns. RBAC, row filters, tenancy, OCC, rate limiting, depth/complexity limits and audit all apply — the tools are a governed front door to the same pipeline as /graphql, never a bypass.

Nested mutations sent through graphql_mutation run in one atomic transaction: if a child row fails, the parent rolls back too.

Resources, not just tools

The server also exposes MCP resources: entity://<entityKey> (the per-entity descriptor) and gqlith://schema/<entity> (the entity’s runtime schema, which clients can subscribe to — it notifies when a tenant’s custom fields change). With storage enabled, file reads attach an MCP resource_link at gqlith://files/{fileId}: reading it runs the same authorization gate as every download, and an agent that isn’t allowed to see the file gets a refusal — never a signed URL by accident.

Locking discovery down

Schema discovery is governed by the top-level schemaDiscovery { raw, projected } config — two independent risk classes, each a tri-state gate (enabled / disabled / role-restricted):

  • projected (default enabled) governs describe_schema / filter_help and the entity:// / gqlith://schema/ resources. Safe to leave on: it only ever shows what the caller can already reach.
  • raw (default disabled) governs the full-SDL surface (schema://sdl, GraphQL __schema).

Error navigation

Errors return guidance the agent can act on without leaving the conversation: an unknown field points at describe_schema('<entity>'); a filter parse error points at filter_help('<entity>').

Limits today

  • Subscriptions are not available over MCP — they remain a GraphQL/SSE feature.
  • Query and mutation results are capped at maxResultBytes (default 1 MiB) with a notice; narrow the selection or split the document.
  • FiltrQL autocomplete (filter_complete) is not shipped yet; filter_help is the syntax reference.