### Run Demo Example Source: https://github.com/kriasoft/oauth-callback/blob/main/examples/README.md Execute the self-contained demo using a mock OAuth server. This is useful for understanding library functionality without external setup. It can be run with or without a browser. ```bash bun run example:demo # Without browser (for CI/testing) bun run examples/demo.ts --no-browser ``` -------------------------------- ### Testing with GitHub Provider Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/getting-started.md Guides users on how to test the implementation with GitHub by setting up client ID and secret credentials in a .env file and running the GitHub example. ```bash # Set credentials in .env file GITHUB_CLIENT_ID=your_client_id GITHUB_CLIENT_SECRET=your_client_secret # Run example bun run example:github ``` -------------------------------- ### Simple MCP Client Setup Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/browser-auth.md Use browserAuth with default settings for a basic OAuth flow with the MCP transport. Ensure the 'open' package is installed to launch the browser. ```typescript import open from "open"; import { browserAuth } from "oauth-callback/mcp"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; // Create OAuth provider - pass open to launch browser const authProvider = browserAuth({ launch: open }); // Use with MCP transport const transport = new StreamableHTTPClientTransport( new URL("https://mcp.example.com"), { authProvider }, ); const client = new Client( { name: "my-app", version: "1.0.0" }, { capabilities: {} }, ); await client.connect(transport); ``` -------------------------------- ### Run Notion MCP Example Source: https://github.com/kriasoft/oauth-callback/blob/main/README.md Execute the Notion MCP example using `bun run example:notion`. This example uses Dynamic Client Registration and integrates with MCP SDK transports. ```bash # No credentials needed - uses Dynamic Client Registration! bun run example:notion ``` -------------------------------- ### Installation Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/index.md Install the oauth-callback package using your preferred package manager. Bun is recommended for optimal performance. ```bash # Using Bun (recommended) bun add oauth-callback # Using npm npm install oauth-callback # Using pnpm pnpm add oauth-callback ``` -------------------------------- ### Run GitHub OAuth Example Source: https://github.com/kriasoft/oauth-callback/blob/main/README.md Execute the GitHub OAuth example using `bun run example:github`. This demonstrates setting up OAuth with GitHub, handling callbacks, and exchanging codes for access tokens. ```bash # Run the GitHub example bun run example:github ``` -------------------------------- ### Quick Start: Get Authorization Code Source: https://github.com/kriasoft/oauth-callback/blob/main/README.md A simple example demonstrating how to obtain an authorization code using the getAuthCode function. Pass the 'open' function to automatically launch the browser for user authorization. ```typescript 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); ``` ```typescript // 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() }); ``` -------------------------------- ### Install Dependencies with Bun Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/examples/notion.md Install the oauth-callback and @modelcontextprotocol/sdk packages using Bun. ```bash bun add oauth-callback @modelcontextprotocol/sdk ``` -------------------------------- ### Run Examples in Non-Interactive Mode Source: https://github.com/kriasoft/oauth-callback/blob/main/examples/README.md Execute the demo and GitHub examples in a non-interactive mode, suitable for CI or testing. The GitHub example requires environment variables for client ID and secret. ```bash # Demo (no browser) bun run examples/demo.ts --no-browser # GitHub (requires credentials) GITHUB_CLIENT_ID=xxx GITHUB_CLIENT_SECRET=yyy bun run examples/github.ts ``` -------------------------------- ### Install oauth-callback with Bun Source: https://github.com/kriasoft/oauth-callback/blob/main/README.md Install the oauth-callback package and the optional 'open' package using Bun. The 'open' package is recommended for launching the browser. ```bash bun add oauth-callback open ``` -------------------------------- ### Custom Storage Implementation Example Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/browser-auth.md Illustrates how to implement a custom token store, using Redis as an example, by adhering to the `TokenStore` interface. ```APIDOC ## Custom Storage Implementation Implement your own storage backend: ```typescript import { TokenStore, Tokens } from "oauth-callback/mcp"; class RedisStore implements TokenStore { constructor(private redis: RedisClient) {} async get(key: string): Promise { const data = await this.redis.get(key); return data ? JSON.parse(data) : null; } async set(key: string, tokens: Tokens): Promise { await this.redis.set(key, JSON.stringify(tokens)); } async delete(key: string): Promise { await this.redis.del(key); } } // Use custom store const authProvider = browserAuth({ launch: open, store: new RedisStore(redisClient), }); ``` ``` -------------------------------- ### Run Documentation Locally Source: https://github.com/kriasoft/oauth-callback/blob/main/README.md Start the VitePress development server to view documentation locally. ```bash # Run documentation locally bun run docs:dev # Start VitePress dev server at http://localhost:5173 ``` -------------------------------- ### Install Dependencies with npm Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/examples/notion.md Install the oauth-callback and @modelcontextprotocol/sdk packages using npm. ```bash npm install oauth-callback @modelcontextprotocol/sdk ``` -------------------------------- ### Install Dependencies with pnpm Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/examples/notion.md Install the oauth-callback and @modelcontextprotocol/sdk packages using pnpm. ```bash pnpm add oauth-callback @modelcontextprotocol/sdk ``` -------------------------------- ### Install oauth-callback with npm Source: https://github.com/kriasoft/oauth-callback/blob/main/README.md Install the oauth-callback package and the optional 'open' package using npm. The 'open' package is recommended for launching the browser. ```bash npm install oauth-callback open ``` -------------------------------- ### Install oauth-callback with pnpm Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/getting-started.md Install the oauth-callback package and the optional 'open' package using pnpm. ```bash pnpm add oauth-callback open ``` -------------------------------- ### Install oauth-callback with Yarn Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/getting-started.md Install the oauth-callback package and the optional 'open' package using Yarn. ```bash yarn add oauth-callback open ``` -------------------------------- ### Install Dependencies Source: https://github.com/kriasoft/oauth-callback/blob/main/README.md Install project dependencies using Bun package manager. ```bash # Install dependencies bun install ``` -------------------------------- ### Complete OAuth Callback Example Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/blog/02-building-oauth-callback.md This function demonstrates a full OAuth 2.0 authorization code flow for CLI tools. It starts a local server, opens the authorization URL in the browser, waits for the callback, and handles success or error responses. Ensure the server is stopped in the finally block for resource cleanup. ```typescript import { createCallbackServer } from "./server"; import { spawn } from "child_process"; export async function getAuthCode(authUrl: string): Promise { const server = createCallbackServer(); try { // Start the server await server.start({ port: 3000, hostname: "localhost", successHtml: "

