InventDB
All articles AI

An MCP server inside the database, signed in with OAuth

InventDB serves the Model Context Protocol from the database process itself. An AI client signs in as a person with OAuth 2.1, works through 51 typed tools under that person's grants, and stages record changes as proposals that only a commit applies.

Connecting an AI agent to a database usually starts with a connection string or a service API key. The agent then holds whatever rights that credential carries, it can write the moment it decides to, and the database cannot tell which person asked for any of it.

The usual remedy is a separate tool server in front of the database that defines the agent's actions and repeats the application's permission checks. That is one more service to deploy, and one more copy of the access rules to keep in step with the real ones.

InventDB serves the Model Context Protocol (MCP) from the database process. An AI client such as Claude Desktop or Cursor connects to one endpoint on your instance, signs in as a person through OAuth, and works through typed tools that call the engine directly.

One endpoint speaking JSON-RPC 2.0

The whole surface is one route, POST /mcp, which accepts a JSON-RPC 2.0 envelope. The handler reads the method and dispatches it: initialize returns the protocol revision (2025-03-26) and the server's capabilities, ping answers with an empty object, tools/list returns the tool catalog and tools/call runs one tool. resources/list and prompts/list return empty lists, so clients that ask for them do not fail.

Routing is a flat match on the method and then on the tool name, so a call costs what the engine operation behind it costs: one id-index lookup for get, a full planned query for sql.

POST /mcp
Authorization: Bearer <access token>
Content-Type: application/json

{
  "jsonrpc": "2.0", "id": 1, "method": "tools/call",
  "params": {
    "name": "sql",
    "arguments": { "query": "SELECT status, COUNT(*) AS units FROM pms.properties GROUP BY status" }
  }
}

A successful call returns the result twice: as a JSON string in a text block, which is what a model reads, and as structuredContent, the same value as real JSON for code. A tool that fails still returns a result, with isError: true and a code: -32001 when the person lacks permission, -32002 when a record, proposal or template does not exist, and -32003 when the tool ran and failed, for example on bad SQL or an expired proposal. Protocol faults, such as a body that is not JSON (-32700) or an unknown method or tool (-32601), use the JSON-RPC error channel instead, so the model reads a tool error as content and can try again.

Signing in: OAuth 2.1 with PKCE and dynamic registration

An MCP client needs a token for /mcp, and asking a person to create one and paste it into a configuration file defeats the purpose. Each InventDB instance is its own OAuth 2.1 authorization server, with no external identity provider, and it supports the flow MCP clients are built to run.

The OAuth sign-in sequence between an MCP client and an InventDB instance, from the first call without a token to a tool call made as the person AI client Your InventDB instance 1 POST /mcp without a token: 401 with a link to the metadata 2 GET /.well-known/oauth-authorization-server 3 POST /oauth/register: the client gets its own id 4 browser opens /oauth/authorize (S256); the person signs in 5 redirect back with a code: valid 60 seconds, used once 6 POST /oauth/token with the PKCE verifier: access and refresh 7 POST /mcp with the bearer token: each tool runs as the person
The sign-in an MCP client runs against an instance. The person types their password only into the instance's own page, and the client ends with a token tied to that person, their role and the client app.
  1. The client calls /mcp without a token. The 401 response carries a WWW-Authenticate header that points to /.well-known/oauth-protected-resource.
  2. The client reads /.well-known/oauth-authorization-server, which lists the authorization, token, registration and revocation endpoints.
  3. It registers itself at /oauth/register, following RFC 7591 dynamic client registration, and receives a client id. A secret is issued only to a client that asks to authenticate with one.
  4. It opens /oauth/authorize in a browser with a PKCE challenge. Only the S256 method is accepted; a plain challenge is refused. The person signs in on the instance's own page and approves the client on a consent page. Consent is remembered for 90 days.
  5. The instance redirects back with an authorization code that is valid for 60 seconds and can be used once.
  6. The client exchanges the code at /oauth/token. The server hashes the verifier with SHA-256, compares the result with the stored challenge, and issues an access token valid for 8 hours and a refresh token valid for 30 days.

Tokens are opaque random values of 32 bytes rather than signed JWTs. Each is stored in the instance with the person it belongs to, the app the client signed in through and the person's role in that app, and every request looks its token up. A refresh looks the role up again. /oauth/revoke withdraws one client's access without touching the person's password or their other clients. Clients, codes, tokens and consents are all records inside the instance, encrypted at rest with the rest of its data.

Scripts and scheduled jobs that cannot open a browser use a service account. An administrator creates the service-account client, and it exchanges its secret for an access token with the client_credentials grant. A client that registered itself through the public endpoint cannot use that grant.

51 tools, grouped by what they do

tools/list returns 51 tools. Each has an input schema and a description written for a model, which says when to use the tool and when another one fits better.

GroupToolsPurpose
Discovery and query (5)schema, describe_type, search, get, sqlList namespaces and types, describe a type's shape, read records.
Record changes (5)propose_insert, propose_update, propose_delete, commit, cancelStage a change, then apply or drop it.
History (2)audit_log, undoRead past changes and reverse one.
Attachments (10)attach_upload_url, attach_upload_from_path, attach_search, attach_read, attach_download, attach_list, attach_url, attach_versions, attach_version_download, attach_version_promoteFiles on records, their extracted text and their versions.
Uploads (2)bulk_upload_url, upload_checkBrowser upload links and their status.
Bulk insert (4)bulk_insert, bulk_insert_preview, bulk_insert_commit, bulk_insert_from_pathSmall inline inserts and file imports.
Validation rules (8)rules_list, rules_propose, rules_save, rules_enable, rules_disable, rules_delete, rules_test, rules_for_typeRules that block or warn on bad records.
Reports (4)report_templates, report_template_get, report_template_save, report_renderSaved report templates rendered to print-ready HTML.
Workflows (7)workflows_list, workflow_get, workflow_versions, workflow_runs, workflow_run, workflow_rollback, workflow_set_activeInspect, run and roll back workflows.
Forms (4)render_record_form, analyze_type_for_form, save_form_template, list_form_templatesData-entry forms for a type.

