MCP reference
This reference is for people who write or maintain MCP integrations for Opair. If you just want to connect Atlassian, Cloudflare, GitHub, GitLab, Linear, Sentry or Slack, see the MCP guide instead.
- How servers are configured
- Config fields
- Validation rules
- Authentication
- Token storage
- Permissions
- Tool naming
- Diagnostics
How servers are configured
One JSON file per server,
in the MCP directory
(<user config dir>/opair/mcp,
overridable with OPAIR_MCP_DIR).
Every .json file in that directory is a server.
Unknown fields are rejected.
Files are re-read when they change on disk;
no restart needed.
Server names are lowercase letters, digits, hyphens and underscores, starting with a letter. The name becomes part of tool names and permission strings, so it must be safe to embed in both.
Example, the shipped GitHub config:
{
"name": "github",
"transport": "http",
"url": "https://api.githubcopilot.com/mcp/",
"auth": "oauth",
"authFlow": "device",
"clientId": "Ov23lifmXOhmKxDGdvq1",
"scopes": ["offline_access", "repo", "read:org"],
"readOnly": true
}
(scopes list abbreviated; see mcp/github.json for the full one)
Config fields
| Field | Applies to | Meaning |
|---|---|---|
name |
all | Server name (see rules above). Required. |
transport |
all | "http" or "stdio". Required. |
url |
http | Server URL. Required for http. |
command |
stdio | Command to run. Required for stdio. |
args |
stdio | Command arguments. |
env |
stdio | Environment variables for the command. $VAR values are expanded. |
headers |
http | HTTP headers sent with every request. $VAR values are expanded. |
auth |
http | Set to "oauth" for stored-token sign-in. |
authFlow |
http | "device" for the device flow. Empty means browser code flow. |
authIssuer |
http | Issuer hint for the authorization server, for servers that don't advertise one themselves. |
authAudience |
http | audience parameter sent during sign-in. |
authScopeParam |
http | Name of the OAuth parameter that carries the requested scopes. Empty or "scope" per RFC 6749; "user_scope" for Slack's user-token flow. |
authAuthorizationEndpoint |
http | Overrides the discovered authorization endpoint. Slack's metadata advertises one that rejects user_scope requests. |
clientId |
http | Pre-registered OAuth client ID. |
clientSecret |
http | Pre-registered client secret. $VAR values are expanded. |
redirectPort |
http | Port for the local redirect during sign-in. Empty: the OS picks a free one. |
scopes |
http | OAuth scopes to request. |
readOnly |
all | Withhold every tool that does not declare readOnlyHint: true (see Read-only servers). |
enabled |
all | Set to false to keep the server configured but disconnected. Unset means enabled. Use this for servers that need no authentication, where connecting is otherwise unconditional. |
permissions |
all | Per-tool permission overrides (see Permissions). |
Validation rules
authis only valid on http servers, and the only valid value is"oauth". Anauth: oauthserver must not set anAuthorizationheader.authFlow,clientId,clientSecret,scopes,authIssuer,authAudience,authScopeParamandauthAuthorizationEndpointrequireauth: oauth.authScopeParammust be"scope"(or empty) or"user_scope".- A stdio server must set
commandand must not seturl. - An http server must set
urland must not setcommand.
Invalid configs fail at load. The server is unavailable; other servers are unaffected.
Authentication
Two ways for a server to authenticate:
- OAuth (
auth: "oauth"): the user signs in through their browser, tokens are stored and refreshed automatically. - Authorization header:
a static header like
Authorization: Bearer $MY_TOKEN. No sign-in flow. Good for personal access tokens.
OAuth flows
Two OAuth flows are supported:
- Browser code flow (default):
the browser opens,
the user signs in,
the authorization server redirects
to a local port (
redirectPort, default chosen by the OS). The redirect URI uses thelocalhosthostname rather than127.0.0.1: Slack only grants desktop-redirect treatment (required for PKCE user-token flows) tolocalhost. - Device flow (
authFlow: "device"): Opair shows a short code, the user types it into a web page. Used for GitHub, whose code flow needs a client secret.
Client registration
Servers with a registration_endpoint
are registered dynamically at sign-in.
Nothing to configure.
Servers without one
need clientId (and often clientSecret) in the config.
Pre-registered credentials always win
over dynamic registration.
Sign-in
The user runs /mcp auth {server} or opair mcp auth {server}.
Both do the same thing.
The flow never runs at startup;
startup only connects from stored tokens.
Token storage
Tokens live in mcp-tokens.json,
a sibling of the MCP directory,
written with 0600 permissions.
It sits outside the MCP directory
because every .json file in there is treated as a server.
Tokens are keyed by authorization server issuer: credentials from one server are never sent to another.
Access tokens refresh automatically on 401.
If a refresh fails with invalid_grant,
the stored tokens are deleted
and the user must sign in again.
A new scope also needs a fresh sign-in;
the client does not step up scopes on its own.
Permissions
MCP tools live in their own permission space,
mcp:<server> (e.g. mcp:github),
separate from the local-disk reads/writes categories.
-
MCP tools always prompt for approval unless a loop's
approvelist grantsmcp:<server>. Fail closed throughout. -
Session approvals work like write approvals: approving a call can cover the session.
-
A server can override per tool:
"prompt": always ask, even when the server is broadly approved."allow": skip asking for a read-only tool.
-
Read/write comes from the tool's
readOnlyHintannotation. The hint is untrusted, so absent or unknown means write.
Read-only servers
A server configured with readOnly: true
only offers tools that declare readOnlyHint: true.
Everything else is withheld.
Two limits to be clear about.
First, readOnly is a guardrail on the harness,
not a trust boundary against the server:
the hint is the server's own claim,
and the OAuth token keeps whatever scopes
it was granted.
Second, readOnly does not narrow scopes;
when a server does offer read-only scopes,
list them explicitly in scopes.
Tool naming
Tools surface to the agent as
mcp__<server>__<tool>,
e.g. mcp__github__create_issue.
The double underscore is the only separator
that every supported LLM provider accepts
in tool names.
Diagnostics
mcpprobe performs a handshake against a server URL, lists its tools and can call one. Useful when an integration misbehaves.