Success! You can close this window.

", errorHtml: "

Error: {{error_description}}

", }); // Open the browser const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open"; spawn(opener, [authUrl], { detached: true }); // Wait for callback const result = await server.waitForCallback("/callback", 30000); if (result.error) { throw new Error(`OAuth error: ${result.error_description}`); } return result.code!; } finally { // Always cleanup await server.stop(); } } // Usage const code = await getAuthCode( "https://github.com/login/oauth/authorize?" + "client_id=xxx&redirect_uri=http://localhost:3000/callback", ); ``` -------------------------------- ### Run GitHub OAuth Example Source: https://github.com/kriasoft/oauth-callback/blob/main/examples/README.md Integrate with GitHub using a real-world OAuth 2.0 authorization code flow. Requires setting up a GitHub OAuth App and exporting client credentials. The example demonstrates token exchange and user profile fetching. ```bash bun run example:github ``` -------------------------------- ### Complete Notion MCP Client Example Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/examples/notion.md This TypeScript code provides a full implementation of a Notion MCP client. It handles authentication using `browserAuth`, establishes a connection, performs a search, and disconnects. Ensure you have the necessary dependencies installed and configured. ```typescript import open from "open"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { browserAuth, fileStore } from "oauth-callback/mcp"; class NotionMCPClient { private client?: Client; private authProvider: any; constructor() { this.authProvider = browserAuth({ launch: open, port: 3000, scope: "read write", store: fileStore("~/.mcp/notion.json"), authTimeout: 300000, // 5 minutes onRequest: this.logRequest.bind(this), }); } private logRequest(req: Request) { const url = new URL(req.url); console.log(`[OAuth] ${req.method} ${url.pathname}`); } async connect(): Promise { const serverUrl = new URL("https://mcp.notion.com/mcp"); const transport = new StreamableHTTPClientTransport(serverUrl, { authProvider: this.authProvider, }); this.client = new Client( { name: "notion-client", version: "1.0.0" }, { capabilities: {} }, ); try { await this.client.connect(transport); console.log("✅ Connected to Notion MCP"); } catch (error: any) { if (error.message.includes("Unauthorized")) { throw new Error("Authorization required. Please check your browser."); } throw error; } } async search(query: string): Promise { if (!this.client) throw new Error("Not connected"); return await this.client.callTool("search_objects", { query, limit: 10, }); } async disconnect(): Promise { if (this.client) { await this.client.close(); this.client = undefined; } } } // Usage async function main() { const notion = new NotionMCPClient(); try { await notion.connect(); const results = await notion.search("project roadmap"); console.log("Search results:", results); await notion.disconnect(); } catch (error) { console.error("Error:", error); } } main(); ``` -------------------------------- ### Custom Callback Server Implementation Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/types.md Implement a custom callback server by extending the CallbackServer interface. This example shows how to start an HTTP server, wait for an OAuth callback on a specific path, and stop the server. ```typescript import type { CallbackServer, ServerOptions, CallbackResult, } from "oauth-callback"; class CustomCallbackServer implements CallbackServer { private server?: HttpServer; async start(options: ServerOptions): Promise { // Start HTTP server this.server = await createServer(options); } async waitForCallback( path: string, timeout: number, ): Promise { // Wait for OAuth callback return new Promise((resolve, reject) => { const timer = setTimeout(() => { reject(new Error("Timeout waiting for callback")); }, timeout); this.server.on("request", (req) => { if (req.url.startsWith(path)) { clearTimeout(timer); const params = parseQueryParams(req.url); resolve(params); } }); }); } async stop(): Promise { // Stop server await this.server?.close(); } } ``` -------------------------------- ### Add New OAuth Provider Example Source: https://github.com/kriasoft/oauth-callback/blob/main/examples/README.md Instructions for adding a new OAuth provider example. This involves creating a new TypeScript file, following the structure of existing examples, and updating the package.json with a new script. ```json "example:provider": "bun run examples/provider.ts" ``` -------------------------------- ### Custom SQLite Token Store Implementation Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/storage-providers.md Implement a custom 'TokenStore' using SQLite for token storage. This example sets up a 'tokens' table and provides methods for getting, setting, and deleting tokens. ```typescript import { TokenStore, Tokens } from "oauth-callback/mcp"; import Database from "better-sqlite3"; class SQLiteTokenStore implements TokenStore { private db: Database.Database; constructor(dbPath = "./tokens.db") { this.db = new Database(dbPath); this.init(); } private init() { this.db.exec(` CREATE TABLE IF NOT EXISTS tokens ( key TEXT PRIMARY KEY, access_token TEXT NOT NULL, refresh_token TEXT, expires_at INTEGER, scope TEXT, updated_at INTEGER DEFAULT (unixepoch()) ) `); } async get(key: string): Promise { const row = this.db.prepare("SELECT * FROM tokens WHERE key = ?").get(key); if (!row) return null; return { accessToken: row.access_token, refreshToken: row.refresh_token, expiresAt: row.expires_at, scope: row.scope, }; } async set(key: string, tokens: Tokens): Promise { this.db .prepare( ` INSERT OR REPLACE INTO tokens (key, access_token, refresh_token, expires_at, scope) VALUES (?, ?, ?, ?, ?) `, ) .run( key, tokens.accessToken, tokens.refreshToken, tokens.expiresAt, tokens.scope, ); } async delete(key: string): Promise { this.db.prepare("DELETE FROM tokens WHERE key = ?").run(key); } } // Usage import open from "open"; const authProvider = browserAuth({ launch: open, store: new SQLiteTokenStore("./oauth-tokens.db"), }); ``` -------------------------------- ### BrowserAuthOptions Usage Example Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/types.md Example demonstrating how to configure and use BrowserAuthOptions with the browserAuth function. ```APIDOC #### Usage Example ```typescript import type { BrowserAuthOptions } from "oauth-callback/mcp"; import { browserAuth, fileStore } from "oauth-callback/mcp"; const options: BrowserAuthOptions = { // Dynamic Client Registration - no credentials needed scope: "read write", // Custom server configuration port: 8080, hostname: "127.0.0.1", // Persistent storage store: fileStore("~/.myapp/tokens.json"), storeKey: "production", // Timeout authTimeout: 600000, // 10 minutes // Custom UI successHtml: "

