MCP tools
Coulisse can borrow tools from Model Context Protocol servers and hand them to your agents. The config has one rule: declare what the server is, not what protocol it speaks. Coulisse infers the transport from the shape of the entry.
Declaring MCP servers
mcp:
# Remote MCP — just paste the URL. OAuth is auto-enabled.
todoist:
url: https://ai.todoist.net/mcp
# Local stdio MCP — give it a command.
hello:
command: uvx
args:
- --from
- git+https://github.com/macsymwang/hello-mcp-server.git
- hello-mcp-server
# Plain HTTP MCP without auth — explicit opt-out.
calculator:
url: http://localhost:8080
oauth: false
The Todoist entry above is zero config: the same UX as ChatGPT. Paste the URL, and Coulisse runs RFC 8414 discovery + RFC 7591 Dynamic Client Registration on first use, mints a per-user connect link, stores the token in the vault.
You never write oauth: for the common case — a url: server gets discover-mode OAuth on its own. Reach for the oauth: map only to override scopes or use static credentials:
mcp:
# Override discovered scopes
custom:
url: https://example.com/mcp
oauth:
scopes: [read:items, write:items] # mode: discover is implied
# Pre-registered (static) OAuth credentials — for providers that
# don't support Dynamic Client Registration
legacy:
url: https://internal.example.com/mcp
oauth:
mode: static
authorization_url: https://auth.example.com/authorize
token_url: https://auth.example.com/token
client_id: my-client
client_secret: my-secret
redirect_uri: http://localhost:8423/mcp/legacy/oauth/callback
That's it. No transport: field, no oauth: block for the common case, no shim wrappers. Coulisse figures out:
url:present → HTTP/SSE transport (SSE if the URL path contains/sse, otherwise streamable HTTP).command:present → stdio transport, with optionalargs:/env:for the child process.oauth:is the only thing you opt into yourself, and only when the server actually needs it.
Auto-detected transport
The path heuristic: if the URL has an /sse path segment (https://mcp.atlassian.com/v1/sse), Coulisse uses the older MCP-over-SSE protocol. Everything else uses streamable HTTP. URLs without /sse that turn out to be SSE-only will fail with a Missing sessionId parameter 404 on first call; switch to the explicit form below.
stdio config fields
command(required) — the executable to spawn (uvx,python,node, …)args(optional) — argumentsenv(optional) — environment variables
mcp:
my-tool:
command: python
args: [-m, my_mcp_server]
env:
DEBUG: "1"
API_KEY: abc123
Explicit transport: (legacy / override)
The verbose form still works if you need to override the auto-detection:
mcp:
legacy:
transport: sse # one of: http, sse, stdio
url: https://example.com/v2/endpoint # despite no /sse segment
Existing YAMLs that use transport: continue to parse unchanged. New code should prefer the URL-only / command-only form above.
Per-user OAuth (optional)
MCP servers that require user-delegated credentials (Todoist, Atlassian, GitHub,
Google Drive, etc.) can be configured with an oauth: block. Coulisse handles
the authorization flow per-user and injects each user's token automatically at
call time — Alice's token is never reachable by Bob.
Two modes:
mode: discover (recommended for modern MCP servers)
Spec-compliant MCP servers (Todoist, Atlassian, Linear, …) advertise their OAuth
endpoints via /.well-known/oauth-authorization-server and accept Dynamic Client
Registration. Coulisse discovers + registers itself lazily, on the first user to
authorise. No credentials in YAML — and no oauth: block at all, since a
URL-based server defaults to discover:
mcp:
todoist:
url: https://ai.todoist.net/mcp
# discover OAuth is automatic; add an oauth: map only to pin scopes:
# oauth:
# scopes: [data:read_write]
A handful of servers only honour tokens issued to mcp-remote's grandfathered
client id and reject fresh DCR registrations (Todoist's hosted MCP is the
current example). For those, run mcp-remote yourself as a stdio child — there
is no special flag:
mcp:
todoist:
command: npx
args: [-y, mcp-remote, https://ai.todoist.net/mcp]
mode: static (for non-DCR providers)
For OAuth providers that require a pre-registered app (GitHub OAuth apps, classic Atlassian Connect, etc.):
mcp:
github:
transport: http
url: https://api.githubcopilot.com/mcp
oauth:
mode: static
authorization_url: https://github.com/login/oauth/authorize
client_id: "${GH_CLIENT_ID}"
client_secret: "${GH_CLIENT_SECRET}"
redirect_uri: https://coulisse.example.com/mcp/github/oauth/callback
scopes: [repo, read:user]
token_url: https://github.com/login/oauth/access_token
static requires: authorization_url, client_id, client_secret,
redirect_uri, token_url. Missing any of these at startup is a fatal error.
Both modes share the same infrastructure secrets (vault encryption + HMAC).
Coulisse auto-generates them on first boot and persists them in
.coulisse/secrets.env — no manual setup needed for local use. Override
with COULISSE_VAULT_KEY / COULISSE_HMAC_KEY env vars for hosted
deployments. auth.mcp_consumer_secret is optional (only gates the admin
POST /connect-link endpoint). Set public_base_url: at the top level
when Coulisse runs on a public hostname; defaults to
http://localhost:{port} for local use.
See Per-user OAuth for MCP for the full flow, endpoints, secrets resolution, and the security trust-model warning.
Granting tool access to agents
An agent only sees tools you explicitly give it. Reference the server name under mcp_tools:
agents:
- name: helper
provider: anthropic
model: claude-sonnet-4-5-20250929
mcp_tools:
- server: hello # all tools from "hello"
Restrict to a subset with only:
mcp_tools:
- server: hello
only:
- say_hello # only this tool, nothing else
Discovering tool names
On startup Coulisse connects to each non-OAuth MCP server and logs the tools it discovered. OAuth-enabled servers connect per-user on first use. Tool names in your only list must match what the server advertises — check the startup output or the server's own docs.
How tool calls work
When a request arrives for an agent with tools:
- Coulisse collects the agent's allowed tools from the MCP servers.
- It forwards them to the model as tool definitions.
- If the model calls a tool, Coulisse dispatches to the MCP server and feeds the result back.
- This loops until the model produces a final answer (up to 8 turns by default, configurable via the agent's
max_turnsfield).
Your client doesn't see any of this — the tool loop is invisible, and only the final assistant message is returned.
See MCP tool integration for a full walkthrough.