oauth-callback 1.0.0 → 1.1.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
@@ -40,13 +42,21 @@ npm install oauth-callback
40
42
  ## Quick Start
41
43
 
42
44
  ```typescript
43
- import { getAuthCode, OAuthError } from "oauth-callback";
45
+ import {
46
+ getAuthCode,
47
+ OAuthError,
48
+ browserAuth,
49
+ fileStore,
50
+ } from "oauth-callback";
44
51
 
45
52
  // Simple usage
46
53
  const result = await getAuthCode(
47
54
  "https://example.com/oauth/authorize?client_id=xxx&redirect_uri=http://localhost:3000/callback",
48
55
  );
49
56
  console.log("Authorization code:", result.code);
57
+
58
+ // MCP SDK integration
59
+ const authProvider = browserAuth({ store: 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";
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";
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
+ 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
+ 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
+ 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 { browserAuth } from "./auth/browser-auth";
7
+ export { inMemoryStore } from "./storage/memory";
8
+ export { fileStore } from "./storage/file";
9
+ export type { BrowserAuthOptions, Tokens, TokenStore } from "./mcp-types";
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,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGlD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3C,YAAY,EAAE,kBAAkB,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,kBAAkB,GAAG,MAAM,GACjC,OAAO,CAAC,cAAc,CAAC,CA8DzB"}
package/dist/index.js CHANGED
@@ -946,6 +946,387 @@ function createCallbackServer() {
946
946
  }
947
947
  return new NodeCallbackServer;
948
948
  }
949
+ // src/auth/browser-auth.ts
950
+ import { randomBytes } from "node:crypto";
951
+
952
+ // src/utils/token.ts
953
+ function calculateExpiry(expiresIn) {
954
+ if (!expiresIn)
955
+ return;
956
+ return Date.now() + expiresIn * 1000;
957
+ }
958
+
959
+ // src/storage/memory.ts
960
+ function inMemoryStore() {
961
+ const store = new Map;
962
+ return {
963
+ async get(key) {
964
+ return store.get(key) ?? null;
965
+ },
966
+ async set(key, tokens) {
967
+ store.set(key, tokens);
968
+ },
969
+ async delete(key) {
970
+ store.delete(key);
971
+ },
972
+ async clear() {
973
+ store.clear();
974
+ }
975
+ };
976
+ }
977
+
978
+ // src/auth/browser-auth.ts
979
+ function browserAuth(options = {}) {
980
+ return new BrowserOAuthProvider(options);
981
+ }
982
+
983
+ class BrowserOAuthProvider {
984
+ _store;
985
+ _storeKey;
986
+ _port;
987
+ _hostname;
988
+ _callbackPath;
989
+ _authTimeout;
990
+ _usePKCE;
991
+ _openBrowser;
992
+ _clientId;
993
+ _clientSecret;
994
+ _scope;
995
+ _successHtml;
996
+ _errorHtml;
997
+ _onRequest;
998
+ _clientInfo;
999
+ _tokens;
1000
+ _codeVerifier;
1001
+ _pendingAuthCode;
1002
+ _pendingAuthState;
1003
+ _isExchangingCode = false;
1004
+ _tokensLoaded = false;
1005
+ _loadingTokens;
1006
+ _authInProgress;
1007
+ _refreshInProgress;
1008
+ constructor(options = {}) {
1009
+ this._store = options.store ?? inMemoryStore();
1010
+ this._storeKey = options.storeKey ?? "mcp-tokens";
1011
+ this._port = options.port ?? 3000;
1012
+ this._hostname = options.hostname ?? "localhost";
1013
+ this._callbackPath = options.callbackPath ?? "/callback";
1014
+ this._authTimeout = options.authTimeout ?? 300000;
1015
+ this._usePKCE = options.usePKCE ?? true;
1016
+ this._openBrowser = options.openBrowser ?? true;
1017
+ this._clientId = options.clientId;
1018
+ this._clientSecret = options.clientSecret;
1019
+ this._scope = options.scope;
1020
+ this._successHtml = options.successHtml;
1021
+ this._errorHtml = options.errorHtml;
1022
+ this._onRequest = options.onRequest;
1023
+ }
1024
+ async _ensureTokensLoaded() {
1025
+ if (this._tokensLoaded)
1026
+ return;
1027
+ if (!this._loadingTokens) {
1028
+ this._loadingTokens = this._loadStoredData();
1029
+ }
1030
+ await this._loadingTokens;
1031
+ }
1032
+ async _loadStoredData() {
1033
+ try {
1034
+ const stored = await this._store.get(this._storeKey);
1035
+ if (stored) {
1036
+ this._tokens = {
1037
+ access_token: stored.accessToken,
1038
+ token_type: "Bearer",
1039
+ refresh_token: stored.refreshToken,
1040
+ expires_in: stored.expiresAt ? Math.floor((stored.expiresAt - Date.now()) / 1000) : undefined,
1041
+ scope: stored.scope
1042
+ };
1043
+ }
1044
+ if (this._isOAuthStore(this._store)) {
1045
+ const clientInfo = await this._store.getClient(this._storeKey);
1046
+ if (clientInfo) {
1047
+ this._clientInfo = {
1048
+ client_id: clientInfo.clientId,
1049
+ client_secret: clientInfo.clientSecret,
1050
+ client_id_issued_at: clientInfo.clientIdIssuedAt,
1051
+ client_secret_expires_at: clientInfo.clientSecretExpiresAt,
1052
+ redirect_uris: [this.redirectUrl]
1053
+ };
1054
+ }
1055
+ const session = await this._store.getSession(this._storeKey);
1056
+ if (session) {
1057
+ this._codeVerifier = session.codeVerifier;
1058
+ }
1059
+ }
1060
+ this._tokensLoaded = true;
1061
+ } catch (error) {
1062
+ console.warn("Failed to load stored data:", error);
1063
+ this._tokensLoaded = true;
1064
+ }
1065
+ }
1066
+ _isOAuthStore(store) {
1067
+ return typeof store.getClient === "function";
1068
+ }
1069
+ get redirectUrl() {
1070
+ return `http://${this._hostname}:${this._port}${this._callbackPath}`;
1071
+ }
1072
+ get clientMetadata() {
1073
+ return {
1074
+ client_name: "OAuth Callback Handler",
1075
+ client_uri: "https://github.com/kriasoft/oauth-callback",
1076
+ redirect_uris: [this.redirectUrl],
1077
+ grant_types: ["authorization_code", "refresh_token"],
1078
+ response_types: ["code"],
1079
+ scope: this._scope,
1080
+ token_endpoint_auth_method: this._clientSecret ? "client_secret_post" : "none"
1081
+ };
1082
+ }
1083
+ async state() {
1084
+ const buffer = randomBytes(32);
1085
+ return buffer.toString("base64url");
1086
+ }
1087
+ async clientInformation() {
1088
+ if (this._clientId) {
1089
+ return {
1090
+ client_id: this._clientId,
1091
+ client_secret: this._clientSecret
1092
+ };
1093
+ }
1094
+ if (this._clientInfo) {
1095
+ return {
1096
+ client_id: this._clientInfo.client_id,
1097
+ client_secret: this._clientInfo.client_secret
1098
+ };
1099
+ }
1100
+ return;
1101
+ }
1102
+ async saveClientInformation(clientInformation) {
1103
+ this._clientInfo = clientInformation;
1104
+ if (this._isOAuthStore(this._store)) {
1105
+ const clientInfo = {
1106
+ clientId: clientInformation.client_id,
1107
+ clientSecret: clientInformation.client_secret,
1108
+ clientIdIssuedAt: clientInformation.client_id_issued_at,
1109
+ clientSecretExpiresAt: clientInformation.client_secret_expires_at
1110
+ };
1111
+ await this._store.setClient(this._storeKey, clientInfo);
1112
+ }
1113
+ }
1114
+ async tokens() {
1115
+ await this._ensureTokensLoaded();
1116
+ if (!this._tokens) {
1117
+ return;
1118
+ }
1119
+ const stored = await this._store.get(this._storeKey);
1120
+ if (stored?.expiresAt) {
1121
+ if (Date.now() >= stored.expiresAt - 60000) {
1122
+ if (this._tokens.refresh_token) {
1123
+ try {
1124
+ await this._refreshTokens();
1125
+ return this._tokens;
1126
+ } catch (error) {
1127
+ console.warn("Token refresh failed:", error);
1128
+ return;
1129
+ }
1130
+ }
1131
+ return;
1132
+ }
1133
+ }
1134
+ return this._tokens;
1135
+ }
1136
+ async saveTokens(tokens) {
1137
+ this._tokens = tokens;
1138
+ this._tokensLoaded = true;
1139
+ const storedTokens = {
1140
+ accessToken: tokens.access_token,
1141
+ refreshToken: tokens.refresh_token,
1142
+ expiresAt: tokens.expires_in ? calculateExpiry(tokens.expires_in) : undefined,
1143
+ scope: tokens.scope
1144
+ };
1145
+ await this._store.set(this._storeKey, storedTokens);
1146
+ }
1147
+ async redirectToAuthorization(authorizationUrl) {
1148
+ if (this._authInProgress) {
1149
+ await this._authInProgress;
1150
+ return;
1151
+ }
1152
+ this._authInProgress = this._doAuthorization(authorizationUrl);
1153
+ try {
1154
+ await this._authInProgress;
1155
+ } finally {
1156
+ this._authInProgress = undefined;
1157
+ }
1158
+ }
1159
+ async _doAuthorization(authorizationUrl) {
1160
+ let lastError;
1161
+ const maxRetries = 2;
1162
+ for (let attempt = 0;attempt <= maxRetries; attempt++) {
1163
+ try {
1164
+ const result = await getAuthCode({
1165
+ authorizationUrl: authorizationUrl.href,
1166
+ port: this._port,
1167
+ hostname: this._hostname,
1168
+ callbackPath: this._callbackPath,
1169
+ timeout: this._authTimeout,
1170
+ openBrowser: typeof this._openBrowser === "boolean" ? this._openBrowser : true,
1171
+ successHtml: this._successHtml,
1172
+ errorHtml: this._errorHtml,
1173
+ onRequest: this._onRequest
1174
+ });
1175
+ this._pendingAuthCode = result.code;
1176
+ this._pendingAuthState = result.state;
1177
+ setTimeout(() => {
1178
+ if (this._pendingAuthCode === result.code) {
1179
+ this._pendingAuthCode = undefined;
1180
+ this._pendingAuthState = undefined;
1181
+ }
1182
+ }, this._authTimeout);
1183
+ return;
1184
+ } catch (error) {
1185
+ lastError = error instanceof Error ? error : new Error(String(error));
1186
+ if (error instanceof OAuthError) {
1187
+ throw error;
1188
+ }
1189
+ if (attempt < maxRetries) {
1190
+ console.warn(`Auth attempt ${attempt + 1} failed, retrying...`, error);
1191
+ await new Promise((resolve) => setTimeout(resolve, 1000 * (attempt + 1)));
1192
+ }
1193
+ }
1194
+ }
1195
+ throw new Error(`OAuth authorization failed after ${maxRetries + 1} attempts: ${lastError?.message}`);
1196
+ }
1197
+ async saveCodeVerifier(codeVerifier) {
1198
+ this._codeVerifier = codeVerifier;
1199
+ if (this._isOAuthStore(this._store)) {
1200
+ const session = {
1201
+ codeVerifier,
1202
+ state: this._pendingAuthState
1203
+ };
1204
+ await this._store.setSession(this._storeKey, session);
1205
+ }
1206
+ }
1207
+ async codeVerifier() {
1208
+ if (!this._codeVerifier) {
1209
+ throw new Error("Code verifier not found");
1210
+ }
1211
+ return this._codeVerifier;
1212
+ }
1213
+ async invalidateCredentials(scope) {
1214
+ if (scope === "all" && this._isExchangingCode) {
1215
+ this._tokens = undefined;
1216
+ await this._store.delete(this._storeKey);
1217
+ return;
1218
+ }
1219
+ if (this._isExchangingCode && (scope === "client" || scope === "all")) {
1220
+ this._isExchangingCode = false;
1221
+ }
1222
+ switch (scope) {
1223
+ case "all":
1224
+ this._clientInfo = undefined;
1225
+ this._tokens = undefined;
1226
+ this._codeVerifier = undefined;
1227
+ this._tokensLoaded = false;
1228
+ await this._store.clear();
1229
+ break;
1230
+ case "client":
1231
+ this._clientInfo = undefined;
1232
+ if (this._isOAuthStore(this._store)) {
1233
+ await this._store.setClient(this._storeKey, {
1234
+ clientId: ""
1235
+ });
1236
+ }
1237
+ break;
1238
+ case "tokens":
1239
+ this._tokens = undefined;
1240
+ await this._store.delete(this._storeKey);
1241
+ break;
1242
+ case "verifier":
1243
+ this._codeVerifier = undefined;
1244
+ if (this._isOAuthStore(this._store)) {
1245
+ await this._store.setSession(this._storeKey, {});
1246
+ }
1247
+ break;
1248
+ }
1249
+ }
1250
+ async validateResourceURL(_serverUrl, _resource) {
1251
+ return;
1252
+ }
1253
+ getPendingAuthCode() {
1254
+ if (this._pendingAuthCode) {
1255
+ const result = {
1256
+ code: this._pendingAuthCode,
1257
+ state: this._pendingAuthState
1258
+ };
1259
+ this._isExchangingCode = true;
1260
+ this._pendingAuthCode = undefined;
1261
+ this._pendingAuthState = undefined;
1262
+ return result;
1263
+ }
1264
+ return;
1265
+ }
1266
+ async _refreshTokens() {
1267
+ if (this._refreshInProgress) {
1268
+ await this._refreshInProgress;
1269
+ return;
1270
+ }
1271
+ this._refreshInProgress = this._doRefreshTokens();
1272
+ try {
1273
+ await this._refreshInProgress;
1274
+ } finally {
1275
+ this._refreshInProgress = undefined;
1276
+ }
1277
+ }
1278
+ async _doRefreshTokens() {
1279
+ if (!this._tokens?.refresh_token) {
1280
+ throw new Error("No refresh token available");
1281
+ }
1282
+ const clientInfo = await this.clientInformation();
1283
+ if (!clientInfo?.client_id) {
1284
+ throw new Error("No client information available for refresh");
1285
+ }
1286
+ throw new Error("Token refresh not yet implemented - requires token endpoint URL");
1287
+ }
1288
+ }
1289
+ // src/storage/file.ts
1290
+ import * as fs6 from "node:fs/promises";
1291
+ import * as path2 from "node:path";
1292
+ import * as os2 from "node:os";
1293
+ function fileStore(filepath) {
1294
+ const file = filepath ?? path2.join(os2.homedir(), ".mcp", "tokens.json");
1295
+ async function ensureDir() {
1296
+ await fs6.mkdir(path2.dirname(file), { recursive: true });
1297
+ }
1298
+ async function readStore() {
1299
+ try {
1300
+ const data = await fs6.readFile(file, "utf-8");
1301
+ return JSON.parse(data);
1302
+ } catch {
1303
+ return {};
1304
+ }
1305
+ }
1306
+ async function writeStore(data) {
1307
+ await ensureDir();
1308
+ await fs6.writeFile(file, JSON.stringify(data, null, 2), "utf-8");
1309
+ }
1310
+ return {
1311
+ async get(key) {
1312
+ const store = await readStore();
1313
+ return store[key] ?? null;
1314
+ },
1315
+ async set(key, tokens) {
1316
+ const store = await readStore();
1317
+ store[key] = tokens;
1318
+ await writeStore(store);
1319
+ },
1320
+ async delete(key) {
1321
+ const store = await readStore();
1322
+ delete store[key];
1323
+ await writeStore(store);
1324
+ },
1325
+ async clear() {
1326
+ await writeStore({});
1327
+ }
1328
+ };
1329
+ }
949
1330
 
950
1331
  // src/index.ts
951
1332
  async function getAuthCode(input) {
@@ -994,6 +1375,9 @@ async function getAuthCode(input) {
994
1375
  }
995
1376
  }
996
1377
  export {
1378
+ inMemoryStore,
997
1379
  getAuthCode,
1380
+ fileStore,
1381
+ browserAuth,
998
1382
  OAuthError
999
1383
  };