MCP reference
This reference is for people who write or maintain MCP integrations for Opair. If you just want to connect GitHub, GitLab or Atlassian, 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. |
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). |
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,authIssuerandauthAudiencerequireauth: oauth.- 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). - 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.