Skip to main content

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).
  • 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 to kvery_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, made read-only or set to hide the SQL, 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:

TransportEndpointUse
MCP (Streamable HTTP)POST /mcpJSON-RPC 2.0 calls from MCP clients, answered with plain JSON
RESTGET /mcp/queries, /mcp/groups, /mcp/search, GET /mcp/queries/{id}, POST /mcp/queries/{id}/executedirect 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 HTTP+SSE transport of MCP protocol version 2024-11-05, 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 number of queries a token reaches is the last column).
  • The list shows how many queries each token reaches (your access when the list is loaded, narrowed by the token's scope tags). Click the number to see these queries with their description and group, execute a query in a new browser tab, or edit it if you can. A query of another 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.
warning

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 use kvery_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 data change — including one made by a function or procedure called from a SELECT — and nothing is ever committed. A schema change, which MySQL / MariaDB commit implicitly, is stopped by the SQL text check: it counts as a write, so a read-only run never sends it.
  • On SQL Server (no read-only transaction mode) a normal transaction is used and the rollback discards anything the query changed. A COMMIT or ROLLBACK in the query's own SQL ends this transaction early, so whatever runs after it is only covered by the SQL text check.
  • 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_query for those.
  • A value inserted into a statement while the query runs (a query variable, :name = …) can't add another statement to it: a read-only run refuses such a statement.

So on MySQL / MariaDB and PostgreSQL a read-only token can't change the data of your database's tables 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 RETURN is still returned, and for database errors 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 / DESC, VALUES or TABLE statement (or starts with an opening parenthesis) 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. So does a construct whose effect a rollback can't undo: sequence steps (NEXT VALUE FOR, nextval(), setval()), statements sent over another connection or to another server (PostgreSQL dblink…(), SQL Server OPENQUERY(), OPENROWSET(), OPENDATASOURCE()), server-side files (lo_export(), lo_import()) and ending other sessions (pg_terminate_backend(), pg_cancel_backend()). The check uses the SQL that actually runs (the published version, snippets, form-field SQL and the parameter values).

note

Database functions called from a SELECT are not inspected by the SQL text check, but a read-only run still can't change your tables: 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). A function or extension of your database that changes something outside the transaction (for example on another server or in a file) can't be recognised from the SQL, so for complete protection connect through a database user that only has read permissions.

Running queries​

  • Parameters are passed by name — kvery_get_query lists them with their field type and label (the field's display name). A display name that is SQL or contains {% %} / [% %] blocks or a $variable is never shown: the label is then the parameter name. 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 (also the one an agent connection relays), as in the editor (not with a token that hides the SQL). 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.
  • The MCP server and its REST API always run the published version of a query: an unpublished draft doesn't change what they return until you publish it. Query details and search show the SQL of that version as well. Drafts are kept only while a query is shared, so for a query that is not shared this is its latest saved version.
  • Each request runs at most one query: when an MCP client sends several execute calls in one batch, only the first one that passes its checks runs, and the others get an error asking to send them in separate requests. A call refused by its checks (for example for a missing parameter) does not count; a call refused by the group's IP whitelist, or because the query's group no longer exists, does.
  • Queries run with the settings of their own group (tags, snippets, package limits). The group's IP whitelist always applies to running queries through the API — also for the group you last opened in the web app, because a token can be used from any address; 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:

StatusMeaning
400Invalid request (for example a missing required parameter), or the query failed
401Missing, invalid or expired token (or wrong Basic Auth credentials)
403No 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
404The query is hidden from you in its group (also for a short time after its group has been deleted)
429Rate limit reached — retry after the number of seconds in the Retry-After header
503Temporary 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. Failures are counted for an IP address and account together, for an IP address (blocking every account from it) and for an account (blocking it from every address), each with its own limit. While blocked, the API answers 429 with a Retry-After header 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 Authorization header. app.kvery.io accepts requests from every origin. The server configuration can restrict browser origins; then browser requests are accepted 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 (the values of password fields are stored as ***), 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. Each call shows the description of its query (hover over it to see the query ID); a query that has been deleted or that you cannot access is shown by its ID. The list is paged and searchable — the search covers the action, token (or Basic Auth), query description and 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; the Query column holds the query ID, and the query description is the last column). 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.