tiny-http-mcp-server 0.1.60 → 0.1.62
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 +11 -3
- package/node_modules/mcp-oauth/dist/client/cancellable-operation.d.ts +2 -0
- package/node_modules/mcp-oauth/dist/client/cancellable-operation.js +18 -0
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +6 -3
- package/node_modules/mcp-oauth/dist/client/session-transaction.js +10 -3
- package/node_modules/mcp-oauth/dist/client/token-grant.js +1 -0
- package/node_modules/tiny-mcp-client/dist/index.js +31 -6
- package/package.json +1 -1
package/dist/composition.json
CHANGED
|
@@ -95,7 +95,11 @@ Always close a successful standalone session in `finally`.
|
|
|
95
95
|
|
|
96
96
|
Provider request inputs accept an optional `signal`. It reaches callback waits,
|
|
97
97
|
registration, token requests and bounded token-body reads. Cancellation retains
|
|
98
|
-
its original reason and does not retry authorization.
|
|
98
|
+
its original reason and does not retry authorization. Native provider calls
|
|
99
|
+
also settle cancellation while host persistence or lazy discovery callbacks are
|
|
100
|
+
waiting. An unfinished transaction keeps its lease until its host work finishes;
|
|
101
|
+
following callers must wait or reach their own lock-acquisition limit. This
|
|
102
|
+
prevents overlap with a pending refresh-intent write. Custom providers should
|
|
99
103
|
observe the supplied signal and pass it to any work they start.
|
|
100
104
|
|
|
101
105
|
`authorizeRequest` may return an owned token snapshot for the request it
|
|
@@ -153,9 +157,13 @@ used only for its resource.
|
|
|
153
157
|
Discovery binds an expired or explicitly rejected grant before silent refresh,
|
|
154
158
|
using the original configured client. Persisted sessions take precedence,
|
|
155
159
|
including sessions whose tokens have been cleared; an import cannot revive them.
|
|
156
|
-
Input tokens are copied
|
|
160
|
+
Input tokens are copied before any host clock anchors a relative lifetime;
|
|
161
|
+
clock mutation cannot replace the selected access/refresh/scope values. Invalid
|
|
162
|
+
expiry values fail before authorization.
|
|
157
163
|
For a raw OAuth response, `parseOAuthTokenGrant(response, { issuedAt, expiresAt })`
|
|
158
|
-
returns normalized `StoredOAuthTokens`.
|
|
164
|
+
returns normalized `StoredOAuthTokens`. Timing options are captured before the
|
|
165
|
+
host clock runs, so it cannot bypass their earlier validation. The parser accepts
|
|
166
|
+
`access_token`, `refresh_token`,
|
|
159
167
|
`token_type`, `scope`, `expires_in` (seconds), `expires_at` (epoch seconds), and
|
|
160
168
|
`expiresAt` (epoch milliseconds). Numeric absolute expiry wins over relative
|
|
161
169
|
lifetime; the options timestamp wins over response timestamps. The optional
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Settle the caller on cancellation without abandoning observation of host completion. */
|
|
2
|
+
export async function waitForOAuthOperation(operation, signal) {
|
|
3
|
+
if (signal === undefined)
|
|
4
|
+
return operation;
|
|
5
|
+
let abort;
|
|
6
|
+
try {
|
|
7
|
+
return await new Promise((resolve, reject) => {
|
|
8
|
+
abort = () => reject(signal.reason);
|
|
9
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
10
|
+
operation.then(resolve, reject);
|
|
11
|
+
if (signal.aborted)
|
|
12
|
+
abort();
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
signal.removeEventListener("abort", abort);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -12,6 +12,7 @@ import { generateCodeChallenge, generateCodeVerifier } from "./pkce.js";
|
|
|
12
12
|
import { exchangeAuthorizationCode, OAuthError, refreshAccessToken, isRetryableOAuthError, readOAuthJsonObjectResponse } from "./token-endpoint.js";
|
|
13
13
|
import { canonicalizeResourceIndicator } from "../resource-indicator.js";
|
|
14
14
|
import { withOAuthSessionTransaction } from "./session-transaction.js";
|
|
15
|
+
import { waitForOAuthOperation } from "./cancellable-operation.js";
|
|
15
16
|
const MAX_JS_DATE_MS = 8_640_000_000_000_000;
|
|
16
17
|
export function createOAuthClientProvider(options) {
|
|
17
18
|
if (isProviderOptions(options)) {
|
|
@@ -83,7 +84,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
83
84
|
return { ...initialGrant.tokens };
|
|
84
85
|
if (input.discover === undefined)
|
|
85
86
|
return;
|
|
86
|
-
const discovery = await input.discover();
|
|
87
|
+
const discovery = await waitForOAuthOperation(input.discover(), input.signal);
|
|
87
88
|
input.signal?.throwIfAborted();
|
|
88
89
|
assertRequestMatchesResource(resource, canonicalizeResourceIndicator(discovery.resource));
|
|
89
90
|
session = await ensureAuthorizedSession(resource, discovery, input.fetch, true, false, input.signal);
|
|
@@ -113,11 +114,12 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
113
114
|
},
|
|
114
115
|
async handleUnauthorized(input) {
|
|
115
116
|
try {
|
|
117
|
+
input.signal?.throwIfAborted();
|
|
116
118
|
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
117
119
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|
|
118
120
|
const resource = canonicalizeResourceIndicator(input.discovery.resource);
|
|
119
121
|
assertRequestMatchesResource(requestUrl, resource);
|
|
120
|
-
const cached = await loadSession(resource);
|
|
122
|
+
const cached = await waitForOAuthOperation(loadSession(resource), input.signal);
|
|
121
123
|
const currentTokens = cached?.tokens ?? (!initialGrantConsumed && initialGrant?.resource === resource ? initialGrant.tokens : undefined);
|
|
122
124
|
let rejectedCurrentGrant = hasCachedAccessToken(cached) || (!initialGrantConsumed && initialGrant?.resource === resource);
|
|
123
125
|
let presentedTokens = input.presentedTokens;
|
|
@@ -601,6 +603,7 @@ function normalizeImportedTokens(value, now) {
|
|
|
601
603
|
const absolute = getOwnEntry(value, "expiresAt");
|
|
602
604
|
const lifetime = getOwnEntry(value, "expiresIn");
|
|
603
605
|
const issuedAt = getOwnEntry(value, "issuedAt");
|
|
606
|
+
const snapshot = { ...value };
|
|
604
607
|
if (lifetime !== undefined && (typeof lifetime !== "number" || !Number.isSafeInteger(lifetime) || lifetime < 0))
|
|
605
608
|
throw new Error("OAuth initial grant has invalid relative expiry");
|
|
606
609
|
if (issuedAt !== undefined && (typeof issuedAt !== "number" || !Number.isSafeInteger(issuedAt) ||
|
|
@@ -608,7 +611,7 @@ function normalizeImportedTokens(value, now) {
|
|
|
608
611
|
throw new Error("OAuth initial grant has invalid issuance time");
|
|
609
612
|
const expiresAt = absolute !== undefined && absolute !== null ? absolute :
|
|
610
613
|
lifetime === undefined ? null : (issuedAt === undefined ? now() : issuedAt) + lifetime * 1000;
|
|
611
|
-
return normalizeStoredTokens({ ...
|
|
614
|
+
return normalizeStoredTokens({ ...snapshot, expiresAt });
|
|
612
615
|
}
|
|
613
616
|
function normalizeStoredTokens(value) {
|
|
614
617
|
if (value === undefined || !isObjectRecord(value)) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { waitForOAuthOperation } from "./cancellable-operation.js";
|
|
1
2
|
const queues = new WeakMap();
|
|
2
3
|
/** Serialize the complete read/redeem/write operation, with independent cancellation for waiters. */
|
|
3
4
|
export async function withOAuthSessionTransaction(store, resource, operation, options = {}) {
|
|
@@ -13,6 +14,7 @@ export async function withOAuthSessionTransaction(store, resource, operation, op
|
|
|
13
14
|
const current = new Promise(resolve => { release = resolve; });
|
|
14
15
|
const tail = previous.then(() => current);
|
|
15
16
|
pending.set(resource, tail);
|
|
17
|
+
let running;
|
|
16
18
|
try {
|
|
17
19
|
let timer;
|
|
18
20
|
let rejectWait;
|
|
@@ -30,12 +32,17 @@ export async function withOAuthSessionTransaction(store, resource, operation, op
|
|
|
30
32
|
options.signal?.removeEventListener("abort", abort);
|
|
31
33
|
}
|
|
32
34
|
options.signal?.throwIfAborted();
|
|
33
|
-
|
|
35
|
+
running = (async () => store.withLock === undefined ? operation() : store.withLock(resource, operation, {
|
|
34
36
|
signal: options.signal, timeoutMs: Math.max(0, timeoutMs - (performance.now() - started))
|
|
35
|
-
});
|
|
37
|
+
}))();
|
|
38
|
+
return await waitForOAuthOperation(running, options.signal);
|
|
36
39
|
}
|
|
37
40
|
finally {
|
|
38
|
-
|
|
41
|
+
// Cancellation settles the caller, but unfinished host work still owns the lease.
|
|
42
|
+
if (running === undefined)
|
|
43
|
+
release();
|
|
44
|
+
else
|
|
45
|
+
void running.then(release, release);
|
|
39
46
|
void tail.then(() => { if (pending.get(resource) === tail)
|
|
40
47
|
pending.delete(resource); });
|
|
41
48
|
}
|
|
@@ -2,6 +2,7 @@ import { copyBoundedOAuthJson } from "./bounded-json.js";
|
|
|
2
2
|
import { normalizeOAuthScope } from "./scope.js";
|
|
3
3
|
/** Validate a raw RFC token response and anchor its lifetime once for persistence. */
|
|
4
4
|
export function parseOAuthTokenGrant(value, options = {}) {
|
|
5
|
+
options = { ...options };
|
|
5
6
|
const invalid = () => new Error("Invalid OAuth token grant");
|
|
6
7
|
const result = copyBoundedOAuthJson(value, "Invalid OAuth token grant");
|
|
7
8
|
if (typeof result !== "object" || result === null || Array.isArray(result))
|
|
@@ -5194,6 +5194,24 @@ function normalizeBearerTokenType(value) {
|
|
|
5194
5194
|
return value.toLowerCase() === "bearer" ? "Bearer" : null;
|
|
5195
5195
|
}
|
|
5196
5196
|
|
|
5197
|
+
// ../mcp-oauth/dist/client/cancellable-operation.js
|
|
5198
|
+
async function waitForOAuthOperation(operation, signal) {
|
|
5199
|
+
if (signal === void 0)
|
|
5200
|
+
return operation;
|
|
5201
|
+
let abort;
|
|
5202
|
+
try {
|
|
5203
|
+
return await new Promise((resolve, reject) => {
|
|
5204
|
+
abort = () => reject(signal.reason);
|
|
5205
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
5206
|
+
operation.then(resolve, reject);
|
|
5207
|
+
if (signal.aborted)
|
|
5208
|
+
abort();
|
|
5209
|
+
});
|
|
5210
|
+
} finally {
|
|
5211
|
+
signal.removeEventListener("abort", abort);
|
|
5212
|
+
}
|
|
5213
|
+
}
|
|
5214
|
+
|
|
5197
5215
|
// ../mcp-oauth/dist/client/session-transaction.js
|
|
5198
5216
|
var queues = /* @__PURE__ */ new WeakMap();
|
|
5199
5217
|
async function withOAuthSessionTransaction(store, resource, operation, options = {}) {
|
|
@@ -5211,6 +5229,7 @@ async function withOAuthSessionTransaction(store, resource, operation, options =
|
|
|
5211
5229
|
});
|
|
5212
5230
|
const tail = previous.then(() => current);
|
|
5213
5231
|
pending.set(resource, tail);
|
|
5232
|
+
let running;
|
|
5214
5233
|
try {
|
|
5215
5234
|
let timer;
|
|
5216
5235
|
let rejectWait;
|
|
@@ -5230,12 +5249,16 @@ async function withOAuthSessionTransaction(store, resource, operation, options =
|
|
|
5230
5249
|
options.signal?.removeEventListener("abort", abort);
|
|
5231
5250
|
}
|
|
5232
5251
|
options.signal?.throwIfAborted();
|
|
5233
|
-
|
|
5252
|
+
running = (async () => store.withLock === void 0 ? operation() : store.withLock(resource, operation, {
|
|
5234
5253
|
signal: options.signal,
|
|
5235
5254
|
timeoutMs: Math.max(0, timeoutMs - (performance.now() - started))
|
|
5236
|
-
});
|
|
5255
|
+
}))();
|
|
5256
|
+
return await waitForOAuthOperation(running, options.signal);
|
|
5237
5257
|
} finally {
|
|
5238
|
-
|
|
5258
|
+
if (running === void 0)
|
|
5259
|
+
release();
|
|
5260
|
+
else
|
|
5261
|
+
void running.then(release, release);
|
|
5239
5262
|
void tail.then(() => {
|
|
5240
5263
|
if (pending.get(resource) === tail)
|
|
5241
5264
|
pending.delete(resource);
|
|
@@ -5312,7 +5335,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5312
5335
|
return { ...initialGrant.tokens };
|
|
5313
5336
|
if (input.discover === void 0)
|
|
5314
5337
|
return;
|
|
5315
|
-
const discovery = await input.discover();
|
|
5338
|
+
const discovery = await waitForOAuthOperation(input.discover(), input.signal);
|
|
5316
5339
|
input.signal?.throwIfAborted();
|
|
5317
5340
|
assertRequestMatchesResource(resource, canonicalizeResourceIndicator(discovery.resource));
|
|
5318
5341
|
session = await ensureAuthorizedSession(resource, discovery, input.fetch, true, false, input.signal);
|
|
@@ -5338,11 +5361,12 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5338
5361
|
},
|
|
5339
5362
|
async handleUnauthorized(input) {
|
|
5340
5363
|
try {
|
|
5364
|
+
input.signal?.throwIfAborted();
|
|
5341
5365
|
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
5342
5366
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|
|
5343
5367
|
const resource = canonicalizeResourceIndicator(input.discovery.resource);
|
|
5344
5368
|
assertRequestMatchesResource(requestUrl, resource);
|
|
5345
|
-
const cached = await loadSession(resource);
|
|
5369
|
+
const cached = await waitForOAuthOperation(loadSession(resource), input.signal);
|
|
5346
5370
|
const currentTokens = cached?.tokens ?? (!initialGrantConsumed && initialGrant?.resource === resource ? initialGrant.tokens : void 0);
|
|
5347
5371
|
let rejectedCurrentGrant = hasCachedAccessToken(cached) || !initialGrantConsumed && initialGrant?.resource === resource;
|
|
5348
5372
|
let presentedTokens = input.presentedTokens;
|
|
@@ -5810,12 +5834,13 @@ function normalizeImportedTokens(value, now) {
|
|
|
5810
5834
|
const absolute = getOwnEntry6(value, "expiresAt");
|
|
5811
5835
|
const lifetime = getOwnEntry6(value, "expiresIn");
|
|
5812
5836
|
const issuedAt = getOwnEntry6(value, "issuedAt");
|
|
5837
|
+
const snapshot = { ...value };
|
|
5813
5838
|
if (lifetime !== void 0 && (typeof lifetime !== "number" || !Number.isSafeInteger(lifetime) || lifetime < 0))
|
|
5814
5839
|
throw new Error("OAuth initial grant has invalid relative expiry");
|
|
5815
5840
|
if (issuedAt !== void 0 && (typeof issuedAt !== "number" || !Number.isSafeInteger(issuedAt) || Math.abs(issuedAt) > MAX_JS_DATE_MS3))
|
|
5816
5841
|
throw new Error("OAuth initial grant has invalid issuance time");
|
|
5817
5842
|
const expiresAt = absolute !== void 0 && absolute !== null ? absolute : lifetime === void 0 ? null : (issuedAt === void 0 ? now() : issuedAt) + lifetime * 1e3;
|
|
5818
|
-
return normalizeStoredTokens({ ...
|
|
5843
|
+
return normalizeStoredTokens({ ...snapshot, expiresAt });
|
|
5819
5844
|
}
|
|
5820
5845
|
function normalizeStoredTokens(value) {
|
|
5821
5846
|
if (value === void 0 || !isObjectRecord3(value)) {
|