oterm/docs/mcp/index.md
Yiorgis Gozadinos efedc8f602
Cover empty-text prompts and qualify the MCP env list by platform
- Test build_user_prompt with empty text, images and no tokens; the
  branch that skips the empty text part was uncovered, leaving
  coverage below the required 100%.
- docs/mcp: the inherited environment list (HOME, PATH, ...) is the
  POSIX one; Windows inherits APPDATA, USERPROFILE and others.
- response_task: separate except blocks for cancellation and errors,
  sharing a discard_turn() helper, instead of one block that branched
  on isinstance.
2026-09-26 17:02:08 +03:00

107 lines
4.8 KiB
Markdown

# Model Context Protocol
`oterm` has support for Anthropic's open-source [Model Context Protocol](https://modelcontextprotocol.io). It connects to [MCP servers](https://github.com/modelcontextprotocol/servers) and exposes their tools to whichever model you're chatting with.
Add MCP servers under the `mcpServers` key in `oterm`'s [config.json](../app_config.md). The schema matches the convention used by Claude Desktop, Cursor, and pydantic-ai, so you can copy a config block between hosts.
!!! warning "Breaking changes from earlier oterm releases"
The MCP integration was rewritten on top of pydantic-ai's MCP support. If you're upgrading, your existing `mcpServers` config likely needs the following edits:
- **`auth: { type: bearer, token: "X" }`** is no longer recognised. Use `headers: { "Authorization": "Bearer X" }` instead. Old configs are silently dropped, and you'll see 401s from the server until you migrate.
- **`ws://` / `wss://` transports** are no longer supported. Use HTTP transport instead.
- **MCP prompts** are not supported. The "Use MCP prompt" command is gone.
- **Stdio subprocesses no longer inherit the parent environment**, apart from a few basics (on Linux and macOS: `HOME`, `LOGNAME`, `PATH`, `SHELL`, `TERM` and `USER`). Declare every other env var you need under `env`. Use `${VAR}` substitution to pull values from the parent environment without committing secrets. See [Environment variables](#environment-variables) below.
## Tools
[MCP tools](https://modelcontextprotocol.io/docs/concepts/tools) appear in oterm's tool selector and can be enabled per chat.
The model sees each MCP tool as `{server}_{tool}`: the `query_prometheus` tool on a server configured as `grafana` is presented as `grafana_query_prometheus`. Two servers can therefore export the same tool name, and each is selectable on its own.
Because the server name reaches the model, it may only contain letters, digits, underscores and hyphens: `k8s-lab` is fine, `k8s.lab` is not. A server whose name has other characters is refused when the config loads, and a tool whose qualified name is over 64 characters is skipped; both are reported in the log.
!!! note
Not all models support tools. For models that don't, the tool selection is disabled.
Smaller LLMs are often less capable with tools than larger ones. If you have issues, try reducing the number of tools attached to a chat, increasing the context size, or using a larger LLM.
## Transports
### `stdio` transport
For local MCP servers. Accepts `command`, `args`, `env` and `cwd`. For the [git](https://github.com/modelcontextprotocol/servers/tree/main/src/git) MCP server:
```json
{
"mcpServers": {
"git": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"--mount",
"type=bind,src=/Users/ggozad/dev/open-source/oterm,dst=/oterm",
"mcp/git"
]
}
}
}
```
### `Streamable HTTP` transport
For remote MCP servers over HTTP. The `url` must start with `http://` or `https://`. URLs ending in `/sse` are treated as SSE; everything else is streamable HTTP.
```json
{
"mcpServers": {
"my_mcp": {
"url": "http://remote:port/path"
}
}
}
```
## HTTP headers (auth)
Use the `headers` dict to attach arbitrary HTTP headers, including `Authorization` for bearer-token auth:
```json
{
"mcpServers": {
"my_mcp": {
"url": "http://remote:port/path",
"headers": {
"Authorization": "Bearer XXX"
}
}
}
}
```
## Environment variables
For security, `stdio` MCP subprocesses do **not** inherit `oterm`'s environment, apart from a few basics: `HOME`, `LOGNAME`, `PATH`, `SHELL`, `TERM` and `USER` on Linux and macOS, and `APPDATA`, `HOMEDRIVE`, `HOMEPATH`, `LOCALAPPDATA`, `PATH`, `PATHEXT`, `PROCESSOR_ARCHITECTURE`, `SYSTEMDRIVE`, `SYSTEMROOT`, `TEMP`, `USERNAME` and `USERPROFILE` on Windows. That keeps credentials like `OPENAI_API_KEY` or `AWS_SECRET_ACCESS_KEY` out of third-party MCP server processes unless you explicitly share them.
Declare env vars in the `env` dict of each server. Any string value (in `env`, `command`, `args`, `url`, or `headers`) can reference the parent environment via `${VAR}` (required) or `${VAR:-default}` (optional with fallback):
```json
{
"mcpServers": {
"github": {
"command": "${HOME}/.local/bin/mcp-github",
"args": ["--repo", "${REPO:-ggozad/oterm}"],
"env": {
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
}
}
}
}
```
If a referenced variable is not set and has no default, server setup fails with an error naming the missing variable.
## Sampling
MCP [sampling](https://modelcontextprotocol.io/docs/concepts/sampling) is not supported. `oterm` advertises sampling as disabled to every server, so any sampling request is rejected by the protocol rather than crashing the chat.