@0xmaxma/claude-gateway 1.8.7 → 1.8.8
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/README.md +96 -0
- package/dist/agent/runner.d.ts +47 -1
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +116 -17
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/connectors-router.d.ts +50 -0
- package/dist/api/connectors-router.d.ts.map +1 -0
- package/dist/api/connectors-router.js +650 -0
- package/dist/api/connectors-router.js.map +1 -0
- package/dist/api/gateway-router.d.ts +4 -0
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +26 -0
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/oauth-connectors-router.d.ts +43 -0
- package/dist/api/oauth-connectors-router.d.ts.map +1 -0
- package/dist/api/oauth-connectors-router.js +384 -0
- package/dist/api/oauth-connectors-router.js.map +1 -0
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +101 -13
- package/dist/api/router.js.map +1 -1
- package/dist/apps/agent-manager.d.ts +6 -2
- package/dist/apps/agent-manager.d.ts.map +1 -1
- package/dist/apps/agent-manager.js +10 -18
- package/dist/apps/agent-manager.js.map +1 -1
- package/dist/config/config-write-lock.d.ts +45 -0
- package/dist/config/config-write-lock.d.ts.map +1 -0
- package/dist/config/config-write-lock.js +164 -0
- package/dist/config/config-write-lock.js.map +1 -0
- package/dist/config/watcher.d.ts.map +1 -1
- package/dist/config/watcher.js +29 -0
- package/dist/config/watcher.js.map +1 -1
- package/dist/connectors/custom-connectors-store.d.ts +54 -0
- package/dist/connectors/custom-connectors-store.d.ts.map +1 -0
- package/dist/connectors/custom-connectors-store.js +204 -0
- package/dist/connectors/custom-connectors-store.js.map +1 -0
- package/dist/connectors/custom.d.ts +69 -0
- package/dist/connectors/custom.d.ts.map +1 -0
- package/dist/connectors/custom.js +158 -0
- package/dist/connectors/custom.js.map +1 -0
- package/dist/connectors/mcp-oauth.d.ts +152 -0
- package/dist/connectors/mcp-oauth.d.ts.map +1 -0
- package/dist/connectors/mcp-oauth.js +522 -0
- package/dist/connectors/mcp-oauth.js.map +1 -0
- package/dist/connectors/oauth-refresh-sweep.d.ts +71 -0
- package/dist/connectors/oauth-refresh-sweep.d.ts.map +1 -0
- package/dist/connectors/oauth-refresh-sweep.js +337 -0
- package/dist/connectors/oauth-refresh-sweep.js.map +1 -0
- package/dist/connectors/pending-oauth-store.d.ts +39 -0
- package/dist/connectors/pending-oauth-store.d.ts.map +1 -0
- package/dist/connectors/pending-oauth-store.js +50 -0
- package/dist/connectors/pending-oauth-store.js.map +1 -0
- package/dist/connectors/resolve.d.ts +52 -0
- package/dist/connectors/resolve.d.ts.map +1 -0
- package/dist/connectors/resolve.js +176 -0
- package/dist/connectors/resolve.js.map +1 -0
- package/dist/connectors/token-env.d.ts +93 -0
- package/dist/connectors/token-env.d.ts.map +1 -0
- package/dist/connectors/token-env.js +323 -0
- package/dist/connectors/token-env.js.map +1 -0
- package/dist/connectors/types.d.ts +126 -0
- package/dist/connectors/types.d.ts.map +1 -0
- package/dist/connectors/types.js +13 -0
- package/dist/connectors/types.js.map +1 -0
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/session/process.d.ts +23 -0
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +78 -2
- package/dist/session/process.js.map +1 -1
- package/dist/types.d.ts +46 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Generic OAuth 2.1 + PKCE (+ optional Dynamic Client Registration) support for
|
|
4
|
+
* "custom" MCP connectors whose only auth option is a real OAuth sign-in (no
|
|
5
|
+
* static API key) — e.g. Firecrawl's `https://mcp.firecrawl.dev/v2/mcp-oauth`.
|
|
6
|
+
*
|
|
7
|
+
* This module never touches Express or the pending-flow store — pure discovery
|
|
8
|
+
* + token functions, unit-testable with plain HTTP mocks. See
|
|
9
|
+
* `pending-oauth-store.ts` for the in-flight-flow state and
|
|
10
|
+
* `api/oauth-connectors-router.ts` for the HTTP endpoints that glue this
|
|
11
|
+
* together with `connectors/custom.ts`'s existing secret storage.
|
|
12
|
+
*
|
|
13
|
+
* Empirically verified against production Firecrawl (2026-09, this repo's own
|
|
14
|
+
* throwaway PoC — see git history / PR description, not reproduced here):
|
|
15
|
+
* DCR genuinely works with an arbitrary redirect_uri (no pre-registration
|
|
16
|
+
* needed), and the RFC 8707 `resource` parameter is REQUIRED on both the
|
|
17
|
+
* authorize request and the token exchange — omitting it yields a token that
|
|
18
|
+
* exchanges fine but is rejected by the MCP endpoint itself
|
|
19
|
+
* ("OAUTH_CONNECTION_INVALID"). Always pass `resource` through this module's
|
|
20
|
+
* functions; never make it optional.
|
|
21
|
+
*/
|
|
22
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.OAuthTokenError = void 0;
|
|
27
|
+
exports.discoverOAuthMetadata = discoverOAuthMetadata;
|
|
28
|
+
exports.discoverOAuthMetadataCached = discoverOAuthMetadataCached;
|
|
29
|
+
exports.clearOAuthMetadataCache = clearOAuthMetadataCache;
|
|
30
|
+
exports.registerClient = registerClient;
|
|
31
|
+
exports.resolveClientId = resolveClientId;
|
|
32
|
+
exports.resolveScope = resolveScope;
|
|
33
|
+
exports.generatePkce = generatePkce;
|
|
34
|
+
exports.generateState = generateState;
|
|
35
|
+
exports.buildAuthorizeUrl = buildAuthorizeUrl;
|
|
36
|
+
exports.exchangeCode = exchangeCode;
|
|
37
|
+
exports.refreshAccessToken = refreshAccessToken;
|
|
38
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
39
|
+
/**
|
|
40
|
+
* Every request here goes to a third-party server the gateway does not control, on
|
|
41
|
+
* paths that run inside a 60s periodic sweep (see oauth-refresh-sweep.ts) and inside
|
|
42
|
+
* admin HTTP handlers. Node's fetch has no default timeout, so a provider that accepts
|
|
43
|
+
* the connection and then never responds would hang the caller indefinitely — the
|
|
44
|
+
* sweep's in-flight guard would then skip every subsequent tick, and refresh would
|
|
45
|
+
* stop happening at all. Bounded here so a hang surfaces as an ordinary failure.
|
|
46
|
+
*/
|
|
47
|
+
const REQUEST_TIMEOUT_MS = 15000;
|
|
48
|
+
/**
|
|
49
|
+
* One request, timed out, with redirect-following DISABLED.
|
|
50
|
+
*
|
|
51
|
+
* Node's fetch defaults to `redirect: 'follow'`, and a followed redirect goes
|
|
52
|
+
* wherever the answering server names — without passing through
|
|
53
|
+
* `assertFetchableUrl`, which only ever sees the URL we ASK for. A single 302 to
|
|
54
|
+
* `http://169.254.169.254/` therefore lands past the guard the rest of this module
|
|
55
|
+
* is built around: the check has to run per hop, not once per call. Nothing here
|
|
56
|
+
* calls raw `fetch` any more; the two wrappers below are the only ways out.
|
|
57
|
+
*/
|
|
58
|
+
function fetchNoRedirect(url, init) {
|
|
59
|
+
return fetch(url, { ...init, redirect: 'manual', signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Enough hops for the redirects a real deployment actually serves — a bare host to
|
|
63
|
+
* its canonical one, a trailing-slash normalisation — and few enough that a
|
|
64
|
+
* redirect loop costs three round trips instead of twenty.
|
|
65
|
+
*/
|
|
66
|
+
const MAX_REDIRECT_HOPS = 3;
|
|
67
|
+
/**
|
|
68
|
+
* `fetchNoRedirect`, following up to `MAX_REDIRECT_HOPS` redirects and re-running
|
|
69
|
+
* `assertFetchableUrl` on every `Location` before going there.
|
|
70
|
+
*
|
|
71
|
+
* Used for the discovery walk and the DCR registration POST — requests that carry no
|
|
72
|
+
* credential, where refusing all redirects outright would break providers that
|
|
73
|
+
* legitimately serve their well-known documents off a canonical host. The token
|
|
74
|
+
* endpoint does NOT use this; see `tokenRequest`.
|
|
75
|
+
*/
|
|
76
|
+
async function fetchFollowingCheckedRedirects(url, what, init) {
|
|
77
|
+
let target = url;
|
|
78
|
+
let nextInit = init;
|
|
79
|
+
for (let hop = 0;; hop++) {
|
|
80
|
+
const resp = await fetchNoRedirect(target, nextInit);
|
|
81
|
+
if (resp.status < 300 || resp.status >= 400)
|
|
82
|
+
return resp;
|
|
83
|
+
const location = resp.headers.get('location');
|
|
84
|
+
// A 3xx with no Location is a malformed response, not a redirect — hand it back
|
|
85
|
+
// and let the caller's own status check produce the error.
|
|
86
|
+
if (!location)
|
|
87
|
+
return resp;
|
|
88
|
+
if (hop >= MAX_REDIRECT_HOPS) {
|
|
89
|
+
throw new Error(`Fetching the ${what} exceeded ${MAX_REDIRECT_HOPS} redirects — giving up at ${target}.`);
|
|
90
|
+
}
|
|
91
|
+
// A relative Location is legal (RFC 9110 §10.2.2) and resolves against the URL
|
|
92
|
+
// just requested, so resolve first and validate the absolute result.
|
|
93
|
+
target = assertFetchableUrl(new URL(location, target).toString(), `${what} redirect target`);
|
|
94
|
+
// RFC 9110 §15.4: only 307/308 preserve the method and body. 301/302/303 on a
|
|
95
|
+
// POST become a bodiless GET, which is what every other client does too.
|
|
96
|
+
nextInit =
|
|
97
|
+
resp.status === 307 || resp.status === 308
|
|
98
|
+
? init
|
|
99
|
+
: { ...init, method: 'GET', body: undefined };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Gate every URL discovery hands us before the gateway fetches it or POSTs a
|
|
104
|
+
* credential to it.
|
|
105
|
+
*
|
|
106
|
+
* The admin picks the MCP server URL. It does NOT pick what comes back from it: the
|
|
107
|
+
* `resource_metadata` URL arrives in that server's own WWW-Authenticate header, the
|
|
108
|
+
* `authorization_servers[0]` issuer arrives in the document that URL returns, and the
|
|
109
|
+
* authorize/token/registration endpoints arrive in the document after that. Three
|
|
110
|
+
* hops downstream, the remote server is choosing what this process connects to —
|
|
111
|
+
* which is a request-forgery primitive pointed at whatever the gateway's VM can
|
|
112
|
+
* reach but the internet cannot (a metadata service, an admin port on 127.0.0.1,
|
|
113
|
+
* a neighbour on the LAN).
|
|
114
|
+
*
|
|
115
|
+
* Requiring TLS is most of the fix on its own: cloud metadata services and internal
|
|
116
|
+
* admin ports overwhelmingly speak plain http, and a host that does terminate TLS
|
|
117
|
+
* proves nothing about the token — but a plain-http token endpoint would receive the
|
|
118
|
+
* refresh_token in cleartext, which is worth refusing on its own merits.
|
|
119
|
+
*
|
|
120
|
+
* This is a narrowing, not a boundary: an attacker-controlled https host on the LAN
|
|
121
|
+
* still passes. The trust boundary is still the admin key that added the connector.
|
|
122
|
+
*/
|
|
123
|
+
/**
|
|
124
|
+
* Loopback only — deliberately NOT config/public-url's `isLocalHostname`.
|
|
125
|
+
*
|
|
126
|
+
* That predicate answers a different question, for a value with a different
|
|
127
|
+
* provenance: whether the ADMIN's own `gateway.publicUrl` is a local address, so
|
|
128
|
+
* the UI can decide whether to show it as reachable. Widening it to `.internal`
|
|
129
|
+
* and `.local` is right there and wrong here — the hostname reaching this
|
|
130
|
+
* function was chosen three hops downstream by the remote MCP server, and
|
|
131
|
+
* `metadata.google.internal` (GCE), `metadata.internal`, and any mDNS `.local`
|
|
132
|
+
* name on the LAN are exactly the plain-http targets the https requirement above
|
|
133
|
+
* exists to refuse. Loopback is the only reason to relax it: a developer running
|
|
134
|
+
* an AS on 127.0.0.1 is talking to a socket no other host can reach.
|
|
135
|
+
*/
|
|
136
|
+
function isLoopbackHostname(hostname) {
|
|
137
|
+
const host = hostname.toLowerCase();
|
|
138
|
+
return host === 'localhost' || /^127\./.test(host) || host === '::1' || host === '[::1]';
|
|
139
|
+
}
|
|
140
|
+
function assertFetchableUrl(raw, what) {
|
|
141
|
+
let url;
|
|
142
|
+
try {
|
|
143
|
+
url = new URL(raw);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
throw new Error(`Discovery returned a ${what} that is not a valid URL: ${raw}`);
|
|
147
|
+
}
|
|
148
|
+
if (url.protocol === 'https:')
|
|
149
|
+
return url.toString();
|
|
150
|
+
if (url.protocol === 'http:' && isLoopbackHostname(url.hostname))
|
|
151
|
+
return url.toString();
|
|
152
|
+
throw new Error(`Discovery returned a ${what} the gateway will not call: ${url.protocol}//${url.host}` +
|
|
153
|
+
` — OAuth endpoints must use https (plain http is allowed only for localhost).`);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* A token endpoint that answered, and answered with a refusal.
|
|
157
|
+
*
|
|
158
|
+
* The distinction this class exists to draw is between "the grant is dead" and
|
|
159
|
+
* "we couldn't reach the provider just now". The refresh sweep gives up on a
|
|
160
|
+
* connector after a few consecutive failures and deletes its stored credentials,
|
|
161
|
+
* which is right for the first case and destructive for the second — a fifteen
|
|
162
|
+
* minute provider outage is an ordinary event and must not cost the user their
|
|
163
|
+
* sign-in. Only an explicit OAuth error response can be classified, so only this
|
|
164
|
+
* error type carries `isPermanent`; anything else the sweep sees (a DNS failure,
|
|
165
|
+
* a socket timeout, a 502, a discovery step that returned the wrong shape) is
|
|
166
|
+
* treated as retryable by default.
|
|
167
|
+
*/
|
|
168
|
+
class OAuthTokenError extends Error {
|
|
169
|
+
constructor(message, status,
|
|
170
|
+
/** RFC 6749 §5.2 `error` code from the response body, when it had one. */
|
|
171
|
+
errorCode) {
|
|
172
|
+
super(message);
|
|
173
|
+
this.status = status;
|
|
174
|
+
this.errorCode = errorCode;
|
|
175
|
+
this.name = 'OAuthTokenError';
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* True when the authorization server has told us this grant will never work
|
|
179
|
+
* again. Deliberately a small allowlist rather than "any 4xx": a 400 with no
|
|
180
|
+
* recognisable `error` code, or a 429, says more about this request than about
|
|
181
|
+
* the grant, and retrying costs nothing but a backoff.
|
|
182
|
+
*/
|
|
183
|
+
get isPermanent() {
|
|
184
|
+
return (this.status >= 400 &&
|
|
185
|
+
this.status < 500 &&
|
|
186
|
+
(this.errorCode === 'invalid_grant' ||
|
|
187
|
+
this.errorCode === 'invalid_client' ||
|
|
188
|
+
this.errorCode === 'unauthorized_client' ||
|
|
189
|
+
this.errorCode === 'invalid_scope'));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
exports.OAuthTokenError = OAuthTokenError;
|
|
193
|
+
/** Parse `resource_metadata="<url>"` out of a WWW-Authenticate header value. */
|
|
194
|
+
function parseResourceMetadataUrl(header) {
|
|
195
|
+
if (!header)
|
|
196
|
+
return null;
|
|
197
|
+
const m = header.match(/resource_metadata="([^"]+)"/);
|
|
198
|
+
return m ? m[1] : null;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* A metadata document's string list, or undefined when it has no such list at all.
|
|
202
|
+
*
|
|
203
|
+
* Filtered element by element, not merely checked for being an array: these
|
|
204
|
+
* documents come from a host we do not control, and `scopesSupported` is
|
|
205
|
+
* `join(' ')`ed straight into the `scope` query parameter of a URL the admin's own
|
|
206
|
+
* browser is then sent to. A `scopes_supported: ["read", 42, null, {"a":1}]` —
|
|
207
|
+
* malformed, but served with a 200 — produced `read 42 null [object Object]`, which
|
|
208
|
+
* the AS rejects as invalid_scope with nothing to say where the garbage came from.
|
|
209
|
+
* Dropping the non-strings asks for the scopes it did understand.
|
|
210
|
+
*
|
|
211
|
+
* The undefined/[] distinction is load-bearing at the one call site: a document
|
|
212
|
+
* that OMITS the list has said nothing and defers to the next document, while one
|
|
213
|
+
* that publishes an empty list has said "none", and that is an answer.
|
|
214
|
+
*/
|
|
215
|
+
function stringsOf(value) {
|
|
216
|
+
if (!Array.isArray(value))
|
|
217
|
+
return undefined;
|
|
218
|
+
return value.filter((s) => typeof s === 'string');
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Discover OAuth metadata for an MCP server by probing it unauthenticated
|
|
222
|
+
* (expects a 401 advertising `resource_metadata`, per RFC 9728), then walking
|
|
223
|
+
* protected-resource metadata → authorization-server metadata (RFC 8414).
|
|
224
|
+
* Throws a descriptive error at whichever step fails — callers surface it
|
|
225
|
+
* to the admin as "can't set up OAuth for this URL", not a generic 500.
|
|
226
|
+
*/
|
|
227
|
+
async function discoverOAuthMetadata(mcpUrl) {
|
|
228
|
+
const probe = await fetchFollowingCheckedRedirects(mcpUrl, 'MCP server URL', {
|
|
229
|
+
method: 'POST',
|
|
230
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/event-stream' },
|
|
231
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: {} }),
|
|
232
|
+
});
|
|
233
|
+
if (probe.status !== 401) {
|
|
234
|
+
throw new Error(`Expected a 401 challenge from ${mcpUrl} (got ${probe.status}) — this server may not require OAuth, or isn't an MCP server at all.`);
|
|
235
|
+
}
|
|
236
|
+
const resourceMetadataUrl = parseResourceMetadataUrl(probe.headers.get('www-authenticate'));
|
|
237
|
+
if (!resourceMetadataUrl) {
|
|
238
|
+
throw new Error(`${mcpUrl} returned 401 but no "resource_metadata" in its WWW-Authenticate header — can't discover its OAuth server.`);
|
|
239
|
+
}
|
|
240
|
+
const prm = await fetchFollowingCheckedRedirects(assertFetchableUrl(resourceMetadataUrl, 'resource_metadata URL'), 'protected-resource metadata').then((r) => {
|
|
241
|
+
if (!r.ok)
|
|
242
|
+
throw new Error(`Protected-resource metadata fetch failed: ${r.status}`);
|
|
243
|
+
return r.json();
|
|
244
|
+
});
|
|
245
|
+
const authServers = prm.authorization_servers;
|
|
246
|
+
if (!Array.isArray(authServers) || typeof authServers[0] !== 'string') {
|
|
247
|
+
throw new Error(`${resourceMetadataUrl} has no authorization_servers listed.`);
|
|
248
|
+
}
|
|
249
|
+
const issuer = new URL(assertFetchableUrl(authServers[0], 'authorization server issuer'));
|
|
250
|
+
// RFC 8414 §3.1: well-known path is inserted before the issuer's own path
|
|
251
|
+
// component (usually empty, as it is for Firecrawl).
|
|
252
|
+
const asMetaUrl = new URL(`/.well-known/oauth-authorization-server${issuer.pathname === '/' ? '' : issuer.pathname}`, issuer.origin);
|
|
253
|
+
const asMeta = await fetchFollowingCheckedRedirects(asMetaUrl.toString(), 'authorization-server metadata').then((r) => {
|
|
254
|
+
if (!r.ok)
|
|
255
|
+
throw new Error(`Authorization-server metadata fetch failed: ${r.status}`);
|
|
256
|
+
return r.json();
|
|
257
|
+
});
|
|
258
|
+
if (typeof asMeta.authorization_endpoint !== 'string' || typeof asMeta.token_endpoint !== 'string') {
|
|
259
|
+
throw new Error(`${asMetaUrl} is missing authorization_endpoint/token_endpoint.`);
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
resource: mcpUrl,
|
|
263
|
+
// Validated here rather than at each use: the authorize endpoint becomes a
|
|
264
|
+
// redirect the admin's own browser follows, the token endpoint receives the
|
|
265
|
+
// refresh_token, and the registration endpoint receives a DCR POST. All three
|
|
266
|
+
// are values this last document chose.
|
|
267
|
+
authorizationEndpoint: assertFetchableUrl(asMeta.authorization_endpoint, 'authorization_endpoint'),
|
|
268
|
+
tokenEndpoint: assertFetchableUrl(asMeta.token_endpoint, 'token_endpoint'),
|
|
269
|
+
registrationEndpoint: typeof asMeta.registration_endpoint === 'string'
|
|
270
|
+
? assertFetchableUrl(asMeta.registration_endpoint, 'registration_endpoint')
|
|
271
|
+
: undefined,
|
|
272
|
+
// The RESOURCE's list first, the authorization server's only as a fallback.
|
|
273
|
+
// These two documents answer different questions: RFC 9728 §2's
|
|
274
|
+
// `scopes_supported` is the scopes needed to reach THIS MCP server, while RFC
|
|
275
|
+
// 8414 §2's is every scope the AS issues for every resource behind it. Asking
|
|
276
|
+
// for the second is asking the admin to grant this gateway the union of an
|
|
277
|
+
// entire provider's privileges — and on an AS whose global list contains
|
|
278
|
+
// scopes this client may not have, it is also an `invalid_scope` refusal that
|
|
279
|
+
// fails the sign-in outright. The AS list stays as the fallback because a
|
|
280
|
+
// provider that publishes no per-resource scopes is a provider this gateway
|
|
281
|
+
// already signs into today.
|
|
282
|
+
scopesSupported: stringsOf(prm.scopes_supported) ?? stringsOf(asMeta.scopes_supported) ?? [],
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* How long a discovered OAuth metadata document is reused. Long enough that a
|
|
287
|
+
* connector refreshing on the hour never re-discovers, short enough that a
|
|
288
|
+
* provider that moves its endpoints is picked up the same day without a restart.
|
|
289
|
+
*/
|
|
290
|
+
const METADATA_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
291
|
+
/**
|
|
292
|
+
* Hard ceiling on cached documents, enforced on top of the TTL.
|
|
293
|
+
*
|
|
294
|
+
* The TTL alone bounds how *stale* an entry can be, not how many there are: an
|
|
295
|
+
* expired entry for a URL nobody ever discovers again is never looked up, so its
|
|
296
|
+
* `expiresAt` check never runs and it sits in the Map for the process lifetime.
|
|
297
|
+
* Every connector edit that changes a URL, and every deleted connector, leaves one
|
|
298
|
+
* behind. Small leak, unbounded shape — so entries are pruned on write and the map
|
|
299
|
+
* is capped.
|
|
300
|
+
*
|
|
301
|
+
* The cap is generous relative to any plausible connector count; it exists so the
|
|
302
|
+
* map cannot grow without limit if `mcpUrl` ever becomes attacker-influenced, not
|
|
303
|
+
* to keep a real install under it.
|
|
304
|
+
*/
|
|
305
|
+
const MAX_METADATA_CACHE_ENTRIES = 256;
|
|
306
|
+
const metadataCache = new Map();
|
|
307
|
+
/**
|
|
308
|
+
* Drop expired entries, then oldest-first until the map is under the cap.
|
|
309
|
+
*
|
|
310
|
+
* Every entry gets the same TTL, so Map insertion order (which `keys()` preserves)
|
|
311
|
+
* is also expiry order — the first key is always the one closest to expiring, and
|
|
312
|
+
* evicting it costs at most one re-discovery.
|
|
313
|
+
*/
|
|
314
|
+
function pruneMetadataCache(now) {
|
|
315
|
+
for (const [url, entry] of metadataCache) {
|
|
316
|
+
if (entry.expiresAt <= now)
|
|
317
|
+
metadataCache.delete(url);
|
|
318
|
+
}
|
|
319
|
+
while (metadataCache.size > MAX_METADATA_CACHE_ENTRIES) {
|
|
320
|
+
const oldest = metadataCache.keys().next();
|
|
321
|
+
if (oldest.done)
|
|
322
|
+
break;
|
|
323
|
+
metadataCache.delete(oldest.value);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* `discoverOAuthMetadata` with a process-local cache, for the background refresh
|
|
328
|
+
* sweep.
|
|
329
|
+
*
|
|
330
|
+
* Discovery costs three round trips — an MCP probe that is *expected* to 401, then
|
|
331
|
+
* protected-resource metadata, then authorization-server metadata — to obtain a
|
|
332
|
+
* document that changes about never. Paying that on every refresh is not just waste:
|
|
333
|
+
* each of those three requests is another way for a refresh to fail, and a failed
|
|
334
|
+
* refresh is what eventually makes the sweep give up and delete the connector's
|
|
335
|
+
* credentials. Fewer moving parts on the periodic path means fewer spurious failures.
|
|
336
|
+
*
|
|
337
|
+
* Only the sweep uses this. An admin-initiated `oauth/start` still calls
|
|
338
|
+
* `discoverOAuthMetadata` directly: that is the moment a human is waiting to find out
|
|
339
|
+
* whether this URL works at all, so it should see today's truth, not a cached answer
|
|
340
|
+
* from before they fixed their provider's configuration.
|
|
341
|
+
*/
|
|
342
|
+
async function discoverOAuthMetadataCached(mcpUrl) {
|
|
343
|
+
const hit = metadataCache.get(mcpUrl);
|
|
344
|
+
if (hit && hit.expiresAt > Date.now())
|
|
345
|
+
return hit.metadata;
|
|
346
|
+
const metadata = await discoverOAuthMetadata(mcpUrl);
|
|
347
|
+
// delete-then-set: re-`set`ting an existing key keeps its ORIGINAL position in a
|
|
348
|
+
// Map, which would leave a freshly-refreshed entry at the front of the eviction
|
|
349
|
+
// order. Re-inserting puts it back where its new expiry belongs.
|
|
350
|
+
metadataCache.delete(mcpUrl);
|
|
351
|
+
const now = Date.now();
|
|
352
|
+
metadataCache.set(mcpUrl, { metadata, expiresAt: now + METADATA_CACHE_TTL_MS });
|
|
353
|
+
pruneMetadataCache(now);
|
|
354
|
+
return metadata;
|
|
355
|
+
}
|
|
356
|
+
/** Drop every cached metadata document. For tests, and for a forced re-discovery. */
|
|
357
|
+
function clearOAuthMetadataCache() {
|
|
358
|
+
metadataCache.clear();
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Register a public (no client_secret) client via RFC 7591 DCR. Throws if the
|
|
362
|
+
* server rejects it — callers fall back to a per-connector static client_id
|
|
363
|
+
* (an env var an admin configured by hand) when this isn't available at all,
|
|
364
|
+
* see `resolveClientId` below.
|
|
365
|
+
*/
|
|
366
|
+
async function registerClient(registrationEndpoint, redirectUri, clientName) {
|
|
367
|
+
const resp = await fetchFollowingCheckedRedirects(registrationEndpoint, 'registration_endpoint', {
|
|
368
|
+
method: 'POST',
|
|
369
|
+
headers: { 'Content-Type': 'application/json' },
|
|
370
|
+
body: JSON.stringify({
|
|
371
|
+
redirect_uris: [redirectUri],
|
|
372
|
+
token_endpoint_auth_method: 'none',
|
|
373
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
374
|
+
response_types: ['code'],
|
|
375
|
+
client_name: clientName,
|
|
376
|
+
}),
|
|
377
|
+
});
|
|
378
|
+
const body = (await resp.json().catch(() => ({})));
|
|
379
|
+
if (!resp.ok || typeof body.client_id !== 'string') {
|
|
380
|
+
throw new Error(`Dynamic client registration failed (${resp.status}): ${JSON.stringify(body)}`);
|
|
381
|
+
}
|
|
382
|
+
return body.client_id;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Resolve a client_id for `metadata`: try DCR first, fall back to a static
|
|
386
|
+
* `MCP_OAUTH_CLIENT_ID__<connectorId>` env var (an admin-configured client_id
|
|
387
|
+
* for a provider that advertises no registration_endpoint at all).
|
|
388
|
+
*/
|
|
389
|
+
async function resolveClientId(metadata, connectorId, redirectUri) {
|
|
390
|
+
if (metadata.registrationEndpoint) {
|
|
391
|
+
return registerClient(metadata.registrationEndpoint, redirectUri, `claude-gateway (${connectorId})`);
|
|
392
|
+
}
|
|
393
|
+
const envKey = `MCP_OAUTH_CLIENT_ID__${connectorId.replace(/[^a-zA-Z0-9]+/g, '_').toUpperCase()}`;
|
|
394
|
+
const staticClientId = process.env[envKey];
|
|
395
|
+
if (!staticClientId) {
|
|
396
|
+
throw new Error(`${metadata.authorizationEndpoint}'s server advertises no Dynamic Client Registration, and no ${envKey} env var is set as a fallback.`);
|
|
397
|
+
}
|
|
398
|
+
return staticClientId;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* The `scope` to send with an authorization request for `metadata`.
|
|
402
|
+
*
|
|
403
|
+
* An admin-set `MCP_OAUTH_SCOPES__<connectorId>` (space-separated) wins, for the
|
|
404
|
+
* same reason `MCP_OAUTH_CLIENT_ID__<connectorId>` exists above: discovery is the
|
|
405
|
+
* provider's story about itself, and when that story is wrong the sign-in fails at
|
|
406
|
+
* the provider with an `invalid_scope` the operator cannot otherwise do anything
|
|
407
|
+
* about. Some servers publish a scope the client is not entitled to; some grant a
|
|
408
|
+
* refresh_token only when `offline_access` is asked for and never advertise it.
|
|
409
|
+
* Both are one env var away from working, instead of a code change away.
|
|
410
|
+
*
|
|
411
|
+
* `offline_access` is the fallback when nothing is discovered or configured: the
|
|
412
|
+
* refresh sweep needs a refresh_token, and an authorization request carrying no
|
|
413
|
+
* scope at all gets whatever the provider's default happens to be.
|
|
414
|
+
*/
|
|
415
|
+
function resolveScope(metadata, connectorId) {
|
|
416
|
+
const envKey = `MCP_OAUTH_SCOPES__${connectorId.replace(/[^a-zA-Z0-9]+/g, '_').toUpperCase()}`;
|
|
417
|
+
const override = process.env[envKey]?.trim();
|
|
418
|
+
if (override)
|
|
419
|
+
return override;
|
|
420
|
+
return metadata.scopesSupported.length > 0 ? metadata.scopesSupported.join(' ') : 'offline_access';
|
|
421
|
+
}
|
|
422
|
+
function base64url(buf) {
|
|
423
|
+
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
424
|
+
}
|
|
425
|
+
function generatePkce() {
|
|
426
|
+
const codeVerifier = base64url(crypto_1.default.randomBytes(32));
|
|
427
|
+
const codeChallenge = base64url(crypto_1.default.createHash('sha256').update(codeVerifier).digest());
|
|
428
|
+
return { codeVerifier, codeChallenge };
|
|
429
|
+
}
|
|
430
|
+
function generateState() {
|
|
431
|
+
return base64url(crypto_1.default.randomBytes(16));
|
|
432
|
+
}
|
|
433
|
+
function buildAuthorizeUrl(opts) {
|
|
434
|
+
const url = new URL(opts.metadata.authorizationEndpoint);
|
|
435
|
+
url.searchParams.set('response_type', 'code');
|
|
436
|
+
url.searchParams.set('client_id', opts.clientId);
|
|
437
|
+
url.searchParams.set('redirect_uri', opts.redirectUri);
|
|
438
|
+
url.searchParams.set('scope', opts.scope);
|
|
439
|
+
url.searchParams.set('code_challenge', opts.codeChallenge);
|
|
440
|
+
url.searchParams.set('code_challenge_method', 'S256');
|
|
441
|
+
url.searchParams.set('state', opts.state);
|
|
442
|
+
// Required — see this file's module doc comment. Never omit.
|
|
443
|
+
url.searchParams.set('resource', opts.metadata.resource);
|
|
444
|
+
return url.toString();
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Keys whose values are credentials, never diagnostics. A token endpoint's response
|
|
448
|
+
* body is reported verbatim in `tokenRequest`'s error message, and that message
|
|
449
|
+
* travels further than a stack trace: oauth-refresh-sweep.ts logs it on every failed
|
|
450
|
+
* refresh, and oauth-connectors-router.ts renders it into the callback page and the
|
|
451
|
+
* 502 body an admin sees in their browser.
|
|
452
|
+
*
|
|
453
|
+
* The reachable case is not an RFC 6749 §5.2 error body — those carry no tokens. It
|
|
454
|
+
* is the OTHER half of the throw condition: a 200 whose `access_token` is missing or
|
|
455
|
+
* not a string. A provider that returns `{"access_token": null, "refresh_token": "…"}`,
|
|
456
|
+
* or nests the token one level down, or 200s with a partial body on a bad day, hands
|
|
457
|
+
* us a live refresh_token and we print it. Redacting is cheap; a refresh_token in a
|
|
458
|
+
* log file is not revocable by us.
|
|
459
|
+
*/
|
|
460
|
+
const SECRET_RESPONSE_KEYS = new Set(['access_token', 'refresh_token', 'id_token']);
|
|
461
|
+
/** The token endpoint's response, safe to put in an error message. Shape and error
|
|
462
|
+
* fields are preserved — those are the diagnostic — and only the values that are
|
|
463
|
+
* credentials are replaced. */
|
|
464
|
+
function redactTokenResponse(parsed) {
|
|
465
|
+
const safe = {};
|
|
466
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
467
|
+
safe[k] = SECRET_RESPONSE_KEYS.has(k) && v != null ? '[redacted]' : v;
|
|
468
|
+
}
|
|
469
|
+
return JSON.stringify(safe);
|
|
470
|
+
}
|
|
471
|
+
async function tokenRequest(tokenEndpoint, body) {
|
|
472
|
+
const resp = await fetchNoRedirect(tokenEndpoint, {
|
|
473
|
+
method: 'POST',
|
|
474
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
475
|
+
body,
|
|
476
|
+
});
|
|
477
|
+
// Alone among the requests in this file, a redirect here is refused rather than
|
|
478
|
+
// re-validated and followed. This body carries the authorization code plus its PKCE
|
|
479
|
+
// verifier, or the refresh_token: a 307 re-POSTs that verbatim to wherever the
|
|
480
|
+
// authorization server names, which is how a token endpoint that passed
|
|
481
|
+
// `assertFetchableUrl` at discovery time hands the gateway's live credential to a
|
|
482
|
+
// host of its choosing. Validating the target would not help — there is no
|
|
483
|
+
// destination for which forwarding this body is the right answer.
|
|
484
|
+
//
|
|
485
|
+
// Classified as transient (status < 400, so `isPermanent` is false): an AS that has
|
|
486
|
+
// started answering 302 here is misconfigured or mid-migration, and the sweep must
|
|
487
|
+
// back off rather than delete a grant that is probably still good.
|
|
488
|
+
if (resp.status >= 300 && resp.status < 400) {
|
|
489
|
+
throw new OAuthTokenError(`Token request to ${tokenEndpoint} was answered with a ${resp.status} redirect` +
|
|
490
|
+
` — the gateway does not forward credentials across redirects.`, resp.status);
|
|
491
|
+
}
|
|
492
|
+
const parsed = (await resp.json().catch(() => ({})));
|
|
493
|
+
if (!resp.ok || typeof parsed.access_token !== 'string') {
|
|
494
|
+
throw new OAuthTokenError(`Token request failed (${resp.status}): ${redactTokenResponse(parsed)}`, resp.status, typeof parsed.error === 'string' ? parsed.error : undefined);
|
|
495
|
+
}
|
|
496
|
+
return {
|
|
497
|
+
access_token: parsed.access_token,
|
|
498
|
+
token_type: typeof parsed.token_type === 'string' ? parsed.token_type : 'bearer',
|
|
499
|
+
expires_in: typeof parsed.expires_in === 'number' ? parsed.expires_in : undefined,
|
|
500
|
+
refresh_token: typeof parsed.refresh_token === 'string' ? parsed.refresh_token : undefined,
|
|
501
|
+
scope: typeof parsed.scope === 'string' ? parsed.scope : undefined,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function exchangeCode(opts) {
|
|
505
|
+
return tokenRequest(opts.metadata.tokenEndpoint, new URLSearchParams({
|
|
506
|
+
grant_type: 'authorization_code',
|
|
507
|
+
code: opts.code,
|
|
508
|
+
redirect_uri: opts.redirectUri,
|
|
509
|
+
client_id: opts.clientId,
|
|
510
|
+
code_verifier: opts.codeVerifier,
|
|
511
|
+
resource: opts.metadata.resource, // required — see module doc comment
|
|
512
|
+
}));
|
|
513
|
+
}
|
|
514
|
+
function refreshAccessToken(opts) {
|
|
515
|
+
return tokenRequest(opts.metadata.tokenEndpoint, new URLSearchParams({
|
|
516
|
+
grant_type: 'refresh_token',
|
|
517
|
+
refresh_token: opts.refreshToken,
|
|
518
|
+
client_id: opts.clientId,
|
|
519
|
+
resource: opts.metadata.resource, // required — see module doc comment
|
|
520
|
+
}));
|
|
521
|
+
}
|
|
522
|
+
//# sourceMappingURL=mcp-oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-oauth.js","sourceRoot":"","sources":["../../src/connectors/mcp-oauth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Periodic refresh for connectors whose credential this gateway owns
|
|
3
|
+
* (CustomConnectorEntry.credentialOwner === 'gateway') — the gateway-side
|
|
4
|
+
* counterpart to whatever refresh loop an external control plane runs for the
|
|
5
|
+
* 'external' entries it pushes in. Runs on the same 60s
|
|
6
|
+
* interval as cliPairingStore.prune() (see gateway-router.ts) via
|
|
7
|
+
* refreshExpiringOAuthConnectors().
|
|
8
|
+
*
|
|
9
|
+
* Storage note: the refresh_token, its client_id, the access_token expiry and this
|
|
10
|
+
* sweep's failure bookkeeping all live in the same mcp-token.env as connector secrets,
|
|
11
|
+
* but under `internalSecretKey()`'s `CUSTOMINT__` prefix, which `customSecretKey()`
|
|
12
|
+
* cannot produce. `PLACEHOLDER_RE` accepts a leading underscore, so it is the prefix —
|
|
13
|
+
* not the `__` naming convention — that keeps a pasted `{__refresh_token}` from
|
|
14
|
+
* resolving to this sweep's slot. See custom.ts's `internalSecretKey`.
|
|
15
|
+
*/
|
|
16
|
+
import type { AgentRunner } from '../agent/runner';
|
|
17
|
+
import type { CustomConnectorsStore } from './custom-connectors-store';
|
|
18
|
+
/** Backoff for the nth consecutive transient failure, capped. */
|
|
19
|
+
export declare function transientBackoffMs(consecutiveFailures: number): number;
|
|
20
|
+
export declare function refreshTokenSecretKey(id: string): string;
|
|
21
|
+
export declare function clientIdSecretKey(id: string): string;
|
|
22
|
+
export declare function expiresAtSecretKey(id: string): string;
|
|
23
|
+
export declare function refreshFailCountSecretKey(id: string): string;
|
|
24
|
+
export declare function refreshBackoffUntilSecretKey(id: string): string;
|
|
25
|
+
/** Consecutive *transient* failures — kept apart from `__refresh_fail_count` so an
|
|
26
|
+
* outage can never accumulate toward deleting a live grant. Drives the exponential
|
|
27
|
+
* backoff and status's `refresh` block; cleared on the first success. */
|
|
28
|
+
export declare function refreshTransientCountSecretKey(id: string): string;
|
|
29
|
+
/** Bumped on every access_token write — by the OAuth callback on a fresh sign-in
|
|
30
|
+
* AND by this sweep on a successful refresh. Read back before this sweep commits,
|
|
31
|
+
* so a manual reconnect landing mid-refresh wins instead of being clobbered by a
|
|
32
|
+
* slower, now-stale refresh. */
|
|
33
|
+
export declare function tokenGenerationSecretKey(id: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* DCR registration cache — deliberately NOT `clientIdSecretKey`.
|
|
36
|
+
*
|
|
37
|
+
* `oauth/start` registers a client before the user has done anything, and the flow can
|
|
38
|
+
* be abandoned. Writing that client_id into the key this sweep reads left the connector
|
|
39
|
+
* pairing refresh_token R1 (issued to the OLD client C1) with client_id C2; the next
|
|
40
|
+
* refresh POSTs that mismatched pair, the AS answers `invalid_client` — permanent, and
|
|
41
|
+
* correctly so for that pair — and three ticks later a working sign-in is deleted
|
|
42
|
+
* because an admin opened a Connect dialog and closed it again.
|
|
43
|
+
*
|
|
44
|
+
* `clientIdSecretKey` is written only by the callback, i.e. only once a token has
|
|
45
|
+
* actually been issued to that client. This key is just a cache so retried Connect
|
|
46
|
+
* clicks don't orphan a fresh registration at the provider every attempt. It lives
|
|
47
|
+
* beside the keys the sweep owns because the give-up path and DELETE /v1/connectors/:id
|
|
48
|
+
* both have to invalidate it (see `clearsDcrRegistration`).
|
|
49
|
+
*/
|
|
50
|
+
export declare function dcrClientIdSecretKey(id: string): string;
|
|
51
|
+
/** The redirect_uri `dcrClientIdSecretKey`'s cached client was registered with. Reuse
|
|
52
|
+
* that client_id only while this matches the current callback URL — otherwise the
|
|
53
|
+
* provider would reject it, so re-registering is correct, not a skipped optimization. */
|
|
54
|
+
export declare function clientRedirectUriSecretKey(id: string): string;
|
|
55
|
+
/** Every key this sweep and the OAuth routers store for one connector, for the paths
|
|
56
|
+
* that clear a connector out entirely. Enumerated once so a new key cannot be added to
|
|
57
|
+
* the writers and forgotten by the deleters — the bug that left dead DCR registrations
|
|
58
|
+
* behind forever. `includeDcrRegistration: false` keeps the cached DCR client (see
|
|
59
|
+
* `clearsDcrRegistration`). */
|
|
60
|
+
export declare function internalSecretKeysOf(id: string, opts?: {
|
|
61
|
+
includeDcrRegistration?: boolean;
|
|
62
|
+
}): string[];
|
|
63
|
+
/**
|
|
64
|
+
* Scan every gateway-owned connector; refresh any whose access_token is within
|
|
65
|
+
* REFRESH_SKEW_MS of its recorded expiry. Best-effort per connector — a failure is
|
|
66
|
+
* logged and skipped, never thrown, so one broken connector can't stop the rest.
|
|
67
|
+
* `agents` lets a successful refresh restart sessions already using the connector (a
|
|
68
|
+
* live MCP subprocess has the OLD token baked in and can't be hot-patched).
|
|
69
|
+
*/
|
|
70
|
+
export declare function refreshExpiringOAuthConnectors(store: CustomConnectorsStore, agents?: Map<string, AgentRunner>): Promise<void>;
|
|
71
|
+
//# sourceMappingURL=oauth-refresh-sweep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-refresh-sweep.d.ts","sourceRoot":"","sources":["../../src/connectors/oauth-refresh-sweep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAmCvE,iEAAiE;AACjE,wBAAgB,kBAAkB,CAAC,mBAAmB,EAAE,MAAM,GAAG,MAAM,CAMtE;AASD,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAExD;AACD,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEpD;AACD,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAErD;AACD,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE5D;AACD,wBAAgB,4BAA4B,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE/D;AACD;;0EAE0E;AAC1E,wBAAgB,8BAA8B,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEjE;AACD;;;iCAGiC;AACjC,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;0FAE0F;AAC1F,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;;gCAIgC;AAChC,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,MAAM,EACV,IAAI,GAAE;IAAE,sBAAsB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9C,MAAM,EAAE,CAeV;AA+BD;;;;;;GAMG;AACH,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,qBAAqB,EAC5B,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC,CAQf"}
|