oauth-callback 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/kriasoft/oauth-callback/blob/main/LICENSE)
6
6
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
7
7
 
8
- A lightweight local HTTP server for handling OAuth 2.0 authorization callbacks in Node.js, Deno, and Bun applications. Perfect for CLI tools, desktop applications, and development environments that need to capture OAuth authorization codes.
8
+ 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.
9
9
 
10
10
  <div align="center">
11
11
  <img src="https://raw.githubusercontent.com/kriasoft/oauth-callback/main/examples/notion.gif" alt="OAuth Callback Demo" width="100%" style="max-width: 800px; height: auto;">
@@ -15,10 +15,12 @@ A lightweight local HTTP server for handling OAuth 2.0 authorization callbacks i
15
15
 
16
16
  - 🚀 **Multi-runtime support** - Works with Node.js 18+, Deno, and Bun
17
17
  - 🔒 **Secure localhost-only server** for OAuth callbacks
18
+ - 🤖 **MCP SDK integration** - Built-in OAuth provider for Model Context Protocol
18
19
  - ⚡ **Minimal dependencies** - Only requires `open` package
19
20
  - 🎯 **TypeScript support** out of the box
20
21
  - 🛡️ **Comprehensive OAuth error handling** with detailed error classes
21
22
  - 🔄 **Automatic server cleanup** after callback
23
+ - 💾 **Flexible token storage** - In-memory and file-based options
22
24
  - 🎪 **Clean success pages** with animated checkmark
23
25
  - 🎨 **Customizable HTML templates** with placeholder support
24
26
  - 🚦 **AbortSignal support** for programmatic cancellation
@@ -47,6 +49,14 @@ const result = await getAuthCode(
47
49
  "https://example.com/oauth/authorize?client_id=xxx&redirect_uri=http://localhost:3000/callback",
48
50
  );
49
51
  console.log("Authorization code:", result.code);
52
+
53
+ // MCP SDK integration - use specific import
54
+ import { browserAuth, fileStore } from "oauth-callback/mcp";
55
+ const authProvider = browserAuth({ store: fileStore() });
56
+
57
+ // Or via namespace import
58
+ import { mcp } from "oauth-callback";
59
+ const authProvider = mcp.browserAuth({ store: mcp.fileStore() });
50
60
  ```
51
61
 
52
62
  ## Usage Examples
@@ -124,6 +134,71 @@ const microsoftAuth = await getAuthCode(
124
134
  );
125
135
  ```
126
136
 
137
+ ### MCP SDK Integration
138
+
139
+ The `browserAuth()` function provides a drop-in OAuth provider for the Model Context Protocol SDK:
140
+
141
+ ```typescript
142
+ import { browserAuth, inMemoryStore } from "oauth-callback/mcp";
143
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
144
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
145
+
146
+ // Create MCP-compatible OAuth provider
147
+ const authProvider = browserAuth({
148
+ port: 3000,
149
+ scope: "read write",
150
+ store: inMemoryStore(), // Or fileStore() for persistence
151
+ });
152
+
153
+ // Use with MCP SDK transport
154
+ const transport = new StreamableHTTPClientTransport(
155
+ new URL("https://mcp.notion.com/mcp"),
156
+ { authProvider },
157
+ );
158
+
159
+ const client = new Client(
160
+ { name: "my-app", version: "1.0.0" },
161
+ { capabilities: {} },
162
+ );
163
+
164
+ await client.connect(transport);
165
+ ```
166
+
167
+ #### Token Storage Options
168
+
169
+ ```typescript
170
+ import { browserAuth, inMemoryStore, fileStore } from "oauth-callback/mcp";
171
+
172
+ // Ephemeral storage (tokens lost on restart)
173
+ const ephemeralAuth = browserAuth({
174
+ store: inMemoryStore(),
175
+ });
176
+
177
+ // Persistent file storage (default: ~/.mcp/tokens.json)
178
+ const persistentAuth = browserAuth({
179
+ store: fileStore(),
180
+ storeKey: "my-app-tokens", // Namespace for multiple apps
181
+ });
182
+
183
+ // Custom file location
184
+ const customAuth = browserAuth({
185
+ store: fileStore("/path/to/tokens.json"),
186
+ });
187
+ ```
188
+
189
+ #### Pre-configured Client Credentials
190
+
191
+ If you have pre-registered OAuth client credentials:
192
+
193
+ ```typescript
194
+ const authProvider = browserAuth({
195
+ clientId: "your-client-id",
196
+ clientSecret: "your-client-secret",
197
+ scope: "read write",
198
+ store: fileStore(), // Persist tokens across sessions
199
+ });
200
+ ```
201
+
127
202
  ### Advanced Usage
