tiny-http-mcp-server 0.1.64 → 0.1.66
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/mcp-oauth/README.md +7 -0
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +13 -4
- package/node_modules/tiny-mcp-client/README.md +1 -1
- package/node_modules/tiny-mcp-client/dist/index.js +35 -9
- package/package.json +1 -1
package/dist/composition.json
CHANGED
|
@@ -94,6 +94,13 @@ accept the same `redirectUri`, `signal` and `timeoutMs` options. Cancellation,
|
|
|
94
94
|
timeout and explicit close settle pending code waits and release listeners.
|
|
95
95
|
Always close a successful standalone session in `finally`.
|
|
96
96
|
|
|
97
|
+
Native provider calls capture request option handles before host work. Unauthorized
|
|
98
|
+
handling also owns complete discovery metadata, presented grant values, rejected
|
|
99
|
+
request headers and the selected challenge error before reading persistence.
|
|
100
|
+
A browser callback cannot redirect a later code exchange by changing caller
|
|
101
|
+
metadata. Explicit authentication owns metadata when lazy discovery returns;
|
|
102
|
+
the selected discovery method retains its original receiver and live host state.
|
|
103
|
+
|
|
97
104
|
Provider request inputs accept an optional `signal`. It reaches callback waits,
|
|
98
105
|
registration, token requests and bounded token-body reads. Cancellation retains
|
|
99
106
|
its original reason and does not retry authorization. Native provider calls
|
|
@@ -73,6 +73,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
73
73
|
let initialGrantConsumed = false;
|
|
74
74
|
return {
|
|
75
75
|
async authenticate(input) {
|
|
76
|
+
input = { ...input, ...(input.discover === undefined ? {} : { discover: input.discover.bind(input) }) };
|
|
76
77
|
input.signal?.throwIfAborted();
|
|
77
78
|
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
78
79
|
const resource = canonicalizeResourceIndicator(input.requestUrl);
|
|
@@ -84,7 +85,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
84
85
|
return { ...initialGrant.tokens };
|
|
85
86
|
if (input.discover === undefined)
|
|
86
87
|
return;
|
|
87
|
-
const discovery = await waitForOAuthOperation(input.discover(), input.signal);
|
|
88
|
+
const discovery = structuredClone(await waitForOAuthOperation(input.discover(), input.signal));
|
|
88
89
|
input.signal?.throwIfAborted();
|
|
89
90
|
assertRequestMatchesResource(resource, canonicalizeResourceIndicator(discovery.resource));
|
|
90
91
|
session = await ensureAuthorizedSession(resource, discovery, input.fetch, true, false, input.signal);
|
|
@@ -93,6 +94,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
93
94
|
return { ...session.tokens };
|
|
94
95
|
},
|
|
95
96
|
async authorizeRequest(input) {
|
|
97
|
+
input = { ...input };
|
|
96
98
|
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
97
99
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|
|
98
100
|
const session = await ensureAuthorizedSession(requestUrl, undefined, input.fetch, false, false, input.signal);
|
|
@@ -113,8 +115,16 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
113
115
|
return { ...session.tokens };
|
|
114
116
|
},
|
|
115
117
|
async handleUnauthorized(input) {
|
|
118
|
+
input = { ...input };
|
|
116
119
|
try {
|
|
117
120
|
input.signal?.throwIfAborted();
|
|
121
|
+
const presented = input.presentedTokens == null ? input.presentedTokens : normalizeStoredTokens(input.presentedTokens);
|
|
122
|
+
if (input.presentedTokens != null && presented === undefined)
|
|
123
|
+
throw new Error("OAuth rejected-request provenance does not match its authorization header");
|
|
124
|
+
const challengeError = input.challenge?.params.error;
|
|
125
|
+
input = { ...input, discovery: structuredClone(input.discovery),
|
|
126
|
+
...(input.requestHeaders === undefined ? {} : { requestHeaders: new Headers(input.requestHeaders) }),
|
|
127
|
+
...(presented === undefined ? {} : { presentedTokens: presented }) };
|
|
118
128
|
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
119
129
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|
|
120
130
|
const resource = canonicalizeResourceIndicator(input.discovery.resource);
|
|
@@ -126,16 +136,15 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
126
136
|
if (input.presentedTokens !== undefined) {
|
|
127
137
|
rejectedCurrentGrant = false;
|
|
128
138
|
if (input.presentedTokens !== null) {
|
|
129
|
-
const presented =
|
|
139
|
+
const presented = input.presentedTokens;
|
|
130
140
|
const header = input.requestHeaders?.get("Authorization") ?? "";
|
|
131
141
|
const separator = header.indexOf(" ");
|
|
132
|
-
if (
|
|
142
|
+
if (header.slice(0, separator).toLowerCase() !== "bearer" || header.slice(separator + 1).trim() !== presented.accessToken)
|
|
133
143
|
throw new Error("OAuth rejected-request provenance does not match its authorization header");
|
|
134
144
|
presentedTokens = presented;
|
|
135
145
|
rejectedCurrentGrant = currentTokens !== undefined && sameTokenGrant(currentTokens, presented);
|
|
136
146
|
}
|
|
137
147
|
}
|
|
138
|
-
const challengeError = input.challenge?.params.error;
|
|
139
148
|
const forceRefresh = rejectedCurrentGrant && (challengeError === "invalid_token" || (input.presentedTokens !== undefined && challengeError === undefined));
|
|
140
149
|
const session = await ensureAuthorizedSession(resource, {
|
|
141
150
|
...input.discovery,
|
|
@@ -101,7 +101,7 @@ const transport = new HttpTransport({
|
|
|
101
101
|
});
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
You can also call `discoverOAuthMetadata(resourceUrl, options)` directly, or instantiate `OAuthMetadataDiscovery` with a custom `fetch` implementation and shared cache. Lookup options accept `signal`; cancellation stops metadata fetches and body reads without trying another discovery candidate.
|
|
104
|
+
You can also call `discoverOAuthMetadata(resourceUrl, options)` directly, or instantiate `OAuthMetadataDiscovery` with a custom `fetch` implementation and shared cache. Lookup options accept `signal`; cancellation stops metadata fetches and body reads without trying another discovery candidate. It also settles while shared-cache reads, writes or eviction wait. Host cache work may finish afterward; its late rejection remains observed, and canceled discovery does not start another candidate.
|
|
105
105
|
|
|
106
106
|
OAuth provider inputs receive the originating request's `signal`, covering header authorization and unauthorized handling. Modern request cancellation stops its OAuth work while leaving other requests usable; transport disposal cancels all pending OAuth operations. The default provider propagates this signal to callback, registration and token work. Custom providers must observe it for their own operations.
|
|
107
107
|
|
|
@@ -5335,6 +5335,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5335
5335
|
let initialGrantConsumed = false;
|
|
5336
5336
|
return {
|
|
5337
5337
|
async authenticate(input) {
|
|
5338
|
+
input = { ...input, ...input.discover === void 0 ? {} : { discover: input.discover.bind(input) } };
|
|
5338
5339
|
input.signal?.throwIfAborted();
|
|
5339
5340
|
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
5340
5341
|
const resource = canonicalizeResourceIndicator(input.requestUrl);
|
|
@@ -5345,7 +5346,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5345
5346
|
return { ...initialGrant.tokens };
|
|
5346
5347
|
if (input.discover === void 0)
|
|
5347
5348
|
return;
|
|
5348
|
-
const discovery = await waitForOAuthOperation(input.discover(), input.signal);
|
|
5349
|
+
const discovery = structuredClone(await waitForOAuthOperation(input.discover(), input.signal));
|
|
5349
5350
|
input.signal?.throwIfAborted();
|
|
5350
5351
|
assertRequestMatchesResource(resource, canonicalizeResourceIndicator(discovery.resource));
|
|
5351
5352
|
session = await ensureAuthorizedSession(resource, discovery, input.fetch, true, false, input.signal);
|
|
@@ -5354,6 +5355,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5354
5355
|
return { ...session.tokens };
|
|
5355
5356
|
},
|
|
5356
5357
|
async authorizeRequest(input) {
|
|
5358
|
+
input = { ...input };
|
|
5357
5359
|
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
5358
5360
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|
|
5359
5361
|
const session = await ensureAuthorizedSession(requestUrl, void 0, input.fetch, false, false, input.signal);
|
|
@@ -5370,8 +5372,19 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5370
5372
|
return { ...session.tokens };
|
|
5371
5373
|
},
|
|
5372
5374
|
async handleUnauthorized(input) {
|
|
5375
|
+
input = { ...input };
|
|
5373
5376
|
try {
|
|
5374
5377
|
input.signal?.throwIfAborted();
|
|
5378
|
+
const presented = input.presentedTokens == null ? input.presentedTokens : normalizeStoredTokens(input.presentedTokens);
|
|
5379
|
+
if (input.presentedTokens != null && presented === void 0)
|
|
5380
|
+
throw new Error("OAuth rejected-request provenance does not match its authorization header");
|
|
5381
|
+
const challengeError = input.challenge?.params.error;
|
|
5382
|
+
input = {
|
|
5383
|
+
...input,
|
|
5384
|
+
discovery: structuredClone(input.discovery),
|
|
5385
|
+
...input.requestHeaders === void 0 ? {} : { requestHeaders: new Headers(input.requestHeaders) },
|
|
5386
|
+
...presented === void 0 ? {} : { presentedTokens: presented }
|
|
5387
|
+
};
|
|
5375
5388
|
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
5376
5389
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|
|
5377
5390
|
const resource = canonicalizeResourceIndicator(input.discovery.resource);
|
|
@@ -5383,16 +5396,15 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5383
5396
|
if (input.presentedTokens !== void 0) {
|
|
5384
5397
|
rejectedCurrentGrant = false;
|
|
5385
5398
|
if (input.presentedTokens !== null) {
|
|
5386
|
-
const
|
|
5399
|
+
const presented2 = input.presentedTokens;
|
|
5387
5400
|
const header = input.requestHeaders?.get("Authorization") ?? "";
|
|
5388
5401
|
const separator = header.indexOf(" ");
|
|
5389
|
-
if (
|
|
5402
|
+
if (header.slice(0, separator).toLowerCase() !== "bearer" || header.slice(separator + 1).trim() !== presented2.accessToken)
|
|
5390
5403
|
throw new Error("OAuth rejected-request provenance does not match its authorization header");
|
|
5391
|
-
presentedTokens =
|
|
5392
|
-
rejectedCurrentGrant = currentTokens !== void 0 && sameTokenGrant(currentTokens,
|
|
5404
|
+
presentedTokens = presented2;
|
|
5405
|
+
rejectedCurrentGrant = currentTokens !== void 0 && sameTokenGrant(currentTokens, presented2);
|
|
5393
5406
|
}
|
|
5394
5407
|
}
|
|
5395
|
-
const challengeError = input.challenge?.params.error;
|
|
5396
5408
|
const forceRefresh = rejectedCurrentGrant && (challengeError === "invalid_token" || input.presentedTokens !== void 0 && challengeError === void 0);
|
|
5397
5409
|
const session = await ensureAuthorizedSession(resource, {
|
|
5398
5410
|
...input.discovery,
|
|
@@ -6411,6 +6423,20 @@ function validateCachedDiscovery(value, resource) {
|
|
|
6411
6423
|
authorizationServerMetadata
|
|
6412
6424
|
});
|
|
6413
6425
|
}
|
|
6426
|
+
async function waitForCache(operation, signal) {
|
|
6427
|
+
if (signal === void 0) return operation;
|
|
6428
|
+
let abort;
|
|
6429
|
+
try {
|
|
6430
|
+
return await new Promise((resolve, reject) => {
|
|
6431
|
+
abort = () => reject(signal.reason);
|
|
6432
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
6433
|
+
Promise.resolve(operation).then(resolve, reject);
|
|
6434
|
+
if (signal.aborted) abort();
|
|
6435
|
+
});
|
|
6436
|
+
} finally {
|
|
6437
|
+
signal.removeEventListener("abort", abort);
|
|
6438
|
+
}
|
|
6439
|
+
}
|
|
6414
6440
|
var OAuthMetadataDiscovery = class {
|
|
6415
6441
|
fetchImpl;
|
|
6416
6442
|
cache;
|
|
@@ -6447,7 +6473,7 @@ var OAuthMetadataDiscovery = class {
|
|
|
6447
6473
|
if (memoryCachedResult !== void 0 && resourceMetadataUrl === void 0) {
|
|
6448
6474
|
return structuredClone(memoryCachedResult);
|
|
6449
6475
|
}
|
|
6450
|
-
const sharedCachedResult = await this.cache?.get(cacheKey);
|
|
6476
|
+
const sharedCachedResult = await waitForCache(this.cache?.get(cacheKey), signal);
|
|
6451
6477
|
signal?.throwIfAborted();
|
|
6452
6478
|
if (sharedCachedResult !== null && sharedCachedResult !== void 0 && resourceMetadataUrl === void 0) {
|
|
6453
6479
|
try {
|
|
@@ -6455,7 +6481,7 @@ var OAuthMetadataDiscovery = class {
|
|
|
6455
6481
|
this.memoryCache.set(cacheKey, structuredClone(result));
|
|
6456
6482
|
return result;
|
|
6457
6483
|
} catch {
|
|
6458
|
-
await this.cache?.delete?.(cacheKey);
|
|
6484
|
+
await waitForCache(this.cache?.delete?.(cacheKey), signal);
|
|
6459
6485
|
}
|
|
6460
6486
|
}
|
|
6461
6487
|
const { location: resourceMetadataLocation, metadata: resourceMetadata } = await this.discoverProtectedResource(cacheKey, resourceMetadataUrl, signal);
|
|
@@ -6478,7 +6504,7 @@ var OAuthMetadataDiscovery = class {
|
|
|
6478
6504
|
authorizationServerMetadata
|
|
6479
6505
|
};
|
|
6480
6506
|
this.memoryCache.set(cacheKey, structuredClone(result));
|
|
6481
|
-
await this.cache?.set(cacheKey, structuredClone(result));
|
|
6507
|
+
await waitForCache(this.cache?.set(cacheKey, structuredClone(result)), signal);
|
|
6482
6508
|
return result;
|
|
6483
6509
|
} catch (error) {
|
|
6484
6510
|
signal?.throwIfAborted();
|