feat(coding-agent): add OAuth sign-in for remote MCP servers

HTTP servers without an Authorization header use OAuth. Connections send the stored token and refresh it after a 401; /mcp login runs the browser authorization code flow (PKCE, dynamic client registration) with a loopback callback or a pasted redirect URL, and /mcp logout removes the credentials. Tokens are stored per server URL in mcp-auth.json. Pre-registered clients are configured with the oauth block.
This commit is contained in:
Armin Ronacher 2026-09-25 09:55:56 +02:00
parent e5a252c102
commit 8cd0ed48de
5 changed files with 885 additions and 29 deletions

View file

@ -23,7 +23,7 @@ Add servers to `~/.pi/agent/mcp.json`, or to `.pi/mcp.json` in a project. The fo
```
- stdio servers take `command`, `args`, `env`, and `cwd`. Relative `cwd` resolves against the session directory.
- HTTP servers take `url` and `headers`. The legacy SSE transport is not supported.
- HTTP servers take `url`, `headers`, and `oauth` (see [Sign in with OAuth](#sign-in-with-oauth)). The legacy SSE transport is not supported.
- `env` and `headers` values can reference environment variables (`${NAME}`) or commands (`!command`), like provider API keys.
- `timeout` sets the per-request timeout in seconds (default 60). Progress notifications from the server reset it.
- `enabled: false` keeps an entry without connecting to it.
@ -32,6 +32,37 @@ Project entries replace global entries with the same name. A project `mcp.json`
Pi connects when a session starts. The first prompt waits until startup connections finish. A server that drops its connection is reconnected on the next call. Run `/mcp` to see server status, tool counts, and errors.
## Sign in with OAuth
Remote servers that use OAuth, such as Sentry, need no credentials in `mcp.json`:
```json
{
"mcpServers": {
"sentry": { "url": "https://mcp.sentry.dev/mcp" }
}
}
```
When such a server rejects the connection, `/mcp` shows it as needing sign-in. Run `/mcp login sentry` to open the authorization page in your browser. After you approve access, the browser redirects to a temporary server on `127.0.0.1` and pi connects. If the browser runs on another machine, for example over SSH, paste the URL it was redirected to into the prompt instead.
Pi registers itself with the authorization server (dynamic client registration), stores tokens in `~/.pi/agent/mcp-auth.json`, and refreshes expired access tokens automatically. `/mcp logout sentry` deletes the stored credentials.
OAuth applies to HTTP servers without an `Authorization` header. For authorization servers that do not support dynamic client registration, configure a pre-registered client:
```json
{
"mcpServers": {
"example": {
"url": "https://mcp.example.com/mcp",
"oauth": { "clientId": "my-client", "clientSecret": "${EXAMPLE_SECRET}", "callbackPort": 8765 }
}
}
}
```
`callbackPort` fixes the redirect URI to `http://127.0.0.1:<port>/oauth/callback`, which must match the redirect URI registered for the client. `clientSecret` is optional and can reference environment variables or commands.
## Exposure
Each server's tools are registered as `mcp__<server>__<tool>`. The `exposure` setting controls how the model reaches them:
@ -42,5 +73,3 @@ Each server's tools are registered as `mcp__<server>__<tool>`. The `exposure` se
MCP tools that declare an `outputSchema` return their `structuredContent` to codemode scripts. Other tools return their text output. Images are passed through as image content.
Every MCP call goes through pi's tool pipeline, so `tool_call` and `tool_result` extension handlers, including permission gates, apply to MCP tools. Calls made from codemode scripts carry the codemode call's id as `parentToolCallId`.
OAuth sign-in is not supported yet. Use header-based authentication for remote servers.