128
203
 
129
204
  ```typescript
@@ -206,6 +281,50 @@ class OAuthError extends Error {
206
281
  }
207
282
  ```
208
283
 
284
+ ### `browserAuth(options)`
285
+
286
+ Available from `oauth-callback/mcp`. Creates an MCP SDK-compatible OAuth provider for browser-based flows. Handles Dynamic Client Registration (DCR), token storage, and automatic refresh.
287
+
288
+ #### Parameters
289
+
290
+ - `options` (BrowserAuthOptions): Configuration object with:
291
+ - `port` (number): Port for callback server (default: 3000)
292
+ - `hostname` (string): Hostname to bind to (default: "localhost")
293
+ - `callbackPath` (string): URL path for OAuth callback (default: "/callback")
294
+ - `scope` (string): OAuth scopes to request
295
+ - `clientId` (string): Pre-registered client ID (optional)
296
+ - `clientSecret` (string): Pre-registered client secret (optional)
297
+ - `store` (TokenStore): Token storage implementation (default: inMemoryStore())
298
+ - `storeKey` (string): Storage key for tokens (default: "mcp-tokens")
299
+ - `authTimeout` (number): Authorization timeout in ms (default: 300000)
300
+ - `successHtml` (string): Custom success page HTML
301
+ - `errorHtml` (string): Custom error page HTML
302
+ - `onRequest` (function): Request logging callback
303
+
304
+ #### Returns
305
+
306
+ OAuthClientProvider compatible with MCP SDK transports.
307
+
308
+ ### `inMemoryStore()`
309
+
310
+ Available from `oauth-callback/mcp`. Creates an ephemeral in-memory token store. Tokens are lost when the process exits.
311
+
312
+ #### Returns
313
+
314
+ TokenStore implementation for temporary token storage.
315
+
316
+ ### `fileStore(filepath?)`
317
+
318
+ Available from `oauth-callback/mcp`. Creates a persistent file-based token store.
319
+
320
+ #### Parameters
321
+
322
+ - `filepath` (string): Optional custom file path (default: `~/.mcp/tokens.json`)
323
+
324
+ #### Returns
325
+
326
+ TokenStore implementation for persistent token storage.
327
+
209
328
  ## How It Works
210
329
 
211
330
  1. **Server Creation**: Creates a temporary HTTP server on the specified port
@@ -279,7 +398,8 @@ This example demonstrates:
279
398
  - Dynamic Client Registration (OAuth 2.0 DCR) - no pre-configured client ID/secret needed
280
399
  - Integration with Model Context Protocol (MCP) servers
281
400
  - Automatic client registration with the authorization server
282
- - Using the MCP SDK's OAuth capabilities
401
+ - Using `browserAuth()` provider with MCP SDK's `StreamableHTTPClientTransport`
402
+ - Token persistence with `inMemoryStore()` for ephemeral sessions
283
403
 
284
404
  ## Development
285
405
 
