Context gateway

Give AI agents one MCP endpoint, with safe, structured access to two things:

Context gateway

Give AI agents one MCP endpoint, with safe, structured access to two things:

  • the records in a KiviDB database: customers, orders, tickets, whatever your application keeps there. You describe them once, as a context model; KiviDB builds the search indexes and gives agents tools such as search_customer and customer_orders, which return only the fields you chose;
  • the tools of your own remote MCP servers (upstreams), proxied through the gateway with credentials the agent never sees.

Each agent connects with its own agent key. A key can be limited to some of the model's entities, can carry fixed filters such as customer.tenant = acme, and names the upstreams it may reach. The filters are added on the server to every query through that key, whatever the agent asks for: a record outside them is not found, exactly as if it did not exist. They work on rows, not just on whole kinds of record.

The context gateway was called the context retriever until November 2026. Only the name changed: models, keys (kvctx_...), the /context/mcp endpoint and the API paths below are the same, and agents set up before keep working.

A context model

A model belongs to one database and lists its entities. Each entity is:

Name and descriptioncustomer: "A customer account with its plan and monthly revenue." The agent reads the description to choose a tool.
Key prefixevery record is a key under it, such as customer:42
Stored ashashes, or JSON documents
Id fieldthe field holding the record's id; it is indexed so records can be fetched by it
Fieldseach one text (searched by words), tag (matched exactly: ids, statuses, tenants) or numeric (compared and ranged)
Related recordse.g. orders: the order records whose customer_id is this customer's id

The records can come from your application or from a data integration pipeline that keeps tables from PostgreSQL or MySQL in KiviDB.

KiviDB builds one search index per entity, kividb:ctx:<model>:<entity>, over the keys already under its prefix, and keeps it up to date as keys change. Saving the model again rebuilds them. Deleting the model drops the indexes and revokes its keys; the records themselves are never touched.

A model may have no entities at all, when all you want is a gateway in front of your MCP servers. It still names a database: that is where the gateway keeps its caches.

The model's tools

For every entity:

ToolDoes
search_<entity>words in its text fields (query), exact values of its tag fields, <field>_min and <field>_max for its numeric fields; up to 50 records
get_<entity>one record by id
<entity>_<relationship>the related records of one record, e.g. customer_orders

Records come back with the id and the model's fields only. Any other field in the key (an internal note, a password hash) is never returned.

Upstreams: your own MCP servers

