Sequence
The name in the heading is whateverclient asked to be called, so read the line under it: it names
the address the code is delivered to. Approve only when that address belongs to the app you started
the pairing from.


Scope, connections and expiry are all editable before Approve
The whole client, in one file
- The verifier is 43 to 128 characters from
A-Z a-z 0-9 - . _ ~. 32 random bytes in base64url is 43. - The challenge is its base64url SHA-256, so exactly 43 base64url characters.
- The redirect is a loopback
httporhttpsURL (127.0.0.1,localhost,::1), or a private-use scheme an installed app has registered. Anything else is refused with “The redirect address is not a local callback, so pairing was refused.” That covers a redirect carrying credentials,tablepro://and the database URL schemes TablePro opens, a scheme a web browser handles (such asgoogle-chrome:), and network schemes whose handler fetches from the host they name, such assmb://,ssh://andwebcal://.
/mcp are the only paths the server serves, and both accept POST only.
Response modes
response_mode decides how the answer reaches redirect. Send query. Use context when your host
passes an extension a single launch parameter named context, as Raycast does.
The
context value is JSON with sorted keys, percent-encoded like any other query value. Any other
response_mode drops the link: no sheet opens and nothing reaches redirect. Query items already on
redirect stay as they were, with these appended after them.
State
state is optional and opaque: up to 1,024 UTF-8 bytes, returned unchanged on approve and on deny,
in every mode. A longer one drops the link the same way. Send a fresh random value with each request
and drop any callback whose state does not match the request you started.
Deprecated: no response_mode
A link without response_mode still gets the delivery in the last row of the table, and is
deprecated. A later release will read a missing response_mode as query, so send it explicitly.
Versioning has the policy.
Scopes
scopes holds one permission level (readOnly, readWrite or fullAccess) plus, for a launcher,
connections:display, separated by spaces or commas. Unknown entries are skipped. When the list
names more than one level, the sheet starts at the lowest.
connections:display lets the token call list_connections
with purpose: "display", which adds connections hidden from AI and each connection’s user name. Ask
for it only to show the user their connections, never to feed a model. The sheet offers it only while
Allow apps to list connections hidden from AI is on under Settings > MCP, and its checkbox
starts off. Otherwise the token is issued without it.
The exchange response names what was granted:
scope lists every granted scope, sorted and space separated. Read it instead of assuming the
request went through: the user can lower the permission level or leave the checkbox off.
What the user approves
The sheet names the client and counts down the five minutes the request is good for, dimming Approve when it runs out. Three controls sit under that: Permission Level (starting at what the link asked for, movable in either direction), Allowed Connections (all, or a checked subset), and Expiration (never, 1, 7, 30 or 90 days). A link that asks forconnections:display adds
List connections hidden from AI, off until the user ticks it. The link’s parameters are a
request, not a grant.
Approving mints the token, holds the plaintext against a one-time code for 5 minutes, and opens the
redirect. Pairing again mints a second token rather than replacing the first, so an extension that
re-pairs should stop using its old one. Revoke either under
Settings > MCP > Authentication.
Security properties
Errors
A failed verification burns the code, so retrying with a guessed verifier is not an option: start a new pair request.
Every failed exchange lands in the activity log under the
auth category with outcome denied.
Clicking Deny opens the redirect with the deny response for the request’s
mode.