@@ -0,0 +1,18 @@
1
+ import type { BrowserAuthOptions } from "../mcp-types";
2
+ import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
3
+ /**
4
+ * Factory for MCP SDK-compatible OAuth provider using browser flow.
5
+ *
6
+ * @param options Configuration for OAuth flow behavior
7
+ * @returns OAuthClientProvider for MCP SDK transport
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const transport = new StreamableHTTPClientTransport(
12
+ * new URL("https://mcp.notion.com/mcp"),
13
+ * { authProvider: browserAuth() }
14
+ * );
15
+ * ```
16
+ */
17
+ export declare function browserAuth(options?: BrowserAuthOptions): OAuthClientProvider;
18
+ //# sourceMappingURL=browser-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-auth.d.ts","sourceRoot":"","sources":["../../src/auth/browser-auth.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,kBAAkB,EAMnB,MAAM,cAAc,CAAC;AAKtB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAQpF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CACzB,OAAO,GAAE,kBAAuB,GAC/B,mBAAmB,CAErB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=browser-auth.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-auth.test.d.ts","sourceRoot":"","sources":["../../src/auth/browser-auth.test.ts"],"names":[],"mappings":""}
package/dist/index.d.ts CHANGED
@@ -3,27 +3,26 @@ import type { GetAuthCodeOptions } from "./types";
3
3
  export type { CallbackResult, CallbackServer, ServerOptions } from "./server";
4
4
  export { OAuthError } from "./errors";
5
5
  export type { GetAuthCodeOptions } from "./types";
