loading…
Search for a command to run...
loading…
Lightweight OAuth 2.0 callback handler for Node.js, Deno, and Bun with built-in browser flow and MCP SDK integration
Lightweight OAuth 2.0 callback handler for Node.js, Deno, and Bun with built-in browser flow and MCP SDK integration
npm version npm downloads MIT License TypeScript Run on Replit
A lightweight OAuth 2.0 callback handler for Node.js, Deno, and Bun with built-in browser flow and MCP SDK integration. Perfect for CLI tools, desktop applications, and development environments that need to capture OAuth authorization codes.
open for browser launchingbun add oauth-callback open
Or with npm:
npm install oauth-callback open
Note: The
openpackage is optional but recommended for browser launching. If using pnpm, install it explicitly:pnpm add open
import open from "open";
import { getAuthCode, OAuthError } from "oauth-callback";
// Simple usage - pass `open` to launch browser
const result = await getAuthCode({
authorizationUrl:
"https://example.com/oauth/authorize?client_id=xxx&redirect_uri=http://localhost:3000/callback",
launch: open,
});
console.log("Authorization code:", result.code);
// MCP SDK integration - use specific import
import { browserAuth, fileStore } from "oauth-callback/mcp";
const authProvider = browserAuth({ launch: open, store: fileStore() });
// Or via namespace import
import { mcp } from "oauth-callback";
const authProvider = mcp.browserAuth({ launch: open, store: mcp.fileStore() });
import open from "open";
import { getAuthCode, OAuthError } from "oauth-callback";
async function authenticate() {
const authUrl =
"https://github.com/login/oauth/authorize?" +
new URLSearchParams({
client_id: "your_client_id",
redirect_uri: "http://localhost:3000/callback",
scope: "user:email",
state: "random_state_string",
});
try {
const result = await getAuthCode({
authorizationUrl: authUrl,
launch: open,
});
console.log("Authorization code:", result.code);
console.log("State:", result.state);
// Exchange code for access token
// ... your token exchange logic here
} catch (error) {
if (error instanceof OAuthError) {
console.error("OAuth error:", error.error);
console.error("Description:", error.error_description);
} else {
console.error("Unexpected error:", error);
}
}
}
import open from "open";
import { getAuthCode } from "oauth-callback";
const result = await getAuthCode({
authorizationUrl: authUrl,
launch: open,
port: 8080, // Use custom port (default: 3000)
timeout: 60000, // Custom timeout in ms (default: 30000)
});
The browserAuth() function provides a drop-in OAuth provider for the Model Context Protocol SDK:
import { browserAuth, inMemoryStore } from "oauth-callback/mcp";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const serverUrl = new URL("https://mcp.notion.com/mcp");
// Create MCP-compatible OAuth provider
const authProvider = browserAuth({
port: 3000,
scope: "read write",
launch: open, // Opens browser for OAuth consent
store: inMemoryStore(), // Or fileStore() for persistence
});
const client = new Client(
{ name: "my-app", version: "1.0.0" },
{ capabilities: {} },
);
// Connect with OAuth retry: first attempt completes OAuth and saves tokens,
// but SDK returns before checking them. Second attempt succeeds.
async function connectWithOAuthRetry() {
const transport = new StreamableHTTPClientTransport(serverUrl, {
authProvider,
});
try {
await client.connect(transport);
} catch (error: any) {
if (error.message === "Unauthorized") {
await client.connect(
new StreamableHTTPClientTransport(serverUrl, { authProvider }),
);
} else throw error;
}
}
await connectWithOAuthRetry();
import { browserAuth, inMemoryStore, fileStore } from "oauth-callback/mcp";
// Ephemeral storage (tokens lost on restart)
const ephemeralAuth = browserAuth({
launch: open,
store: inMemoryStore(),
});
// Persistent file storage (default: ~/.mcp/tokens.json)
const persistentAuth = browserAuth({
launch: open,
store: fileStore(),
storeKey: "my-app-tokens", // Namespace for multiple apps
});
// Custom file location
const customAuth = browserAuth({
launch: open,
store: fileStore("/path/to/tokens.json"),
});
If you have pre-registered OAuth client credentials:
const authProvider = browserAuth({
clientId: "your-client-id",
clientSecret: "your-client-secret",
scope: "read write",
launch: open, // Opens browser for OAuth consent
store: fileStore(), // Persist tokens across sessions
});
import open from "open";
// With custom HTML templates and logging
const result = await getAuthCode({
authorizationUrl: authUrl,
launch: open,
port: 3000,
hostname: "127.0.0.1", // Bind to specific IP
successHtml: "<h1>Success! You can close this window.</h1>",
errorHtml: "<h1>Error: {{error_description}}</h1>",
onRequest: (req) => {
console.log(`Received request: ${req.method} ${req.url}`);
},
});
// With cancellation support
const controller = new AbortController();
// Cancel after 10 seconds
setTimeout(() => controller.abort(), 10000);
try {
const result = await getAuthCode({
authorizationUrl: authUrl,
signal: controller.signal,
});
} catch (error) {
if (error.message === "Operation aborted") {
console.log("Authorization was cancelled");
}
}
getAuthCode(input)Starts a local HTTP server to capture OAuth callbacks. Optionally launches the authorization URL via the launch callback.
input (string | GetAuthCodeOptions): Either a string containing the OAuth authorization URL, or an options object with:authorizationUrl (string): The OAuth authorization URLport (number): Port for the local server (default: 3000)hostname (string): Hostname to bind the server to (default: "localhost")callbackPath (string): URL path for the OAuth callback (default: "/callback")timeout (number): Timeout in milliseconds (default: 30000)launch (function): Optional callback to launch the authorization URL (e.g., open)successHtml (string): Custom HTML to display on successful authorizationerrorHtml (string): Custom HTML to display on authorization errorsignal (AbortSignal): AbortSignal for cancellation supportonRequest (function): Callback fired when a request is received (for logging/debugging)Promise that resolves to:
{
code: string; // Authorization code
state?: string; // State parameter (if provided)
[key: string]: any; // Additional query parameters
}
OAuthError: When the OAuth provider returns an error (always thrown for OAuth errors)Error: For timeout or other unexpected errorsOAuthErrorCustom error class for OAuth-specific errors.
class OAuthError extends Error {
error: string; // OAuth error code
error_description?: string; // Human-readable error description
error_uri?: string; // URI with error information
}
browserAuth(options)Available from oauth-callback/mcp. Creates an MCP SDK-compatible OAuth provider for browser-based flows. Handles Dynamic Client Registration (DCR) and token storage. Expired tokens trigger re-authentication.
options (BrowserAuthOptions): Configuration object with:port (number): Port for callback server (default: 3000)hostname (string): Hostname to bind to (default: "localhost")callbackPath (string): URL path for OAuth callback (default: "/callback")scope (string): OAuth scopes to requestclientId (string): Pre-registered client ID (optional)clientSecret (string): Pre-registered client secret (optional)store (TokenStore): Token storage implementation (default: inMemoryStore())storeKey (string): Storage key for tokens (default: "mcp-tokens")launch (function): Callback to launch auth URL (e.g., open)authTimeout (number): Authorization timeout in ms (default: 300000)successHtml (string): Custom success page HTMLerrorHtml (string): Custom error page HTMLonRequest (function): Request logging callbackOAuthClientProvider compatible with MCP SDK transports.
inMemoryStore()Available from oauth-callback/mcp. Creates an ephemeral in-memory token store. Tokens are lost when the process exits.
TokenStore implementation for temporary token storage.
fileStore(filepath?)Available from oauth-callback/mcp. Creates a persistent file-based token store.
filepath (string): Optional custom file path (default: ~/.mcp/tokens.json)TokenStore implementation for persistent token storage.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Your App │────▶│Local Server │────▶│ Browser │────▶│OAuth Server │
│ │ │ :3000 │ │ │ │ │
│ getAuthCode │ │ │◀────│ Callback │◀────│ Redirect │
│ ▼ │◀────│ Returns │ │ /callback │ │ with code │
│ {code} │ │ auth code │ │ │ │ │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
launch callback provided)Try the library instantly with the built-in demo that includes a mock OAuth server:
# Run the demo - no credentials needed!
bun run example:demo
# Run without opening browser (for CI/testing)
bun run examples/demo.ts --no-browser
The demo showcases:
For testing with GitHub OAuth:
# Set up GitHub OAuth App credentials
export GITHUB_CLIENT_ID="your_client_id"
export GITHUB_CLIENT_SECRET="your_client_secret"
# Run the GitHub example
bun run example:github
This example demonstrates:
For testing with Notion's Model Context Protocol server:
# No credentials needed - uses Dynamic Client Registration!
bun run example:notion
This example demonstrates:
browserAuth() provider with MCP SDK's StreamableHTTPClientTransportinMemoryStore() for ephemeral sessions# Install dependencies
bun install
# Run tests
bun test
# Build
bun run build
# Run documentation locally
bun run docs:dev # Start VitePress dev server at http://localhost:5173
# Run examples
bun run example:demo # Interactive demo
bun run example:github # GitHub OAuth example
bun run example:notion # Notion MCP example with Dynamic Client Registration
http://localhost:[port]/callbackIf port 3000 is already in use, specify a different port:
const result = await getAuthCode({
authorizationUrl: authUrl,
launch: open,
port: 8080,
});
On first run, your OS may show a firewall warning. Allow the connection for localhost only.
If the browser doesn't open automatically, manually navigate to the authorization URL.
Contributions are welcome! See CONTRIBUTING.md for setup instructions.
Maintainers wanted — we're looking for people to help maintain this project. If interested, reach out on Discord or open an issue.
This project is released under the MIT License. Feel free to use it in your projects, modify it to suit your needs, and share it with others. We believe in open source and hope this tool makes OAuth integration easier for everyone!
Support this project by becoming a backer. Your logo will show up here with a link to your website.
Found a bug or have a question? Please open an issue on the GitHub issue tracker and we'll be happy to help. If this project saves you time and you'd like to support its continued development, consider becoming a sponsor. Every bit of support helps maintain and improve this tool for the community. Thank you!
Добавь это в claude_desktop_config.json и перезапусти Claude Desktop.
{
"mcpServers": {
"oauth-callback": {
"command": "npx",
"args": [
"-y",
"oauth-callback"
]
}
}
}