tiny-http-mcp-server 0.1.21 → 0.1.22
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 +22 -1
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +27 -13
- package/node_modules/mcp-oauth/dist/client/loopback-authorization.d.ts +5 -0
- package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +134 -72
- package/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +2 -0
- package/node_modules/mcp-oauth/dist/client/token-endpoint.js +5 -1
- package/node_modules/mcp-oauth/dist/client/types.d.ts +9 -1
- package/node_modules/tiny-mcp-client/README.md +3 -1
- package/node_modules/tiny-mcp-client/dist/index.d.ts +12 -2
- package/node_modules/tiny-mcp-client/dist/index.js +226 -112
- package/package.json +1 -1
package/dist/composition.json
CHANGED
|
@@ -40,10 +40,14 @@ const verifier = createJwksTokenVerifier({
|
|
|
40
40
|
- `client`
|
|
41
41
|
- `mode: "dynamic"` with optional `metadata`
|
|
42
42
|
- `mode: "static"` with `clientId`, optional `clientSecret`, optional `metadata`
|
|
43
|
-
- `
|
|
43
|
+
- `allowInteractive: false` prevents interactive login while retaining cached tokens and silent refresh
|
|
44
|
+
- `browser.openBrowser(url)` optional
|
|
44
45
|
- `browser.readLine()` optional
|
|
45
46
|
- `browser.createServer()` optional
|
|
46
47
|
- `browser.landingPage` optional
|
|
48
|
+
- `browser.redirectUri` optional exact registered HTTP loopback callback with a fixed port
|
|
49
|
+
- `browser.signal` optional cancellation signal
|
|
50
|
+
- `browser.timeoutMs` optional authorization deadline (default 120,000 ms)
|
|
47
51
|
- `sessionStore` optional
|
|
48
52
|
- `authStore` optional `auth-store` backend config for the default session store
|
|
49
53
|
- `now()` optional clock override
|
|
@@ -62,6 +66,23 @@ const verifier = createJwksTokenVerifier({
|
|
|
62
66
|
| `requireAccessTokenType` | `boolean` | `false` | Require the JWT `typ` protected header to be `at+jwt`. |
|
|
63
67
|
| `fetch` | `typeof fetch` | global `fetch` | Custom fetch implementation. |
|
|
64
68
|
|
|
69
|
+
Fixed redirects support `localhost`, `127.0.0.1`, and `::1` over HTTP. Their
|
|
70
|
+
exact spelling, port, path and query are preserved through registration,
|
|
71
|
+
authorization and code exchange. Credentials, fragments, port zero and reserved
|
|
72
|
+
OAuth callback query parameters are rejected before binding a listener. Omit
|
|
73
|
+
`redirectUri` to allocate a random loopback port. Standalone callback sessions
|
|
74
|
+
accept the same `redirectUri`, `signal` and `timeoutMs` options. Cancellation,
|
|
75
|
+
timeout and explicit close settle pending code waits and release listeners.
|
|
76
|
+
Always close a successful standalone session in `finally`.
|
|
77
|
+
|
|
78
|
+
Provider request inputs accept an optional `signal`. It reaches callback waits,
|
|
79
|
+
registration, token requests and bounded token-body reads. Cancellation retains
|
|
80
|
+
its original reason and does not retry authorization. Custom providers should
|
|
81
|
+
observe the supplied signal and pass it to any work they start.
|
|
82
|
+
|
|
83
|
+
Configure `client.metadata.scope` to request a precise scope set; broader
|
|
84
|
+
discovery metadata does not override it.
|
|
85
|
+
|
|
65
86
|
`createAuthStoreSessionStore(options)` accepts the standard `auth-store` config.
|
|
66
87
|
|
|
67
88
|
## Environment Variables
|
|
@@ -25,7 +25,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
25
25
|
async authorizeRequest(input) {
|
|
26
26
|
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
27
27
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|
|
28
|
-
const session = await ensureAuthorizedSession(requestUrl, undefined, input.fetch, false);
|
|
28
|
+
const session = await ensureAuthorizedSession(requestUrl, undefined, input.fetch, false, false, input.signal);
|
|
29
29
|
const accessToken = session?.tokens?.accessToken;
|
|
30
30
|
if (session === null ||
|
|
31
31
|
accessToken === undefined ||
|
|
@@ -47,13 +47,14 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
47
47
|
const session = await ensureAuthorizedSession(resource, {
|
|
48
48
|
...input.discovery,
|
|
49
49
|
resource
|
|
50
|
-
}, input.fetch, true, forceRefresh);
|
|
50
|
+
}, input.fetch, true, forceRefresh, input.signal);
|
|
51
51
|
if (session?.tokens?.accessToken === undefined) {
|
|
52
52
|
return { action: "fail" };
|
|
53
53
|
}
|
|
54
54
|
return { action: "retry" };
|
|
55
55
|
}
|
|
56
56
|
catch (error) {
|
|
57
|
+
input.signal?.throwIfAborted();
|
|
57
58
|
return {
|
|
58
59
|
action: "fail",
|
|
59
60
|
error: error instanceof Error ? error : new Error(String(error))
|
|
@@ -61,9 +62,11 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
};
|
|
64
|
-
async function ensureAuthorizedSession(resource, discovery, fetch, allowInteractive, forceRefresh = false) {
|
|
65
|
+
async function ensureAuthorizedSession(resource, discovery, fetch, allowInteractive, forceRefresh = false, signal) {
|
|
66
|
+
signal?.throwIfAborted();
|
|
65
67
|
const canonicalResource = canonicalizeResourceIndicator(resource);
|
|
66
68
|
let session = await loadSession(canonicalResource);
|
|
69
|
+
signal?.throwIfAborted();
|
|
67
70
|
if (discovery !== undefined && getOwnString(discovery.authorizationServerMetadata, "issuer") !== discovery.authorizationServer) {
|
|
68
71
|
throw new Error("OAuth discovery authorization-server issuer mismatch");
|
|
69
72
|
}
|
|
@@ -80,7 +83,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
80
83
|
if (session?.tokens?.refreshToken !== undefined &&
|
|
81
84
|
sessionDiscovery !== undefined &&
|
|
82
85
|
(forceRefresh || isExpired(session.tokens, now))) {
|
|
83
|
-
session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch);
|
|
86
|
+
session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch, signal);
|
|
84
87
|
if (session?.tokens !== undefined && !isExpired(session.tokens, now)) {
|
|
85
88
|
return session;
|
|
86
89
|
}
|
|
@@ -92,9 +95,12 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
92
95
|
if (!allowInteractive || sessionDiscovery === undefined) {
|
|
93
96
|
return session;
|
|
94
97
|
}
|
|
95
|
-
|
|
98
|
+
if (options.allowInteractive === false)
|
|
99
|
+
throw new Error("OAuth interactive authorization is disabled");
|
|
100
|
+
return authorizeSession(canonicalResource, session, sessionDiscovery, fetch, signal);
|
|
96
101
|
}
|
|
97
|
-
async function refreshSession(resource, session, discovery, fetch) {
|
|
102
|
+
async function refreshSession(resource, session, discovery, fetch, signal) {
|
|
103
|
+
signal?.throwIfAborted();
|
|
98
104
|
assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
|
|
99
105
|
const inFlight = refreshPromises.get(resource);
|
|
100
106
|
if (inFlight !== undefined) {
|
|
@@ -115,12 +121,13 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
115
121
|
clientSecret: session.client.clientSecret,
|
|
116
122
|
refreshToken: session.tokens.refreshToken,
|
|
117
123
|
resource,
|
|
118
|
-
fetch,
|
|
124
|
+
fetch, signal,
|
|
119
125
|
now
|
|
120
126
|
});
|
|
121
127
|
break;
|
|
122
128
|
}
|
|
123
129
|
catch (error) {
|
|
130
|
+
signal?.throwIfAborted();
|
|
124
131
|
if (error instanceof OAuthError && error.error === "invalid_grant") {
|
|
125
132
|
const clearedSession = clearSessionTokens(session);
|
|
126
133
|
await saveSession(resource, clearedSession);
|
|
@@ -156,7 +163,8 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
156
163
|
refreshPromises.set(resource, promise);
|
|
157
164
|
return promise;
|
|
158
165
|
}
|
|
159
|
-
async function authorizeSession(resource, existingSession, discovery, fetch) {
|
|
166
|
+
async function authorizeSession(resource, existingSession, discovery, fetch, signal) {
|
|
167
|
+
signal?.throwIfAborted();
|
|
160
168
|
const inFlight = authorizationPromises.get(resource);
|
|
161
169
|
if (inFlight !== undefined) {
|
|
162
170
|
return inFlight;
|
|
@@ -172,11 +180,14 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
172
180
|
openBrowser: options.browser.openBrowser,
|
|
173
181
|
readLine: options.browser.readLine,
|
|
174
182
|
createServer: options.browser.createServer,
|
|
175
|
-
landingPage: options.browser.landingPage
|
|
183
|
+
landingPage: options.browser.landingPage,
|
|
184
|
+
redirectUri: options.browser.redirectUri,
|
|
185
|
+
signal: options.browser.signal === undefined ? signal : signal === undefined ? options.browser.signal : AbortSignal.any([signal, options.browser.signal]),
|
|
186
|
+
timeoutMs: options.browser.timeoutMs
|
|
176
187
|
});
|
|
177
188
|
let resolvedClient = null;
|
|
178
189
|
try {
|
|
179
|
-
resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch);
|
|
190
|
+
resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch, signal);
|
|
180
191
|
const sessionWithoutTokens = {
|
|
181
192
|
resource,
|
|
182
193
|
authorizationServer: discovery.authorizationServer,
|
|
@@ -203,7 +214,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
203
214
|
codeVerifier: verifier,
|
|
204
215
|
redirectUri: loopback.redirectUri,
|
|
205
216
|
resource,
|
|
206
|
-
fetch,
|
|
217
|
+
fetch, signal,
|
|
207
218
|
now
|
|
208
219
|
});
|
|
209
220
|
const session = {
|
|
@@ -214,6 +225,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
214
225
|
return session;
|
|
215
226
|
}
|
|
216
227
|
catch (error) {
|
|
228
|
+
signal?.throwIfAborted();
|
|
217
229
|
if (shouldReRegisterStoredDynamicClient(error, resolvedClient, reRegistrationAttempted)) {
|
|
218
230
|
reRegistrationAttempted = true;
|
|
219
231
|
await clearRegisteredClient(discovery.authorizationServer);
|
|
@@ -240,7 +252,8 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
240
252
|
authorizationPromises.set(resource, finalPromise);
|
|
241
253
|
return finalPromise;
|
|
242
254
|
}
|
|
243
|
-
async function resolveClient(existingSession, discovery, redirectUri, fetch) {
|
|
255
|
+
async function resolveClient(existingSession, discovery, redirectUri, fetch, parentSignal) {
|
|
256
|
+
parentSignal?.throwIfAborted();
|
|
244
257
|
const configuredClient = normalizeConfiguredClient(options.client);
|
|
245
258
|
if (options.client.mode === "static") {
|
|
246
259
|
if (configuredClient === null) {
|
|
@@ -292,7 +305,8 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
292
305
|
}
|
|
293
306
|
}
|
|
294
307
|
const registrationBody = buildClientRegistrationBody(getClientMetadata(options.client), redirectUri);
|
|
295
|
-
const
|
|
308
|
+
const deadline = AbortSignal.timeout(30_000);
|
|
309
|
+
const signal = parentSignal === undefined ? deadline : AbortSignal.any([parentSignal, deadline]);
|
|
296
310
|
const response = await fetchMcpResponse(fetch, registrationEndpoint, {
|
|
297
311
|
method: "POST",
|
|
298
312
|
headers: {
|
|
@@ -9,6 +9,11 @@ export interface LoopbackAuthorizationOptions {
|
|
|
9
9
|
createServer?: () => http.Server;
|
|
10
10
|
landingPage?: OAuthLandingPage;
|
|
11
11
|
callbackPath?: string;
|
|
12
|
+
/** Exact registered HTTP loopback redirect, including its fixed port and query. */
|
|
13
|
+
redirectUri?: string;
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
/** Bounds listener setup and authorization; defaults to two minutes. */
|
|
16
|
+
timeoutMs?: number;
|
|
12
17
|
}
|
|
13
18
|
export interface LoopbackAuthorizationSession {
|
|
14
19
|
redirectUri: string;
|
|
@@ -1,77 +1,150 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import { parseAuthorizationState } from "./authorization-state.js";
|
|
3
3
|
export async function createLoopbackAuthorizationSession(options = {}) {
|
|
4
|
-
|
|
4
|
+
options.signal?.throwIfAborted();
|
|
5
|
+
const timeoutMs = options.timeoutMs ?? 120_000;
|
|
6
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2_147_483_647)
|
|
7
|
+
throw new Error("OAuth authorization timeoutMs must be a positive supported timer interval");
|
|
8
|
+
const target = loopbackTarget(options);
|
|
5
9
|
const server = options.createServer ? options.createServer() : http.createServer();
|
|
6
|
-
const
|
|
7
|
-
|
|
10
|
+
const controller = new AbortController();
|
|
11
|
+
let closed = false;
|
|
12
|
+
let used = false;
|
|
13
|
+
const callerAbort = () => controller.abort(options.signal?.reason);
|
|
14
|
+
const teardown = () => {
|
|
15
|
+
if (closed)
|
|
16
|
+
return;
|
|
17
|
+
closed = true;
|
|
18
|
+
clearTimeout(timer);
|
|
19
|
+
options.signal?.removeEventListener("abort", callerAbort);
|
|
20
|
+
server.closeAllConnections?.();
|
|
21
|
+
server.close();
|
|
22
|
+
};
|
|
23
|
+
const timer = setTimeout(() => controller.abort(new Error("OAuth authorization timed out")), timeoutMs);
|
|
24
|
+
timer.unref?.();
|
|
25
|
+
controller.signal.addEventListener("abort", teardown, { once: true });
|
|
26
|
+
options.signal?.addEventListener("abort", callerAbort, { once: true });
|
|
27
|
+
if (options.signal?.aborted)
|
|
28
|
+
callerAbort();
|
|
29
|
+
let port;
|
|
30
|
+
try {
|
|
31
|
+
port = await startServer(server, target.port, target.host, controller.signal);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
controller.abort(error);
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
const redirectUri = options.redirectUri ?? `http://127.0.0.1:${port}${target.callbackPath}`;
|
|
8
38
|
return {
|
|
9
39
|
redirectUri,
|
|
10
40
|
async waitForCode(authorizationUrl) {
|
|
11
|
-
|
|
41
|
+
controller.signal.throwIfAborted();
|
|
42
|
+
if (used)
|
|
43
|
+
throw new Error("OAuth authorization session has already been used");
|
|
44
|
+
used = true;
|
|
45
|
+
try {
|
|
46
|
+
return await waitForAuthorizationCode(server, authorizationUrl, options, target.callbackPath, controller.signal);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
}
|
|
12
51
|
},
|
|
13
52
|
close() {
|
|
14
|
-
|
|
15
|
-
server.close();
|
|
53
|
+
controller.abort(new Error("OAuth authorization session closed"));
|
|
16
54
|
}
|
|
17
55
|
};
|
|
18
56
|
}
|
|
19
|
-
|
|
57
|
+
function loopbackTarget(options) {
|
|
58
|
+
if (options.redirectUri !== undefined) {
|
|
59
|
+
let url;
|
|
60
|
+
try {
|
|
61
|
+
url = new URL(options.redirectUri);
|
|
62
|
+
}
|
|
63
|
+
catch (cause) {
|
|
64
|
+
throw new Error("Invalid OAuth loopback redirect URI", { cause });
|
|
65
|
+
}
|
|
66
|
+
const forbiddenQuery = ["code", "state", "error", "error_description", "iss"];
|
|
67
|
+
if (url.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)
|
|
68
|
+
|| url.username || url.password || url.hash || url.port === "0"
|
|
69
|
+
|| forbiddenQuery.some(name => url.searchParams.has(name))
|
|
70
|
+
|| [...options.redirectUri].some(char => char.codePointAt(0) <= 32)
|
|
71
|
+
|| (options.callbackPath !== undefined && options.callbackPath !== url.pathname))
|
|
72
|
+
throw new Error("Invalid OAuth loopback redirect URI");
|
|
73
|
+
return { port: url.port ? Number(url.port) : 80, host: url.hostname === "[::1]" ? "::1" : url.hostname, callbackPath: url.pathname };
|
|
74
|
+
}
|
|
75
|
+
const callbackPath = options.callbackPath ?? "/callback";
|
|
76
|
+
const parsed = new URL(callbackPath, "http://127.0.0.1");
|
|
77
|
+
if (!callbackPath.startsWith("/") || parsed.origin !== "http://127.0.0.1" || parsed.pathname !== callbackPath || parsed.search || parsed.hash)
|
|
78
|
+
throw new Error("Invalid OAuth loopback callback path");
|
|
79
|
+
return { port: 0, host: "127.0.0.1", callbackPath };
|
|
80
|
+
}
|
|
81
|
+
async function startServer(server, port, host, signal) {
|
|
82
|
+
signal.throwIfAborted();
|
|
20
83
|
return new Promise((resolve, reject) => {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
84
|
+
const cleanup = () => { server.off("error", handleError); signal.removeEventListener("abort", aborted); };
|
|
85
|
+
const handleError = (error) => { cleanup(); reject(error); };
|
|
86
|
+
const aborted = () => { cleanup(); reject(signal.reason); };
|
|
25
87
|
server.once("error", handleError);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
88
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
89
|
+
try {
|
|
90
|
+
server.listen(port, host, () => {
|
|
91
|
+
cleanup();
|
|
92
|
+
if (signal.aborted) {
|
|
93
|
+
server.close();
|
|
94
|
+
reject(signal.reason);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const address = server.address();
|
|
98
|
+
if (address === null || typeof address === "string") {
|
|
99
|
+
reject(new Error("OAuth listener has no TCP address"));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
resolve(address.port);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
cleanup();
|
|
107
|
+
reject(error);
|
|
108
|
+
}
|
|
31
109
|
});
|
|
32
110
|
}
|
|
33
|
-
function waitForAuthorizationCode(server, authorizationUrl, options, callbackPath) {
|
|
111
|
+
function waitForAuthorizationCode(server, authorizationUrl, options, callbackPath, signal) {
|
|
112
|
+
signal.throwIfAborted();
|
|
34
113
|
const expectedAuthorization = readExpectedAuthorizationCallback(authorizationUrl);
|
|
35
114
|
return new Promise((resolve, reject) => {
|
|
36
115
|
let settled = false;
|
|
37
116
|
const settle = (fn) => {
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
117
|
+
if (settled)
|
|
118
|
+
return;
|
|
119
|
+
settled = true;
|
|
120
|
+
server.off("request", request);
|
|
121
|
+
signal.removeEventListener("abort", aborted);
|
|
122
|
+
fn();
|
|
42
123
|
};
|
|
43
|
-
|
|
44
|
-
|
|
124
|
+
const aborted = () => settle(() => reject(signal.reason));
|
|
125
|
+
const request = (req, res) => {
|
|
126
|
+
let url;
|
|
127
|
+
try {
|
|
128
|
+
url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
res.writeHead(400);
|
|
132
|
+
res.end("Invalid callback URL");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
45
135
|
if (url.pathname !== callbackPath) {
|
|
46
136
|
res.writeHead(404);
|
|
47
137
|
res.end("Not found");
|
|
48
138
|
return;
|
|
49
139
|
}
|
|
50
140
|
const callbackParameters = {
|
|
51
|
-
code: url.searchParams.get("code"),
|
|
52
|
-
|
|
53
|
-
errorDescription: url.searchParams.get("error_description"),
|
|
54
|
-
state: url.searchParams.get("state"),
|
|
55
|
-
iss: url.searchParams.get("iss")
|
|
141
|
+
code: url.searchParams.get("code"), error: url.searchParams.get("error"),
|
|
142
|
+
errorDescription: url.searchParams.get("error_description"), state: url.searchParams.get("state"), iss: url.searchParams.get("iss")
|
|
56
143
|
};
|
|
57
144
|
try {
|
|
58
145
|
validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
res.writeHead(400);
|
|
62
|
-
res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
|
|
63
|
-
settle(() => reject(error instanceof Error ? error : new Error(String(error))));
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const authorizationError = callbackParameters.error;
|
|
67
|
-
if (authorizationError !== null) {
|
|
68
|
-
const description = callbackParameters.errorDescription ?? authorizationError;
|
|
69
|
-
res.writeHead(400);
|
|
70
|
-
res.end(`Authorization failed: ${description}`);
|
|
71
|
-
settle(() => reject(createAuthorizationError(authorizationError, description)));
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
try {
|
|
146
|
+
if (callbackParameters.error !== null)
|
|
147
|
+
throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
|
|
75
148
|
const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
|
|
76
149
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
77
150
|
res.end(buildSuccessPage(options.landingPage));
|
|
@@ -80,39 +153,28 @@ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPat
|
|
|
80
153
|
catch (error) {
|
|
81
154
|
res.writeHead(400);
|
|
82
155
|
res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
|
|
83
|
-
settle(() => reject(error
|
|
156
|
+
settle(() => reject(error));
|
|
84
157
|
}
|
|
85
|
-
}
|
|
158
|
+
};
|
|
159
|
+
server.on("request", request);
|
|
160
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
86
161
|
if (options.readLine !== undefined) {
|
|
87
|
-
options
|
|
88
|
-
|
|
89
|
-
.then((input) => {
|
|
90
|
-
const callbackParameters = extractCallbackParametersFromInput(input);
|
|
91
|
-
if (callbackParameters === null) {
|
|
92
|
-
settle(() => reject(new Error("OAuth callback missing authorization code")));
|
|
162
|
+
void Promise.resolve().then(() => settled ? undefined : options.readLine()).then(input => {
|
|
163
|
+
if (settled)
|
|
93
164
|
return;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
catch (error) {
|
|
105
|
-
settle(() => reject(error instanceof Error ? error : new Error(String(error))));
|
|
106
|
-
}
|
|
107
|
-
})
|
|
108
|
-
.catch((error) => {
|
|
109
|
-
settle(() => reject(error instanceof Error ? error : new Error(String(error))));
|
|
110
|
-
});
|
|
165
|
+
const callbackParameters = extractCallbackParametersFromInput(input);
|
|
166
|
+
if (callbackParameters === null)
|
|
167
|
+
throw new Error("OAuth callback missing authorization code");
|
|
168
|
+
validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
|
|
169
|
+
if (callbackParameters.error !== null)
|
|
170
|
+
throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
|
|
171
|
+
const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
|
|
172
|
+
settle(() => resolve(code));
|
|
173
|
+
}).catch(error => settle(() => reject(error)));
|
|
111
174
|
}
|
|
112
175
|
if (options.openBrowser !== undefined) {
|
|
113
|
-
|
|
114
|
-
settle(() => reject(error));
|
|
115
|
-
});
|
|
176
|
+
void Promise.resolve().then(() => settled ? undefined : options.openBrowser(authorizationUrl))
|
|
177
|
+
.catch(error => settle(() => reject(error)));
|
|
116
178
|
}
|
|
117
179
|
});
|
|
118
180
|
}
|
|
@@ -25,6 +25,7 @@ export declare function exchangeAuthorizationCode(input: {
|
|
|
25
25
|
redirectUri: string;
|
|
26
26
|
resource: string;
|
|
27
27
|
fetch: OAuthMetadataFetch;
|
|
28
|
+
signal?: AbortSignal;
|
|
28
29
|
now: () => number;
|
|
29
30
|
}): Promise<StoredOAuthTokens>;
|
|
30
31
|
export declare function refreshAccessToken(input: {
|
|
@@ -34,6 +35,7 @@ export declare function refreshAccessToken(input: {
|
|
|
34
35
|
refreshToken: string;
|
|
35
36
|
resource: string;
|
|
36
37
|
fetch: OAuthMetadataFetch;
|
|
38
|
+
signal?: AbortSignal;
|
|
37
39
|
now: () => number;
|
|
38
40
|
}): Promise<StoredOAuthTokens>;
|
|
39
41
|
export declare function readOAuthJsonObjectResponse(response: Response, signal?: AbortSignal): Promise<Record<string, unknown>>;
|
|
@@ -44,6 +44,7 @@ export async function exchangeAuthorizationCode(input) {
|
|
|
44
44
|
resource
|
|
45
45
|
},
|
|
46
46
|
fetch: input.fetch,
|
|
47
|
+
signal: input.signal,
|
|
47
48
|
now: input.now
|
|
48
49
|
});
|
|
49
50
|
}
|
|
@@ -59,6 +60,7 @@ export async function refreshAccessToken(input) {
|
|
|
59
60
|
resource
|
|
60
61
|
},
|
|
61
62
|
fetch: input.fetch,
|
|
63
|
+
signal: input.signal,
|
|
62
64
|
now: input.now
|
|
63
65
|
});
|
|
64
66
|
}
|
|
@@ -70,7 +72,9 @@ async function requestTokens(input) {
|
|
|
70
72
|
if (input.clientSecret !== undefined) {
|
|
71
73
|
body.set("client_secret", input.clientSecret);
|
|
72
74
|
}
|
|
73
|
-
|
|
75
|
+
input.signal?.throwIfAborted();
|
|
76
|
+
const deadline = AbortSignal.timeout(30_000);
|
|
77
|
+
const signal = input.signal === undefined ? deadline : AbortSignal.any([input.signal, deadline]);
|
|
74
78
|
const response = await fetchMcpResponse(input.fetch, input.tokenEndpoint, {
|
|
75
79
|
method: "POST",
|
|
76
80
|
headers: {
|
|
@@ -32,6 +32,7 @@ export interface OAuthClientProvider {
|
|
|
32
32
|
requestUrl: URL;
|
|
33
33
|
headers: Headers;
|
|
34
34
|
fetch: OAuthMetadataFetch;
|
|
35
|
+
signal?: AbortSignal;
|
|
35
36
|
}): Promise<void> | void;
|
|
36
37
|
handleUnauthorized(input: {
|
|
37
38
|
requestUrl: URL;
|
|
@@ -39,6 +40,7 @@ export interface OAuthClientProvider {
|
|
|
39
40
|
challenge: OAuthUnauthorizedChallenge | null;
|
|
40
41
|
discovery: OAuthDiscoveryResult;
|
|
41
42
|
fetch: OAuthMetadataFetch;
|
|
43
|
+
signal?: AbortSignal;
|
|
42
44
|
}): Promise<{
|
|
43
45
|
action: "retry";
|
|
44
46
|
} | {
|
|
@@ -95,8 +97,14 @@ export interface DefaultOAuthClientProviderOptions {
|
|
|
95
97
|
clientSecret?: string;
|
|
96
98
|
metadata?: OAuthClientMetadata;
|
|
97
99
|
};
|
|
100
|
+
/** Disable interactive authorization while allowing cached tokens and silent refresh. */
|
|
101
|
+
allowInteractive?: boolean;
|
|
98
102
|
browser: {
|
|
99
|
-
openBrowser(url: string): Promise<void>;
|
|
103
|
+
openBrowser?(url: string): Promise<void>;
|
|
104
|
+
/** Exact registered HTTP loopback redirect URI. */
|
|
105
|
+
redirectUri?: string;
|
|
106
|
+
signal?: AbortSignal;
|
|
107
|
+
timeoutMs?: number;
|
|
100
108
|
readLine?: () => Promise<string>;
|
|
101
109
|
createServer?: () => http.Server;
|
|
102
110
|
landingPage?: {
|
|
@@ -66,7 +66,9 @@ const transport = new HttpTransport({
|
|
|
66
66
|
});
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
You can also call `discoverOAuthMetadata(resourceUrl, options)` directly, or instantiate `OAuthMetadataDiscovery` with a custom `fetch` implementation and shared cache.
|
|
69
|
+
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.
|
|
70
|
+
|
|
71
|
+
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.
|
|
70
72
|
|
|
71
73
|
## Testing helpers
|
|
72
74
|
|
|
@@ -109,6 +109,7 @@ interface OAuthClientProvider {
|
|
|
109
109
|
requestUrl: URL;
|
|
110
110
|
headers: Headers;
|
|
111
111
|
fetch: OAuthMetadataFetch;
|
|
112
|
+
signal?: AbortSignal;
|
|
112
113
|
}): Promise<void> | void;
|
|
113
114
|
handleUnauthorized(input: {
|
|
114
115
|
requestUrl: URL;
|
|
@@ -116,6 +117,7 @@ interface OAuthClientProvider {
|
|
|
116
117
|
challenge: OAuthUnauthorizedChallenge | null;
|
|
117
118
|
discovery: OAuthDiscoveryResult;
|
|
118
119
|
fetch: OAuthMetadataFetch;
|
|
120
|
+
signal?: AbortSignal;
|
|
119
121
|
}): Promise<{
|
|
120
122
|
action: "retry";
|
|
121
123
|
} | {
|
|
@@ -172,8 +174,14 @@ interface DefaultOAuthClientProviderOptions {
|
|
|
172
174
|
clientSecret?: string;
|
|
173
175
|
metadata?: OAuthClientMetadata;
|
|
174
176
|
};
|
|
177
|
+
/** Disable interactive authorization while allowing cached tokens and silent refresh. */
|
|
178
|
+
allowInteractive?: boolean;
|
|
175
179
|
browser: {
|
|
176
|
-
openBrowser(url: string): Promise<void>;
|
|
180
|
+
openBrowser?(url: string): Promise<void>;
|
|
181
|
+
/** Exact registered HTTP loopback redirect URI. */
|
|
182
|
+
redirectUri?: string;
|
|
183
|
+
signal?: AbortSignal;
|
|
184
|
+
timeoutMs?: number;
|
|
177
185
|
readLine?: () => Promise<string>;
|
|
178
186
|
createServer?: () => http.Server;
|
|
179
187
|
landingPage?: {
|
|
@@ -202,6 +210,7 @@ interface OAuthMetadataDiscoveryOptions {
|
|
|
202
210
|
}
|
|
203
211
|
interface OAuthMetadataLookupOptions {
|
|
204
212
|
resourceMetadataUrl?: string | URL;
|
|
213
|
+
signal?: AbortSignal;
|
|
205
214
|
}
|
|
206
215
|
declare class OAuthMetadataDiscovery {
|
|
207
216
|
private readonly fetchImpl;
|
|
@@ -209,7 +218,7 @@ declare class OAuthMetadataDiscovery {
|
|
|
209
218
|
private readonly memoryCache;
|
|
210
219
|
constructor({ fetch, cache }?: OAuthMetadataDiscoveryOptions);
|
|
211
220
|
private discoverProtectedResource;
|
|
212
|
-
discover(resourceUrl: string | URL, { resourceMetadataUrl }?: OAuthMetadataLookupOptions): Promise<OAuthDiscoveryResult>;
|
|
221
|
+
discover(resourceUrl: string | URL, { resourceMetadataUrl, signal }?: OAuthMetadataLookupOptions): Promise<OAuthDiscoveryResult>;
|
|
213
222
|
}
|
|
214
223
|
declare function discoverOAuthMetadata(resourceUrl: string | URL, options?: OAuthMetadataDiscoveryOptions & OAuthMetadataLookupOptions): Promise<OAuthDiscoveryResult>;
|
|
215
224
|
|
|
@@ -634,6 +643,7 @@ declare class HttpTransport implements McpTransport {
|
|
|
634
643
|
private readonly oauthProvider;
|
|
635
644
|
private readonly oauthMetadataDiscovery;
|
|
636
645
|
private readonly inFlightFetchAbortControllers;
|
|
646
|
+
private readonly inFlightOAuthAbortControllers;
|
|
637
647
|
private readonly openResponseReaders;
|
|
638
648
|
private readonly modernRequests;
|
|
639
649
|
private modernMode;
|
|
@@ -4102,47 +4102,137 @@ function getOwnEntry4(record2, key2) {
|
|
|
4102
4102
|
|
|
4103
4103
|
// ../mcp-oauth/dist/client/loopback-authorization.js
|
|
4104
4104
|
async function createLoopbackAuthorizationSession(options = {}) {
|
|
4105
|
-
|
|
4105
|
+
options.signal?.throwIfAborted();
|
|
4106
|
+
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
4107
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647)
|
|
4108
|
+
throw new Error("OAuth authorization timeoutMs must be a positive supported timer interval");
|
|
4109
|
+
const target = loopbackTarget(options);
|
|
4106
4110
|
const server = options.createServer ? options.createServer() : http.createServer();
|
|
4107
|
-
const
|
|
4108
|
-
|
|
4111
|
+
const controller = new AbortController();
|
|
4112
|
+
let closed = false;
|
|
4113
|
+
let used = false;
|
|
4114
|
+
const callerAbort = () => controller.abort(options.signal?.reason);
|
|
4115
|
+
const teardown = () => {
|
|
4116
|
+
if (closed)
|
|
4117
|
+
return;
|
|
4118
|
+
closed = true;
|
|
4119
|
+
clearTimeout(timer);
|
|
4120
|
+
options.signal?.removeEventListener("abort", callerAbort);
|
|
4121
|
+
server.closeAllConnections?.();
|
|
4122
|
+
server.close();
|
|
4123
|
+
};
|
|
4124
|
+
const timer = setTimeout(() => controller.abort(new Error("OAuth authorization timed out")), timeoutMs);
|
|
4125
|
+
timer.unref?.();
|
|
4126
|
+
controller.signal.addEventListener("abort", teardown, { once: true });
|
|
4127
|
+
options.signal?.addEventListener("abort", callerAbort, { once: true });
|
|
4128
|
+
if (options.signal?.aborted)
|
|
4129
|
+
callerAbort();
|
|
4130
|
+
let port;
|
|
4131
|
+
try {
|
|
4132
|
+
port = await startServer(server, target.port, target.host, controller.signal);
|
|
4133
|
+
} catch (error) {
|
|
4134
|
+
controller.abort(error);
|
|
4135
|
+
throw error;
|
|
4136
|
+
}
|
|
4137
|
+
const redirectUri = options.redirectUri ?? `http://127.0.0.1:${port}${target.callbackPath}`;
|
|
4109
4138
|
return {
|
|
4110
4139
|
redirectUri,
|
|
4111
4140
|
async waitForCode(authorizationUrl) {
|
|
4112
|
-
|
|
4141
|
+
controller.signal.throwIfAborted();
|
|
4142
|
+
if (used)
|
|
4143
|
+
throw new Error("OAuth authorization session has already been used");
|
|
4144
|
+
used = true;
|
|
4145
|
+
try {
|
|
4146
|
+
return await waitForAuthorizationCode(server, authorizationUrl, options, target.callbackPath, controller.signal);
|
|
4147
|
+
} finally {
|
|
4148
|
+
clearTimeout(timer);
|
|
4149
|
+
}
|
|
4113
4150
|
},
|
|
4114
4151
|
close() {
|
|
4115
|
-
|
|
4116
|
-
server.close();
|
|
4152
|
+
controller.abort(new Error("OAuth authorization session closed"));
|
|
4117
4153
|
}
|
|
4118
4154
|
};
|
|
4119
4155
|
}
|
|
4120
|
-
|
|
4156
|
+
function loopbackTarget(options) {
|
|
4157
|
+
if (options.redirectUri !== void 0) {
|
|
4158
|
+
let url;
|
|
4159
|
+
try {
|
|
4160
|
+
url = new URL(options.redirectUri);
|
|
4161
|
+
} catch (cause) {
|
|
4162
|
+
throw new Error("Invalid OAuth loopback redirect URI", { cause });
|
|
4163
|
+
}
|
|
4164
|
+
const forbiddenQuery = ["code", "state", "error", "error_description", "iss"];
|
|
4165
|
+
if (url.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname) || url.username || url.password || url.hash || url.port === "0" || forbiddenQuery.some((name) => url.searchParams.has(name)) || [...options.redirectUri].some((char) => char.codePointAt(0) <= 32) || options.callbackPath !== void 0 && options.callbackPath !== url.pathname)
|
|
4166
|
+
throw new Error("Invalid OAuth loopback redirect URI");
|
|
4167
|
+
return { port: url.port ? Number(url.port) : 80, host: url.hostname === "[::1]" ? "::1" : url.hostname, callbackPath: url.pathname };
|
|
4168
|
+
}
|
|
4169
|
+
const callbackPath = options.callbackPath ?? "/callback";
|
|
4170
|
+
const parsed = new URL(callbackPath, "http://127.0.0.1");
|
|
4171
|
+
if (!callbackPath.startsWith("/") || parsed.origin !== "http://127.0.0.1" || parsed.pathname !== callbackPath || parsed.search || parsed.hash)
|
|
4172
|
+
throw new Error("Invalid OAuth loopback callback path");
|
|
4173
|
+
return { port: 0, host: "127.0.0.1", callbackPath };
|
|
4174
|
+
}
|
|
4175
|
+
async function startServer(server, port, host, signal) {
|
|
4176
|
+
signal.throwIfAborted();
|
|
4121
4177
|
return new Promise((resolve, reject) => {
|
|
4122
|
-
const
|
|
4178
|
+
const cleanup = () => {
|
|
4123
4179
|
server.off("error", handleError);
|
|
4180
|
+
signal.removeEventListener("abort", aborted);
|
|
4181
|
+
};
|
|
4182
|
+
const handleError = (error) => {
|
|
4183
|
+
cleanup();
|
|
4124
4184
|
reject(error);
|
|
4125
4185
|
};
|
|
4186
|
+
const aborted = () => {
|
|
4187
|
+
cleanup();
|
|
4188
|
+
reject(signal.reason);
|
|
4189
|
+
};
|
|
4126
4190
|
server.once("error", handleError);
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4191
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
4192
|
+
try {
|
|
4193
|
+
server.listen(port, host, () => {
|
|
4194
|
+
cleanup();
|
|
4195
|
+
if (signal.aborted) {
|
|
4196
|
+
server.close();
|
|
4197
|
+
reject(signal.reason);
|
|
4198
|
+
return;
|
|
4199
|
+
}
|
|
4200
|
+
const address = server.address();
|
|
4201
|
+
if (address === null || typeof address === "string") {
|
|
4202
|
+
reject(new Error("OAuth listener has no TCP address"));
|
|
4203
|
+
return;
|
|
4204
|
+
}
|
|
4205
|
+
resolve(address.port);
|
|
4206
|
+
});
|
|
4207
|
+
} catch (error) {
|
|
4208
|
+
cleanup();
|
|
4209
|
+
reject(error);
|
|
4210
|
+
}
|
|
4132
4211
|
});
|
|
4133
4212
|
}
|
|
4134
|
-
function waitForAuthorizationCode(server, authorizationUrl, options, callbackPath) {
|
|
4213
|
+
function waitForAuthorizationCode(server, authorizationUrl, options, callbackPath, signal) {
|
|
4214
|
+
signal.throwIfAborted();
|
|
4135
4215
|
const expectedAuthorization = readExpectedAuthorizationCallback(authorizationUrl);
|
|
4136
4216
|
return new Promise((resolve, reject) => {
|
|
4137
4217
|
let settled = false;
|
|
4138
4218
|
const settle = (fn) => {
|
|
4139
|
-
if (
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4219
|
+
if (settled)
|
|
4220
|
+
return;
|
|
4221
|
+
settled = true;
|
|
4222
|
+
server.off("request", request);
|
|
4223
|
+
signal.removeEventListener("abort", aborted);
|
|
4224
|
+
fn();
|
|
4143
4225
|
};
|
|
4144
|
-
|
|
4145
|
-
|
|
4226
|
+
const aborted = () => settle(() => reject(signal.reason));
|
|
4227
|
+
const request = (req, res) => {
|
|
4228
|
+
let url;
|
|
4229
|
+
try {
|
|
4230
|
+
url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
4231
|
+
} catch {
|
|
4232
|
+
res.writeHead(400);
|
|
4233
|
+
res.end("Invalid callback URL");
|
|
4234
|
+
return;
|
|
4235
|
+
}
|
|
4146
4236
|
if (url.pathname !== callbackPath) {
|
|
4147
4237
|
res.writeHead(404);
|
|
4148
4238
|
res.end("Not found");
|
|
@@ -4157,21 +4247,8 @@ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPat
|
|
|
4157
4247
|
};
|
|
4158
4248
|
try {
|
|
4159
4249
|
validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
|
|
4163
|
-
settle(() => reject(error instanceof Error ? error : new Error(String(error))));
|
|
4164
|
-
return;
|
|
4165
|
-
}
|
|
4166
|
-
const authorizationError = callbackParameters.error;
|
|
4167
|
-
if (authorizationError !== null) {
|
|
4168
|
-
const description = callbackParameters.errorDescription ?? authorizationError;
|
|
4169
|
-
res.writeHead(400);
|
|
4170
|
-
res.end(`Authorization failed: ${description}`);
|
|
4171
|
-
settle(() => reject(createAuthorizationError(authorizationError, description)));
|
|
4172
|
-
return;
|
|
4173
|
-
}
|
|
4174
|
-
try {
|
|
4250
|
+
if (callbackParameters.error !== null)
|
|
4251
|
+
throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
|
|
4175
4252
|
const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
|
|
4176
4253
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
4177
4254
|
res.end(buildSuccessPage(options.landingPage));
|
|
@@ -4179,35 +4256,27 @@ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPat
|
|
|
4179
4256
|
} catch (error) {
|
|
4180
4257
|
res.writeHead(400);
|
|
4181
4258
|
res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
|
|
4182
|
-
settle(() => reject(error
|
|
4259
|
+
settle(() => reject(error));
|
|
4183
4260
|
}
|
|
4184
|
-
}
|
|
4261
|
+
};
|
|
4262
|
+
server.on("request", request);
|
|
4263
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
4185
4264
|
if (options.readLine !== void 0) {
|
|
4186
|
-
options.readLine().then((input) => {
|
|
4187
|
-
|
|
4188
|
-
if (callbackParameters === null) {
|
|
4189
|
-
settle(() => reject(new Error("OAuth callback missing authorization code")));
|
|
4265
|
+
void Promise.resolve().then(() => settled ? void 0 : options.readLine()).then((input) => {
|
|
4266
|
+
if (settled)
|
|
4190
4267
|
return;
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
} catch (error) {
|
|
4201
|
-
settle(() => reject(error instanceof Error ? error : new Error(String(error))));
|
|
4202
|
-
}
|
|
4203
|
-
}).catch((error) => {
|
|
4204
|
-
settle(() => reject(error instanceof Error ? error : new Error(String(error))));
|
|
4205
|
-
});
|
|
4268
|
+
const callbackParameters = extractCallbackParametersFromInput(input);
|
|
4269
|
+
if (callbackParameters === null)
|
|
4270
|
+
throw new Error("OAuth callback missing authorization code");
|
|
4271
|
+
validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
|
|
4272
|
+
if (callbackParameters.error !== null)
|
|
4273
|
+
throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
|
|
4274
|
+
const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
|
|
4275
|
+
settle(() => resolve(code));
|
|
4276
|
+
}).catch((error) => settle(() => reject(error)));
|
|
4206
4277
|
}
|
|
4207
4278
|
if (options.openBrowser !== void 0) {
|
|
4208
|
-
options.openBrowser(authorizationUrl).catch((error) =>
|
|
4209
|
-
settle(() => reject(error));
|
|
4210
|
-
});
|
|
4279
|
+
void Promise.resolve().then(() => settled ? void 0 : options.openBrowser(authorizationUrl)).catch((error) => settle(() => reject(error)));
|
|
4211
4280
|
}
|
|
4212
4281
|
});
|
|
4213
4282
|
}
|
|
@@ -4387,6 +4456,7 @@ async function exchangeAuthorizationCode(input) {
|
|
|
4387
4456
|
resource
|
|
4388
4457
|
},
|
|
4389
4458
|
fetch: input.fetch,
|
|
4459
|
+
signal: input.signal,
|
|
4390
4460
|
now: input.now
|
|
4391
4461
|
});
|
|
4392
4462
|
}
|
|
@@ -4402,6 +4472,7 @@ async function refreshAccessToken(input) {
|
|
|
4402
4472
|
resource
|
|
4403
4473
|
},
|
|
4404
4474
|
fetch: input.fetch,
|
|
4475
|
+
signal: input.signal,
|
|
4405
4476
|
now: input.now
|
|
4406
4477
|
});
|
|
4407
4478
|
}
|
|
@@ -4413,7 +4484,9 @@ async function requestTokens(input) {
|
|
|
4413
4484
|
if (input.clientSecret !== void 0) {
|
|
4414
4485
|
body.set("client_secret", input.clientSecret);
|
|
4415
4486
|
}
|
|
4416
|
-
|
|
4487
|
+
input.signal?.throwIfAborted();
|
|
4488
|
+
const deadline = AbortSignal.timeout(3e4);
|
|
4489
|
+
const signal = input.signal === void 0 ? deadline : AbortSignal.any([input.signal, deadline]);
|
|
4417
4490
|
const response = await fetchMcpResponse(input.fetch, input.tokenEndpoint, {
|
|
4418
4491
|
method: "POST",
|
|
4419
4492
|
headers: {
|
|
@@ -4525,7 +4598,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4525
4598
|
async authorizeRequest(input) {
|
|
4526
4599
|
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
4527
4600
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|
|
4528
|
-
const session = await ensureAuthorizedSession(requestUrl, void 0, input.fetch, false);
|
|
4601
|
+
const session = await ensureAuthorizedSession(requestUrl, void 0, input.fetch, false, false, input.signal);
|
|
4529
4602
|
const accessToken = session?.tokens?.accessToken;
|
|
4530
4603
|
if (session === null || accessToken === void 0 || session.tokens === void 0 || isExpired(session.tokens, now)) {
|
|
4531
4604
|
return;
|
|
@@ -4543,12 +4616,13 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4543
4616
|
const session = await ensureAuthorizedSession(resource, {
|
|
4544
4617
|
...input.discovery,
|
|
4545
4618
|
resource
|
|
4546
|
-
}, input.fetch, true, forceRefresh);
|
|
4619
|
+
}, input.fetch, true, forceRefresh, input.signal);
|
|
4547
4620
|
if (session?.tokens?.accessToken === void 0) {
|
|
4548
4621
|
return { action: "fail" };
|
|
4549
4622
|
}
|
|
4550
4623
|
return { action: "retry" };
|
|
4551
4624
|
} catch (error) {
|
|
4625
|
+
input.signal?.throwIfAborted();
|
|
4552
4626
|
return {
|
|
4553
4627
|
action: "fail",
|
|
4554
4628
|
error: error instanceof Error ? error : new Error(String(error))
|
|
@@ -4556,9 +4630,11 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4556
4630
|
}
|
|
4557
4631
|
}
|
|
4558
4632
|
};
|
|
4559
|
-
async function ensureAuthorizedSession(resource, discovery, fetch2, allowInteractive, forceRefresh = false) {
|
|
4633
|
+
async function ensureAuthorizedSession(resource, discovery, fetch2, allowInteractive, forceRefresh = false, signal) {
|
|
4634
|
+
signal?.throwIfAborted();
|
|
4560
4635
|
const canonicalResource = canonicalizeResourceIndicator(resource);
|
|
4561
4636
|
let session = await loadSession(canonicalResource);
|
|
4637
|
+
signal?.throwIfAborted();
|
|
4562
4638
|
if (discovery !== void 0 && getOwnString2(discovery.authorizationServerMetadata, "issuer") !== discovery.authorizationServer) {
|
|
4563
4639
|
throw new Error("OAuth discovery authorization-server issuer mismatch");
|
|
4564
4640
|
}
|
|
@@ -4571,7 +4647,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4571
4647
|
return session;
|
|
4572
4648
|
}
|
|
4573
4649
|
if (session?.tokens?.refreshToken !== void 0 && sessionDiscovery !== void 0 && (forceRefresh || isExpired(session.tokens, now))) {
|
|
4574
|
-
session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch2);
|
|
4650
|
+
session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch2, signal);
|
|
4575
4651
|
if (session?.tokens !== void 0 && !isExpired(session.tokens, now)) {
|
|
4576
4652
|
return session;
|
|
4577
4653
|
}
|
|
@@ -4583,9 +4659,12 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4583
4659
|
if (!allowInteractive || sessionDiscovery === void 0) {
|
|
4584
4660
|
return session;
|
|
4585
4661
|
}
|
|
4586
|
-
|
|
4662
|
+
if (options.allowInteractive === false)
|
|
4663
|
+
throw new Error("OAuth interactive authorization is disabled");
|
|
4664
|
+
return authorizeSession(canonicalResource, session, sessionDiscovery, fetch2, signal);
|
|
4587
4665
|
}
|
|
4588
|
-
async function refreshSession(resource, session, discovery, fetch2) {
|
|
4666
|
+
async function refreshSession(resource, session, discovery, fetch2, signal) {
|
|
4667
|
+
signal?.throwIfAborted();
|
|
4589
4668
|
assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
|
|
4590
4669
|
const inFlight = refreshPromises.get(resource);
|
|
4591
4670
|
if (inFlight !== void 0) {
|
|
@@ -4607,10 +4686,12 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4607
4686
|
refreshToken: session.tokens.refreshToken,
|
|
4608
4687
|
resource,
|
|
4609
4688
|
fetch: fetch2,
|
|
4689
|
+
signal,
|
|
4610
4690
|
now
|
|
4611
4691
|
});
|
|
4612
4692
|
break;
|
|
4613
4693
|
} catch (error) {
|
|
4694
|
+
signal?.throwIfAborted();
|
|
4614
4695
|
if (error instanceof OAuthError && error.error === "invalid_grant") {
|
|
4615
4696
|
const clearedSession = clearSessionTokens(session);
|
|
4616
4697
|
await saveSession(resource, clearedSession);
|
|
@@ -4645,7 +4726,8 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4645
4726
|
refreshPromises.set(resource, promise);
|
|
4646
4727
|
return promise;
|
|
4647
4728
|
}
|
|
4648
|
-
async function authorizeSession(resource, existingSession, discovery, fetch2) {
|
|
4729
|
+
async function authorizeSession(resource, existingSession, discovery, fetch2, signal) {
|
|
4730
|
+
signal?.throwIfAborted();
|
|
4649
4731
|
const inFlight = authorizationPromises.get(resource);
|
|
4650
4732
|
if (inFlight !== void 0) {
|
|
4651
4733
|
return inFlight;
|
|
@@ -4661,11 +4743,14 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4661
4743
|
openBrowser: options.browser.openBrowser,
|
|
4662
4744
|
readLine: options.browser.readLine,
|
|
4663
4745
|
createServer: options.browser.createServer,
|
|
4664
|
-
landingPage: options.browser.landingPage
|
|
4746
|
+
landingPage: options.browser.landingPage,
|
|
4747
|
+
redirectUri: options.browser.redirectUri,
|
|
4748
|
+
signal: options.browser.signal === void 0 ? signal : signal === void 0 ? options.browser.signal : AbortSignal.any([signal, options.browser.signal]),
|
|
4749
|
+
timeoutMs: options.browser.timeoutMs
|
|
4665
4750
|
});
|
|
4666
4751
|
let resolvedClient = null;
|
|
4667
4752
|
try {
|
|
4668
|
-
resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch2);
|
|
4753
|
+
resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch2, signal);
|
|
4669
4754
|
const sessionWithoutTokens = {
|
|
4670
4755
|
resource,
|
|
4671
4756
|
authorizationServer: discovery.authorizationServer,
|
|
@@ -4693,6 +4778,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4693
4778
|
redirectUri: loopback.redirectUri,
|
|
4694
4779
|
resource,
|
|
4695
4780
|
fetch: fetch2,
|
|
4781
|
+
signal,
|
|
4696
4782
|
now
|
|
4697
4783
|
});
|
|
4698
4784
|
const session = {
|
|
@@ -4702,6 +4788,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4702
4788
|
await saveSession(resource, session);
|
|
4703
4789
|
return session;
|
|
4704
4790
|
} catch (error) {
|
|
4791
|
+
signal?.throwIfAborted();
|
|
4705
4792
|
if (shouldReRegisterStoredDynamicClient(error, resolvedClient, reRegistrationAttempted)) {
|
|
4706
4793
|
reRegistrationAttempted = true;
|
|
4707
4794
|
await clearRegisteredClient(discovery.authorizationServer);
|
|
@@ -4727,7 +4814,8 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4727
4814
|
authorizationPromises.set(resource, finalPromise);
|
|
4728
4815
|
return finalPromise;
|
|
4729
4816
|
}
|
|
4730
|
-
async function resolveClient(existingSession, discovery, redirectUri, fetch2) {
|
|
4817
|
+
async function resolveClient(existingSession, discovery, redirectUri, fetch2, parentSignal) {
|
|
4818
|
+
parentSignal?.throwIfAborted();
|
|
4731
4819
|
const configuredClient = normalizeConfiguredClient(options.client);
|
|
4732
4820
|
if (options.client.mode === "static") {
|
|
4733
4821
|
if (configuredClient === null) {
|
|
@@ -4777,7 +4865,8 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4777
4865
|
}
|
|
4778
4866
|
}
|
|
4779
4867
|
const registrationBody = buildClientRegistrationBody(getClientMetadata(options.client), redirectUri);
|
|
4780
|
-
const
|
|
4868
|
+
const deadline = AbortSignal.timeout(3e4);
|
|
4869
|
+
const signal = parentSignal === void 0 ? deadline : AbortSignal.any([parentSignal, deadline]);
|
|
4781
4870
|
const response = await fetchMcpResponse(fetch2, registrationEndpoint, {
|
|
4782
4871
|
method: "POST",
|
|
4783
4872
|
headers: {
|
|
@@ -5340,8 +5429,10 @@ async function readJsonResponse(response, label, signal) {
|
|
|
5340
5429
|
throw new Error(`${label} response must be valid JSON`);
|
|
5341
5430
|
}
|
|
5342
5431
|
}
|
|
5343
|
-
async function fetchMetadata(fetch2, location, label) {
|
|
5344
|
-
|
|
5432
|
+
async function fetchMetadata(fetch2, location, label, parentSignal) {
|
|
5433
|
+
parentSignal?.throwIfAborted();
|
|
5434
|
+
const deadline = AbortSignal.timeout(1e4);
|
|
5435
|
+
const signal = parentSignal === void 0 ? deadline : AbortSignal.any([deadline, parentSignal]);
|
|
5345
5436
|
const response = await fetchMcpResponse(fetch2, location, {
|
|
5346
5437
|
method: "GET",
|
|
5347
5438
|
headers: { Accept: "application/json" },
|
|
@@ -5443,7 +5534,7 @@ var OAuthMetadataDiscovery = class {
|
|
|
5443
5534
|
this.fetchImpl = fetch2;
|
|
5444
5535
|
this.cache = cache;
|
|
5445
5536
|
}
|
|
5446
|
-
async discoverProtectedResource(resource, resourceMetadataUrl) {
|
|
5537
|
+
async discoverProtectedResource(resource, resourceMetadataUrl, signal) {
|
|
5447
5538
|
const locations = /* @__PURE__ */ new Set([resolveProtectedResourceMetadataUrl(resource, resourceMetadataUrl)]);
|
|
5448
5539
|
if (resourceMetadataUrl === void 0) {
|
|
5449
5540
|
locations.add(new URL("/.well-known/oauth-protected-resource", resource).toString());
|
|
@@ -5452,17 +5543,19 @@ var OAuthMetadataDiscovery = class {
|
|
|
5452
5543
|
for (const location of locations) {
|
|
5453
5544
|
try {
|
|
5454
5545
|
const metadata = validateProtectedResourceMetadata(
|
|
5455
|
-
await fetchMetadata(this.fetchImpl, location, "Protected resource metadata"),
|
|
5546
|
+
await fetchMetadata(this.fetchImpl, location, "Protected resource metadata", signal),
|
|
5456
5547
|
resource
|
|
5457
5548
|
);
|
|
5458
5549
|
return { location, metadata };
|
|
5459
5550
|
} catch (error) {
|
|
5551
|
+
signal?.throwIfAborted();
|
|
5460
5552
|
lastError = error;
|
|
5461
5553
|
}
|
|
5462
5554
|
}
|
|
5463
5555
|
throw lastError;
|
|
5464
5556
|
}
|
|
5465
|
-
async discover(resourceUrl, { resourceMetadataUrl } = {}) {
|
|
5557
|
+
async discover(resourceUrl, { resourceMetadataUrl, signal } = {}) {
|
|
5558
|
+
signal?.throwIfAborted();
|
|
5466
5559
|
const cacheKey = canonicalizeResourceIndicator(resourceUrl);
|
|
5467
5560
|
resolveProtectedResourceMetadataUrl(cacheKey, resourceMetadataUrl);
|
|
5468
5561
|
const memoryCachedResult = this.memoryCache.get(cacheKey);
|
|
@@ -5470,6 +5563,7 @@ var OAuthMetadataDiscovery = class {
|
|
|
5470
5563
|
return structuredClone(memoryCachedResult);
|
|
5471
5564
|
}
|
|
5472
5565
|
const sharedCachedResult = await this.cache?.get(cacheKey);
|
|
5566
|
+
signal?.throwIfAborted();
|
|
5473
5567
|
if (sharedCachedResult !== null && sharedCachedResult !== void 0 && resourceMetadataUrl === void 0) {
|
|
5474
5568
|
try {
|
|
5475
5569
|
const result = validateCachedDiscovery(sharedCachedResult, cacheKey);
|
|
@@ -5479,7 +5573,7 @@ var OAuthMetadataDiscovery = class {
|
|
|
5479
5573
|
await this.cache?.delete?.(cacheKey);
|
|
5480
5574
|
}
|
|
5481
5575
|
}
|
|
5482
|
-
const { location: resourceMetadataLocation, metadata: resourceMetadata } = await this.discoverProtectedResource(cacheKey, resourceMetadataUrl);
|
|
5576
|
+
const { location: resourceMetadataLocation, metadata: resourceMetadata } = await this.discoverProtectedResource(cacheKey, resourceMetadataUrl, signal);
|
|
5483
5577
|
const authorizationServerErrors = [];
|
|
5484
5578
|
for (const authorizationServer of resourceMetadata.authorization_servers) {
|
|
5485
5579
|
const normalizedAuthorizationServer = validateAuthorizationServerIssuer(authorizationServer);
|
|
@@ -5487,7 +5581,7 @@ var OAuthMetadataDiscovery = class {
|
|
|
5487
5581
|
for (const authorizationServerMetadataUrl of metadataLocations) {
|
|
5488
5582
|
try {
|
|
5489
5583
|
const authorizationServerMetadata = validateAuthorizationServerMetadata(
|
|
5490
|
-
await fetchMetadata(this.fetchImpl, authorizationServerMetadataUrl, "Authorization server metadata"),
|
|
5584
|
+
await fetchMetadata(this.fetchImpl, authorizationServerMetadataUrl, "Authorization server metadata", signal),
|
|
5491
5585
|
normalizedAuthorizationServer
|
|
5492
5586
|
);
|
|
5493
5587
|
const result = {
|
|
@@ -5502,6 +5596,7 @@ var OAuthMetadataDiscovery = class {
|
|
|
5502
5596
|
await this.cache?.set(cacheKey, structuredClone(result));
|
|
5503
5597
|
return result;
|
|
5504
5598
|
} catch (error) {
|
|
5599
|
+
signal?.throwIfAborted();
|
|
5505
5600
|
authorizationServerErrors.push(
|
|
5506
5601
|
`${authorizationServerMetadataUrl}: ${error instanceof Error ? error.message : String(error)}`
|
|
5507
5602
|
);
|
|
@@ -6642,6 +6737,7 @@ var HttpTransport = class {
|
|
|
6642
6737
|
oauthProvider;
|
|
6643
6738
|
oauthMetadataDiscovery;
|
|
6644
6739
|
inFlightFetchAbortControllers = /* @__PURE__ */ new Set();
|
|
6740
|
+
inFlightOAuthAbortControllers = /* @__PURE__ */ new Set();
|
|
6645
6741
|
openResponseReaders = /* @__PURE__ */ new Set();
|
|
6646
6742
|
modernRequests = /* @__PURE__ */ new Map();
|
|
6647
6743
|
modernMode = false;
|
|
@@ -6711,7 +6807,7 @@ var HttpTransport = class {
|
|
|
6711
6807
|
this.rejectLegacyEndpoint = void 0;
|
|
6712
6808
|
this.resolveLegacyEndpoint = void 0;
|
|
6713
6809
|
this.toolParameterHeaders.clear();
|
|
6714
|
-
this.abortInFlightFetches();
|
|
6810
|
+
this.abortInFlightFetches(reason);
|
|
6715
6811
|
this.cancelOpenResponseReaders();
|
|
6716
6812
|
if (!this.writeStream.destroyed && !this.writeStream.writableEnded) {
|
|
6717
6813
|
this.writeStream.end();
|
|
@@ -6747,12 +6843,14 @@ var HttpTransport = class {
|
|
|
6747
6843
|
this.resolveClosed = void 0;
|
|
6748
6844
|
resolveClosed?.({ reason: closeReason });
|
|
6749
6845
|
}
|
|
6750
|
-
abortInFlightFetches() {
|
|
6751
|
-
for (const controller of this.modernRequests.values()) controller.abort();
|
|
6846
|
+
abortInFlightFetches(reason) {
|
|
6847
|
+
for (const controller of this.modernRequests.values()) controller.abort(reason);
|
|
6752
6848
|
this.modernRequests.clear();
|
|
6753
6849
|
for (const abortController of this.inFlightFetchAbortControllers) {
|
|
6754
|
-
abortController.abort();
|
|
6850
|
+
abortController.abort(reason);
|
|
6755
6851
|
}
|
|
6852
|
+
for (const controller of this.inFlightOAuthAbortControllers) controller.abort(reason);
|
|
6853
|
+
this.inFlightOAuthAbortControllers.clear();
|
|
6756
6854
|
this.inFlightFetchAbortControllers.clear();
|
|
6757
6855
|
}
|
|
6758
6856
|
cancelOpenResponseReaders() {
|
|
@@ -6818,7 +6916,7 @@ var HttpTransport = class {
|
|
|
6818
6916
|
const response = await this.fetchWithOAuthRetry({
|
|
6819
6917
|
url: postUrl,
|
|
6820
6918
|
method: "POST",
|
|
6821
|
-
createHeaders: () => this.createPostHeaders(message, modern),
|
|
6919
|
+
createHeaders: (signal) => this.createPostHeaders(message, modern, signal),
|
|
6822
6920
|
body: line,
|
|
6823
6921
|
controller
|
|
6824
6922
|
});
|
|
@@ -6858,7 +6956,7 @@ var HttpTransport = class {
|
|
|
6858
6956
|
this.modernRequests.delete(id);
|
|
6859
6957
|
}
|
|
6860
6958
|
}
|
|
6861
|
-
async createPostHeaders(message, modern = false) {
|
|
6959
|
+
async createPostHeaders(message, modern = false, signal) {
|
|
6862
6960
|
const headers = new Headers(this.headers);
|
|
6863
6961
|
headers.set("Accept", "application/json, text/event-stream");
|
|
6864
6962
|
headers.set("Content-Type", "application/json");
|
|
@@ -6888,9 +6986,9 @@ var HttpTransport = class {
|
|
|
6888
6986
|
headers.set("Mcp-Session-Id", this.sessionId);
|
|
6889
6987
|
headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
|
|
6890
6988
|
}
|
|
6891
|
-
return this.authorizeRequestHeaders(headers);
|
|
6989
|
+
return this.authorizeRequestHeaders(headers, signal);
|
|
6892
6990
|
}
|
|
6893
|
-
async createGetHeaders() {
|
|
6991
|
+
async createGetHeaders(signal) {
|
|
6894
6992
|
const headers = new Headers(this.headers);
|
|
6895
6993
|
headers.set("Accept", "text/event-stream");
|
|
6896
6994
|
if (this.sessionId !== void 0) {
|
|
@@ -6900,20 +6998,26 @@ var HttpTransport = class {
|
|
|
6900
6998
|
if (this.lastEventId !== void 0) {
|
|
6901
6999
|
headers.set("Last-Event-ID", this.lastEventId);
|
|
6902
7000
|
}
|
|
6903
|
-
return this.authorizeRequestHeaders(headers);
|
|
7001
|
+
return this.authorizeRequestHeaders(headers, signal);
|
|
6904
7002
|
}
|
|
6905
|
-
async createDeleteHeaders(sessionId) {
|
|
7003
|
+
async createDeleteHeaders(sessionId, signal) {
|
|
6906
7004
|
const headers = new Headers(this.headers);
|
|
6907
7005
|
headers.set("Mcp-Session-Id", sessionId);
|
|
6908
7006
|
headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
|
|
6909
|
-
return this.authorizeRequestHeaders(headers);
|
|
7007
|
+
return this.authorizeRequestHeaders(headers, signal);
|
|
6910
7008
|
}
|
|
6911
|
-
async authorizeRequestHeaders(headers) {
|
|
7009
|
+
async authorizeRequestHeaders(headers, signal) {
|
|
7010
|
+
signal?.throwIfAborted();
|
|
6912
7011
|
await this.oauthProvider?.authorizeRequest?.({
|
|
6913
7012
|
requestUrl: new URL(this.url),
|
|
6914
7013
|
headers,
|
|
6915
|
-
|
|
7014
|
+
signal,
|
|
7015
|
+
fetch: (url, init) => fetchMcpResponse(this.fetchImpl, url, {
|
|
7016
|
+
...init,
|
|
7017
|
+
signal: signal === void 0 ? init?.signal : init?.signal == null ? signal : AbortSignal.any([signal, init.signal])
|
|
7018
|
+
})
|
|
6916
7019
|
});
|
|
7020
|
+
signal?.throwIfAborted();
|
|
6917
7021
|
return headers;
|
|
6918
7022
|
}
|
|
6919
7023
|
captureSessionId(response) {
|
|
@@ -6950,7 +7054,7 @@ var HttpTransport = class {
|
|
|
6950
7054
|
return this.legacyEndpointReady;
|
|
6951
7055
|
}
|
|
6952
7056
|
async sendSessionTerminationRequest(sessionId, signal) {
|
|
6953
|
-
const headers = await this.createDeleteHeaders(sessionId);
|
|
7057
|
+
const headers = await this.createDeleteHeaders(sessionId, signal);
|
|
6954
7058
|
signal.throwIfAborted();
|
|
6955
7059
|
const response = await fetchMcpResponse(this.fetchImpl, this.url, {
|
|
6956
7060
|
method: "DELETE",
|
|
@@ -6973,7 +7077,7 @@ var HttpTransport = class {
|
|
|
6973
7077
|
async consumeGetSseStream() {
|
|
6974
7078
|
const response = await this.fetchWithOAuthRetry({
|
|
6975
7079
|
method: "GET",
|
|
6976
|
-
createHeaders: () => this.createGetHeaders()
|
|
7080
|
+
createHeaders: (signal) => this.createGetHeaders(signal)
|
|
6977
7081
|
});
|
|
6978
7082
|
if (this.disposed) {
|
|
6979
7083
|
void response.body?.cancel().catch(() => void 0);
|
|
@@ -7056,7 +7160,7 @@ var HttpTransport = class {
|
|
|
7056
7160
|
const message = responseBody.length === 0 ? `HTTP transport POST failed (${statusDescriptor})` : `HTTP transport POST failed (${statusDescriptor}): ${responseBody}`;
|
|
7057
7161
|
throw new HttpTransportError(message, response.status, "POST");
|
|
7058
7162
|
}
|
|
7059
|
-
async maybeHandleUnauthorizedResponse(response) {
|
|
7163
|
+
async maybeHandleUnauthorizedResponse(response, signal) {
|
|
7060
7164
|
if (response.status !== 401 || this.oauthProvider === void 0) {
|
|
7061
7165
|
return false;
|
|
7062
7166
|
}
|
|
@@ -7067,7 +7171,7 @@ var HttpTransport = class {
|
|
|
7067
7171
|
const challenge = parseBearerWwwAuthenticateHeader(response.headers.get("WWW-Authenticate"));
|
|
7068
7172
|
const resourceMetadataUrl = challenge?.params.resource_metadata;
|
|
7069
7173
|
try {
|
|
7070
|
-
const discovery = await discoveryClient.discover(this.url, { resourceMetadataUrl });
|
|
7174
|
+
const discovery = await discoveryClient.discover(this.url, { resourceMetadataUrl, signal });
|
|
7071
7175
|
const providerResponse = response.clone();
|
|
7072
7176
|
let result;
|
|
7073
7177
|
try {
|
|
@@ -7076,8 +7180,13 @@ var HttpTransport = class {
|
|
|
7076
7180
|
response: providerResponse,
|
|
7077
7181
|
challenge,
|
|
7078
7182
|
discovery,
|
|
7079
|
-
|
|
7183
|
+
signal,
|
|
7184
|
+
fetch: (url, init) => fetchMcpResponse(this.fetchImpl, url, {
|
|
7185
|
+
...init,
|
|
7186
|
+
signal: init?.signal == null ? signal : AbortSignal.any([signal, init.signal])
|
|
7187
|
+
})
|
|
7080
7188
|
});
|
|
7189
|
+
signal.throwIfAborted();
|
|
7081
7190
|
} finally {
|
|
7082
7191
|
void providerResponse.body?.cancel().catch(() => void 0);
|
|
7083
7192
|
}
|
|
@@ -7207,25 +7316,30 @@ var HttpTransport = class {
|
|
|
7207
7316
|
`);
|
|
7208
7317
|
}
|
|
7209
7318
|
async fetchWithOAuthRetry(input) {
|
|
7210
|
-
const
|
|
7211
|
-
|
|
7212
|
-
|
|
7319
|
+
const controller = input.controller ?? new AbortController();
|
|
7320
|
+
this.inFlightOAuthAbortControllers.add(controller);
|
|
7321
|
+
const request = async () => {
|
|
7322
|
+
controller.signal.throwIfAborted();
|
|
7323
|
+
const headers = await input.createHeaders(controller.signal);
|
|
7324
|
+
controller.signal.throwIfAborted();
|
|
7325
|
+
return this.fetchWithAbort(input.url ?? this.url, {
|
|
7213
7326
|
method: input.method,
|
|
7214
|
-
headers
|
|
7327
|
+
headers,
|
|
7215
7328
|
body: input.body
|
|
7216
|
-
},
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7329
|
+
}, controller);
|
|
7330
|
+
};
|
|
7331
|
+
try {
|
|
7332
|
+
let response = await request();
|
|
7333
|
+
if (await this.maybeHandleUnauthorizedResponse(response, controller.signal)) response = await request();
|
|
7334
|
+
const oauthError = this.oauthProvider === void 0 ? null : this.readOAuthChallengeError(response);
|
|
7335
|
+
if (oauthError !== null) {
|
|
7336
|
+
void response.body?.cancel().catch(() => void 0);
|
|
7337
|
+
throw oauthError;
|
|
7338
|
+
}
|
|
7339
|
+
return response;
|
|
7340
|
+
} finally {
|
|
7341
|
+
this.inFlightOAuthAbortControllers.delete(controller);
|
|
7227
7342
|
}
|
|
7228
|
-
return response;
|
|
7229
7343
|
}
|
|
7230
7344
|
readOAuthChallengeError(response) {
|
|
7231
7345
|
if (response.status !== 401 && response.status !== 403) {
|