termal.in

← Blog

MCP servers, explained: how to give an AI agent tools without handing over your keys

· Termalin team mcpai-agentssecuritytutorial

Every useful AI agent hits the same wall. The model can reason all day, but on its own it can’t read your files, query your database, or restart a service — it has no hands. It needs tools. MCP, the Model Context Protocol, is the standard way to give a model tools without hard-wiring each one into the app. It’s worth understanding for its own sake, because the moment a tool touches something real — a database, an API, a server — you’ve made a security decision whether you meant to or not.

What an MCP server actually is

MCP is an open protocol for connecting AI applications to outside capabilities. It has three roles, and the names are worth getting straight:

  • Host — the app the model lives in: a desktop chat client, a coding agent, an IDE plugin. The host is what the user talks to.
  • Client — a connector inside the host. There’s one client per server, and it manages that one connection.
  • Server — a small program that exposes some capability. A filesystem server, a GitHub server, an SSH server. It does one job and speaks MCP.

The flow is always the same. The server advertises what it can do. The host shows those capabilities to the model. When the model wants to use one, the host calls the server and feeds the result back into the conversation. The model never executes anything itself — it asks, and the host does the work. That indirection is the whole reason MCP matters for security: there is a checkpoint between “the model decided to” and “it happened,” and that’s where you get to impose rules.

Tools, resources, prompts

An MCP server can expose three kinds of things, and the distinction is practical, not academic:

  • Tools — actions the model can invoke. run_query, create_issue, ssh_exec. Tools do something and can change state.
  • Resources — data the model can read. A file, a table, a page of docs. Read-only context.
  • Prompts — canned templates a user can pick from a menu, pre-filled by the server.

Resources are mostly harmless — the worst case is the model reading something it shouldn’t. Tools are where the risk lives. A tool changes state, and to change state on a real system it needs a credential. Which brings us to the question that decides everything.

stdio and HTTP: local servers and remote ones

Before that, one practical split. MCP servers connect over one of two transports:

  • stdio — the host launches the server as a local subprocess and talks to it over standard input/output. The server runs on your machine, as you, with your permissions. Most local servers work this way.
  • Streamable HTTP — the server is a network endpoint the host calls over HTTPS. This is how hosted or shared servers work, and how one server can back many users.

Registering a local (stdio) server is usually a one-liner:

claude mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem /path/to/dir

A remote (HTTP) server is a small block of config:

{
  "mcpServers": {
    "example": {
      "url": "https://example.com/mcp",
      "headers": { "Authorization": "Bearer ..." }
    }
  }
}

The transport doesn’t change the security question. It only changes where the credential lives — on your machine, or on someone’s server.

The credential question every server has to answer

Here’s the through-line. Any tool that does real work needs a secret: a database password, an API token, an SSH key. There are only two places to put it, and the choice is the entire security story.

Put it in the agent’s environment. The direct route — drop the secret into the server’s config or an env var the agent controls, and let the tool use it. This works immediately, and it’s the trap. Now the model’s context effectively holds a bearer credential: whoever holds the bytes can act as you. You can’t un-share it — once a secret has passed through a model’s context, a transcript, or a tool call you didn’t read, you can’t prove it didn’t leak. And prompt injection reaches it: a poisoned document or a booby-trapped tool result can talk the model into exfiltrating the very secret it was handed.

Let the server hold it and act on the agent’s behalf. The server keeps the credential and does the authenticating. The agent gets the ability to request a scoped action — not the secret itself. Compromise the agent’s context and all you get is “can ask the broker to do listed things,” not “can impersonate you anywhere.” Turning access off is a toggle, because nothing was ever shared.

That’s it. That single design choice — who holds the credential — separates an MCP setup you can sleep on from one you can’t. Everything else is detail.

SSH is the sharpest version of the problem

Nowhere is this starker than SSH. An SSH private key is the purest bearer credential there is: hold it and you are the user, on every host that trusts it, with a full shell. So an SSH MCP server that keeps the key in the agent’s environment is the worst case of the naive pattern — maximum blast radius, zero take-backs.

The good version is what infrastructure teams already call the custodian model. The server holds the keys (or mints short-lived certificates), signs on the agent’s behalf, and adds the controls the naive version lacks: the agent reaches only listed hosts, each host carries a command policy, a human can watch live, and every action is logged and attributable. We walk through that end to end in letting an agent deploy without your keys and, for models running on your own hardware, giving a local LLM safe SSH access.

How Termalin does it

Termalin is an SSH client with a built-in MCP server, so the custodian pattern is the default rather than a project you have to assemble.

  • Local server, over stdio. Register it with your agent (claude mcp add termalin -- <path>/termalin-mcp) and it exposes tools — list hosts, run a command, open a persistent session, read and write files over SFTP. Agent access is off by default; you enable it per host.
  • Your key stays with the custodian. You unlock your keys once, and Termalin signs on the agent’s behalf. No key file is written for the agent to read, so there’s nothing in its environment to leak.
  • Per host, you set the policy — full access, an allowlist of commands, or blocked — so a confused model can’t run something you never authorized on that box.
  • You watch it happen. Agent sessions appear as live terminal tabs; the watch grid mirrors them and marks every command as the agent’s, with the device and IP it came from in the audit log.
  • No desktop running? A hosted endpoint reaches only enrolled servers, with API keys scoped to specific hosts and an expiry you pick, authenticating each run with a short-lived certificate — so even a leaked key can’t reach an unlisted host.

Where to start

Connect a read-only server first — a filesystem or docs server — and watch how the model uses tools before you give it any that write. When you do add a tool that changes state, ask the one question that matters before you wire it up: who holds the credential? If the answer is “the agent,” you’ve built something you can’t take back. If it’s “the server, on the agent’s behalf,” you’ve built something you can turn off with a toggle. Pick the second one.


Termalin is a free, cross-platform SSH client with a built-in MCP server, a key custodian and per-host agent policy — download it, or read how it handles keys safely.

Try it on one host.

Termalin is a fast SSH client for you — and your agents.

Free tier · 14-day Pro trial · pricing