Success!

", // Debugging onRequest: (req) => { console.log(`[OAuth] ${new URL(req.url).pathname}`); }, }; const authProvider = browserAuth(options); ``` ``` -------------------------------- ### Custom Redis Token Store Implementation Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/storage-providers.md Implement a custom 'TokenStore' using Redis for token storage. This example includes methods for getting, setting, and deleting tokens, with automatic handling of Time-To-Live (TTL) based on 'expiresAt'. ```typescript import { TokenStore, Tokens } from "oauth-callback/mcp"; import Redis from "ioredis"; class RedisTokenStore implements TokenStore { private redis: Redis; private prefix: string; constructor(redis: Redis, prefix = "oauth:") { this.redis = redis; this.prefix = prefix; } async get(key: string): Promise { const data = await this.redis.get(this.prefix + key); return data ? JSON.parse(data) : null; } async set(key: string, tokens: Tokens): Promise { const ttl = tokens.expiresAt ? Math.floor((tokens.expiresAt - Date.now()) / 1000) : undefined; if (ttl && ttl > 0) { await this.redis.setex(this.prefix + key, ttl, JSON.stringify(tokens)); } else { await this.redis.set(this.prefix + key, JSON.stringify(tokens)); } } async delete(key: string): Promise { await this.redis.del(this.prefix + key); } } // Usage import open from "open"; const redis = new Redis(); const authProvider = browserAuth({ launch: open, store: new RedisTokenStore(redis), }); ``` -------------------------------- ### Notion MCP Integration Example Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/browser-auth.md A complete example demonstrating Dynamic Client Registration (DCR) with Notion MCP, including connecting, listing tools, and calling a tool. Uses file storage for persistence. ```typescript import open from "open"; import { browserAuth, fileStore } from "oauth-callback/mcp"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; async function connectToNotion() { // No client credentials needed - uses DCR! const authProvider = browserAuth({ launch: open, // Opens browser for OAuth consent store: fileStore(), // Persist tokens and client registration scope: "read write", onRequest: (req) => { console.log(`[Notion OAuth] ${new URL(req.url).pathname}`); }, }); const transport = new StreamableHTTPClientTransport( new URL("https://mcp.notion.com/mcp"), { authProvider }, ); const client = new Client( { name: "notion-client", version: "1.0.0" }, { capabilities: {} }, ); try { await client.connect(transport); console.log("Connected to Notion MCP!"); // List available tools const tools = await client.listTools(); console.log("Available tools:", tools); // Use a tool const result = await client.callTool("search", { query: "meeting notes", }); console.log("Search results:", result); } catch (error) { console.error("Failed to connect:", error); } finally { await client.close(); } } connectToNotion(); ``` -------------------------------- ### In-Memory Store Example Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/browser-auth.md Provides an example of using the in-memory token store, which is suitable for development, testing, or short-lived sessions as tokens are lost on restart. ```APIDOC ## In-Memory Store Ephemeral storage (tokens lost on restart): ```typescript import open from "open"; import { browserAuth, inMemoryStore } from "oauth-callback/mcp"; const authProvider = browserAuth({ launch: open, store: inMemoryStore(), }); ``` **Use cases:** - Development and testing - Short-lived CLI sessions - Maximum security (no persistence) ``` -------------------------------- ### ServerOptions Usage Example Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/types.md Example of configuring `ServerOptions` for an OAuth callback server. Demonstrates setting a specific port, hostname, and providing custom HTML for a success message that includes a script to close the window. ```typescript import type { ServerOptions } from "oauth-callback"; const serverOptions: ServerOptions = { port: 3000, hostname: "127.0.0.1", successHtml: "

