MCP Server
Kvery ships a built-in MCP (Model Context Protocol) server, so AI assistants that speak MCP can discover and run your queries as tools — letting an assistant answer questions straight from your databases through Kvery.
What MCP exposes
Through the MCP server an assistant can:
- List queries available to it (
kvery_list_queries). A query that belongs to several of your groups is listed once per group. - List groups (
kvery_list_groups). - Search queries by description, internal note or SQL (
kvery_search; SQL only for queries you can edit, and not with a token that hides the SQL). - Get a query's details and parameters (
kvery_get_query). - Execute a query that reads data (with parameters) and read the result
(
kvery_execute_query). A query that modifies data is refused with a message that points tokvery_execute_write_query. - Execute any query, including one that modifies data
(
kvery_execute_write_query) — not offered to read-only tokens.
Every tool except kvery_execute_write_query is marked as read-only for MCP
clients, so a client can let reads run without asking and ask for confirmation
before a query that modifies data. Lists, search results and query details tell
whether a query modifies data (isWrite). The SQL, the connection name, the
average runtime and the time of the last run are only shown for queries you can
edit, as in the web app — and a token can hide the SQL even then (see
Hiding the SQL).
Access is scoped by the token you issue (see below) — an assistant only ever sees what its token is allowed to see. Basic Auth with your e-mail address and password is also accepted, but prefer tokens: Basic Auth has the full access of your account, cannot be limited with scope tags or made read-only, sends your password with every request and does not ask for the two-factor code.
Connecting
The server offers the MCP protocol and a REST API on the same address:
| Transport | Endpoint | Use |
|---|---|---|
| MCP (Streamable HTTP) | POST /mcp | JSON-RPC 2.0 calls from MCP clients, answered with plain JSON |
| REST | GET /mcp/queries, /mcp/groups, /mcp/search, GET /mcp/queries/{id}, POST /mcp/queries/{id}/execute | direct integration |
GET /mcp only announces the POST /mcp endpoint (in an endpoint event) and
then closes the stream. MCP clients that can only use the legacy HTTP+SSE
transport, which expects the answers on that event stream, are not supported.
Point your MCP-capable client at the /mcp endpoint and authenticate with a Kvery
MCP token. The token dialog in Kvery gives you a ready-to-paste client
configuration:
{
"mcpServers": {
"kvery": {
"url": "https://app.kvery.io/mcp",
"headers": {
"Authorization": "Bearer kvery_mcp_..."
}
}
}
}
The same token works on the REST endpoints, for scripts, CI pipelines or any HTTP client — see Using the REST API.
MCP tokens
You manage access with MCP tokens, which you create and revoke from your profile menu → MCP / API Keys:
- Every token is prefixed with
kvery_mcp_, so it's easy to recognise. It is shown only once, when you create it. - Tokens expire 90 days after they are created. Expired tokens stay in the list, marked Expired, and are deleted automatically 30 days later.
- Each token can carry optional scope tags (it then only reaches queries with
at least one of those tags), so you can hand different assistants different,
least-privilege access. Only tags you may use can be chosen, and tags starting
with
_cannot be used. - A token can be read-only and can hide the SQL of your queries (see below).
- Revoke a token at any time to immediately cut off access.
- A token name can be up to 100 characters long, a token can have up to 100 scope tags, and you can create up to 5 tokens per hour.
- The token list is paged and searchable, and can be exported to CSV or Excel (with times in your local time).
- The list shows how many queries each token reaches right now (your current access, narrowed by the token's scope tags). Click the number to see these queries with their description and groups, execute a query in a new browser tab, or edit it if you can. A query that isn't only in your currently selected group opens for editing in the same window, because the editor switches to the query's group. A token that reaches no query shows a highlighted 0; an expired token reaches none and shows a dash.
- Changing or resetting your password revokes all of your tokens — create new ones afterwards. Your profile warns you before the change and tells you if the tokens could not be revoked.
An MCP token grants an assistant the ability to run your queries. Store it like a password, scope it tightly, and revoke tokens you don't use.
Read-only tokens
Tick Read-only when you create a token, and the token can only run queries that read data:
- MCP clients using it don't get
kvery_execute_write_query, so they can only usekvery_execute_query, which runs queries that read data. - On the REST API, executing a query that modifies data answers
403. - Listing, searching and query details work as usual.
Read-only is enforced at the database, not only by checking the SQL text. Every
read-only run (kvery_execute_query with any token, and any run with a read-only
token) executes inside a transaction that is always rolled back:
- On MySQL / MariaDB and PostgreSQL the transaction is
READ ONLY, so the database itself rejects any change — including one made by a function or procedure called from aSELECT— and nothing is ever committed. - On SQL Server (no read-only transaction mode) a normal transaction is used and the rollback discards anything the query changed.
- Agent connections can't be enforced this way, so a read-only run of an
agent-connection query is refused; use a full-access (non read-only) token with
kvery_execute_write_queryfor those.
So a read-only token can't change data even if the SQL text check misjudges a query.
Hiding the SQL
The SQL of a query is only shown to people who can edit it. Tick Hide SQL when you create a token, and the token doesn't reveal the SQL even when you are an editor — as far as the SQL is concerned, it behaves as if you couldn't edit the queries:
- Query details (
kvery_get_query) contain no SQL, and search (kvery_search) doesn't look into the SQL. - When a query is refused because it modifies data, only the kind of cause is named, never the keyword that triggered it.
- When a query fails, the generic error message is returned instead of the
database's error text, also on agent connections (a message the query defines with
ON ERROR RETURNis still returned, and the full error stays in the query log).
Everything else works as usual: the token reaches and runs the same queries, and still sees whether a query modifies data, its parameters, connection name and run statistics.
Read and write queries
A query counts as reading only if every statement in it is a SELECT, WITH,
SHOW, EXPLAIN, DESCRIBE, VALUES or TABLE statement without any data or
schema changing command; anything Kvery can't classify safely counts as a write.
A query with a webhook directive (such as
ON SUCCESS POST '…' TO '…') or an inline HTTP fetch directive (GET('…'),
POST('…'), PUT('…'), PATCH('…'), RPOST('…'), RPUT('…'), RPATCH('…'),
RDELETE('…')) also counts as a write, because it makes an outbound HTTP request
when the query runs. The check uses the SQL that actually runs (including an
unpublished draft, snippets, form-field SQL and the
parameter values).
Database functions called from a SELECT are not inspected by the SQL text check,
but a read-only run still can't change data: it runs in an always-rolled-back
transaction, so on MySQL / MariaDB and PostgreSQL the database rejects the change
and on SQL Server it is discarded. This does not apply to agent connections
(a read-only run of one is refused). For defence in depth you can still connect
through a database user that only has read permissions.
Running queries
- Parameters are passed by name —
kvery_get_querylists them. A value can be a string, number, boolean or a list (for multi-select fields). - Results come back as the query ID plus column names and rows:
{"queryId": "…", "result": {"columns": ["id", "name"], "data": [["1", "Acme Ltd"], ...]}}. - If the query fails (for example, an SQL error), the tool reports an error instead
of an empty result. If you can edit the query, the message is the database's
error text, as in the editor. Otherwise it is a generic message, unless the query
sets its own message with
ON ERROR RETURN, which everyone gets. - Runs are recorded in the query's logs with their real result, so log levels that record only errors or only successes, and webhooks, work as for runs in the app.
- A query with an unpublished draft runs in its draft version, as in the editor and on dashboards. Shared links and the public query API use the published version.
- Each request runs at most one query: when an MCP client sends several execute calls in one batch, only the first one runs, and the others get an error asking to send them in separate requests.
- Queries run with the settings of their own group (tags, snippets, package limits). The group's IP whitelist applies to running queries as in the web app (to the queries of groups other than the one you last opened); listing, searching and query details are not restricted by it.
Using the REST API
Send the token in the Authorization header of every request (the word Bearer
is not case-sensitive):
Authorization: Bearer kvery_mcp_...
Every response is JSON in the same envelope:
{ "responseCode": "OK", "response": { ... } }
{ "responseCode": "ERROR", "response": "error message" }
To run a query, send its parameters in a parameters object to
POST /mcp/queries/{id}/execute:
curl -X POST 'https://app.kvery.io/mcp/queries/1727775300aB3dE5fG7hJ9kL1mN3pQ5r/execute' \
-H 'Authorization: Bearer kvery_mcp_...' \
-H 'Content-Type: application/json' \
-d '{"parameters":{"status":"active"}}'
{
"responseCode": "OK",
"response": {
"queryId": "1727775300aB3dE5fG7hJ9kL1mN3pQ5r",
"result": {
"columns": ["id", "name", "status"],
"data": [
["1", "Acme Ltd", "active"],
["2", "Globex Inc", "active"]
]
}
}
}
The main status codes:
| Status | Meaning |
|---|---|
400 | Invalid request (for example a missing required parameter), or the query failed |
401 | Missing, invalid or expired token (or wrong Basic Auth credentials) |
403 | No access to the query, a read-only token tried to run a query that modifies data, a read-only run was attempted on an agent-connection query, or the group's IP whitelist refused the call |
404 | The query is hidden from you in its group |
429 | Rate limit reached — retry after the number of seconds in the Retry-After header |
503 | Temporary server problem (for example the rate limiter is unavailable) — try again later |
Limits and security
- Rate limit: 60 requests per minute per user; every REST request and every tool call counts, also inside a batch. Users behind the same IP address (e.g. an office network) can send up to 600 requests per minute together. A batch can contain up to 20 messages. The group and query rate limit applies to the public query API only, not to the MCP server.
- Basic Auth: repeated failed attempts temporarily block Basic Auth for that IP
address and account: the API answers
429with aRetry-Afterheader until 15 minutes have passed since the last failed attempt (tokens are not affected). - Sessions: every request is authenticated on its own. The API never creates a web session and does not accept the session cookie of a browser signed in to Kvery.
- Browser requests: browser apps can send a token in the
Authorizationheader. app.kvery.io accepts requests from every origin; a self-hosted server can restrict browser origins, and then accepts browser requests only from Kvery itself and the allowed origins (servers, scripts and desktop MCP clients are not affected). - Activity log: every tool call and every REST call made with valid credentials is logged, including calls refused by the rate limit, with the token, query, parameters, result size and IP address. Entries are kept for 90 days. Connection set-up messages (such as listing the tools), token management, failed sign-ins and requests refused before sign-in are not logged.
- Your activity: MCP / API Keys → Activity lists your own calls, newest
first, with times in your local time; calls made with a token that has been
revoked or deleted show Deleted token. The list is paged and searchable —
the search covers the action, token (or Basic Auth), query ID, parameters, error
message, IP address and the time in UTC (
YYYY-MM-DD HH:MM:SS) — and can be exported to CSV or Excel (up to 10,000 entries, newest first). Other users' activity is never shown. - Live activity: while the Activity tab is open, it refreshes itself every 10 seconds and highlights the new calls for a moment. Switch Auto-refresh off to stop it (your browser remembers the choice), or use the refresh button. The automatic refresh pauses while you look at an older page.
Tips
- Publish and name queries clearly so an assistant can find the right one via search.
- Use tags to group the queries a token should expose.
- Use read-only tokens for assistants that only need to read data.
- Tick Hide SQL for assistants that should run your queries without seeing how they are written.