Skip to content

MCP

MCP Integration

The Model Context Protocol (MCP) lets Oasis CLI pull tools from external servers (your ticket system, your historian, an internal API) and hand them to the agent alongside the built-in tools.

An MCP server is a capability source. It hands back tool descriptors, and the agent loop treats them exactly as it treats a built-in tool or a capability pack. Nothing in the loop knows a tool arrived from a remote server.

Protocol version2025-06-18
Transportsstdio (local subprocess), Streamable HTTP (remote)
AuthenticationStatic headers, or OAuth 2.1 with PKCE
Scopeinitializetools/listtools/call

What is deliberately not implemented

Resources, prompts, sampling and roots are out of scope. A partial implementation of each would be worse than their absence. A client that half-supports a capability is harder to reason about than one that does not claim it.

Connecting servers

Point the agent at a config file at startup:

bash
oasis-agent --mcp-config ~/.oasis/mcp-servers.json

Or set it once so every run picks it up:

bash
export OASIS_MCP_CONFIG=~/.oasis/mcp-servers.json
json
{
  "mcp_config": "/home/operator/.oasis/mcp-servers.json"
}

Curation happens at config time

Every server listed is connected at startup and its tools autoloaded. There is no progressive discovery, with no "ask the server what it has when you need it".

That is deliberate. Discovery at inference time costs round trips the model cannot spare, and it makes the tool schema unstable, which breaks the prompt cache. Decide what a project's agent can reach when you write the config, not while it is thinking.

Configuration file

The file uses an mcpServers object keyed by server name.

json
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
    },
    "historian": {
      "type": "http",
      "url": "https://historian.internal/mcp",
      "headers": { "Authorization": "Bearer ${HISTORIAN_TOKEN}" },
      "readOnly": true,
      "timeoutMs": 120000
    },
    "tickets": {
      "url": "https://tickets.example.com/mcp",
      "oauth": { "scopes": "tickets.read tickets.write" },
      "tools": ["create_ticket", "search_tickets"],
      "required": true
    }
  }
}

Every key is documented in the MCP Server Reference.

Choosing a transport

TransportSelected whenUse for
stdiocommand is setA local subprocess you control: a script, a vendor CLI wrapper
Streamable HTTPtype is http or streamable-http, or url is set and command is notA remote service

Tool naming

Tools from MCP servers are namespaced and appended after the built-in tools:

text
mcp__<server>__<tool>

Appending matters: the cached prompt prefix must not move when a server is added, so MCP tools land at the tail rather than being interleaved.

RuleBehaviour
Maximum length64 characters. Longer names are truncated
CollisionsTwo tools that sanitise to the same name are disambiguated with a short hash suffix, and the rename is reported at startup

A collision usually means two config entries that wanted to be the same server. Read the startup output. The model will see the renamed tool, not the obvious one.

Failure behaviour

SettingA server that fails to connect
DefaultIs skipped. The run continues without its tools
"required": trueFails startup

Graceful degradation is right by default, but it silently changes the tool schema, and the schema is part of the cache key. A flaky server costs a full prefill on every run it misses, and the turn proceeds without tools you were counting on. required is the opt-out for anyone who would rather not start at all.

MCP and the prompt cache

The same rule as capability packs: the tool set is part of the cache key.

ChangeCost
Config unchanged between runs100% prefix reuse
A server added or removedOne cold prefill
A server silently unavailableOne cold prefill, every run it misses

Keep the server list stable per project. Use required: true on anything whose absence should stop the run rather than quietly reshape the schema.

Authentication

Static headers

json
{
  "headers": { "Authorization": "Bearer ${HISTORIAN_TOKEN}" }
}

${VAR} expands from the environment, so the token lives in the environment rather than in a committed config file.

An unset variable expands to empty with a warning rather than failing. A missing token should produce a 401 you can read, not a startup crash that hides which header was wrong.

OAuth 2.1

For remote servers that are OAuth 2.1 Resource Servers, add an oauth object:

json
{
  "url": "https://tickets.example.com/mcp",
  "oauth": { "scopes": "tickets.read tickets.write" }
}

Oasis CLI acts as a public client using the authorisation-code flow with mandatory PKCE (S256). Discovery walks RFC 9728 (Protected Resource Metadata) → RFC 8414 (Authorization Server Metadata), with optional RFC 7591 dynamic client registration, and the token is bound to that server with a resource indicator (RFC 8707).

Logging in once

bash
oasis-agent --mcp-config ~/.oasis/mcp-servers.json --mcp-login

This runs the interactive browser flow for every configured HTTP server, then exits. Do it once from a machine with a browser.

Tokens are cached at ~/.config/oasis-agent/mcp-tokens.json and refreshed automatically. A token is refreshed a minute before it actually expires, so a request in flight when the clock crosses does not 401.

Headless runs cannot open a browser

A headless run with no cached token fails with instructions rather than hanging waiting for a redirect. Run --mcp-login once from a machine with a browser and copy the token store, or use static headers instead.

Restricting what a server exposes

Two levers, and they compose.

json
{
  "url": "https://historian.internal/mcp",
  "readOnly": true,
  "tools": ["query_series", "list_tags"]
}
KeyEffect
toolsAn allowlist. Only the named tools are registered. The primary curation lever
readOnlyEvery tool from this server is treated as safe-tier and never prompts

readOnly is a claim you are making

Setting it means the agent will call that server's tools without asking. Only set it on a server you know cannot change anything.

Seeing what loaded

bash
oasis-agent --show-config

At startup, each server reports as it connects: how many tools it registered, whether it is read-only, and any tool that had to be renamed.

Over the control protocol, mcp.servers lists connected servers and mcp.login drives the OAuth flow. See the OCP Method Reference.

Timeouts

The default per-request timeout is 30 seconds. Override it per server:

json
{ "timeoutMs": 120000 }

One number cannot serve both a local process that should answer in milliseconds and a remote build server that legitimately takes minutes, so it is set per server by whoever knows which is which.

Next

software-defined automation