What is OAuth Callback?
In the OAuth 2.0 authorization code flow, the authorization server sends the user's browser back to a redirect URI with a code (or an error) and the state your app sent. A web app handles that on a route of its server. A CLI tool or desktop app has no public server, so it listens on the loopback interface instead, as recommended by RFC 8252 (OAuth 2.0 for Native Apps).
OAuth Callback does exactly that part: it turns a browser authorization into a validated authorization code on a loopback redirect URI, in Node.js, Deno and Bun. For Model Context Protocol clients, oauth-callback/mcp adds a browser authorization provider for the MCP SDK.
The loopback flow
getAuthCode() handles the listener, the state, the browser and the callback. Token exchange, PKCE and client secrets stay in your code, so it works with any provider. With oauth-callback/mcp, the MCP SDK does the OAuth work and the library supplies the browser side.
What it handles
- Redirect URI. Binds
http://127.0.0.1:<free port>/callbackby default and calls your builder with it, so you never pick a port. A fixedredirectUri(includinglocalhostor[::1]) works too. - State. Every flow has one. Only a callback with exactly that
stateand an unambiguouscodeorerrorcompletes the flow; anything else is refused (400, or 404/405 for a wrong path or method) and the flow keeps waiting. - URL validation. The authorization URL must be
https:(or loopbackhttp:), withresponse_typeabsent orcodeand a query response mode. Unsafe URLs never reach a browser. - Browser. Opens the system browser by default, or hands the URL to your
launchfunction (headless, SSH, QR codes, tests). - Pages. The browser sees a neutral page that never renders callback data, sent with
Content-Security-Policy,Cache-Control: no-store,Referrer-Policy: no-referrerandX-Content-Type-Options: nosniff. - Cleanup. A timeout (5 minutes by default) and an optional
AbortSignalbound the flow; the listener always closes.
MCP integration
import { Client } from "@modelcontextprotocol/client";
import { browserAuth } from "oauth-callback/mcp";
const auth = browserAuth({
serverUrl: "https://mcp.notion.com/mcp",
redirectUri: "http://127.0.0.1:8765/callback",
clientName: "Acme CLI",
});
const client = new Client({ name: "acme", version: "1.0.0" });
await auth.connect(client);The MCP SDK owns discovery, Dynamic Client Registration, PKCE, token exchange, refresh and issuer checks. browserAuth() owns the browser, the loopback listener, flow ownership and credential storage. See browserAuth.
When to use it
Good fit:
- CLI tools and desktop apps that sign users in through their browser
- MCP clients connecting to OAuth-protected servers
- Scripts and dev tools that need a user's token once
Look elsewhere when:
- You run a web app with its own server: handle the redirect on a route
- There is no browser on the user's machine: the device authorization grant fits better, though a custom
launchthat prints the URL covers many SSH cases - You need machine-to-machine auth: use the client credentials grant
Requirements
- Node.js 22+, Deno 2 or Bun 1.2+
- A browser on the user's machine
- An OAuth client whose redirect URIs include your loopback URI, e.g.
http://127.0.0.1/callbackfor providers that allow any loopback port (RFC 8252 §7.3), or a fixedhttp://127.0.0.1:8765/callback. MCP servers that support Dynamic Client Registration need no pre-registration. @modelcontextprotocol/client2.2+ foroauth-callback/mcp
The package has zero runtime dependencies.