Authorization successful!

", onRequest: (req) => { const url = new URL(req.url); console.log(`[${req.method}] ${url.pathname}`); }, }; ``` -------------------------------- ### Set Up GitHub OAuth Credentials Source: https://github.com/kriasoft/oauth-callback/blob/main/README.md Configure GitHub OAuth by exporting your client ID and secret as environment variables before running the GitHub example. ```bash # Set up GitHub OAuth App credentials export GITHUB_CLIENT_ID="your_client_id" export GITHUB_CLIENT_SECRET="your_client_secret" ``` -------------------------------- ### Complete OAuth Flow Example Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/getting-started.md Demonstrates the complete OAuth flow: authenticating, exchanging the code for a token, and fetching user information. This is a high-level example. ```typescript // Complete flow async function main() { const code = await authenticate(); const token = await exchangeCodeForToken(code); const user = await getUserInfo(token); console.log(`Hello, ${user.name}! 👋`); console.log(`Email: ${user.email}`); } main().catch(console.error); ``` -------------------------------- ### GetAuthCodeOptions Usage Example Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/types.md Example demonstrating how to configure and use `GetAuthCodeOptions` with custom settings for port, timeout, and HTML responses. Includes an `onRequest` handler for logging requests. ```typescript import type { GetAuthCodeOptions } from "oauth-callback"; const options: GetAuthCodeOptions = { authorizationUrl: "https://oauth.example.com/authorize?...", port: 8080, timeout: 60000, successHtml: "

Success!

", errorHtml: "

Error: {{error_description}}

