MCP servers¶
The Model Context Protocol (MCP) lets you connect external tool servers to kin. Their tools appear in the registry alongside the built-ins, gated by the same permission system.
Configuring servers¶
kin reads MCP servers from a .mcp.json file. Two locations are checked, and a
project entry wins on a name clash:
<workdir>/.mcp.json~/.kin/mcp.json
The shape follows the cross-tool mcpServers convention:
{
"mcpServers": {
"files": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"]
},
"api": {
"type": "http",
"url": "https://example.com/mcp",
"headers": { "Authorization": "Bearer ${MY_TOKEN}" }
}
}
}
| Field | Notes |
|---|---|
type |
stdio (default when command is set), http / streamable-http, or sse. sse is parsed but never connected — it's warned as deprecated and skipped in favor of http; ws is skipped outright (not in the spec) |
command / args |
The process to spawn for an stdio server |
url / headers |
The endpoint and headers for an http server |
env |
Extra environment variables, merged over the SDK's safe baseline — never your full environment |
timeout |
Per-tool execution timeout in milliseconds |
disabled |
true turns a server off without deleting it — it's skipped on load (the /mcp manager toggles this) |
allowed_tools |
A per-server capability allowlist (also accepted as allowedTools). When present, only the listed tool names may register — anything else the server exposes is skipped with a warning, including tools it tries to add later via tools/list_changed. Fail-closed: an empty list exposes nothing |
auth |
"oauth" opts an http server into the OAuth 2.1 sign-in flow — see OAuth for remote servers |
Values support ${VAR} and ${VAR:-default} expansion, so secrets stay in
your environment rather than in the file. Malformed entries are skipped with a
warning rather than failing the launch. Both stdio and http /
streamable-http are fully supported live transports (including
OAuth on the http side); sse and ws are not.
Connecting & trust¶
A server's command is spawned only after you approve it. On first run kin shows
the approval modal with the server's transport and command; choose allow-once or
allow-always. "Always" persists the grant to ~/.kin/mcp_trust.json, so the
server connects silently on later launches.
The grant is keyed by the server name and a hash of its launch-affecting
fields (command, args, url, env). If any of those change, the key no longer
matches and kin re-prompts — a rug-pull defense, so a server can't quietly swap
in a different command or env after you've trusted it. A cloned repo's poisoned
.mcp.json therefore can't execute anything on launch without your approval.
Namespacing¶
Each server's tools are flattened into the registry namespaced as
mcp__<server>__<tool> — for example mcp__files__read_file. (A name that would
exceed the 64-character tool-name limit is truncated with a short hash suffix so
two long names can't collide.) Tool descriptions are sanitized before the model
sees them: zero-width and bidi control characters are stripped, the text is
length-capped, and injected-override phrasing is flagged — defenses against tool
poisoning.
Permissions¶
MCP tools ask for approval by default (they carry the MCP risk kind, so the
gate prompts on each first use). A tool is auto-allowed as read-only only when its
approved server declares readOnlyHint for it in the server's initial tool list.
A tool that appears later via a tools/list_changed notification is always
treated as unapproved and re-vetted on first call, even if it claims to be
read-only — so a server can't slip in a "read-only" tool to dodge the gate.
Every MCP tool result is wrapped in untrusted-content framing before it enters the conversation, since server output is attacker-influenceable. See Modes & permissions for the gate, and Tools for how tools surface.
An "always allow" grant on an MCP tool is scoped to that tool's name only,
kept deliberately separate from shell-command grants — an MCP tool that
declares a command parameter can never piggyback on (or feed) a standing
shell grant.
If a server redefines a tool mid-session (same name, changed description,
schema, or read-only hint), kin treats it as a rug-pull signal, not routine
churn: a ⚠ system note and a warning toast name the redefined tools, any
standing "always allow" grant for those names is revoked, and the tool must be
approved again on its next use. kin also compares the running mcp SDK
version against the one this build was vetted with and warns loudly on drift
(a supply-chain posture: a stale or unexpectedly-upgraded SDK should be
re-vetted, not silently trusted).
Elicitation¶
An MCP server can ask you for input mid-call (the spec's
elicitation/create). kin bridges each request onto the same question modal
the ask tool uses — one prompt per schema field, with the header naming
which server is asking. Enum fields become option lists, booleans become
Yes/No, free-form fields take typed text; press Esc at any point to
decline the whole request (nothing partial is ever sent). Requests whose
schema falls outside the spec's flat-primitives subset are declined
automatically, and kin never auto-answers: in a headless run
an elicitation is recorded as needs-human (exit 2) and declined.
URL-mode elicitation (the newer spec revision, used for sensitive out-of-band flows like sign-ins) shows you the full URL and asks for consent — kin never opens or fetches the URL itself; you visit it out of band if you choose.
Resources¶
When a connected server declares the resources capability, kin registers two
read-only tools: list_mcp_resources (enumerate URIs, optionally per server)
and read_mcp_resource (fetch one by URI). Both are read kind — resources
are read-only by protocol and the server already cleared the first-run
approval gate — and everything they return is wrapped in untrusted-content
framing, with binary content summarized rather than decoded and long text
truncated.
OAuth for remote servers¶
Set "auth": "oauth" on an http server to authenticate with OAuth 2.1
instead of a static header:
{
"mcpServers": {
"notion": { "type": "http", "url": "https://mcp.notion.com/mcp", "auth": "oauth" }
}
}
On first connect kin runs the MCP authorization flow — discovery, dynamic
client registration where offered, then authorization code + PKCE with a
one-shot loopback callback on 127.0.0.1. The sign-in URL is posted to the
transcript and a browser open is attempted; finish the consent screen and the
connect completes (first-time OAuth connects get a 300 s handshake budget).
Tokens (and the registered client info) persist per endpoint under
~/.kin/mcp_auth/ in 0600 files, so later launches reconnect silently via
the stored/refreshed token — the token itself never appears in the transcript,
journal, or /mcp status (which shows only auth: oauth).
Auth-code + PKCE is the flow the MCP spec and the hosted-server ecosystem
actually support (device flow appears in no spec revision and no major hosted
server). On a box with no reachable browser — Outpost, a bare SSH host —
prefer a PAT/bearer token in headers ("Authorization": "Bearer ${MY_PAT}"),
which GitHub, Linear, Atlassian and Stripe all document for exactly this case.
Managing servers¶
Run /mcp to open the interactive server manager. The left pane lists every
configured server (and an "Add server…" row); the right pane shows the selected
server's details with secrets redacted — the endpoint with query tokens and
credentials stripped, and http headers with secret values masked (Bearer ●●●●,
never the token). Resolved secrets are never shown.
Keys in the manager:
| Key | Action |
|---|---|
a |
Add a server (opens a form: name, scope, transport, command/args/env or url/headers) |
enter / e |
Edit the selected server |
d / x |
Enable / disable the selected server (flips its disabled field) |
t |
Test — a live connect isn't run from the modal (that must stay on the one MCP lifecycle task); save + apply instead, and watch for the connect note |
delete |
Remove the selected server (confirms first) |
ctrl+s |
Save & apply — writes the changed .mcp.json file(s) and reconnects live |
esc |
Cancel (discards changes) |
For larger edits, /edit-mcp [instruction] lets kin draft the whole .mcp.json
for you and shows a diff to approve — see
Editing config artifacts. The
manager (/mcp) is the quick toggle/add path; /edit-mcp is the
draft-from-a-description path. Both reconnect live on apply.
On save, kin writes each touched scope's file (<workdir>/.mcp.json or
~/.kin/mcp.json) and reconnects the MCP servers live — no restart. The
reconnect re-checks approvals (an unchanged server you've trusted reconnects
silently; a changed command re-prompts).
/reload re-reads both .mcp.json and settings.toml and applies them live
(reconnecting MCP and, if the wire changed, swapping the backend). It's refused
while a turn is running.
Revoke a standing trust grant with /grants (which also clears the persistent
trust entry); see Slash commands. To put MCP server config
in a TOML settings file context, see
Settings (TOML).
Adding servers from the Outpost Dashboard¶
The Outpost Dashboard offers a second, host-level surface for the
global ~/.kin/mcp.json — useful when you want every workspace's sessions
on the box to adopt the same server without opening /mcp in each one. The
MCP Servers card lists configured servers (name, transport, masked
endpoint, enabled/disabled), with an enable/disable toggle and a delete
button per row, plus an "Add server" form: paste the same mcpServers entry
shape shown above (one server's JSON snippet, copied straight from its
README) and save. Every read goes through the same safe_endpoint /
masked_headers redaction described above — a resolved ${VAR} secret never
reaches the browser — and every write persists the raw, unexpanded snippet,
never a secret-resolved copy. It complements, not replaces, the in-TUI /mcp
manager (which is project-scoped and live-reconnects); a server added or
toggled from the dashboard takes effect for sessions started after the
change — an already-open terminal needs a restart to pick it up.