tiny-http-mcp-server 0.1.25 → 0.1.27
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/dist/composition.json +1 -1
- package/node_modules/auth-store/README.md +16 -0
- package/node_modules/auth-store/dist/encrypted-file-store.d.ts +3 -0
- package/node_modules/auth-store/dist/encrypted-file-store.js +7 -0
- package/node_modules/auth-store/dist/index.d.ts +1 -0
- package/node_modules/auth-store/dist/keychain-store.d.ts +8 -0
- package/node_modules/auth-store/dist/keychain-store.js +13 -0
- package/node_modules/auth-store/dist/transaction-lock.d.ts +24 -0
- package/node_modules/auth-store/dist/transaction-lock.js +152 -0
- package/node_modules/auth-store/dist/types.d.ts +2 -0
- package/node_modules/mcp-oauth/README.md +16 -0
- package/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +6 -0
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +159 -182
- package/node_modules/mcp-oauth/dist/client/session-transaction.d.ts +6 -0
- package/node_modules/mcp-oauth/dist/client/session-transaction.js +42 -0
- package/node_modules/mcp-oauth/dist/client/types.d.ts +7 -0
- package/node_modules/tiny-mcp-client/dist/index.d.ts +31 -0
- package/node_modules/tiny-mcp-client/dist/index.js +442 -243
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ import { createAuthorizationState } from "./authorization-state.js";
|
|
|
7
7
|
import { generateCodeChallenge, generateCodeVerifier } from "./pkce.js";
|
|
8
8
|
import { exchangeAuthorizationCode, OAuthError, refreshAccessToken, isRetryableOAuthError, readOAuthJsonObjectResponse } from "./token-endpoint.js";
|
|
9
9
|
import { canonicalizeResourceIndicator } from "../resource-indicator.js";
|
|
10
|
+
import { withOAuthSessionTransaction } from "./session-transaction.js";
|
|
10
11
|
const MAX_JS_DATE_MS = 8_640_000_000_000_000;
|
|
11
12
|
export function createOAuthClientProvider(options) {
|
|
12
13
|
if (isProviderOptions(options)) {
|
|
@@ -20,8 +21,6 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
20
21
|
const clientStore = options.authStore === undefined ? null : createAuthStoreClientStore(options.authStore);
|
|
21
22
|
const now = options.now ?? Date.now;
|
|
22
23
|
const registeredClients = new Map();
|
|
23
|
-
const refreshPromises = new Map();
|
|
24
|
-
const authorizationPromises = new Map();
|
|
25
24
|
if (options.initialGrant !== undefined) {
|
|
26
25
|
let resource;
|
|
27
26
|
try {
|
|
@@ -115,205 +114,183 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
115
114
|
async function ensureAuthorizedSession(resource, discovery, fetch, allowInteractive, forceRefresh = false, signal, rejectedTokens) {
|
|
116
115
|
signal?.throwIfAborted();
|
|
117
116
|
const canonicalResource = canonicalizeResourceIndicator(resource);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (discovery !== undefined && getOwnString(discovery.authorizationServerMetadata, "issuer") !== discovery.authorizationServer) {
|
|
123
|
-
throw new Error("OAuth discovery authorization-server issuer mismatch");
|
|
124
|
-
}
|
|
125
|
-
if (session !== null && (canonicalizeResourceIndicator(session.resource) !== canonicalResource
|
|
126
|
-
|| getOwnString(session.discovery.authorizationServerMetadata, "issuer") !== session.authorizationServer
|
|
127
|
-
|| (discovery !== undefined && discovery.authorizationServer !== session.authorizationServer))) {
|
|
128
|
-
await clearSession(canonicalResource);
|
|
129
|
-
session = null;
|
|
130
|
-
}
|
|
131
|
-
if (session === null && discovery !== undefined && !initialGrantConsumed && initialGrant?.resource === canonicalResource &&
|
|
132
|
-
initialGrant.tokens !== undefined && initialGrant.client !== null) {
|
|
133
|
-
assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
|
|
134
|
-
session = { resource: canonicalResource, authorizationServer: discovery.authorizationServer,
|
|
135
|
-
client: initialGrant.client, tokens: initialGrant.tokens, discovery: toStoredDiscovery(discovery) };
|
|
136
|
-
await saveSession(canonicalResource, session);
|
|
137
|
-
initialGrantConsumed = true;
|
|
117
|
+
return withOAuthSessionTransaction(sessionStore, canonicalResource, async () => {
|
|
118
|
+
let session = await loadSession(canonicalResource);
|
|
119
|
+
if (session !== null && initialGrant?.resource === canonicalResource)
|
|
120
|
+
initialGrantConsumed = true;
|
|
138
121
|
signal?.throwIfAborted();
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
122
|
+
if (discovery !== undefined && getOwnString(discovery.authorizationServerMetadata, "issuer") !== discovery.authorizationServer) {
|
|
123
|
+
throw new Error("OAuth discovery authorization-server issuer mismatch");
|
|
124
|
+
}
|
|
125
|
+
if (session !== null && (canonicalizeResourceIndicator(session.resource) !== canonicalResource
|
|
126
|
+
|| getOwnString(session.discovery.authorizationServerMetadata, "issuer") !== session.authorizationServer
|
|
127
|
+
|| (discovery !== undefined && discovery.authorizationServer !== session.authorizationServer))) {
|
|
128
|
+
await clearSession(canonicalResource);
|
|
129
|
+
session = null;
|
|
130
|
+
}
|
|
131
|
+
if (session === null && discovery !== undefined && !initialGrantConsumed && initialGrant?.resource === canonicalResource &&
|
|
132
|
+
initialGrant.tokens !== undefined && initialGrant.client !== null) {
|
|
133
|
+
assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
|
|
134
|
+
session = { resource: canonicalResource, authorizationServer: discovery.authorizationServer,
|
|
135
|
+
client: initialGrant.client, tokens: initialGrant.tokens, discovery: toStoredDiscovery(discovery) };
|
|
136
|
+
await saveSession(canonicalResource, session);
|
|
137
|
+
initialGrantConsumed = true;
|
|
138
|
+
signal?.throwIfAborted();
|
|
139
|
+
}
|
|
140
|
+
if (forceRefresh && rejectedTokens !== undefined && (rejectedTokens === null || session?.tokens === undefined || !sameTokenGrant(session.tokens, rejectedTokens)))
|
|
141
|
+
forceRefresh = false;
|
|
142
|
+
const sessionDiscovery = resolveDiscovery(discovery, session);
|
|
143
|
+
if (session?.tokens !== undefined && !forceRefresh && !isExpired(session.tokens, now)) {
|
|
151
144
|
return session;
|
|
152
145
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
146
|
+
if (session?.tokens?.refreshToken !== undefined &&
|
|
147
|
+
sessionDiscovery !== undefined &&
|
|
148
|
+
(forceRefresh || isExpired(session.tokens, now))) {
|
|
149
|
+
session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch, signal);
|
|
150
|
+
if (session?.tokens !== undefined && !isExpired(session.tokens, now)) {
|
|
151
|
+
return session;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (forceRefresh && session?.tokens !== undefined) {
|
|
155
|
+
session = clearSessionTokens(session);
|
|
156
|
+
await saveSession(canonicalResource, session);
|
|
157
|
+
}
|
|
158
|
+
if (!allowInteractive || sessionDiscovery === undefined) {
|
|
159
|
+
return session;
|
|
160
|
+
}
|
|
161
|
+
if (options.allowInteractive === false)
|
|
162
|
+
throw new Error("OAuth interactive authorization is disabled");
|
|
163
|
+
return authorizeSession(canonicalResource, session, sessionDiscovery, fetch, signal);
|
|
164
|
+
}, { signal, timeoutMs: options.sessionLockTimeoutMs });
|
|
164
165
|
}
|
|
165
166
|
async function refreshSession(resource, session, discovery, fetch, signal) {
|
|
166
167
|
signal?.throwIfAborted();
|
|
167
168
|
assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return inFlight;
|
|
169
|
+
if (session.tokens?.refreshToken === undefined) {
|
|
170
|
+
return session;
|
|
171
171
|
}
|
|
172
|
-
|
|
172
|
+
let refreshAttempted = false;
|
|
173
|
+
let refreshedTokens;
|
|
174
|
+
while (true) {
|
|
173
175
|
try {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
refreshedTokens = await refreshAccessToken({
|
|
177
|
+
tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
|
|
178
|
+
clientId: session.client.clientId,
|
|
179
|
+
clientSecret: session.client.clientSecret,
|
|
180
|
+
refreshToken: session.tokens.refreshToken,
|
|
181
|
+
resource,
|
|
182
|
+
fetch, signal,
|
|
183
|
+
now
|
|
184
|
+
});
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
signal?.throwIfAborted();
|
|
189
|
+
if (error instanceof OAuthError && error.error === "invalid_grant") {
|
|
190
|
+
const clearedSession = clearSessionTokens(session);
|
|
191
|
+
await saveSession(resource, clearedSession);
|
|
192
|
+
return clearedSession;
|
|
176
193
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
refreshedTokens = await refreshAccessToken({
|
|
182
|
-
tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
|
|
183
|
-
clientId: session.client.clientId,
|
|
184
|
-
clientSecret: session.client.clientSecret,
|
|
185
|
-
refreshToken: session.tokens.refreshToken,
|
|
186
|
-
resource,
|
|
187
|
-
fetch, signal,
|
|
188
|
-
now
|
|
189
|
-
});
|
|
190
|
-
break;
|
|
191
|
-
}
|
|
192
|
-
catch (error) {
|
|
193
|
-
signal?.throwIfAborted();
|
|
194
|
-
if (error instanceof OAuthError && error.error === "invalid_grant") {
|
|
195
|
-
const clearedSession = clearSessionTokens(session);
|
|
196
|
-
await saveSession(resource, clearedSession);
|
|
197
|
-
return clearedSession;
|
|
198
|
-
}
|
|
199
|
-
if (shouldReRegisterStoredDynamicClient(error, await loadRegisteredClient(discovery.authorizationServer), false)) {
|
|
200
|
-
await clearRegisteredClient(discovery.authorizationServer);
|
|
201
|
-
await clearSession(resource);
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
if (!refreshAttempted && isRetryableOAuthError(error)) {
|
|
205
|
-
refreshAttempted = true;
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
throw error;
|
|
209
|
-
}
|
|
194
|
+
if (shouldReRegisterStoredDynamicClient(error, await loadRegisteredClient(discovery.authorizationServer), false)) {
|
|
195
|
+
await clearRegisteredClient(discovery.authorizationServer);
|
|
196
|
+
await clearSession(resource);
|
|
197
|
+
return null;
|
|
210
198
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
},
|
|
217
|
-
discovery: toStoredDiscovery(discovery)
|
|
218
|
-
};
|
|
219
|
-
await saveSession(resource, updatedSession);
|
|
220
|
-
return updatedSession;
|
|
221
|
-
}
|
|
222
|
-
finally {
|
|
223
|
-
refreshPromises.delete(resource);
|
|
199
|
+
if (!refreshAttempted && isRetryableOAuthError(error)) {
|
|
200
|
+
refreshAttempted = true;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
throw error;
|
|
224
204
|
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
205
|
+
}
|
|
206
|
+
const updatedSession = {
|
|
207
|
+
...session,
|
|
208
|
+
tokens: {
|
|
209
|
+
...refreshedTokens,
|
|
210
|
+
refreshToken: refreshedTokens.refreshToken ?? session.tokens.refreshToken
|
|
211
|
+
},
|
|
212
|
+
discovery: toStoredDiscovery(discovery)
|
|
213
|
+
};
|
|
214
|
+
await saveSession(resource, updatedSession);
|
|
215
|
+
return updatedSession;
|
|
228
216
|
}
|
|
229
217
|
async function authorizeSession(resource, existingSession, discovery, fetch, signal) {
|
|
230
218
|
signal?.throwIfAborted();
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
219
|
+
assertS256PkceSupport(discovery.authorizationServerMetadata);
|
|
220
|
+
assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
|
|
221
|
+
let currentSession = existingSession;
|
|
222
|
+
let transientRetryAttempted = false;
|
|
223
|
+
let reRegistrationAttempted = false;
|
|
224
|
+
while (true) {
|
|
225
|
+
const loopback = await createLoopbackAuthorizationSession({
|
|
226
|
+
openBrowser: options.browser.openBrowser,
|
|
227
|
+
readLine: options.browser.readLine,
|
|
228
|
+
createServer: options.browser.createServer,
|
|
229
|
+
landingPage: options.browser.landingPage,
|
|
230
|
+
redirectUri: options.browser.redirectUri,
|
|
231
|
+
signal: options.browser.signal === undefined ? signal : signal === undefined ? options.browser.signal : AbortSignal.any([signal, options.browser.signal]),
|
|
232
|
+
timeoutMs: options.browser.timeoutMs
|
|
233
|
+
});
|
|
234
|
+
let resolvedClient = null;
|
|
235
|
+
try {
|
|
236
|
+
resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch, signal);
|
|
237
|
+
const sessionWithoutTokens = {
|
|
238
|
+
resource,
|
|
239
|
+
authorizationServer: discovery.authorizationServer,
|
|
240
|
+
client: resolvedClient.client,
|
|
241
|
+
discovery: toStoredDiscovery(discovery)
|
|
242
|
+
};
|
|
243
|
+
await saveSession(resource, sessionWithoutTokens);
|
|
244
|
+
const verifier = generateCodeVerifier();
|
|
245
|
+
const challenge = generateCodeChallenge(verifier);
|
|
246
|
+
const authorizationUrl = buildAuthorizationUrl({
|
|
247
|
+
metadata: discovery.authorizationServerMetadata,
|
|
248
|
+
resource,
|
|
249
|
+
clientId: resolvedClient.client.clientId,
|
|
250
|
+
redirectUri: loopback.redirectUri,
|
|
251
|
+
codeChallenge: challenge,
|
|
252
|
+
clientMetadata: getClientMetadata(options.client)
|
|
250
253
|
});
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
redirectUri: loopback.redirectUri,
|
|
279
|
-
resource,
|
|
280
|
-
fetch, signal,
|
|
281
|
-
now
|
|
282
|
-
});
|
|
283
|
-
const session = {
|
|
284
|
-
...sessionWithoutTokens,
|
|
285
|
-
tokens
|
|
286
|
-
};
|
|
287
|
-
await saveSession(resource, session);
|
|
288
|
-
return session;
|
|
289
|
-
}
|
|
290
|
-
catch (error) {
|
|
291
|
-
signal?.throwIfAborted();
|
|
292
|
-
if (shouldReRegisterStoredDynamicClient(error, resolvedClient, reRegistrationAttempted)) {
|
|
293
|
-
reRegistrationAttempted = true;
|
|
294
|
-
await clearRegisteredClient(discovery.authorizationServer);
|
|
295
|
-
await clearSession(resource);
|
|
296
|
-
currentSession = null;
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
299
|
-
if (!transientRetryAttempted && isRetryableOAuthError(error)) {
|
|
300
|
-
transientRetryAttempted = true;
|
|
301
|
-
await clearSession(resource);
|
|
302
|
-
currentSession = null;
|
|
303
|
-
continue;
|
|
304
|
-
}
|
|
305
|
-
throw error;
|
|
254
|
+
const code = await loopback.waitForCode(authorizationUrl);
|
|
255
|
+
const tokens = await exchangeAuthorizationCode({
|
|
256
|
+
tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
|
|
257
|
+
clientId: resolvedClient.client.clientId,
|
|
258
|
+
clientSecret: resolvedClient.client.clientSecret,
|
|
259
|
+
code,
|
|
260
|
+
codeVerifier: verifier,
|
|
261
|
+
redirectUri: loopback.redirectUri,
|
|
262
|
+
resource,
|
|
263
|
+
fetch, signal,
|
|
264
|
+
now
|
|
265
|
+
});
|
|
266
|
+
const session = {
|
|
267
|
+
...sessionWithoutTokens,
|
|
268
|
+
tokens
|
|
269
|
+
};
|
|
270
|
+
await saveSession(resource, session);
|
|
271
|
+
return session;
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
signal?.throwIfAborted();
|
|
275
|
+
if (shouldReRegisterStoredDynamicClient(error, resolvedClient, reRegistrationAttempted)) {
|
|
276
|
+
reRegistrationAttempted = true;
|
|
277
|
+
await clearRegisteredClient(discovery.authorizationServer);
|
|
278
|
+
await clearSession(resource);
|
|
279
|
+
currentSession = null;
|
|
280
|
+
continue;
|
|
306
281
|
}
|
|
307
|
-
|
|
308
|
-
|
|
282
|
+
if (!transientRetryAttempted && isRetryableOAuthError(error)) {
|
|
283
|
+
transientRetryAttempted = true;
|
|
284
|
+
await clearSession(resource);
|
|
285
|
+
currentSession = null;
|
|
286
|
+
continue;
|
|
309
287
|
}
|
|
288
|
+
throw error;
|
|
310
289
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
authorizationPromises.set(resource, finalPromise);
|
|
316
|
-
return finalPromise;
|
|
290
|
+
finally {
|
|
291
|
+
loopback.close();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
317
294
|
}
|
|
318
295
|
async function resolveClient(existingSession, discovery, redirectUri, fetch, parentSignal) {
|
|
319
296
|
parentSignal?.throwIfAborted();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { OAuthSessionStore } from "./types.js";
|
|
2
|
+
/** Serialize the complete read/redeem/write operation, with independent cancellation for waiters. */
|
|
3
|
+
export declare function withOAuthSessionTransaction<T>(store: OAuthSessionStore, resource: string, operation: () => Promise<T>, options?: {
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
}): Promise<T>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const queues = new WeakMap();
|
|
2
|
+
/** Serialize the complete read/redeem/write operation, with independent cancellation for waiters. */
|
|
3
|
+
export async function withOAuthSessionTransaction(store, resource, operation, options = {}) {
|
|
4
|
+
const timeoutMs = options.timeoutMs ?? 30_000;
|
|
5
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2_147_483_647)
|
|
6
|
+
throw new Error("sessionLockTimeoutMs must be an integer from 1 to 2147483647 milliseconds");
|
|
7
|
+
options.signal?.throwIfAborted();
|
|
8
|
+
const started = performance.now();
|
|
9
|
+
const pending = queues.get(store) ?? new Map();
|
|
10
|
+
queues.set(store, pending);
|
|
11
|
+
const previous = pending.get(resource) ?? Promise.resolve();
|
|
12
|
+
let release;
|
|
13
|
+
const current = new Promise(resolve => { release = resolve; });
|
|
14
|
+
const tail = previous.then(() => current);
|
|
15
|
+
pending.set(resource, tail);
|
|
16
|
+
try {
|
|
17
|
+
let timer;
|
|
18
|
+
let rejectWait;
|
|
19
|
+
const waiting = new Promise((resolve, reject) => { rejectWait = reject; previous.then(resolve, reject); });
|
|
20
|
+
const abort = () => rejectWait(options.signal?.reason);
|
|
21
|
+
try {
|
|
22
|
+
timer = setTimeout(() => rejectWait(new Error("Timed out waiting for OAuth session transaction lock")), timeoutMs);
|
|
23
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
24
|
+
if (options.signal?.aborted)
|
|
25
|
+
abort();
|
|
26
|
+
await waiting;
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
clearTimeout(timer);
|
|
30
|
+
options.signal?.removeEventListener("abort", abort);
|
|
31
|
+
}
|
|
32
|
+
options.signal?.throwIfAborted();
|
|
33
|
+
return store.withLock === undefined ? await operation() : await store.withLock(resource, operation, {
|
|
34
|
+
signal: options.signal, timeoutMs: Math.max(0, timeoutMs - (performance.now() - started))
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
release();
|
|
39
|
+
void tail.then(() => { if (pending.get(resource) === tail)
|
|
40
|
+
pending.delete(resource); });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -88,6 +88,11 @@ export interface OAuthSessionStore {
|
|
|
88
88
|
load(resource: string): Promise<StoredOAuthSession | null>;
|
|
89
89
|
save(resource: string, session: StoredOAuthSession): Promise<void>;
|
|
90
90
|
clear(resource: string): Promise<void>;
|
|
91
|
+
/** Backend-wide lock covering a complete read/redeem/write transaction. */
|
|
92
|
+
withLock?<T>(resource: string, operation: () => Promise<T>, options: {
|
|
93
|
+
signal?: AbortSignal;
|
|
94
|
+
timeoutMs: number;
|
|
95
|
+
}): Promise<T>;
|
|
91
96
|
}
|
|
92
97
|
export interface DefaultOAuthClientProviderOptions {
|
|
93
98
|
client: {
|
|
@@ -103,6 +108,8 @@ export interface DefaultOAuthClientProviderOptions {
|
|
|
103
108
|
};
|
|
104
109
|
/** Disable interactive authorization while allowing cached tokens and silent refresh. */
|
|
105
110
|
allowInteractive?: boolean;
|
|
111
|
+
/** Maximum wait to acquire a session transaction lock (default 30,000 ms). */
|
|
112
|
+
sessionLockTimeoutMs?: number;
|
|
106
113
|
/** Import an existing grant for one resource. Persisted sessions take precedence. */
|
|
107
114
|
initialGrant?: {
|
|
108
115
|
resource: string;
|
|
@@ -20,11 +20,31 @@ interface McpSubscription {
|
|
|
20
20
|
cancel(): void;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
interface SecretStoreLockFileSystem {
|
|
24
|
+
mkdir(path: string, options?: {
|
|
25
|
+
recursive?: boolean;
|
|
26
|
+
mode?: number;
|
|
27
|
+
}): Promise<unknown>;
|
|
28
|
+
readdir(path: string): Promise<string[]>;
|
|
29
|
+
readFile(path: string, encoding: BufferEncoding): Promise<string>;
|
|
30
|
+
writeFile(path: string, value: string, options: {
|
|
31
|
+
encoding: BufferEncoding;
|
|
32
|
+
flag: string;
|
|
33
|
+
mode: number;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
rename(from: string, to: string): Promise<void>;
|
|
36
|
+
unlink(path: string): Promise<void>;
|
|
37
|
+
lstat(path: string): Promise<{
|
|
38
|
+
isSymbolicLink(): boolean;
|
|
39
|
+
}>;
|
|
40
|
+
}
|
|
41
|
+
|
|
23
42
|
interface MachineIdentity {
|
|
24
43
|
hostname: string;
|
|
25
44
|
username: string;
|
|
26
45
|
}
|
|
27
46
|
interface EncryptedFileStoreFileSystem {
|
|
47
|
+
readdir?(path: string): Promise<string[]>;
|
|
28
48
|
readFile(path: string, encoding: BufferEncoding): Promise<string>;
|
|
29
49
|
writeFile(path: string, data: string | NodeJS.ArrayBufferView, options?: {
|
|
30
50
|
encoding?: BufferEncoding;
|
|
@@ -65,6 +85,10 @@ interface KeychainStoreInput {
|
|
|
65
85
|
runCommand?: KeychainCommandRunner;
|
|
66
86
|
service: string;
|
|
67
87
|
account: string;
|
|
88
|
+
lock?: {
|
|
89
|
+
fs?: SecretStoreLockFileSystem;
|
|
90
|
+
directory?: string;
|
|
91
|
+
};
|
|
68
92
|
}
|
|
69
93
|
|
|
70
94
|
type StoreBackend = "file" | "keychain";
|
|
@@ -165,6 +189,11 @@ interface OAuthSessionStore {
|
|
|
165
189
|
load(resource: string): Promise<StoredOAuthSession | null>;
|
|
166
190
|
save(resource: string, session: StoredOAuthSession): Promise<void>;
|
|
167
191
|
clear(resource: string): Promise<void>;
|
|
192
|
+
/** Backend-wide lock covering a complete read/redeem/write transaction. */
|
|
193
|
+
withLock?<T>(resource: string, operation: () => Promise<T>, options: {
|
|
194
|
+
signal?: AbortSignal;
|
|
195
|
+
timeoutMs: number;
|
|
196
|
+
}): Promise<T>;
|
|
168
197
|
}
|
|
169
198
|
interface DefaultOAuthClientProviderOptions {
|
|
170
199
|
client: {
|
|
@@ -180,6 +209,8 @@ interface DefaultOAuthClientProviderOptions {
|
|
|
180
209
|
};
|
|
181
210
|
/** Disable interactive authorization while allowing cached tokens and silent refresh. */
|
|
182
211
|
allowInteractive?: boolean;
|
|
212
|
+
/** Maximum wait to acquire a session transaction lock (default 30,000 ms). */
|
|
213
|
+
sessionLockTimeoutMs?: number;
|
|
183
214
|
/** Import an existing grant for one resource. Persisted sessions take precedence. */
|
|
184
215
|
initialGrant?: {
|
|
185
216
|
resource: string;
|