Add up to 10 remote MCP servers to a model, in the console (the model's Upstreams tab) or with the API. Each has:

Namelowercase letters, digits and -, starting with a letter. It prefixes the server's tools: an upstream named crm brings crm__find_contact.
URLthe server's Streamable HTTP endpoint, https:// only
Headersstatic headers sent with every call, typically Authorization: Bearer .... Stored encrypted, never returned: the console and the API show only their names.
Cacheoff, or up to 300 seconds (see below)

Only servers that speak the Streamable HTTP transport over https can be added. Servers that run as a local process on your machine (stdio) cannot be reached from the gateway.

What an agent sees. A key lists an upstream's tools only when the upstream is on the key's list. Keys reach no upstream unless you add one, so keys made before upstreams existed behave exactly as they did. An agent calls crm__find_contact like any other tool; the gateway calls find_contact on the crm server with the stored headers and passes the answer back.

Credentials stay on the gateway. The agent never sees an upstream's headers. If a server repeats a stored header value back in an answer or an error, the gateway replaces it with [redacted by the gateway].

Addresses. The URL must reach the public internet. The gateway refuses private, loopback, link-local and cloud metadata addresses, IPv4 and IPv6, when the upstream is saved and again every time it connects, so a name that later resolves somewhere private is still refused. Redirects are never followed: give the final URL. Put credentials in headers, not in the URL.

Limits.

Upstreams per model10
Time per call15 seconds, including connecting; then the agent gets an error naming the upstream
Answer size1 MB. A longer answer is cut, and the text ends with a note saying so.
Tools per upstream100; tool names must be at most 94 characters of letters, digits, _, - and .
Tool listskept for 5 minutes in your database, or refreshed from the console
Calls600 a minute per agent key, the model's tools and upstreams together

An upstream that cannot be reached does not hide the others: its tools are left out of the list, GET /context/tools names it under problems, and the console shows the last error on the upstream.

Result cache. Off by default. With a number of seconds set (at most 300), a call with the same tool and the same arguments is answered from your database for that long, under kividb:ctxgw:<model id>:cache:, with that TTL. The answer then carries _meta: {"kividb/cache": "hit"}. Turn it on only for servers whose tools only read: a cached call does not reach the server, so a tool that sends an email or creates a ticket would not do it again. Errors and cut-off answers are never cached.

Tool search

A gateway with several upstreams can bring hundreds of tools, too many for an agent to carry in its context. With tool search on, the agent sees two tools instead:

ToolDoes
search_tools{"query": "look up a customer's open invoices", "limit": 5}: the tools that best match, with their names, descriptions and input schemas (up to 20)
call_tool{"name": "billing__list_invoices", "arguments": {...}}: runs one of them

Tools are ranked by meaning, with the all-MiniLM-L6-v2 embedding model that runs inside KiviDB Cloud; when it is busy they are ranked by keywords and the answer says "ranked_by": "keywords". Every tool the key may use is searched: the model's own and its upstreams'. Turn tool search on for the whole model, or for one key; a key's own setting wins.

Agent keys

Make keys in the console (the model's Agent keys tab) or with the API. The key, which starts kvctx_, is shown once and stored only as a hash. Its upstreams and tool search can be changed later; its entities and filters are fixed for its life. Revoke a key at any time.

Connect an MCP client with:

{
  "mcpServers": {
    "kividb-support": {
      "type": "http",
      "url": "https://api.kividb.io/context/mcp",
      "headers": { "Authorization": "Bearer kvctx_..." }
    }
  }
}

An agent without MCP lists its tools with GET /context/tools and calls one with POST /context/tools/{tool} and a body of {"arguments": {...}}, with the same Authorization header.

The fixed filters apply to the model's own tools. An upstream sees what its own credentials allow; the key decides only whether the agent may reach it at all.

In the console

The Context gateway section lists every model in your organization; each database also has a Context gateway tab. A model shows its entities with how many records each index holds; its upstreams, with a test that lists a server's tools before you save it; its tools, with a place to run any of them, the upstreams' included, either seeing everything or exactly as one of its keys would; and its keys, with the upstreams each may reach. Context models are not available on sharded databases yet.

API

Request
GET /context-modelsevery model in the organization
GET /instances/{id}/context-modelsthe models over one database
PUT /context-models/{name}create or change a model: {"instance_id", "description", "entities": [...]}; its indexes are rebuilt
GET /context-models/{name}the model, its own tools, record counts, keys and upstreams
DELETE /context-models/{name}delete it, its indexes, keys and upstreams
POST /context-models/{name}/tryrun a tool: {"tool", "arguments", "key_id"?}
GET /context-models/{name}/gateway-tools?key_id=every tool, upstreams' included, as the console or one key sees them
PUT /context-models/{name}/tool-search{"tool_search": true} for the whole model
POST /context-models/{name}/keysmake a key: {"name", "entities"?, "filters"?: {"customer": {"tenant": "acme"}}, "upstreams"?: ["crm"], "tool_search"?: true}
PATCH /context-models/{name}/keys/{keyId}change a key's upstreams and tool_search (null: the model's setting)
DELETE /context-models/{name}/keys/{keyId}revoke a key
GET /context-models/{name}/upstreamsthe upstreams, header names only
PUT /context-models/{name}/upstreams/{upstream}add or change one: {"url", "description"?, "headers"?, "cache_seconds"?}. Headers left out keep the stored ones; {} clears them. The tools are listed straight away.
POST /context-models/{name}/upstreams/{upstream}/refreshlist its tools now
POST /context-models/{name}/upstream-testtry a URL before saving it: {"url", "headers"?}
DELETE /context-models/{name}/upstreams/{upstream}remove it, and from every key
curl -X PUT https://api.kividb.io/context-models/support \
  -H "Authorization: Bearer $KIVIDB_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "instance_id": "'"$DB"'",
    "description": "Customers and their orders, for the support assistant.",
    "entities": [
      { "name": "customer", "description": "A customer account.", "prefix": "customer:", "storage": "hash", "id_field": "id",
        "fields": [ { "name": "name", "type": "text" }, { "name": "tenant", "type": "tag" }, { "name": "plan", "type": "tag" } ],
        "relationships": [ { "name": "orders", "entity": "order", "field": "customer_id" } ] },
      { "name": "order", "description": "An order a customer placed.", "prefix": "order:", "storage": "json", "id_field": "id",
        "fields": [ { "name": "customer_id", "type": "tag" }, { "name": "status", "type": "tag" }, { "name": "total", "type": "numeric" } ] }
    ]
  }'

curl -X PUT https://api.kividb.io/context-models/support/upstreams/crm \
  -H "Authorization: Bearer $KIVIDB_API_KEY" -H "Content-Type: application/json" \
  -d '{ "url": "https://mcp.example.com/mcp", "description": "Our CRM",
        "headers": { "Authorization": "Bearer '"$CRM_TOKEN"'" } }'

curl -X POST https://api.kividb.io/context-models/support/keys \
  -H "Authorization: Bearer $KIVIDB_API_KEY" -H "Content-Type: application/json" \
  -d '{ "name": "support assistant, tenant acme", "filters": { "customer": { "tenant": "acme" } }, "upstreams": ["crm"] }'