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 version | 2025-06-18 |
| Transports | stdio (local subprocess), Streamable HTTP (remote) |
| Authentication | Static headers, or OAuth 2.1 with PKCE |
| Scope | initialize → tools/list → tools/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:
oasis-agent --mcp-config ~/.oasis/mcp-servers.jsonOr set it once so every run picks it up:
export OASIS_MCP_CONFIG=~/.oasis/mcp-servers.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.
{
"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
| Transport | Selected when | Use for |
|---|---|---|
| stdio | command is set | A local subprocess you control: a script, a vendor CLI wrapper |
| Streamable HTTP | type is http or streamable-http, or url is set and command is not | A remote service |
Tool naming
Tools from MCP servers are namespaced and appended after the built-in tools:
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.
| Rule | Behaviour |
|---|---|
| Maximum length | 64 characters. Longer names are truncated |
| Collisions | Two 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
| Setting | A server that fails to connect |
|---|---|
| Default | Is skipped. The run continues without its tools |
"required": true | Fails 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.
| Change | Cost |
|---|---|
| Config unchanged between runs | 100% prefix reuse |
| A server added or removed | One cold prefill |
| A server silently unavailable | One 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
{
"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:
{
"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
oasis-agent --mcp-config ~/.oasis/mcp-servers.json --mcp-loginThis 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.
{
"url": "https://historian.internal/mcp",
"readOnly": true,
"tools": ["query_series", "list_tags"]
}| Key | Effect |
|---|---|
tools | An allowlist. Only the named tools are registered. The primary curation lever |
readOnly | Every 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
oasis-agent --show-configAt 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:
{ "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
- MCP Server Reference: every configuration key
- Capability Packs: built-in protocol tools
- Permission Model: how MCP tools are tiered