6
+ export { inMemoryStore } from "./storage/memory";
7
+ export { fileStore } from "./storage/file";
8
+ import * as mcp from "./mcp";
9
+ export { mcp };
6
10
  /**
7
- * Get the OAuth authorization code from the authorization URL.
11
+ * Captures OAuth authorization code via localhost callback.
12
+ * Opens browser to auth URL, waits for provider redirect to localhost.
8
13
  *
9
- * Creates a temporary local HTTP server to handle the OAuth callback,
10
- * opens the authorization URL in the user's browser, and waits for the
11
- * OAuth provider to redirect back with an authorization code.
12
- *
13
- * @param input - Either a string containing the OAuth authorization URL,
14
- * or a GetAuthCodeOptions object with detailed configuration
15
- * @returns Promise that resolves to CallbackResult containing the authorization code
16
- * and other parameters, or rejects with OAuthError if authorization fails
17
- * @throws {OAuthError} When OAuth provider returns an error (e.g., access_denied)
18
- * @throws {Error} For timeout, network errors, or other unexpected failures
14
+ * @param input - Auth URL string or GetAuthCodeOptions with config
15
+ * @returns Promise<CallbackResult> with code and params
16
+ * @throws {OAuthError} Provider errors (access_denied, invalid_scope)
17
+ * @throws {Error} Timeout, network failures, port conflicts
19
18
  *
20
19
  * @example
21
20
  * ```typescript
22
- * // Simple usage with URL string
21
+ * // Simple
23
22
  * const result = await getAuthCode('https://oauth.example.com/authorize?...');
24
23
  * console.log('Code:', result.code);
25
24
  *
26
- * // Advanced usage with options
25
+ * // Custom port/timeout
27
26
  * const result = await getAuthCode({
28
27
  * authorizationUrl: 'https://oauth.example.com/authorize?...',
29
28
  * port: 8080,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGlD,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,kBAAkB,GAAG,MAAM,GACjC,OAAO,CAAC,cAAc,CAAC,CAqEzB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGlD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3C,OAAO,KAAK,GAAG,MAAM,OAAO,CAAC;AAC7B,OAAO,EAAE,GAAG,EAAE,CAAC;AAEf;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,kBAAkB,GAAG,MAAM,GACjC,OAAO,CAAC,cAAc,CAAC,CA8DzB"}
package/dist/index.js CHANGED
@@ -15,6 +15,15 @@ var __toESM = (mod, isNodeMode, target) => {
15
15
  });
16
16
  return to;
17
17
  };
18
+ var __export = (target, all) => {
19
+ for (var name in all)
20
+ __defProp(target, name, {
21
+ get: all[name],
22
+ enumerable: true,
23
+ configurable: true,
24
+ set: (newValue) => all[name] = () => newValue
25
+ });
26
+ };
18
27
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
28
 
20
29
  // node_modules/open/index.js
@@ -946,7 +955,394 @@ function createCallbackServer() {
946
955
  }
947
956
  return new NodeCallbackServer;
948
957
  }
958
+ // src/storage/memory.ts
959
+ function inMemoryStore() {
960
+ const store = new Map;
961
+ return {
962
+ async get(key) {
963
+ return store.get(key) ?? null;
964
+ },
965
+ async set(key, tokens) {
966
+ store.set(key, tokens);
967
+ },
968
+ async delete(key) {
969
+ store.delete(key);
970
+ },
971
+ async clear() {
972
+ store.clear();
973
+ }
974
+ };
975
+ }
976
+ // src/storage/file.ts
977
+ import * as fs6 from "node:fs/promises";
978
+ import * as path2 from "node:path";
979
+ import * as os2 from "node:os";
980
+ function fileStore(filepath) {
981
+ const file = filepath ?? path2.join(os2.homedir(), ".mcp", "tokens.json");
982
+ async function ensureDir() {
983
+ await fs6.mkdir(path2.dirname(file), { recursive: true });
984
+ }
985
+ async function readStore() {
986
+ try {
987
+ const data = await fs6.readFile(file, "utf-8");
988
+ return JSON.parse(data);
989
+ } catch {
990
+ return {};
991
+ }
992
+ }
993
+ async function writeStore(data) {
994
+ await ensureDir();
995
+ await fs6.writeFile(file, JSON.stringify(data, null, 2), "utf-8");
996
+ }
997
+ return {
998
+ async get(key) {
999
+ const store = await readStore();
1000
+ return store[key] ?? null;
1001
+ },
1002
+ async set(key, tokens) {
1003
+ const store = await readStore();
1004
+ store[key] = tokens;
1005
+ await writeStore(store);
1006
+ },
1007
+ async delete(key) {
1008
+ const store = await readStore();
1009
+ delete store[key];
1010
+ await writeStore(store);
1011
+ },
1012
+ async clear() {
1013
+ await writeStore({});
1014
+ }
1015
+ };
1016
+ }
1017
+ // src/mcp.ts
1018
+ var exports_mcp = {};
1019
+ __export(exports_mcp, {
1020
+ inMemoryStore: () => inMemoryStore,
1021
+ fileStore: () => fileStore,
1022
+ browserAuth: () => browserAuth
1023
+ });
1024
+
1025
+ // src/auth/browser-auth.ts
1026
+ import { randomBytes } from "node:crypto";
1027
+
1028
+ // src/utils/token.ts
1029
+ function calculateExpiry(expiresIn) {
1030
+ if (!expiresIn)
1031
+ return;
1032
+ return Date.now() + expiresIn * 1000;
1033
+ }
1034
+
1035
+ // src/auth/browser-auth.ts
1036
+ function browserAuth(options = {}) {
1037
+ return new BrowserOAuthProvider(options);
1038
+ }
949
1039
 
1040
+ class BrowserOAuthProvider {
1041
+ _store;
1042
+ _storeKey;
1043
+ _port;
1044
+ _hostname;
1045
+ _callbackPath;
1046
+ _authTimeout;
1047
+ _usePKCE;
1048
+ _openBrowser;
1049
+ _clientId;
1050
+ _clientSecret;
1051
+ _scope;
1052
+ _successHtml;
1053
+ _errorHtml;
1054
+ _onRequest;
1055
+ _clientInfo;
1056
+ _tokens;
1057
+ _codeVerifier;
1058
+ _pendingAuthCode;
1059
+ _pendingAuthState;
1060
+ _isExchangingCode = false;
1061
+ _tokensLoaded = false;
1062
+ _loadingTokens;
1063
+ _authInProgress;
1064
+ _refreshInProgress;
1065
+ constructor(options = {}) {
1066
+ this._store = options.store ?? inMemoryStore();
1067
+ this._storeKey = options.storeKey ?? "mcp-tokens";
1068
+ this._port = options.port ?? 3000;
1069
+ this._hostname = options.hostname ?? "localhost";
1070
+ this._callbackPath = options.callbackPath ?? "/callback";
1071
+ this._authTimeout = options.authTimeout ?? 300000;
1072
+ this._usePKCE = options.usePKCE ?? true;
1073
+ this._openBrowser = options.openBrowser ?? true;
1074
+ this._clientId = options.clientId;
1075
+ this._clientSecret = options.clientSecret;
1076
+ this._scope = options.scope;
1077
+ this._successHtml = options.successHtml;
1078
+ this._errorHtml = options.errorHtml;
1079
+ this._onRequest = options.onRequest;
1080
+ }
1081
+ async _ensureTokensLoaded() {
1082
+ if (this._tokensLoaded)
1083
+ return;
1084
+ if (!this._loadingTokens) {
1085
+ this._loadingTokens = this._loadStoredData();
1086
+ }
1087
+ await this._loadingTokens;
1088
+ }
1089
+ async _loadStoredData() {
1090
+ try {
1091
+ const stored = await this._store.get(this._storeKey);
1092
+ if (stored) {
1093
+ this._tokens = {
1094
+ access_token: stored.accessToken,
1095
+ token_type: "Bearer",
1096
+ refresh_token: stored.refreshToken,
1097
+ expires_in: stored.expiresAt ? Math.floor((stored.expiresAt - Date.now()) / 1000) : undefined,
1098
+ scope: stored.scope
1099
+ };
1100
+ }
1101
+ if (this._isOAuthStore(this._store)) {
1102
+ const clientInfo = await this._store.getClient(this._storeKey);
1103
+ if (clientInfo) {
1104
+ this._clientInfo = {
1105
+ client_id: clientInfo.clientId,
1106
+ client_secret: clientInfo.clientSecret,
1107
+ client_id_issued_at: clientInfo.clientIdIssuedAt,
1108
+ client_secret_expires_at: clientInfo.clientSecretExpiresAt,
1109
+ redirect_uris: [this.redirectUrl]
1110
+ };
1111
+ }
1112
+ const session = await this._store.getSession(this._storeKey);
1113
+ if (session) {
1114
+ this._codeVerifier = session.codeVerifier;
1115
+ }
1116
+ }
1117
+ this._tokensLoaded = true;
1118
+ } catch (error) {
1119
+ console.warn("Failed to load stored data:", error);
1120
+ this._tokensLoaded = true;
1121
+ }
1122
+ }
1123
+ _isOAuthStore(store) {
1124
+ return typeof store.getClient === "function";
1125
+ }
1126
+ get redirectUrl() {
1127
+ return `http://${this._hostname}:${this._port}${this._callbackPath}`;
1128
+ }
1129
+ get clientMetadata() {
1130
+ return {
1131
+ client_name: "OAuth Callback Handler",
1132
+ client_uri: "https://github.com/kriasoft/oauth-callback",
1133
+ redirect_uris: [this.redirectUrl],
1134
+ grant_types: ["authorization_code", "refresh_token"],
1135
+ response_types: ["code"],
1136
+ scope: this._scope,
1137
+ token_endpoint_auth_method: this._clientSecret ? "client_secret_post" : "none"
1138
+ };
1139
+ }
1140
+ async state() {
1141
+ const buffer = randomBytes(32);
1142
+ return buffer.toString("base64url");
1143
+ }
1144
+ async clientInformation() {
1145
+ if (this._clientId) {
1146
+ return {
1147
+ client_id: this._clientId,
1148
+ client_secret: this._clientSecret
1149
+ };
1150
+ }
1151
+ if (this._clientInfo) {
1152
+ return {
1153
+ client_id: this._clientInfo.client_id,
1154
+ client_secret: this._clientInfo.client_secret
1155
+ };
1156
+ }
1157
+ return;
1158
+ }
1159
+ async saveClientInformation(clientInformation) {
1160
+ this._clientInfo = clientInformation;
1161
+ if (this._isOAuthStore(this._store)) {
1162
+ const clientInfo = {
1163
+ clientId: clientInformation.client_id,
1164
+ clientSecret: clientInformation.client_secret,
1165
+ clientIdIssuedAt: clientInformation.client_id_issued_at,
1166
+ clientSecretExpiresAt: clientInformation.client_secret_expires_at
1167
+ };
1168
+ await this._store.setClient(this._storeKey, clientInfo);
1169
+ }
1170
+ }
1171
+ async tokens() {
1172
+ await this._ensureTokensLoaded();
1173
+ if (!this._tokens) {
1174
+ return;
1175
+ }
1176
+ const stored = await this._store.get(this._storeKey);
1177
+ if (stored?.expiresAt) {
1178
+ if (Date.now() >= stored.expiresAt - 60000) {
1179
+ if (this._tokens.refresh_token) {
1180
+ try {
1181
+ await this._refreshTokens();
1182
+ return this._tokens;
1183
+ } catch (error) {
1184
+ console.warn("Token refresh failed:", error);
1185
+ return;
1186
+ }
1187
+ }
1188
+ return;
1189
+ }
1190
+ }
1191
+ return this._tokens;
1192
+ }
1193
+ async saveTokens(tokens) {
1194
+ this._tokens = tokens;
1195
+ this._tokensLoaded = true;
1196
+ const storedTokens = {
1197
+ accessToken: tokens.access_token,
1198
+ refreshToken: tokens.refresh_token,
1199
+ expiresAt: tokens.expires_in ? calculateExpiry(tokens.expires_in) : undefined,
1200
+ scope: tokens.scope
1201
+ };
1202
+ await this._store.set(this._storeKey, storedTokens);
1203
+ }
1204
+ async redirectToAuthorization(authorizationUrl) {
1205
+ if (this._authInProgress) {
1206
+ await this._authInProgress;
1207
+ return;
1208
+ }
1209
+ this._authInProgress = this._doAuthorization(authorizationUrl);
1210
+ try {
1211
+ await this._authInProgress;
1212
+ } finally {
1213
+ this._authInProgress = undefined;
1214
+ }
1215
+ }
1216
+ async _doAuthorization(authorizationUrl) {
1217
+ let lastError;
1218
+ const maxRetries = 2;
1219
+ for (let attempt = 0;attempt <= maxRetries; attempt++) {
1220
+ try {
1221
+ const result = await getAuthCode({
1222
+ authorizationUrl: authorizationUrl.href,
1223
+ port: this._port,
1224
+ hostname: this._hostname,
1225
+ callbackPath: this._callbackPath,
1226
+ timeout: this._authTimeout,
1227
+ openBrowser: typeof this._openBrowser === "boolean" ? this._openBrowser : true,
1228
+ successHtml: this._successHtml,
1229
+ errorHtml: this._errorHtml,
1230
+ onRequest: this._onRequest
1231
+ });
1232
+ this._pendingAuthCode = result.code;
1233
+ this._pendingAuthState = result.state;
1234
+ setTimeout(() => {
1235
+ if (this._pendingAuthCode === result.code) {
1236
+ this._pendingAuthCode = undefined;
1237
+ this._pendingAuthState = undefined;
1238
+ }
1239
+ }, this._authTimeout);
1240
+ return;
1241
+ } catch (error) {
1242
+ lastError = error instanceof Error ? error : new Error(String(error));
1243
+ if (error instanceof OAuthError) {
1244
+ throw error;
1245
+ }
1246
+ if (attempt < maxRetries) {
1247
+ console.warn(`Auth attempt ${attempt + 1} failed, retrying...`, error);
1248
+ await new Promise((resolve) => setTimeout(resolve, 1000 * (attempt + 1)));
1249
+ }
1250
+ }
1251
+ }
1252
+ throw new Error(`OAuth authorization failed after ${maxRetries + 1} attempts: ${lastError?.message}`);
1253
+ }
1254
+ async saveCodeVerifier(codeVerifier) {
1255
+ this._codeVerifier = codeVerifier;
1256
+ if (this._isOAuthStore(this._store)) {
1257
+ const session = {
1258
+ codeVerifier,
1259
+ state: this._pendingAuthState
1260
+ };
1261
+ await this._store.setSession(this._storeKey, session);
1262
+ }
1263
+ }
1264
+ async codeVerifier() {
1265
+ if (!this._codeVerifier) {
1266
+ throw new Error("Code verifier not found");
1267
+ }
1268
+ return this._codeVerifier;
1269
+ }
1270
+ async invalidateCredentials(scope) {
1271
+ if (scope === "all" && this._isExchangingCode) {
1272
+ this._tokens = undefined;
1273
+ await this._store.delete(this._storeKey);
1274
+ return;
1275
+ }
1276
+ if (this._isExchangingCode && (scope === "client" || scope === "all")) {
1277
+ this._isExchangingCode = false;
1278
+ }
1279
+ switch (scope) {
1280
+ case "all":
1281
+ this._clientInfo = undefined;
1282
+ this._tokens = undefined;
1283
+ this._codeVerifier = undefined;
1284
+ this._tokensLoaded = false;
1285
+ await this._store.clear();
1286
+ break;
1287
+ case "client":
1288
+ this._clientInfo = undefined;
1289
+ if (this._isOAuthStore(this._store)) {
1290
+ await this._store.setClient(this._storeKey, {
1291
+ clientId: ""
1292
+ });
1293
+ }
1294
+ break;
1295
+ case "tokens":
1296
+ this._tokens = undefined;
1297
+ await this._store.delete(this._storeKey);
1298
+ break;
1299
+ case "verifier":
1300
+ this._codeVerifier = undefined;
1301
+ if (this._isOAuthStore(this._store)) {
1302
+ await this._store.setSession(this._storeKey, {});
1303
+ }
1304
+ break;
1305
+ }
1306
+ }
1307
+ async validateResourceURL(_serverUrl, _resource) {
1308
+ return;
1309
+ }
1310
+ getPendingAuthCode() {
1311
+ if (this._pendingAuthCode) {
1312
+ const result = {
1313
+ code: this._pendingAuthCode,
1314
+ state: this._pendingAuthState
1315
+ };
1316
+ this._isExchangingCode = true;
1317
+ this._pendingAuthCode = undefined;
1318
+ this._pendingAuthState = undefined;
1319
+ return result;
1320
+ }
1321
+ return;
1322
+ }
1323
+ async _refreshTokens() {
1324
+ if (this._refreshInProgress) {
1325
+ await this._refreshInProgress;
1326
+ return;
1327
+ }
1328
+ this._refreshInProgress = this._doRefreshTokens();
1329
+ try {
1330
+ await this._refreshInProgress;
1331
+ } finally {
1332
+ this._refreshInProgress = undefined;
1333
+ }
1334
+ }
1335
+ async _doRefreshTokens() {
1336
+ if (!this._tokens?.refresh_token) {
1337
+ throw new Error("No refresh token available");
1338
+ }
1339
+ const clientInfo = await this.clientInformation();
1340
+ if (!clientInfo?.client_id) {
1341
+ throw new Error("No client information available for refresh");
1342
+ }
1343
+ throw new Error("Token refresh not yet implemented - requires token endpoint URL");
1344
+ }
1345
+ }
950
1346
  // src/index.ts
951
1347
  async function getAuthCode(input) {
952
1348
  const options = typeof input === "string" ? { authorizationUrl: input } : input;
@@ -994,6 +1390,9 @@ async function getAuthCode(input) {
994
1390
  }
995
1391
  }
996
1392
  export {
1393
+ exports_mcp as mcp,
1394
+ inMemoryStore,
997
1395
  getAuthCode,
1396
+ fileStore,
998
1397
  OAuthError
999
1398
  };