InventDB stores JSON records without a declared schema, so describe_type reports a type's shape from the data. It samples 500 records by default, at most 5,000, and gives for each property its JSON types, how often it is present, top values, percentiles for numbers and a few examples. The model infers units and meaning from those and from what the person tells it.

sql runs one read-only statement with the full grammar, including joins, GROUP BY and MEANING() for semantic search; INSERT, UPDATE and DELETE are refused. If an administrator has set a row cap for the instance, the tool applies it exactly as SQL over HTTP does and returns truncated: true, so the model narrows its query instead of assuming it saw everything.

File contents do not pass through the model's tokens. attach_upload_url and bulk_upload_url return an upload link that works once and expires after 15 minutes; the person opens it, drops the file, and the browser sends the bytes to the instance. attach_search answers questions about files from their extracted text without downloading them.

Changes are proposals until a commit

A model that writes directly can write the wrong thing before anyone has looked. The record-change tools therefore work in two steps. propose_insert, propose_update and propose_delete write nothing. Each checks the person's grant on the type, builds the change and returns a proposal_id with a preview: the new record for an insert, the record before and after for an update, and the record to be removed for a delete. An update takes a sparse patch, so the model sends only the fields that change.

{ "name": "propose_update",
  "arguments": { "namespace": "pms", "type": "leases",
                 "id": "c129df1c-73b9-4339-8fc8-ad271fd505e8",
                 "patch": { "contract_rent": 4750 } } }

// structuredContent of the result, shortened
{ "proposal_id": "prop_3f9c...", "kind": "update",
  "before": { "contract_rent": 4625, ... },
  "after":  { "contract_rent": 4750, ... },
  "warnings": [], "rule_violations": [], "blocked": false, "ttl_seconds": 300 }

Two kinds of check run when a proposal is made. Shape warnings compare the change with a sample of up to 200 existing records and flag a property that at least 95% of records carry but the change leaves out, or a value whose JSON type differs from what the type holds. They are advice only. Validation rules are rules the person's team writes for a type with the rules tools; a rule with severity error blocks the change, and the response then carries no proposal id.

A proposal is held in the server's memory for five minutes, and only the person who made it can commit or cancel it. commit checks the grant and the validation rules again, because either may have changed since the proposal was made, then applies the change through the engine's normal write path and writes an entry to the audit log with the record before and after. cancel drops the proposal. undo takes an audit entry id and reverses that change, and records the reversal as a new entry that points back at the original.

Two tools write without a separate commit. bulk_insert takes at most 20 records written out in the call, checks each one against the validation rules, and records every inserted row in the audit log. Larger imports go through an upload link: the server previews the file, the model shows the inferred column types to the person, and bulk_insert_commit applies the staged import.

Who approves. The server guarantees that a proposal changes nothing and that a commit is checked again. Whether a person reads the preview before the commit depends on the client and its setting for confirming tool calls.

Whose permissions a tool runs with

Every request to /mcp passes through the same authentication step as the rest of the API. The token resolves to a person, a role and an app, and that identity is attached to the request before the tool runs.

The data tools check the person's grant on each type they touch: read for discovery and queries, write for proposals and commits, and delete for deletions. Queries from sql and search run through the engine with the person's row rules joined to the WHERE clause, as they are for SQL over HTTP, and every write is checked against the row rules where the engine stores it. Administrators are not filtered, as elsewhere in InventDB.

To give an agent less than you have, create a user with a narrower role and sign the client in as that user. The agent works within that user's grants, and revoking its client leaves your own access untouched.

What it does not do

  • Resources and prompts are declared but empty. Everything the server offers is a tool.
  • The tool list is the same for every caller. Permission is checked when a tool is called, so a person with narrow grants still sees every tool and receives -32001 from the ones they cannot use.
  • Pending proposals live in memory for five minutes, so a restart of the instance drops them and the model has to propose again.
  • Each proposal covers one record. No MCP tool applies changes to several records in one transaction; that is the job of the change sets described in our article on safe AI changes, which also lists exactly which writes the audit log records.
  • The audit log records committed record changes, bulk inserts, undo, validation-rule changes and report-template saves. Reads, file uploads and workflow or form changes made through the tools are not logged.

Connecting a client

The endpoint is your instance's address with /mcp on the end. In Claude Desktop it goes under Settings, Connectors, Add custom connector. A client configured by file takes the same URL with OAuth as its auth type:

{
  "mcpServers": {
    "inventdb": {
      "url": "https://<your-workspace>.inventdb.com/mcp",
      "auth": { "type": "oauth2" }
    }
  }
}

On the first connection the client opens the instance's sign-in page in a browser, and after you approve it the client lists the tools. A record count per type makes a good first question, because a wrong answer is obvious. The MCP setup guide covers each step and the common failures.

The MCP server is part of both InventDB SOAR and InventDB Serverless. The reasoning happens in your AI client on your own subscription: reading, searching and proposing changes call no model inside the database.