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_customerandcustomer_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 description | customer: "A customer account with its plan and monthly revenue." The agent reads the description to choose a tool. |
| Key prefix | every record is a key under it, such as customer:42 |
| Stored as | hashes, or JSON documents |
| Id field | the field holding the record's id; it is indexed so records can be fetched by it |
| Fields | each one text (searched by words), tag (matched exactly: ids, statuses, tenants) or numeric (compared and ranged) |
| Related records | e.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:
| Tool | Does |
|---|---|
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:
| Name | lowercase letters, digits and -, starting with a letter. It prefixes the server's tools: an upstream named crm brings crm__find_contact. |
| URL | the server's Streamable HTTP endpoint, https:// only |
| Headers | static headers sent with every call, typically Authorization: Bearer .... Stored encrypted, never returned: the console and the API show only their names. |
| Cache | off, 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 model | 10 |
| Time per call | 15 seconds, including connecting; then the agent gets an error naming the upstream |
| Answer size | 1 MB. A longer answer is cut, and the text ends with a note saying so. |
| Tools per upstream | 100; tool names must be at most 94 characters of letters, digits, _, - and . |
| Tool lists | kept for 5 minutes in your database, or refreshed from the console |
| Calls | 600 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:
| Tool | Does |
|---|---|
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-models | every model in the organization |
GET /instances/{id}/context-models | the 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}/try | run 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}/keys | make 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}/upstreams | the 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}/refresh | list its tools now |
POST /context-models/{name}/upstream-test | try 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"] }'