OAuth Flow
- Complete token exchange synchronously within
redirectToAuthorization()— tokens are now available immediately after the firstconnect()attempt - Add CSRF protection via state parameter validation
- New
authServerUrloption for separate auth/token endpoint origins
Storage API (breaking)
- Remove
clear()fromTokenStoreinterface - Add explicit
deleteClient()anddeleteCodeVerifier()toOAuthStore - Use symbol brand for type-safe
OAuthStoredetection vs duck typing - Atomic file writes via temp file + rename
Bug Fixes
invalidateCredentials('all')was clearing the entire store; now only clears the provider's ownstoreKey- Token expiry check was reading from store unnecessarily; now uses in-memory
expiresAt
Usage
import { browserAuth, fileStore } from "oauth-callback/mcp";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const authProvider = browserAuth({
launch: open,
store: fileStore(),
});
const client = new Client({ name: "my-app", version: "1.0.0" }, { capabilities: {} });
const serverUrl = new URL("https://mcp.example.com/mcp");
// First attempt completes OAuth, second succeeds with stored tokens
const transport = new StreamableHTTPClientTransport(serverUrl, { authProvider });
try {
await client.connect(transport);
} catch (error) {
if (error.message === "Unauthorized") {
await client.connect(new StreamableHTTPClientTransport(serverUrl, { authProvider }));
} else throw error;
}Full Changelog: v2.1.1...v2.2.0