privacy-first-ai
Connect an AI agent (MCP)
Give an AI assistant access to your infrastructure without giving it your credentials. Kutrion exposes the vault and the SSH broker as MCP tools, so Claude Code, Claude Desktop, Cursor and Copilot act through the broker instead of holding secrets.
Updated Oct 3, 2026
Most MCP servers ask you to paste an API key into a JSON config file. That key is long-lived, scoped to everything the key can do, sits in plaintext on disk, and leaves no record of what was done with it. Kutrion takes the opposite approach: the agent gets access, not credentials.
When an AI assistant connects to Kutrion over the Model Context Protocol, it authenticates as an agent principal — a non-human identity that belongs to you, expires on its own, and can be revoked without touching your own sessions. When it uses a credential, the secret is decrypted on the server, used, and discarded. It never crosses the wire to the model.
Connect a client
One command, from a machine where you are signed in to the CLI:
kt mcp install --client claude-code
Supported values for --client:
| Client | Config written | Scope |
|---|---|---|
claude-code | .mcp.json in the current directory | Project |
claude-desktop | claude_desktop_config.json in your Claude application-support directory | User |
cursor | ~/.cursor/mcp.json | User |
copilot | .vscode/mcp.json in the current directory | Workspace |
Use --dir to write a project-scoped config somewhere other than the current directory.
The command mints an agent principal, stores its credential in your operating system’s keychain, writes the client config, and verifies the connection before it reports success. Then restart the client — Claude Desktop needs a full quit and reopen; Claude Code will ask you to approve the project-scoped server the first time you run it.
The config file contains no secret
This is the part that matters. The entry we write looks like this:
{
"mcpServers": {
"kutrion": {
"command": "kt",
"args": ["mcp"],
"env": { "KT_API_BASE": "https://your-kutrion-host", "KT_MCP_CLIENT": "claude-code" }
}
}
}
There is no token in it. kt mcp reads a short-lived credential from your OS keychain at launch, so
the file is safe to commit to a repository, safe to screenshot, and safe to share with a teammate who
will run kt mcp install themselves. Revoking access never requires editing it.
What the agent can do
The agent sees only the tools its scopes allow, and can only reach what you can reach — an agent is a delegation of your access, never an escalation of it. By default it is granted:
| Scope | Tools | What it can do |
|---|---|---|
vault.list | vault_list, vault_search | See which vaults and credentials exist. Metadata only — never a value. |
vault.use | vault_use | Authenticate to an existing connection with the credential bound to it and report whether it worked. The tool takes a connection id, not a host, and returns no secret material. |
ssh.exec | ssh_run | Run a command on a brokered host and get its output and exit code. |
files.read | files_list, files_get | Browse and read files over SFTP, FTP/FTPS, WebDAV or object storage. |
audit.read | audit_query | Read its own audit trail — not yours, and not another agent’s. |
Two discovery tools sit alongside these and are granted by any scope that needs them: ssh_list
returns the SSH connections the agent can reach, and files_connections returns the FTP/FTPS/WebDAV
and object-storage targets. Both are metadata only. They are how the model learns the connection ids
that ssh_run, vault_use and the file tools take. They list what the agent can actually use: a
connection whose credential sits in a vault the agent cannot read is left out rather than advertised
and then refused, and an SSH host with no credential appears only while it is enrolled for
certificate access. Cloud storage connections (Dropbox, OneDrive, Google Drive, Box) have no
discovery tool; their ids come from you, through GET /agents/scope-options described below.
Two more scopes are available but off by default, and minting a principal with either requires a fresh step-up so that granting them is a deliberate act by a present human:
vault.reveal— the only tool that returns plaintext secret material.vault_revealrefuses a credential marked injection-only, for every agent, regardless of scope. Note that the flag constrains the reveal path rather than containing the secret: the broker may use the secret without revealing it, to a destination recorded on the credential — see SSH broker overview for the full statement.files.write— modifying remote files.
Choose scopes explicitly with --scopes:
kt mcp install --client cursor --scopes vault.list,ssh.exec --days 7
Narrowing further
An agent principal can carry a resource scope, two lists of ids that restrict it to specific vaults and connections, so a coding assistant wired to one project cannot reach the rest of your infrastructure. The scope sits on top of your own access: it can only narrow what you can reach, never widen it, and every use is still checked against you.
How the two lists read:
- An empty or absent list means no restriction of that kind.
{}is “everything I can reach”. vault_idslimits which vaults the agent may use credentials from. A connection whose bound credential sits in another vault is refused.connection_idslimits which connections the agent may name, across SSH, file and cloud connections alike. Once it lists one id it is an allow-list for all three kinds, so naming one cloud connection means naming every other connection the agent should keep.- A vault restriction covers only what holds a vault credential. A connection with no
credential behind it — an enrolled SSH host that authenticates by certificate, or a cloud storage
connection, which holds a provider grant — has no vault to check. While
vault_idsis set, such a connection is reachable only ifconnection_idsnames it.
To see which ids you can use, GET /agents/scope-options lists your readable vaults and your SSH,
file and cloud connections with their ids. It is the only place cloud connection ids are listed.
Set the scope at mint (resource_scope on POST /agents), or change it later:
PUT /agents/{id}/resource-scope
{"vault_ids": ["<vault id>"], "connection_ids": ["<connection id>", "<cloud connection id>"]}
The body replaces the whole scope. Only the agent’s owner can change it; an org admin can still
revoke any agent, but cannot change what another member’s agent reaches. Every id the edit adds must
be one you can reach, or the request is refused with the ids it could not accept. Narrowing takes
effect at once and needs nothing more. Widening — removing a restriction, or naming a connection
under a vault restriction — needs the plan’s agent access and, for an agent holding vault.reveal
or files.write, a fresh step-up, as minting it did. Each change is audited with the scope before
and after, and any SSH session the agent holds open is closed so its next call runs under the new
scope.
Where the agent’s model runs
When you connect an agent you declare whether its model is hosted (Claude, Copilot, Cursor’s cloud models — your context leaves your network) or self-hosted on your network.
We ask rather than detect, deliberately. The only signal a connection carries is the client’s own self-reported name, which anything can set to anything; enforcing a security policy on that would look like protection without being it. A declaration is made by a person, recorded, and auditable.
By default any class is allowed — you chose the client, and it is already receiving your context. An organization that needs the restriction can turn on require agents to run on our own network, after which only self-hosted agents can be connected or used. Because an undeclared agent is treated as hosted, turning the policy on refuses anything that has not been explicitly vouched for rather than silently assuming it is safe. Agents already issued are suspended on their next call, not at the end of their lifetime.
kt mcp install --client claude-code --egress lan
What is protected
- Secrets are stripped from output. Command output and file contents pass through the same redaction step as every other AI feature before they reach the model. Read what that step is worth from AI privacy model: it removes shaped secrets and PII, and it is not a transcript filter — the command text, hostnames and file contents themselves still reach whichever model you pointed the agent at.
- Fetched content cannot pose as instructions. A file or a command’s output can contain text aimed at the model reading it — “ignore your previous instructions”, a forged role marker. Tool results are returned as fenced, marked-up-as-data spans with those markers stripped, so something the agent read cannot impersonate something you said.
- Your file policy still applies. A brokered read by an agent is evaluated against the same data-loss policy as any other file access, and refused if the policy refuses it.
- Arguments are screened. A model that has read a web page, a log line or a ticket can be steered into calling a tool with hostile arguments. Tool arguments cross the prompt-injection guard before any brokered action runs, and attempts to exfiltrate secrets are refused and recorded.
- Arguments are validated, and tools say what they do. Every tool publishes a JSON Schema for
its arguments and for its results, and the server validates arguments against it before anything
runs — a malformed call comes back as something the model can correct rather than as a half-done
action. Each tool also carries the protocol’s behavior hints, so a client can tell you that
ssh_runchanges things on a host before it runs, and can decline to treatvault_revealas safe to approve on your behalf. - Tool calls are rate limited. Every agent principal has a floor on how many tool calls it can
make per minute, so a model stuck in a retry loop cannot hammer a brokered host. Exceeding it
answers
429with aRetry-After;kt mcp statusandGET /mcp/whoamireport the limit in force. - Everything is audited. Every tool call is attributed to the agent principal, its owner, and the client it was minted for. You can answer “what did the AI do in our environment” with evidence.
- On-LAN by default. If your organization runs a self-hosted model, terminal output never leaves your network — see AI privacy model.
Approve a reveal as it happens
An organization can require a human to approve each secret reveal at the moment the agent asks for
it, rather than deciding once when the agent is minted. With MCP_JIT_APPROVAL turned on, a
vault_reveal call comes back as a question rather than a secret: your MCP client shows an approval
prompt naming the credential, and the reveal completes only after you accept.
Each approval covers exactly one call. It is bound to that agent, that credential and that request, expires in minutes, and is redeemed server-side so a captured approval cannot be replayed into a second reveal. A client that cannot show an approval prompt is refused rather than allowed through, so the control cannot be sidestepped by connecting a simpler client.
This is off by default, and today it applies to vault_reveal only.
Check and revoke
kt mcp status
Shows, for each client on this machine, whether it is wired up and the state of its own agent: live, expired or revoked, when it expires and which scopes it holds.
A bridge started with KT_MCP_CLIENT uses that client’s own agent, or the single shared agent that
installs from before per-client agents stored. A bridge started without it uses a stored agent only
when there is exactly one, counting that shared agent: with none it has nothing to use, and with more
than one it refuses and asks you to set KT_MCP_CLIENT or re-run kt mcp install --client <id>,
rather than acting as whichever client it found first.
kt mcp uninstall --client claude-code
Revokes the agent principal, removes our entry from the client config — leaving any other MCP servers you have configured untouched — and clears the stored credential. Revocation takes effect on the agent’s next call, not when its TTL expires.
Each client holds its own credential, so uninstalling one leaves the others working. Omit
--client to remove every client at once. Re-running install for a client you already set up
replaces it: the new principal is minted and the old one revoked, rather than both staying live.
You can also revoke from the web console at any time, including agents belonging to other members of your organization if you are an org admin. An expired or revoked principal stops authenticating immediately; so does one whose owner is deactivated or moved out of the organization.
Plan limits
Agent access is available on every plan, with a limit on how many agents can be live at once. The count is of live principals, so revoking one or letting it expire frees the slot immediately. See Billing and plans.
Protocol notes
Kutrion implements MCP revision 2026-07-28 and also answers the older handshake-based revisions,
so clients that have not yet updated keep working. The server is reachable two ways:
- stdio —
kt mcp, which is whatkt mcp installconfigures. The CLI bridges your local client to the server; the broker itself always runs server-side. - Streamable HTTP —
POST /mcpon your Kutrion host, for a client that can hold a bearer credential directly.
Both authenticate with an agent principal. A normal user session token is refused on both.
Authorization discovery
POST /mcp publishes OAuth 2.0 Protected Resource Metadata (RFC 9728) at
/.well-known/oauth-protected-resource and /.well-known/oauth-protected-resource/mcp, so a
standards-compliant MCP client can discover what this resource is and which scopes an operation
needs instead of being wired to a credential by hand. Unauthenticated calls answer 401 with a
WWW-Authenticate challenge carrying resource_metadata and a baseline scope; a call the agent’s
scopes do not cover answers 403 with error="insufficient_scope" and every scope that would
satisfy it, in one challenge.
Kutrion names itself as the authorization server in that document — it mints the credential and owns the consent surface.
Authorizing a client over OAuth 2.1
The endpoints behind that name are live. Authorization-server metadata (RFC 8414) is published at
/.well-known/oauth-authorization-server and /.well-known/oauth-authorization-server/mcp, naming:
authorization_endpoint—/mcp/oauth/authorizetoken_endpoint—/mcp/oauth/token
The flow is authorization code with PKCE, which OAuth 2.1 makes mandatory. S256 is the only
challenge method; plain is not accepted. Your client must also send the RFC 8707 resource
parameter naming the MCP endpoint it wants a token for — a token issued here is audience-bound to
that resource and will not be accepted anywhere else. Authorization responses carry iss (RFC 9207)
so your client can detect a response injected by a different server.
Registration is by Client ID Metadata Document, and that is the preferred path. Your
client_id is an https URL that serves a JSON document declaring that same client_id and your
redirect_uris; Kutrion fetches it and that is the registration. There is no client secret.
Redirect URIs are matched as exact strings — a trailing slash or an added query parameter is a
different URI — with one exception: a native client may register a loopback redirect (127.0.0.1,
[::1] or the name localhost) and the port it actually listens on is allowed to float, since an
ephemeral callback port cannot be known when the document is written.
Dynamic Client Registration is offered too, but it is not open. POST /mcp/oauth/register
requires an existing authenticated Kutrion session, which is what
registration_endpoint_auth_methods_supported: ["bearer"] in the authorization-server document
tells you. Present your Kutrion access token on the x-kutrion-registration-token header:
POST /mcp/oauth/register
x-kutrion-registration-token: <your Kutrion access token>
Content-Type: application/json
{"client_name": "my agent", "redirect_uris": ["http://127.0.0.1:6191/oauth/callback"]}
Authorization: Bearer is accepted for the same purpose and is what RFC 7591 §3.1 names, but the
dedicated header exists because several clients merge their configured Authorization value into
every request they make — including the calls to /mcp itself, where an agent principal is required
and a user token is refused. x-kutrion-registration-token is inert everywhere else, so it cannot
have that effect.
Posting there without one returns 401 with error: "invalid_token", a description naming the
header, and a WWW-Authenticate challenge. A client built on the MCP SDK’s defaults registers
anonymously and will stop there — the SDK takes the metadata-document route only when the
application sets clientMetadataUrl, and few do — so the Client ID Metadata Document path above,
which needs no registration call at all, is the one to reach for. What registration mints is scoped to the principal that registered
it and ends when that session’s token is signed out. Anonymous RFC 7591 registration is deliberately
not offered — it would leave an attacker-writable table behind an unauthenticated endpoint on a
credential broker.
Any request to /mcp without a credential answers 401 with a WWW-Authenticate challenge
naming the protected-resource document and the baseline scopes, whatever method you used. GET and
DELETE answer 405 once you are authenticated — there is no standalone SSE stream and no
protocol-level session — but an unauthenticated probe is told how to authorize instead.
What a client receives is an agent principal — the same vta_ credential kt mcp install
provisions, with the same TTL, the same revocation and the same per-call enforcement. OAuth is a
second way for a human to consent to one, not a second kind of identity.
The elevated scopes are not available over OAuth. vault.reveal and files.write are absent
from scopes_supported and are refused at the authorization endpoint. Granting an agent the ability
to read plaintext or write files requires a fresh step-up by the owner on the agent page, which a
browser redirect cannot carry. An org that requires its agents to run on its own network also cannot
authorize an external OAuth client, since we cannot attest where that client’s model runs.
Approving a client needs a signed-in owner. /mcp/oauth/authorize validates the request and
redirects to the approval screen in the web app, which asks a human to confirm the client, the
scopes and the redirect URI before any credential is minted. That screen only mints a code for
someone already signed in to Kutrion, so a flow driven from a machine with no browser and no session
stops there by design rather than by omission.