", onRequest: (req) => console.log(`Request: ${req.url}`), }; const result = await getAuthCode(options); ``` -------------------------------- ### File Token Store Example (Custom Location) Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/browser-auth.md Demonstrates configuring browser authentication with a file-based token store at a custom specified path. This allows for flexible storage management. ```typescript // Custom location const customAuth = browserAuth({ launch: open, store: fileStore("/path/to/tokens.json"), }); ``` -------------------------------- ### Run Interactive Demo Source: https://github.com/kriasoft/oauth-callback/blob/main/README.md Execute the interactive demo using `bun run example:demo`. This showcases various scenarios including DCR, success/error pages, and token simulation. ```bash # Run the demo - no credentials needed! bun run example:demo ``` -------------------------------- ### Using inMemoryStore with browserAuth Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/storage-providers.md Example of initializing the browserAuth provider with an in-memory token store. ```typescript import open from "open"; import { browserAuth, inMemoryStore } from "oauth-callback/mcp"; const authProvider = browserAuth({ launch: open, store: inMemoryStore(), }); ``` -------------------------------- ### Runtime Detection Examples Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/core-concepts.md The library adapts to different runtime environments like Node.js, Deno, and Bun. ```typescript // Node.js import { createServer } from "node:http"; // Deno Deno.serve({ port: 3000 }); // Bun Bun.serve({ port: 3000 }); ``` -------------------------------- ### File Store Example Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/browser-auth.md Demonstrates how to use the file store for persistent token storage. Tokens are saved to a JSON file, with options for default or custom file locations. ```APIDOC ## File Store Persistent storage to JSON file: ```typescript import open from "open"; import { browserAuth, fileStore } from "oauth-callback/mcp"; // Default location: ~/.mcp/tokens.json const authProvider = browserAuth({ launch: open, store: fileStore(), }); // Custom location const customAuth = browserAuth({ launch: open, store: fileStore("/path/to/tokens.json"), }); ``` **Use cases:** - Desktop applications - Long-running services - Multi-session authentication ``` -------------------------------- ### getAuthCode(input) Source: https://github.com/kriasoft/oauth-callback/blob/main/README.md Starts a local HTTP server to capture OAuth callbacks. Optionally launches the authorization URL via the `launch` callback. ```APIDOC ## API Reference ### `getAuthCode(input)` Starts a local HTTP server to capture OAuth callbacks. Optionally launches the authorization URL via the `launch` callback. #### Parameters - `input` (string | GetAuthCodeOptions): Either a string containing the OAuth authorization URL, or an options object with: - `authorizationUrl` (string): The OAuth authorization URL - `port` (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 authorization - `errorHtml` (string): Custom HTML to display on authorization error - `signal` (AbortSignal): AbortSignal for cancellation support - `onRequest` (function): Callback fired when a request is received (for logging/debugging) #### Returns Promise that resolves to: ```typescript { code: string; // Authorization code state?: string; // State parameter (if provided) [key: string]: any; // Additional query parameters } ``` #### Throws - `OAuthError`: When the OAuth provider returns an error (always thrown for OAuth errors) - `Error`: For timeout or other unexpected errors ### `OAuthError` Custom error class for OAuth-specific errors. ```typescript class OAuthError extends Error { error: string; // OAuth error code error_description?: string; // Human-readable error description error_uri?: string; // URI with error information } ``` ``` -------------------------------- ### Custom Redis Token Store Implementation Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/browser-auth.md Provides an example of implementing a custom TokenStore using Redis. This demonstrates how to integrate external storage solutions with the SDK. ```typescript import { TokenStore, Tokens } from "oauth-callback/mcp"; class RedisStore implements TokenStore { constructor(private redis: RedisClient) {} async get(key: string): Promise { const data = await this.redis.get(key); return data ? JSON.parse(data) : null; } async set(key: string, tokens: Tokens): Promise { await this.redis.set(key, JSON.stringify(tokens)); } async delete(key: string): Promise { await this.redis.del(key); } } // Use custom store const authProvider = browserAuth({ launch: open, store: new RedisStore(redisClient), }); ``` -------------------------------- ### Dynamic Client Registration (DCR) Example Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/browser-auth.md Demonstrates how to use the `browserAuth` function with dynamic client registration, where no pre-registration of OAuth clients is needed. The SDK automatically registers and stores client credentials. ```APIDOC ## DCR Example No pre-registration needed: ```typescript import open from "open"; // No clientId or clientSecret required! const authProvider = browserAuth({ scope: "read write", launch: open, store: fileStore(), // Persist dynamically registered client }); // The provider will automatically: // 1. Register a new OAuth client on first use // 2. Store the client credentials // 3. Reuse them for future sessions ``` ``` -------------------------------- ### Custom Container Examples Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/markdown-examples.md Shows the usage of custom markdown containers for displaying different types of information such as info, tip, warning, danger, and details. ```md ::: info This is an info box. ::: ::: tip This is a tip. ::: ::: warning This is a warning. ::: ::: danger This is a dangerous warning. ::: ::: details This is a details block. ::: ``` -------------------------------- ### MCP SDK with Pre-configured Client Credentials Source: https://github.com/kriasoft/oauth-callback/blob/main/README.md Set up browser authentication for the MCP SDK using pre-registered client ID and secret. This example also demonstrates persisting tokens across sessions. ```typescript 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 }); ``` -------------------------------- ### Troubleshooting: Browser Not Opening Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/browser-auth.md Conditionally launch the browser based on the environment, for example, by disabling it in CI environments. ```typescript import open from "open"; // Conditionally open browser based on environment const authProvider = browserAuth({ launch: process.env.CI ? () => {} : open, }); ``` -------------------------------- ### Basic OAuth Flow Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/index.md Use this snippet to get an authorization code by providing your OAuth URL. It automatically handles server setup, browser launching, and cleanup. ```typescript import { getAuthCode } from "oauth-callback"; // Just pass your OAuth URL - that's it! const result = await getAuthCode( "https://github.com/login/oauth/authorize?client_id=xxx", ); console.log("Auth code:", result.code); ``` -------------------------------- ### Non-Blocking OAuth Callback Server Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/blog/03-browser-auto-open.md Starts a callback server and launches the browser asynchronously. Ensures the server is ready before waiting for the callback, even if browser launch fails or is slow. ```typescript export async function getAuthCode(options: GetAuthCodeOptions) { const server = createCallbackServer(); try { // Start server first await server.start({ port: options.port, hostname: options.hostname, }); // Launch browser without waiting (best-effort) if (options.launch) { Promise.resolve() .then(() => options.launch(options.authorizationUrl)) .catch(() => {}); } // Server is ready regardless of browser status const result = await server.waitForCallback( options.callbackPath, options.timeout, ); return result; } finally { await server.stop(); } } ``` -------------------------------- ### Implement Custom Token Storage with Redis Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/index.md Provides an example of implementing a custom `TokenStore` using Redis for storing and retrieving OAuth tokens. This allows for persistent storage across application restarts. ```typescript import { TokenStore, Tokens } from "oauth-callback/mcp"; class RedisStore implements TokenStore { async get(key: string): Promise { // Implementation } async set(key: string, tokens: Tokens): Promise { // Implementation } // ... other methods } const authProvider = browserAuth({ launch: open, store: new RedisStore(), }); ``` -------------------------------- ### File Token Store Example (Default Location) Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/browser-auth.md Configures browser authentication with a file-based token store, persisting tokens to a default JSON file location. This is ideal for desktop applications and long-running services. ```typescript import open from "open"; import { browserAuth, fileStore } from "oauth-callback/mcp"; // Default location: ~/.mcp/tokens.json const authProvider = browserAuth({ launch: open, store: fileStore(), }); ``` -------------------------------- ### Integrate MCP SDK with Browser Authentication Source: https://github.com/kriasoft/oauth-callback/blob/main/README.md Use the browserAuth function to create an OAuth provider for the MCP SDK. This example shows basic setup and a retry mechanism for connecting to the client. ```typescript 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(); ``` -------------------------------- ### Local Testing with Demo Server Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/getting-started.md Instructions for running the interactive demo to test the library locally using a mock OAuth server, without needing real OAuth credentials. ```bash # Run interactive demo bun run example:demo # The demo includes a mock OAuth server for testing ``` -------------------------------- ### Get OAuth Authorization Code Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/getting-started.md Use getAuthCode to capture an OAuth authorization code. This function starts a local server, opens the browser, captures the callback, and returns the code and state. The 'open' package is used to launch the browser. ```typescript import open from "open"; import { getAuthCode } from "oauth-callback"; // Construct your OAuth authorization URL 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: crypto.randomUUID(), // For CSRF protection }); // Get the authorization code (launch: open opens the browser) const result = await getAuthCode({ authorizationUrl: authUrl, launch: open }); console.log("Authorization code:", result.code); console.log("State:", result.state); ``` -------------------------------- ### Dynamic Client Registration (DCR) Example Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/browser-auth.md Demonstrates setting up browser authentication with Dynamic Client Registration. No pre-registration of OAuth apps is needed as the SDK handles client registration and credential persistence automatically. ```typescript import open from "open"; // No clientId or clientSecret required! const authProvider = browserAuth({ scope: "read write", launch: open, store: fileStore(), // Persist dynamically registered client }); // The provider will automatically: // 1. Register a new OAuth client on first use // 2. Store the client credentials // 3. Reuse them for future sessions ``` -------------------------------- ### Error Handling Example Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/types.md Example demonstrating how to catch and handle OAuthError and TimeoutError exceptions. ```APIDOC #### Error Handling Example ```typescript import { OAuthError, TimeoutError } from "oauth-callback"; import type { CallbackResult } from "oauth-callback"; function handleAuthResult(result: CallbackResult) { // Check for OAuth errors in result if (result.error) { throw new OAuthError( result.error, result.error_description, result.error_uri, ); } if (!result.code) { throw new Error("No authorization code received"); } return result.code; } // Usage with proper error handling try { const code = await getAuthCode(authUrl); } catch (error) { if (error instanceof OAuthError) { console.error(`OAuth error: ${error.error}`); } else if (error instanceof TimeoutError) { console.error("Authorization timed out"); } else { console.error("Unexpected error:", error); } } ``` ``` -------------------------------- ### Build Project Source: https://github.com/kriasoft/oauth-callback/blob/main/README.md Build the project using the Bun package manager. ```bash # Build bun run build ``` -------------------------------- ### Testing with Notion MCP Provider Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/getting-started.md Instructions for testing with the Notion MCP provider, noting that no specific credentials are required as it utilizes Dynamic Client Registration. ```bash # No credentials needed - uses Dynamic Client Registration bun run example:notion ``` -------------------------------- ### TimeoutError — Callback Timeout Error Class Source: https://context7.com/kriasoft/oauth-callback/llms.txt Thrown when no OAuth callback is received within the configured `timeout` window. The timeout starts when the callback server is ready (after `start()` resolves), not when the browser is launched. ```APIDOC ## `TimeoutError` — Callback Timeout Error Class Thrown when no OAuth callback is received within the configured `timeout` window. The timeout starts when the callback server is ready (after `start()` resolves), not when the browser is launched. ```typescript import { getAuthCode, TimeoutError } from "oauth-callback"; import open from "open"; try { const result = await getAuthCode({ authorizationUrl: "https://example.com/oauth/authorize?...", launch: open, timeout: 15000, // 15 seconds }); console.log("Code:", result.code); } catch (error) { if (error instanceof TimeoutError) { console.error("Timeout:", error.message); // "OAuth callback timeout after 15000ms waiting for /callback" console.error("Name:", error.name); // "TimeoutError" } } ``` ``` -------------------------------- ### CallbackResult Usage Example Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/types.md Example of handling a `CallbackResult` object. It checks for errors, logs the authorization code if present, and includes a crucial step for validating the state parameter to prevent CSRF attacks before exchanging the code for tokens. ```typescript import type { CallbackResult } from "oauth-callback"; function handleCallback(result: CallbackResult) { if (result.error) { console.error(`OAuth error: ${result.error}`); if (result.error_description) { console.error(`Details: ${result.error_description}`); } return; } if (result.code) { console.log(`Authorization code: ${result.code}`); // Validate state for CSRF protection if (result.state !== expectedState) { throw new Error("State mismatch - possible CSRF attack"); } // Exchange code for tokens exchangeCodeForTokens(result.code); } } ``` -------------------------------- ### Get Current Linear Cycle Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/examples/linear.md Retrieve details of the current development cycle for a specific team. ```typescript // Get current cycle const currentCycle = await client.callTool("get_current_cycle", { teamId: "ENG", }); ``` -------------------------------- ### Open URL Using Linux Desktop Environment Preferences Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/blog/03-browser-auto-open.md Attempts to open a URL using a list of common Linux commands, respecting environment variables like BROWSER. It iterates through openers until one succeeds. ```bash const openers = [ process.env.BROWSER, "xdg-open", "gnome-open", "kde-open", ].filter(Boolean); for (const opener of openers) { try { await spawn(opener, [url]); break; } catch { // Try next opener } } ``` -------------------------------- ### Run Tests Source: https://github.com/kriasoft/oauth-callback/blob/main/README.md Execute the project's test suite using the Bun package manager. ```bash # Run tests bun test ``` -------------------------------- ### Get Linear Project Details Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/examples/linear.md Retrieve information about a specific Linear project by its ID using the `get_project` tool. ```typescript // Get project details const project = await client.callTool("get_project", { projectId: "PROJ-123", }); ``` -------------------------------- ### TokenStore Interface Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/browser-auth.md Defines the basic interface for token storage, including methods to get, set, and delete tokens. ```APIDOC ## TokenStore (Basic) ```typescript interface TokenStore { get(key: string): Promise; set(key: string, tokens: Tokens): Promise; delete(key: string): Promise; } ``` ``` -------------------------------- ### Custom TokenStore Implementation Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/types.md Example implementation of the TokenStore interface using an in-memory Map. Includes a check for storing expired tokens. ```typescript import type { Tokens, TokenStore } from "oauth-callback/mcp"; class CustomTokenStore implements TokenStore { private storage = new Map(); async get(key: string): Promise { return this.storage.get(key) ?? null; } async set(key: string, tokens: Tokens): Promise { // Check if token is expired if (tokens.expiresAt && Date.now() >= tokens.expiresAt) { console.warn("Storing expired token"); } this.storage.set(key, tokens); } async delete(key: string): Promise { this.storage.delete(key); } } ``` -------------------------------- ### Run Demo Without Browser Source: https://github.com/kriasoft/oauth-callback/blob/main/README.md Run the demo without automatically opening a browser, useful for CI/testing environments. ```bash # Run without opening browser (for CI/testing) bun run examples/demo.ts --no-browser ``` -------------------------------- ### Configure Browser Auth Timeout Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/browser-auth.md Set a custom timeout for the browser authentication flow. Defaults to 10 minutes for initial setup. ```typescript import open from "open"; const authProvider = browserAuth({ launch: open, authTimeout: 600000, // 10 minutes for first-time setup }); ``` -------------------------------- ### Implement PKCE for Public Clients Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/index.md Demonstrates the implementation of Proof Key for Code Exchange (PKCE) for public clients, which adds an extra layer of security by preventing authorization code interception attacks. ```typescript // Implement PKCE for public clients const verifier = randomBytes(32).toString("base64url"); const challenge = createHash("sha256").update(verifier).digest("base64url"); ``` -------------------------------- ### Troubleshooting: DCR Not Working Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/browser-auth.md If the server does not support Dynamic Client Registration, provide pre-registered client credentials. ```typescript import open from "open"; // Fallback to pre-registered credentials const authProvider = browserAuth({ launch: open, clientId: "your-client-id", clientSecret: "your-client-secret", }); ``` -------------------------------- ### Namespace Export Example Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/types.md Demonstrates how to access MCP types and functions through a namespace import. This provides a structured way to use the MCP features. ```typescript // Also available via namespace import { mcp } from "oauth-callback"; // All MCP types and functions under mcp namespace const authProvider = mcp.browserAuth({ store: mcp.fileStore(), }); ``` -------------------------------- ### CallbackServer Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/types.md Interface for OAuth callback server implementations across different runtimes, defining methods to start, wait for callbacks, and stop the server. ```APIDOC ## CallbackServer ### Description Interface for OAuth callback server implementations across different runtimes. ### Interface ```typescript interface CallbackServer { start(options: ServerOptions): Promise; waitForCallback(path: string, timeout: number): Promise; stop(): Promise; } ``` ``` -------------------------------- ### Get Linear Cycle Analytics Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/examples/linear.md Retrieve performance analytics for a specific development cycle, including completion rate and number of issues completed. ```typescript // Get cycle analytics const analytics = await client.callTool("get_cycle_analytics", { cycleId: currentCycle.id, }); console.log("Cycle completion:", analytics.completionRate); console.log("Issues completed:", analytics.completedCount); ``` -------------------------------- ### Using fileStore with browserAuth (Environment-Specific Path) Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/storage-providers.md Initializes the browserAuth provider with an environment-specific file path for token storage. ```typescript // Environment-specific storage const envAuth = browserAuth({ launch: open, store: fileStore(`~/.myapp/${process.env.NODE_ENV}-tokens.json`), }); ``` -------------------------------- ### ServerOptions Interface Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/types.md Configuration options for setting up the OAuth callback server. Requires a port and optionally accepts hostname, custom HTML for success and error pages, a cancellation signal, and a request handler. ```typescript interface ServerOptions { port: number; // Port to bind to hostname?: string; // Hostname (default: "localhost") successHtml?: string; // Custom success HTML errorHtml?: string; // Error HTML template signal?: AbortSignal; // Cancellation signal onRequest?: (req: Request) => void; // Request callback } ``` -------------------------------- ### Using fileStore with browserAuth (Default Path) Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/storage-providers.md Initializes the browserAuth provider using the default file path for token storage. ```typescript import open from "open"; import { browserAuth, fileStore } from "oauth-callback/mcp"; // Use default location (~/.mcp/tokens.json) const authProvider = browserAuth({ launch: open, store: fileStore(), }); ``` -------------------------------- ### OAuth Error Handling Example Source: https://github.com/kriasoft/oauth-callback/blob/main/docs/api/types.md Demonstrates how to handle potential OAuth and timeout errors during an authentication process. Includes type checking for specific error instances. ```typescript import { OAuthError, TimeoutError } from "oauth-callback"; import type { CallbackResult } from "oauth-callback"; function handleAuthResult(result: CallbackResult) { // Check for OAuth errors in result if (result.error) { throw new OAuthError( result.error, result.error_description, result.error_uri, ); } if (!result.code) { throw new Error("No authorization code received"); } return result.code; } // Usage with proper error handling try { const code = await getAuthCode(authUrl); } catch (error) { if (error instanceof OAuthError) { console.error(`OAuth error: ${error.error}`); } else if (error instanceof TimeoutError) { console.error("Authorization timed out"); } else { console.error("Unexpected error:", error); } } ```