tiny-http-mcp-server 0.1.27 → 0.1.28
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/auth-store-session-store.js +2 -0
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +18 -1
- package/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +3 -1
- package/node_modules/mcp-oauth/dist/client/token-endpoint.js +7 -3
- package/node_modules/mcp-oauth/dist/client/types.d.ts +2 -0
- package/node_modules/tiny-mcp-client/dist/index.d.ts +2 -0
- package/node_modules/tiny-mcp-client/dist/index.js +24 -5
- package/package.json +1 -1
package/dist/composition.json
CHANGED
|
@@ -118,9 +118,17 @@ acquisition, while token and browser operations retain their own deadlines.
|
|
|
118
118
|
The native `auth-store` session adapter implements this hook for both encrypted
|
|
119
119
|
files and Keychain identities, including across independent processes. Locks
|
|
120
120
|
cover the complete read, refresh/authorization and persisted winner. Dead-owner
|
|
121
|
-
claims are recovered without stealing a live transaction.
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
claims are recovered without stealing a live transaction.
|
|
122
|
+
|
|
123
|
+
Before sending a refresh request, the provider persists a tokenless session with
|
|
124
|
+
`refreshState: "pending"`, retaining the original client and discovery binding.
|
|
125
|
+
A successful response replaces it with the rotated grant. A crash, cancellation,
|
|
126
|
+
network disconnect or incomplete response leaves the marker, so another process
|
|
127
|
+
cannot replay a possibly consumed refresh token or revive the initial import.
|
|
128
|
+
Such a session requires fresh authorization. Interactive unauthorized handling
|
|
129
|
+
can recover it; headless requests fail with an explicit unknown-outcome error.
|
|
130
|
+
Only complete OAuth error responses establish a rejected request and allow a
|
|
131
|
+
transient retry or restoration of the original grant. Gateway error pages do not.
|
|
124
132
|
|
|
125
133
|
## Environment Variables
|
|
126
134
|
|
|
@@ -125,6 +125,8 @@ function isStoredOAuthSession(value) {
|
|
|
125
125
|
isNonBlankOwnString(value, "authorizationServer") &&
|
|
126
126
|
isStoredOAuthClient(getOwnEntry(value, "client")) &&
|
|
127
127
|
isStoredOAuthDiscovery(getOwnEntry(value, "discovery")) &&
|
|
128
|
+
(getOwnEntry(value, "refreshState") === undefined ||
|
|
129
|
+
(getOwnEntry(value, "refreshState") === "pending" && getOwnEntry(value, "tokens") === undefined)) &&
|
|
128
130
|
isStoredOAuthTokensOrMissing(getOwnEntry(value, "tokens")));
|
|
129
131
|
}
|
|
130
132
|
function isStoredOAuthClient(value) {
|
|
@@ -140,6 +140,11 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
140
140
|
if (forceRefresh && rejectedTokens !== undefined && (rejectedTokens === null || session?.tokens === undefined || !sameTokenGrant(session.tokens, rejectedTokens)))
|
|
141
141
|
forceRefresh = false;
|
|
142
142
|
const sessionDiscovery = resolveDiscovery(discovery, session);
|
|
143
|
+
if (session?.refreshState === "pending") {
|
|
144
|
+
if (!allowInteractive || options.allowInteractive === false || sessionDiscovery === undefined)
|
|
145
|
+
throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
|
|
146
|
+
return authorizeSession(canonicalResource, clearSessionTokens(session), sessionDiscovery, fetch, signal);
|
|
147
|
+
}
|
|
143
148
|
if (session?.tokens !== undefined && !forceRefresh && !isExpired(session.tokens, now)) {
|
|
144
149
|
return session;
|
|
145
150
|
}
|
|
@@ -169,6 +174,9 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
169
174
|
if (session.tokens?.refreshToken === undefined) {
|
|
170
175
|
return session;
|
|
171
176
|
}
|
|
177
|
+
const pendingSession = { ...clearSessionTokens(session), refreshState: "pending" };
|
|
178
|
+
await saveSession(resource, pendingSession);
|
|
179
|
+
signal?.throwIfAborted();
|
|
172
180
|
let refreshAttempted = false;
|
|
173
181
|
let refreshedTokens;
|
|
174
182
|
while (true) {
|
|
@@ -186,7 +194,11 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
186
194
|
}
|
|
187
195
|
catch (error) {
|
|
188
196
|
signal?.throwIfAborted();
|
|
189
|
-
|
|
197
|
+
// Network errors, lost/malformed bodies and gateway failures cannot
|
|
198
|
+
// establish whether a rotating refresh token was already consumed.
|
|
199
|
+
if (!(error instanceof OAuthError) || !error.outcomeKnown)
|
|
200
|
+
throw error;
|
|
201
|
+
if (error.error === "invalid_grant") {
|
|
190
202
|
const clearedSession = clearSessionTokens(session);
|
|
191
203
|
await saveSession(resource, clearedSession);
|
|
192
204
|
return clearedSession;
|
|
@@ -200,6 +212,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
200
212
|
refreshAttempted = true;
|
|
201
213
|
continue;
|
|
202
214
|
}
|
|
215
|
+
await saveSession(resource, session);
|
|
203
216
|
throw error;
|
|
204
217
|
}
|
|
205
218
|
}
|
|
@@ -457,6 +470,7 @@ function sameTokenGrant(left, right) {
|
|
|
457
470
|
function clearSessionTokens(session) {
|
|
458
471
|
const nextSession = { ...session };
|
|
459
472
|
delete nextSession.tokens;
|
|
473
|
+
delete nextSession.refreshState;
|
|
460
474
|
return nextSession;
|
|
461
475
|
}
|
|
462
476
|
function hasCachedAccessToken(session) {
|
|
@@ -466,6 +480,9 @@ function normalizeLoadedSession(session) {
|
|
|
466
480
|
if (session === null) {
|
|
467
481
|
return null;
|
|
468
482
|
}
|
|
483
|
+
const refreshState = getOwnEntry(session, "refreshState");
|
|
484
|
+
if (refreshState !== undefined && (refreshState !== "pending" || getOwnEntry(session, "tokens") !== undefined))
|
|
485
|
+
throw new Error("Stored OAuth refresh state is invalid");
|
|
469
486
|
const client = normalizeStoredClient(getOwnEntry(session, "client"));
|
|
470
487
|
if (client === null) {
|
|
471
488
|
return { ...session, client: { clientId: "" }, tokens: undefined };
|
|
@@ -13,7 +13,9 @@ export declare class OAuthError extends Error {
|
|
|
13
13
|
readonly status: number;
|
|
14
14
|
readonly retryable: boolean;
|
|
15
15
|
readonly terminal: boolean;
|
|
16
|
-
|
|
16
|
+
/** True only when a complete OAuth error response establishes rejection. */
|
|
17
|
+
readonly outcomeKnown: boolean;
|
|
18
|
+
constructor(shape: OAuthErrorShape, status: number, outcomeKnown?: boolean);
|
|
17
19
|
}
|
|
18
20
|
export declare function isRetryableOAuthError(error: unknown): error is OAuthError;
|
|
19
21
|
export declare function exchangeAuthorizationCode(input: {
|
|
@@ -11,7 +11,9 @@ export class OAuthError extends Error {
|
|
|
11
11
|
status;
|
|
12
12
|
retryable;
|
|
13
13
|
terminal;
|
|
14
|
-
|
|
14
|
+
/** True only when a complete OAuth error response establishes rejection. */
|
|
15
|
+
outcomeKnown;
|
|
16
|
+
constructor(shape, status, outcomeKnown = true) {
|
|
15
17
|
super(shape.error_description ?? shape.error);
|
|
16
18
|
this.name = "OAuthError";
|
|
17
19
|
this.error = shape.error;
|
|
@@ -22,6 +24,7 @@ export class OAuthError extends Error {
|
|
|
22
24
|
this.status = status;
|
|
23
25
|
this.retryable = isRetryableOAuthError(this);
|
|
24
26
|
this.terminal = !this.retryable;
|
|
27
|
+
this.outcomeKnown = outcomeKnown;
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
export function isRetryableOAuthError(error) {
|
|
@@ -147,7 +150,8 @@ export async function readOAuthJsonObjectResponse(response, signal) {
|
|
|
147
150
|
}
|
|
148
151
|
const record = payload;
|
|
149
152
|
if (!response.ok) {
|
|
150
|
-
|
|
153
|
+
const error = getOwnEntry(record, "error");
|
|
154
|
+
throw new OAuthError(readOAuthError(record, fallbackError.error), response.status, typeof error === "string" && error.trim().length > 0);
|
|
151
155
|
}
|
|
152
156
|
return record;
|
|
153
157
|
}
|
|
@@ -166,7 +170,7 @@ function getOwnEntry(record, key) {
|
|
|
166
170
|
}
|
|
167
171
|
function createFallbackOAuthError(status) {
|
|
168
172
|
const error = status === 503 ? "temporarily_unavailable" : "server_error";
|
|
169
|
-
return new OAuthError({ error }, status);
|
|
173
|
+
return new OAuthError({ error }, status, false);
|
|
170
174
|
}
|
|
171
175
|
function normalizeBearerTokenType(value) {
|
|
172
176
|
if (typeof value !== "string") {
|
|
@@ -78,6 +78,8 @@ export interface StoredOAuthSession {
|
|
|
78
78
|
clientSecret?: string;
|
|
79
79
|
};
|
|
80
80
|
tokens?: StoredOAuthTokens;
|
|
81
|
+
/** A refresh was begun; its winning response may not have been persisted. */
|
|
82
|
+
refreshState?: "pending";
|
|
81
83
|
discovery: {
|
|
82
84
|
resourceMetadataUrl: string;
|
|
83
85
|
resourceMetadata: Record<string, unknown>;
|
|
@@ -179,6 +179,8 @@ interface StoredOAuthSession {
|
|
|
179
179
|
clientSecret?: string;
|
|
180
180
|
};
|
|
181
181
|
tokens?: StoredOAuthTokens;
|
|
182
|
+
/** A refresh was begun; its winning response may not have been persisted. */
|
|
183
|
+
refreshState?: "pending";
|
|
182
184
|
discovery: {
|
|
183
185
|
resourceMetadataUrl: string;
|
|
184
186
|
resourceMetadata: Record<string, unknown>;
|
|
@@ -4176,7 +4176,7 @@ function isStoredOAuthSession(value) {
|
|
|
4176
4176
|
if (!isObjectRecord(value)) {
|
|
4177
4177
|
return false;
|
|
4178
4178
|
}
|
|
4179
|
-
return isNonBlankOwnString(value, "resource") && isNonBlankOwnString(value, "authorizationServer") && isStoredOAuthClient(getOwnEntry3(value, "client")) && isStoredOAuthDiscovery(getOwnEntry3(value, "discovery")) && isStoredOAuthTokensOrMissing(getOwnEntry3(value, "tokens"));
|
|
4179
|
+
return isNonBlankOwnString(value, "resource") && isNonBlankOwnString(value, "authorizationServer") && isStoredOAuthClient(getOwnEntry3(value, "client")) && isStoredOAuthDiscovery(getOwnEntry3(value, "discovery")) && (getOwnEntry3(value, "refreshState") === void 0 || getOwnEntry3(value, "refreshState") === "pending" && getOwnEntry3(value, "tokens") === void 0) && isStoredOAuthTokensOrMissing(getOwnEntry3(value, "tokens"));
|
|
4180
4180
|
}
|
|
4181
4181
|
function isStoredOAuthClient(value) {
|
|
4182
4182
|
if (!isObjectRecord(value) || !isNonBlankOwnString(value, "clientId")) {
|
|
@@ -4605,7 +4605,9 @@ var OAuthError = class extends Error {
|
|
|
4605
4605
|
status;
|
|
4606
4606
|
retryable;
|
|
4607
4607
|
terminal;
|
|
4608
|
-
|
|
4608
|
+
/** True only when a complete OAuth error response establishes rejection. */
|
|
4609
|
+
outcomeKnown;
|
|
4610
|
+
constructor(shape, status, outcomeKnown = true) {
|
|
4609
4611
|
super(shape.error_description ?? shape.error);
|
|
4610
4612
|
this.name = "OAuthError";
|
|
4611
4613
|
this.error = shape.error;
|
|
@@ -4616,6 +4618,7 @@ var OAuthError = class extends Error {
|
|
|
4616
4618
|
this.status = status;
|
|
4617
4619
|
this.retryable = isRetryableOAuthError(this);
|
|
4618
4620
|
this.terminal = !this.retryable;
|
|
4621
|
+
this.outcomeKnown = outcomeKnown;
|
|
4619
4622
|
}
|
|
4620
4623
|
};
|
|
4621
4624
|
function isRetryableOAuthError(error) {
|
|
@@ -4730,7 +4733,8 @@ async function readOAuthJsonObjectResponse(response, signal) {
|
|
|
4730
4733
|
}
|
|
4731
4734
|
const record2 = payload;
|
|
4732
4735
|
if (!response.ok) {
|
|
4733
|
-
|
|
4736
|
+
const error = getOwnEntry5(record2, "error");
|
|
4737
|
+
throw new OAuthError(readOAuthError(record2, fallbackError.error), response.status, typeof error === "string" && error.trim().length > 0);
|
|
4734
4738
|
}
|
|
4735
4739
|
return record2;
|
|
4736
4740
|
}
|
|
@@ -4749,7 +4753,7 @@ function getOwnEntry5(record2, key2) {
|
|
|
4749
4753
|
}
|
|
4750
4754
|
function createFallbackOAuthError(status) {
|
|
4751
4755
|
const error = status === 503 ? "temporarily_unavailable" : "server_error";
|
|
4752
|
-
return new OAuthError({ error }, status);
|
|
4756
|
+
return new OAuthError({ error }, status, false);
|
|
4753
4757
|
}
|
|
4754
4758
|
function normalizeBearerTokenType(value) {
|
|
4755
4759
|
if (typeof value !== "string") {
|
|
@@ -4935,6 +4939,11 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4935
4939
|
if (forceRefresh && rejectedTokens !== void 0 && (rejectedTokens === null || session?.tokens === void 0 || !sameTokenGrant(session.tokens, rejectedTokens)))
|
|
4936
4940
|
forceRefresh = false;
|
|
4937
4941
|
const sessionDiscovery = resolveDiscovery(discovery, session);
|
|
4942
|
+
if (session?.refreshState === "pending") {
|
|
4943
|
+
if (!allowInteractive || options.allowInteractive === false || sessionDiscovery === void 0)
|
|
4944
|
+
throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
|
|
4945
|
+
return authorizeSession(canonicalResource, clearSessionTokens(session), sessionDiscovery, fetch2, signal);
|
|
4946
|
+
}
|
|
4938
4947
|
if (session?.tokens !== void 0 && !forceRefresh && !isExpired(session.tokens, now)) {
|
|
4939
4948
|
return session;
|
|
4940
4949
|
}
|
|
@@ -4962,6 +4971,9 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4962
4971
|
if (session.tokens?.refreshToken === void 0) {
|
|
4963
4972
|
return session;
|
|
4964
4973
|
}
|
|
4974
|
+
const pendingSession = { ...clearSessionTokens(session), refreshState: "pending" };
|
|
4975
|
+
await saveSession(resource, pendingSession);
|
|
4976
|
+
signal?.throwIfAborted();
|
|
4965
4977
|
let refreshAttempted = false;
|
|
4966
4978
|
let refreshedTokens;
|
|
4967
4979
|
while (true) {
|
|
@@ -4979,7 +4991,9 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4979
4991
|
break;
|
|
4980
4992
|
} catch (error) {
|
|
4981
4993
|
signal?.throwIfAborted();
|
|
4982
|
-
if (error instanceof OAuthError
|
|
4994
|
+
if (!(error instanceof OAuthError) || !error.outcomeKnown)
|
|
4995
|
+
throw error;
|
|
4996
|
+
if (error.error === "invalid_grant") {
|
|
4983
4997
|
const clearedSession = clearSessionTokens(session);
|
|
4984
4998
|
await saveSession(resource, clearedSession);
|
|
4985
4999
|
return clearedSession;
|
|
@@ -4993,6 +5007,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4993
5007
|
refreshAttempted = true;
|
|
4994
5008
|
continue;
|
|
4995
5009
|
}
|
|
5010
|
+
await saveSession(resource, session);
|
|
4996
5011
|
throw error;
|
|
4997
5012
|
}
|
|
4998
5013
|
}
|
|
@@ -5239,6 +5254,7 @@ function sameTokenGrant(left, right) {
|
|
|
5239
5254
|
function clearSessionTokens(session) {
|
|
5240
5255
|
const nextSession = { ...session };
|
|
5241
5256
|
delete nextSession.tokens;
|
|
5257
|
+
delete nextSession.refreshState;
|
|
5242
5258
|
return nextSession;
|
|
5243
5259
|
}
|
|
5244
5260
|
function hasCachedAccessToken(session) {
|
|
@@ -5248,6 +5264,9 @@ function normalizeLoadedSession(session) {
|
|
|
5248
5264
|
if (session === null) {
|
|
5249
5265
|
return null;
|
|
5250
5266
|
}
|
|
5267
|
+
const refreshState = getOwnEntry6(session, "refreshState");
|
|
5268
|
+
if (refreshState !== void 0 && (refreshState !== "pending" || getOwnEntry6(session, "tokens") !== void 0))
|
|
5269
|
+
throw new Error("Stored OAuth refresh state is invalid");
|
|
5251
5270
|
const client = normalizeStoredClient(getOwnEntry6(session, "client"));
|
|
5252
5271
|
if (client === null) {
|
|
5253
5272
|
return { ...session, client: { clientId: "" }, tokens: void 0 };
|