@nyuchi/mzizi-mcp 0.14.0 → 0.16.0
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 +111 -39
- package/dist/auth/cors.d.ts +33 -0
- package/dist/auth/cors.d.ts.map +1 -0
- package/dist/auth/cors.js +110 -0
- package/dist/auth/cors.js.map +1 -0
- package/dist/auth/front.d.ts +8 -0
- package/dist/auth/front.d.ts.map +1 -1
- package/dist/auth/front.js +10 -0
- package/dist/auth/front.js.map +1 -1
- package/dist/auth/mcp-api-handler.d.ts +1 -1
- package/dist/auth/mcp-api-handler.d.ts.map +1 -1
- package/dist/auth/mcp-api-handler.js +4 -3
- package/dist/auth/mcp-api-handler.js.map +1 -1
- package/dist/auth/provider-options.d.ts +16 -0
- package/dist/auth/provider-options.d.ts.map +1 -0
- package/dist/auth/provider-options.js +21 -0
- package/dist/auth/provider-options.js.map +1 -0
- package/dist/env.d.ts +1 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/floors.d.ts +117 -0
- package/dist/floors.d.ts.map +1 -0
- package/dist/floors.js +103 -0
- package/dist/floors.js.map +1 -0
- package/dist/generated/registry-pin.d.ts +8 -1
- package/dist/generated/registry-pin.d.ts.map +1 -1
- package/dist/generated/registry-pin.js +3 -1
- package/dist/generated/registry-pin.js.map +1 -1
- package/dist/generated/registry.d.ts +2 -2
- package/dist/generated/registry.js +198 -198
- package/dist/generated/registry.js.map +1 -1
- package/dist/generated/skills.d.ts +1 -1
- package/dist/generated/skills.js +13 -13
- package/dist/generated/skills.js.map +1 -1
- package/dist/http.d.ts +14 -4
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +38 -54
- package/dist/http.js.map +1 -1
- package/dist/origin.d.ts +68 -0
- package/dist/origin.d.ts.map +1 -0
- package/dist/origin.js +141 -0
- package/dist/origin.js.map +1 -0
- package/dist/rate-limit.d.ts +1 -1
- package/dist/rate-limit.d.ts.map +1 -1
- package/dist/rate-limit.js +3 -2
- package/dist/rate-limit.js.map +1 -1
- package/dist/server.d.ts +47 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +14 -8
- package/dist/server.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +115 -48
- package/dist/tools.js.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +25 -11
- package/dist/worker.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/generated/skills.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,6DAA6D;AAC7D,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAC,QAAQ,EAAC,UAAU,EAAC,OAAO,CAAU,CAAA;AAuB5E,MAAM,CAAC,MAAM,cAAc,GAA4B;IACrD;QACE,MAAM,EAAE,gBAAgB;QACxB,UAAU,EAAE,UAAU;QACtB,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/generated/skills.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
CHANGED
|
@@ -11,6 +11,15 @@
|
|
|
11
11
|
import type { FundiToolContext } from "./fundi-client.js";
|
|
12
12
|
import type { IssueDeskContext } from "./tools.js";
|
|
13
13
|
import type { DocsFederationOptions } from "./docs-federation.js";
|
|
14
|
+
export { originRejection } from "./origin.js";
|
|
15
|
+
/**
|
|
16
|
+
* Add CORS headers: the caller's `Origin` echoed back when it was allowed (see
|
|
17
|
+
* `origin.ts`), never `*`. `Vary` is appended to, not replaced. Exported for
|
|
18
|
+
* tests only.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export declare function withCors(response: Response, origin: string | null, allowed: boolean): Response;
|
|
14
23
|
export interface HttpHandlerOptions {
|
|
15
24
|
/** When set, expose the fundi trigger tool bound to the signed-in user. */
|
|
16
25
|
fundi?: FundiToolContext;
|
|
@@ -26,12 +35,13 @@ export interface HttpHandlerOptions {
|
|
|
26
35
|
/** Docs federation settings, or `false` for none. Defaults to docs.mzizi.dev. */
|
|
27
36
|
docs?: DocsFederationOptions | false;
|
|
28
37
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
38
|
+
* Browser origins allowed to call the endpoint, by hostname, with the same
|
|
39
|
+
* scheme and port rules as the default list (see `origin.ts`). Omitted, the
|
|
40
|
+
* first-party sites and localhost. The Worker passes the default plus
|
|
41
|
+
* `MZIZI_ALLOWED_ORIGINS`.
|
|
31
42
|
*/
|
|
32
|
-
allowedOrigins?: string[];
|
|
43
|
+
allowedOrigins?: readonly string[];
|
|
33
44
|
}
|
|
34
|
-
export declare function originRejection(request: Request, allowedOrigins?: readonly string[]): Response | undefined;
|
|
35
45
|
/**
|
|
36
46
|
* Build a fetch handler that serves the Mzizi MCP over Streamable HTTP, on the
|
|
37
47
|
* official SDK (v2, `@modelcontextprotocol/server`).
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AASH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAGjE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE7C;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,OAAO,GAAG,QAAQ,CAa9F;AAqBD,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,KAAK,CAAC,EAAE,gBAAgB,CAAA;IACxB,+EAA+E;IAC/E,IAAI,CAAC,EAAE,gBAAgB,CAAA;IACvB;;;OAGG;IACH,SAAS,CAAC,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAA;IAC9B,iFAAiF;IACjF,IAAI,CAAC,EAAE,qBAAqB,GAAG,KAAK,CAAA;IACpC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACnC;AAiDD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,kBAAuB,IAcxC,SAAS,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAsClE"}
|
package/dist/http.js
CHANGED
|
@@ -8,17 +8,28 @@
|
|
|
8
8
|
* anonymous (the free tier) or signed in (WorkOS AuthKit via the OAuth provider)
|
|
9
9
|
* before it reaches this handler.
|
|
10
10
|
*/
|
|
11
|
-
import { createMcpHandler, isLegacyRequest,
|
|
11
|
+
import { createMcpHandler, isLegacyRequest, WebStandardStreamableHTTPServerTransport, } from "@modelcontextprotocol/server";
|
|
12
|
+
import { corsHeadersFor, forbiddenOrigin, isAllowedOrigin } from "./origin.js";
|
|
12
13
|
import { createMziziMcpServer } from "./server.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
// Part of the public `./http` export since before 0.14.0; kept (same signature).
|
|
15
|
+
export { originRejection } from "./origin.js";
|
|
16
|
+
/**
|
|
17
|
+
* Add CORS headers: the caller's `Origin` echoed back when it was allowed (see
|
|
18
|
+
* `origin.ts`), never `*`. `Vary` is appended to, not replaced. Exported for
|
|
19
|
+
* tests only.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export function withCors(response, origin, allowed) {
|
|
19
24
|
const headers = new Headers(response.headers);
|
|
20
|
-
for (const [key, value] of Object.entries(
|
|
21
|
-
|
|
25
|
+
for (const [key, value] of Object.entries(corsHeadersFor(origin, allowed))) {
|
|
26
|
+
if (key === "Vary") {
|
|
27
|
+
if (!/(^|,)\s*origin\s*(,|$)/i.test(headers.get("Vary") ?? ""))
|
|
28
|
+
headers.append(key, value);
|
|
29
|
+
}
|
|
30
|
+
else
|
|
31
|
+
headers.set(key, value);
|
|
32
|
+
}
|
|
22
33
|
return new Response(response.body, {
|
|
23
34
|
status: response.status,
|
|
24
35
|
statusText: response.statusText,
|
|
@@ -40,39 +51,6 @@ const OAUTH_DISCOVERY_PREFIXES = [
|
|
|
40
51
|
function isOAuthDiscoveryPath(pathname) {
|
|
41
52
|
return OAUTH_DISCOVERY_PREFIXES.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`));
|
|
42
53
|
}
|
|
43
|
-
/**
|
|
44
|
-
* Origin validation (Streamable HTTP §Security: servers MUST validate `Origin`
|
|
45
|
-
* and answer 403 to an invalid one).
|
|
46
|
-
*
|
|
47
|
-
* This is a PUBLIC server: it holds no cookies, answers anonymous callers by
|
|
48
|
-
* design, and browser-based MCP clients on other sites (hosted inspectors, web
|
|
49
|
-
* chat clients) used it before this check existed. So the default policy is
|
|
50
|
-
* not an allowlist of sites. It refuses what DNS rebinding and mixed-content
|
|
51
|
-
* abuse need: an `Origin` that does not parse (the opaque `null` included), and
|
|
52
|
-
* a plain-`http` origin that is not localhost. Every `https` origin and
|
|
53
|
-
* localhost pass, and so does a request with no `Origin` (every non-browser
|
|
54
|
-
* client). Pass `allowedOrigins` to restrict to named hostnames instead.
|
|
55
|
-
*/
|
|
56
|
-
const LOCALHOST_ORIGINS = localhostAllowedOrigins();
|
|
57
|
-
export function originRejection(request, allowedOrigins) {
|
|
58
|
-
if (allowedOrigins)
|
|
59
|
-
return originValidationResponse(request, [...allowedOrigins]);
|
|
60
|
-
const origin = request.headers.get("origin");
|
|
61
|
-
if (!origin)
|
|
62
|
-
return undefined;
|
|
63
|
-
try {
|
|
64
|
-
const url = new URL(origin);
|
|
65
|
-
if (url.protocol === "https:")
|
|
66
|
-
return undefined;
|
|
67
|
-
if (url.protocol === "http:" && LOCALHOST_ORIGINS.includes(url.hostname))
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
// An unparseable origin (including the opaque "null") is refused below.
|
|
72
|
-
}
|
|
73
|
-
// Same shape as the SDK's own originValidationResponse.
|
|
74
|
-
return originValidationResponse(request, [...LOCALHOST_ORIGINS]);
|
|
75
|
-
}
|
|
76
54
|
/**
|
|
77
55
|
* Build a fetch handler that serves the Mzizi MCP over Streamable HTTP.
|
|
78
56
|
* Stateless — each request creates a fresh transport + server pair.
|
|
@@ -141,32 +119,38 @@ export function createMziziHttpHandler(options = {}) {
|
|
|
141
119
|
return transport.handleRequest(request);
|
|
142
120
|
}
|
|
143
121
|
return async function handle(request) {
|
|
122
|
+
const origin = request.headers.get("origin");
|
|
123
|
+
const allowed = isAllowedOrigin(origin, options.allowedOrigins);
|
|
124
|
+
const cors = (response) => withCors(response, origin, allowed);
|
|
125
|
+
// DNS-rebinding / cross-site defence (Streamable HTTP §Security): a
|
|
126
|
+
// request whose Origin is present and not on the allowlist is refused with
|
|
127
|
+
// 403 before anything else, preflight included, and before any server is
|
|
128
|
+
// built. No Origin header (every non-browser client) passes.
|
|
129
|
+
if (!allowed)
|
|
130
|
+
return cors(forbiddenOrigin(origin));
|
|
131
|
+
// (On the Worker the OAuth provider answers OPTIONS on /mcp itself, so this
|
|
132
|
+
// branch serves the embedded `./http` export; see the CHANGELOG, #295.)
|
|
144
133
|
if (request.method === "OPTIONS") {
|
|
145
|
-
return new Response(null, { status: 204
|
|
134
|
+
return cors(new Response(null, { status: 204 }));
|
|
146
135
|
}
|
|
147
136
|
// Answer auth-discovery probes with 404 so compliant MCP clients fall back
|
|
148
137
|
// to anonymous access. Without this the request reaches the MCP handler,
|
|
149
138
|
// which cannot answer a discovery GET with metadata, and clients abort the
|
|
150
139
|
// connection instead of connecting unauthenticated.
|
|
151
140
|
if (isOAuthDiscoveryPath(new URL(request.url).pathname)) {
|
|
152
|
-
return
|
|
141
|
+
return cors(new Response("Not Found", { status: 404 }));
|
|
153
142
|
}
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
// 403 before any server is built. No Origin header (every non-browser
|
|
157
|
-
// client) passes.
|
|
158
|
-
const rejected = originRejection(request, options.allowedOrigins);
|
|
159
|
-
if (rejected)
|
|
160
|
-
return withCors(rejected);
|
|
143
|
+
// Stateless: there is no standalone SSE stream to open and no session to
|
|
144
|
+
// end, so GET and DELETE are 405 rather than held-open connections.
|
|
161
145
|
if (request.method !== "POST") {
|
|
162
|
-
return
|
|
146
|
+
return cors(Response.json({ jsonrpc: "2.0", id: null, error: { code: -32000, message: "Method not allowed. This server is stateless: send every request as a POST." } }, { status: 405, headers: { Allow: "POST, OPTIONS" } }));
|
|
163
147
|
}
|
|
164
148
|
if (await isLegacyRequest(request, undefined, { maxRequestBodySize: MAX_REQUEST_BODY })) {
|
|
165
|
-
return
|
|
149
|
+
return cors(await serveLegacy(request));
|
|
166
150
|
}
|
|
167
151
|
// The per-request options ride to the shared handler's factory on authInfo,
|
|
168
152
|
// which the SDK passes through untouched and never reads itself.
|
|
169
|
-
return
|
|
153
|
+
return cors(await modernHandler().fetch(request, { authInfo: carry(options) }));
|
|
170
154
|
};
|
|
171
155
|
}
|
|
172
156
|
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/origin.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser `Origin` policy for the MCP endpoint (Streamable HTTP §Security, and
|
|
3
|
+
* the fix list in #295, P0 item 1).
|
|
4
|
+
*
|
|
5
|
+
* Servers MUST validate `Origin` against DNS rebinding and answer 403 to an
|
|
6
|
+
* invalid one. The policy:
|
|
7
|
+
*
|
|
8
|
+
* - no `Origin` header allowed: every non-browser client (the SDKs, curl,
|
|
9
|
+
* Claude, Cursor) sends none;
|
|
10
|
+
* - a present `Origin` allowed only if its host is on the list, otherwise
|
|
11
|
+
* 403 before any MCP server is built;
|
|
12
|
+
* - CORS `Access-Control-Allow-Origin` echoes an allowed
|
|
13
|
+
* `Origin` (with `Vary: Origin`). It is never `*`
|
|
14
|
+
* here; the public discovery documents are `*` (`auth/cors.ts`).
|
|
15
|
+
*
|
|
16
|
+
* The list is hostnames. A localhost name matches over `http` or `https` on any
|
|
17
|
+
* port (the MCP Inspector runs at http://localhost:6274). Every other name
|
|
18
|
+
* matches only over `https` on the default port, exactly: no subdomains, no
|
|
19
|
+
* `http`, no `:8443`. The same rule applies to a list the caller supplies.
|
|
20
|
+
*/
|
|
21
|
+
/** The first-party sites: this Worker, the site, the docs and the console. */
|
|
22
|
+
export declare const ALLOWED_ORIGIN_HOSTS: readonly string[];
|
|
23
|
+
/** The default list: the first-party sites and localhost. */
|
|
24
|
+
export declare const DEFAULT_ALLOWED_HOSTS: readonly string[];
|
|
25
|
+
/**
|
|
26
|
+
* The default list plus the extra origins in `MZIZI_ALLOWED_ORIGINS`
|
|
27
|
+
* (comma-separated `https://host` origins; case and a trailing slash are
|
|
28
|
+
* tolerated). An entry that is not an `https` origin with no path, port or
|
|
29
|
+
* credentials is dropped with a `console.warn`, so a typo cannot open the
|
|
30
|
+
* endpoint wider than named. The result is cached per value, so the Worker pays
|
|
31
|
+
* for the parse once per isolate, not once per request.
|
|
32
|
+
*/
|
|
33
|
+
export declare function allowedHostsFromEnv(env: {
|
|
34
|
+
MZIZI_ALLOWED_ORIGINS?: string;
|
|
35
|
+
}): readonly string[];
|
|
36
|
+
/**
|
|
37
|
+
* True when `origin` may call the endpoint. `null` (no header at all) is
|
|
38
|
+
* allowed; a header that is present is judged, so an empty value and the opaque
|
|
39
|
+
* `"null"` are refused like any other unparseable origin.
|
|
40
|
+
*/
|
|
41
|
+
export declare function isAllowedOrigin(origin: string | null, hosts?: readonly string[]): boolean;
|
|
42
|
+
/** The 403 for a present-but-unlisted `Origin` (a JSON-RPC error body, like the SDK's own). */
|
|
43
|
+
export declare function forbiddenOrigin(origin: string | null): Response;
|
|
44
|
+
/** A 403 for a present-but-unlisted `Origin`, or undefined when the request may proceed. */
|
|
45
|
+
export declare function originRejection(request: Request, hosts?: readonly string[]): Response | undefined;
|
|
46
|
+
/** What a route's CORS answer advertises beyond the origin decision. */
|
|
47
|
+
export interface CorsPolicy {
|
|
48
|
+
readonly methods: string;
|
|
49
|
+
readonly headers: string;
|
|
50
|
+
/** Seconds a browser may cache a preflight; omitted when not set. */
|
|
51
|
+
readonly maxAge?: number;
|
|
52
|
+
}
|
|
53
|
+
/** The MCP endpoint (`/mcp`): its methods and the headers a Streamable HTTP client sends. */
|
|
54
|
+
export declare const MCP_CORS: CorsPolicy;
|
|
55
|
+
/** `/token` and `/register`: POST only, a form or JSON body, and client credentials in `Authorization`. */
|
|
56
|
+
export declare const OAUTH_ENDPOINT_CORS: CorsPolicy;
|
|
57
|
+
/**
|
|
58
|
+
* The CORS headers for a request whose `Origin` is `origin`, already judged
|
|
59
|
+
* `allowed`: the origin echoed back, never `*`. No `Origin`, or one that is not
|
|
60
|
+
* allowed, gets no `Access-Control-Allow-Origin`; `Vary: Origin` is always
|
|
61
|
+
* present so a cache cannot hand one origin's answer to another. Merge `Vary`
|
|
62
|
+
* with `Headers.append`, not `set`. No `Access-Control-Allow-Credentials`: no
|
|
63
|
+
* route reads a cookie cross-origin.
|
|
64
|
+
*/
|
|
65
|
+
export declare function corsHeadersFor(origin: string | null, allowed: boolean, policy?: CorsPolicy): Record<string, string>;
|
|
66
|
+
/** `corsHeadersFor`, judging the request's own `Origin` against `hosts`. */
|
|
67
|
+
export declare function corsHeaders(request: Request, hosts?: readonly string[]): Record<string, string>;
|
|
68
|
+
//# sourceMappingURL=origin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origin.d.ts","sourceRoot":"","sources":["../src/origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAOH,8EAA8E;AAC9E,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAMjD,CAAA;AAED,6DAA6D;AAC7D,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAA8C,CAAA;AAIjG;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE;IAAE,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,MAAM,EAAE,CAsB9F;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,GAAE,SAAS,MAAM,EAA0B,GAAG,OAAO,CAahH;AAED,+FAA+F;AAC/F,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,QAAQ,CAK/D;AAED,4FAA4F;AAC5F,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,GAAG,SAAS,CAGjG;AAED,wEAAwE;AACxE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,qEAAqE;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,6FAA6F;AAC7F,eAAO,MAAM,QAAQ,EAAE,UAGtB,CAAA;AAED,2GAA2G;AAC3G,eAAO,MAAM,mBAAmB,EAAE,UAIjC,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAE,UAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS7H;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAG/F"}
|
package/dist/origin.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser `Origin` policy for the MCP endpoint (Streamable HTTP §Security, and
|
|
3
|
+
* the fix list in #295, P0 item 1).
|
|
4
|
+
*
|
|
5
|
+
* Servers MUST validate `Origin` against DNS rebinding and answer 403 to an
|
|
6
|
+
* invalid one. The policy:
|
|
7
|
+
*
|
|
8
|
+
* - no `Origin` header allowed: every non-browser client (the SDKs, curl,
|
|
9
|
+
* Claude, Cursor) sends none;
|
|
10
|
+
* - a present `Origin` allowed only if its host is on the list, otherwise
|
|
11
|
+
* 403 before any MCP server is built;
|
|
12
|
+
* - CORS `Access-Control-Allow-Origin` echoes an allowed
|
|
13
|
+
* `Origin` (with `Vary: Origin`). It is never `*`
|
|
14
|
+
* here; the public discovery documents are `*` (`auth/cors.ts`).
|
|
15
|
+
*
|
|
16
|
+
* The list is hostnames. A localhost name matches over `http` or `https` on any
|
|
17
|
+
* port (the MCP Inspector runs at http://localhost:6274). Every other name
|
|
18
|
+
* matches only over `https` on the default port, exactly: no subdomains, no
|
|
19
|
+
* `http`, no `:8443`. The same rule applies to a list the caller supplies.
|
|
20
|
+
*/
|
|
21
|
+
import { localhostAllowedOrigins } from "@modelcontextprotocol/server";
|
|
22
|
+
/** Hostnames that count as local development. */
|
|
23
|
+
const LOCAL_HOSTS = localhostAllowedOrigins();
|
|
24
|
+
/** The first-party sites: this Worker, the site, the docs and the console. */
|
|
25
|
+
export const ALLOWED_ORIGIN_HOSTS = [
|
|
26
|
+
"mzizi.dev",
|
|
27
|
+
"www.mzizi.dev",
|
|
28
|
+
"docs.mzizi.dev",
|
|
29
|
+
"app.mzizi.dev",
|
|
30
|
+
"mcp.mzizi.dev",
|
|
31
|
+
];
|
|
32
|
+
/** The default list: the first-party sites and localhost. */
|
|
33
|
+
export const DEFAULT_ALLOWED_HOSTS = [...ALLOWED_ORIGIN_HOSTS, ...LOCAL_HOSTS];
|
|
34
|
+
const hostsCache = new Map();
|
|
35
|
+
/**
|
|
36
|
+
* The default list plus the extra origins in `MZIZI_ALLOWED_ORIGINS`
|
|
37
|
+
* (comma-separated `https://host` origins; case and a trailing slash are
|
|
38
|
+
* tolerated). An entry that is not an `https` origin with no path, port or
|
|
39
|
+
* credentials is dropped with a `console.warn`, so a typo cannot open the
|
|
40
|
+
* endpoint wider than named. The result is cached per value, so the Worker pays
|
|
41
|
+
* for the parse once per isolate, not once per request.
|
|
42
|
+
*/
|
|
43
|
+
export function allowedHostsFromEnv(env) {
|
|
44
|
+
const raw = env.MZIZI_ALLOWED_ORIGINS;
|
|
45
|
+
if (!raw)
|
|
46
|
+
return DEFAULT_ALLOWED_HOSTS;
|
|
47
|
+
const cached = hostsCache.get(raw);
|
|
48
|
+
if (cached)
|
|
49
|
+
return cached;
|
|
50
|
+
const extra = [];
|
|
51
|
+
for (const entry of raw.split(",")) {
|
|
52
|
+
const value = entry.trim().replace(/\/$/, "").toLowerCase();
|
|
53
|
+
if (!value)
|
|
54
|
+
continue;
|
|
55
|
+
let host;
|
|
56
|
+
try {
|
|
57
|
+
const url = new URL(value);
|
|
58
|
+
if (url.protocol === "https:" && url.origin === value && url.port === "")
|
|
59
|
+
host = url.hostname;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Not an origin: dropped below.
|
|
63
|
+
}
|
|
64
|
+
if (host)
|
|
65
|
+
extra.push(host);
|
|
66
|
+
else
|
|
67
|
+
console.warn(`mzizi-mcp: MZIZI_ALLOWED_ORIGINS entry ignored (not a plain https origin): ${entry.trim()}`);
|
|
68
|
+
}
|
|
69
|
+
const hosts = [...DEFAULT_ALLOWED_HOSTS, ...extra];
|
|
70
|
+
hostsCache.set(raw, hosts);
|
|
71
|
+
return hosts;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* True when `origin` may call the endpoint. `null` (no header at all) is
|
|
75
|
+
* allowed; a header that is present is judged, so an empty value and the opaque
|
|
76
|
+
* `"null"` are refused like any other unparseable origin.
|
|
77
|
+
*/
|
|
78
|
+
export function isAllowedOrigin(origin, hosts = DEFAULT_ALLOWED_HOSTS) {
|
|
79
|
+
if (origin === null)
|
|
80
|
+
return true;
|
|
81
|
+
let url;
|
|
82
|
+
try {
|
|
83
|
+
url = new URL(origin);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
// An Origin is scheme://host[:port] and nothing else.
|
|
89
|
+
if (url.origin !== origin)
|
|
90
|
+
return false;
|
|
91
|
+
if (!hosts.includes(url.hostname))
|
|
92
|
+
return false;
|
|
93
|
+
if (LOCAL_HOSTS.includes(url.hostname))
|
|
94
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
95
|
+
return url.protocol === "https:" && url.port === "";
|
|
96
|
+
}
|
|
97
|
+
/** The 403 for a present-but-unlisted `Origin` (a JSON-RPC error body, like the SDK's own). */
|
|
98
|
+
export function forbiddenOrigin(origin) {
|
|
99
|
+
return Response.json({ jsonrpc: "2.0", id: null, error: { code: -32000, message: `Invalid Origin header: ${origin ?? ""}` } }, { status: 403, headers: { Vary: "Origin" } });
|
|
100
|
+
}
|
|
101
|
+
/** A 403 for a present-but-unlisted `Origin`, or undefined when the request may proceed. */
|
|
102
|
+
export function originRejection(request, hosts) {
|
|
103
|
+
const origin = request.headers.get("origin");
|
|
104
|
+
return isAllowedOrigin(origin, hosts) ? undefined : forbiddenOrigin(origin);
|
|
105
|
+
}
|
|
106
|
+
/** The MCP endpoint (`/mcp`): its methods and the headers a Streamable HTTP client sends. */
|
|
107
|
+
export const MCP_CORS = {
|
|
108
|
+
methods: "GET, POST, DELETE, OPTIONS",
|
|
109
|
+
headers: "Content-Type, MCP-Protocol-Version, MCP-Session-Id, Mcp-Method, Mcp-Name, apikey",
|
|
110
|
+
};
|
|
111
|
+
/** `/token` and `/register`: POST only, a form or JSON body, and client credentials in `Authorization`. */
|
|
112
|
+
export const OAUTH_ENDPOINT_CORS = {
|
|
113
|
+
methods: "POST, OPTIONS",
|
|
114
|
+
headers: "Content-Type, Authorization",
|
|
115
|
+
maxAge: 600,
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* The CORS headers for a request whose `Origin` is `origin`, already judged
|
|
119
|
+
* `allowed`: the origin echoed back, never `*`. No `Origin`, or one that is not
|
|
120
|
+
* allowed, gets no `Access-Control-Allow-Origin`; `Vary: Origin` is always
|
|
121
|
+
* present so a cache cannot hand one origin's answer to another. Merge `Vary`
|
|
122
|
+
* with `Headers.append`, not `set`. No `Access-Control-Allow-Credentials`: no
|
|
123
|
+
* route reads a cookie cross-origin.
|
|
124
|
+
*/
|
|
125
|
+
export function corsHeadersFor(origin, allowed, policy = MCP_CORS) {
|
|
126
|
+
const headers = { Vary: "Origin" };
|
|
127
|
+
if (origin && allowed) {
|
|
128
|
+
headers["Access-Control-Allow-Origin"] = origin;
|
|
129
|
+
headers["Access-Control-Allow-Methods"] = policy.methods;
|
|
130
|
+
headers["Access-Control-Allow-Headers"] = policy.headers;
|
|
131
|
+
if (policy.maxAge !== undefined)
|
|
132
|
+
headers["Access-Control-Max-Age"] = String(policy.maxAge);
|
|
133
|
+
}
|
|
134
|
+
return headers;
|
|
135
|
+
}
|
|
136
|
+
/** `corsHeadersFor`, judging the request's own `Origin` against `hosts`. */
|
|
137
|
+
export function corsHeaders(request, hosts) {
|
|
138
|
+
const origin = request.headers.get("origin");
|
|
139
|
+
return corsHeadersFor(origin, isAllowedOrigin(origin, hosts));
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=origin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origin.js","sourceRoot":"","sources":["../src/origin.ts"],"names":[],"mappings":"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"}
|
package/dist/rate-limit.d.ts
CHANGED
|
@@ -37,5 +37,5 @@ export declare function clientKey(request: Request): string;
|
|
|
37
37
|
* The body is a JSON-RPC error so an MCP client can show it, and it names the
|
|
38
38
|
* way past the limit: signing in.
|
|
39
39
|
*/
|
|
40
|
-
export declare function limitAnonymous(limiter: RateLimiter | undefined, request: Request): Promise<Response | null>;
|
|
40
|
+
export declare function limitAnonymous(limiter: RateLimiter | undefined, request: Request, allowedHosts?: readonly string[]): Promise<Response | null>;
|
|
41
41
|
//# sourceMappingURL=rate-limit.d.ts.map
|
package/dist/rate-limit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,sEAAsE;AACtE,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,CAAC,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAChE,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAElD;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,WAAW,GAAG,SAAS,EAChC,OAAO,EAAE,OAAO,EAChB,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,GAC/B,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAuB1B"}
|
package/dist/rate-limit.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* free tier down with a missing binding, and the limit is a guard, not a gate.
|
|
17
17
|
*/
|
|
18
18
|
import { ANONYMOUS_RATE_LIMIT, SIGN_IN_MCP_URL } from "./access.js";
|
|
19
|
+
import { corsHeaders } from "./origin.js";
|
|
19
20
|
/**
|
|
20
21
|
* The key for one client: the connecting IP Cloudflare reports.
|
|
21
22
|
*
|
|
@@ -32,7 +33,7 @@ export function clientKey(request) {
|
|
|
32
33
|
* The body is a JSON-RPC error so an MCP client can show it, and it names the
|
|
33
34
|
* way past the limit: signing in.
|
|
34
35
|
*/
|
|
35
|
-
export async function limitAnonymous(limiter, request) {
|
|
36
|
+
export async function limitAnonymous(limiter, request, allowedHosts) {
|
|
36
37
|
if (!limiter)
|
|
37
38
|
return null;
|
|
38
39
|
const { success } = await limiter.limit({ key: clientKey(request) });
|
|
@@ -51,7 +52,7 @@ export async function limitAnonymous(limiter, request) {
|
|
|
51
52
|
headers: {
|
|
52
53
|
"Content-Type": "application/json",
|
|
53
54
|
"Retry-After": String(periodSeconds),
|
|
54
|
-
|
|
55
|
+
...corsHeaders(request, allowedHosts),
|
|
55
56
|
},
|
|
56
57
|
});
|
|
57
58
|
}
|
package/dist/rate-limit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO1C;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,OAAgB;IACxC,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,SAAS,CAAC;AAC9D,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAAgC,EAChC,OAAgB,EAChB,YAAgC;IAEhC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACrE,IAAI,OAAO;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,oBAAoB,CAAC;IACtD,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC;QACb,OAAO,EAAE,KAAK;QACd,EAAE,EAAE,IAAI;QACR,KAAK,EAAE;YACL,IAAI,EAAE,CAAC,KAAK;YACZ,OAAO,EAAE,iDAAiD,KAAK,QAAQ,aAAa,iEAAiE,eAAe,gBAAgB;SACrL;KACF,CAAC,EACF;QACE,MAAM,EAAE,GAAG;QACX,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,aAAa,EAAE,MAAM,CAAC,aAAa,CAAC;YACpC,GAAG,WAAW,CAAC,OAAO,EAAE,YAAY,CAAC;SACtC;KACF,CACF,CAAC;AACJ,CAAC"}
|
package/dist/server.d.ts
CHANGED
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
* whatever the docs server lists.
|
|
13
13
|
*/
|
|
14
14
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
15
|
-
import type
|
|
15
|
+
import { type Registry } from "./registry.js";
|
|
16
|
+
import { floorsOf } from "./floors.js";
|
|
16
17
|
import { type DocsFederationOptions } from "./docs-federation.js";
|
|
17
18
|
import type { FundiToolContext } from "./fundi-client.js";
|
|
18
19
|
import { type IssueDeskContext } from "./tools.js";
|
|
19
|
-
export declare const MZIZI_MCP_VERSION = "0.
|
|
20
|
+
export declare const MZIZI_MCP_VERSION = "0.16.0";
|
|
20
21
|
export type CreateServerOptions = {
|
|
21
22
|
fundi?: FundiToolContext;
|
|
22
23
|
/** The signed-in user's issue desk. See `tools.ts` — absent off the Worker. */
|
|
@@ -39,7 +40,7 @@ export type CreateServerOptions = {
|
|
|
39
40
|
* hand-maintained fourth copy is how the old `mcp_tool_registry` drifted from
|
|
40
41
|
* the tools it claimed to describe.
|
|
41
42
|
*/
|
|
42
|
-
export declare function describeTools(registered: string[]): {
|
|
43
|
+
export declare function describeTools(registered: string[], floors?: Awaited<ReturnType<typeof floorsOf>>): {
|
|
43
44
|
version: string;
|
|
44
45
|
source: {
|
|
45
46
|
registry: string;
|
|
@@ -57,6 +58,27 @@ export declare function describeTools(registered: string[]): {
|
|
|
57
58
|
};
|
|
58
59
|
rateLimit: string;
|
|
59
60
|
};
|
|
61
|
+
floors: {
|
|
62
|
+
spacing: null;
|
|
63
|
+
hitAreaPx: number | null;
|
|
64
|
+
note: string;
|
|
65
|
+
} | {
|
|
66
|
+
source: string;
|
|
67
|
+
meaning: {
|
|
68
|
+
readonly insetPx: "Padding between text and the edge of its container.";
|
|
69
|
+
readonly controlGapPx: "Gap between adjacent interactive controls, visible edge to visible edge. Hit regions may touch, never overlap.";
|
|
70
|
+
readonly labelGapPx: "Gap between a label and its field.";
|
|
71
|
+
readonly itemGapPx: "Gap between sibling items in a list, grid or toolbar.";
|
|
72
|
+
readonly leadingBody: "Body line height (WCAG 1.4.12). Headings keep their own leading.";
|
|
73
|
+
};
|
|
74
|
+
enforcement: "A base-layer rule in the registry's token layer gives every element the floor even when you add no padding or gap; the token test fails a component spec below it; each component contract asserts its spacing minimums (TypeScript and Rust agree); a rendered geometry guard checks neighbouring controls, text inset and WCAG 1.4.12 stress; `mzizi check` (@nyuchi/mzizi-cli 0.8.0 or later) fails a project's CSS or TSX that goes under it, and `mzizi_check_accessibility` checks values you pass it.";
|
|
75
|
+
nothingGoesUnder: "No brand pack, density layer, component or app may lower a floor. `data-mzizi-sizes=\"legacy\"` on the root is the one opt-out, and it steps out of the other Bundu floors too.";
|
|
76
|
+
whatToDo: "Use the components and the tokens (--inset-floor, --gap-floor, --leading-floor). Never write p-0, gap-0, a negative margin or leading-none on a text container or a row of controls without an exception comment: `mzizi-spacing-exempt: <reason>`.";
|
|
77
|
+
spacing: import("./floors.js").SpacingFloor;
|
|
78
|
+
customProperties: Record<keyof import("./floors.js").SpacingFloor, string>;
|
|
79
|
+
hitAreaPx: number | null;
|
|
80
|
+
note?: undefined;
|
|
81
|
+
} | null;
|
|
60
82
|
skills: {
|
|
61
83
|
version: string;
|
|
62
84
|
tool: string;
|
|
@@ -93,6 +115,7 @@ export declare function describeTools(registered: string[]): {
|
|
|
93
115
|
export declare function mziziToolCatalogue(opts?: {
|
|
94
116
|
fundi?: boolean;
|
|
95
117
|
docs?: DocsFederationOptions | false;
|
|
118
|
+
registry?: () => Promise<Registry>;
|
|
96
119
|
}): Promise<{
|
|
97
120
|
version: string;
|
|
98
121
|
source: {
|
|
@@ -111,6 +134,27 @@ export declare function mziziToolCatalogue(opts?: {
|
|
|
111
134
|
};
|
|
112
135
|
rateLimit: string;
|
|
113
136
|
};
|
|
137
|
+
floors: {
|
|
138
|
+
spacing: null;
|
|
139
|
+
hitAreaPx: number | null;
|
|
140
|
+
note: string;
|
|
141
|
+
} | {
|
|
142
|
+
source: string;
|
|
143
|
+
meaning: {
|
|
144
|
+
readonly insetPx: "Padding between text and the edge of its container.";
|
|
145
|
+
readonly controlGapPx: "Gap between adjacent interactive controls, visible edge to visible edge. Hit regions may touch, never overlap.";
|
|
146
|
+
readonly labelGapPx: "Gap between a label and its field.";
|
|
147
|
+
readonly itemGapPx: "Gap between sibling items in a list, grid or toolbar.";
|
|
148
|
+
readonly leadingBody: "Body line height (WCAG 1.4.12). Headings keep their own leading.";
|
|
149
|
+
};
|
|
150
|
+
enforcement: "A base-layer rule in the registry's token layer gives every element the floor even when you add no padding or gap; the token test fails a component spec below it; each component contract asserts its spacing minimums (TypeScript and Rust agree); a rendered geometry guard checks neighbouring controls, text inset and WCAG 1.4.12 stress; `mzizi check` (@nyuchi/mzizi-cli 0.8.0 or later) fails a project's CSS or TSX that goes under it, and `mzizi_check_accessibility` checks values you pass it.";
|
|
151
|
+
nothingGoesUnder: "No brand pack, density layer, component or app may lower a floor. `data-mzizi-sizes=\"legacy\"` on the root is the one opt-out, and it steps out of the other Bundu floors too.";
|
|
152
|
+
whatToDo: "Use the components and the tokens (--inset-floor, --gap-floor, --leading-floor). Never write p-0, gap-0, a negative margin or leading-none on a text container or a row of controls without an exception comment: `mzizi-spacing-exempt: <reason>`.";
|
|
153
|
+
spacing: import("./floors.js").SpacingFloor;
|
|
154
|
+
customProperties: Record<keyof import("./floors.js").SpacingFloor, string>;
|
|
155
|
+
hitAreaPx: number | null;
|
|
156
|
+
note?: undefined;
|
|
157
|
+
} | null;
|
|
114
158
|
skills: {
|
|
115
159
|
version: string;
|
|
116
160
|
tool: string;
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,SAAS,EAAoB,MAAM,8BAA8B,CAAC;AAG3E,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,SAAS,EAAoB,MAAM,8BAA8B,CAAC;AAG3E,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC9D,OAAO,EAAE,QAAQ,EAAsB,MAAM,aAAa,CAAA;AAE1D,OAAO,EAAmC,KAAK,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAClG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,OAAO,EAAkD,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAIlG,eAAO,MAAM,iBAAiB,WAAW,CAAA;AAEzC,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,CAAC,EAAE,gBAAgB,CAAA;IACxB,+EAA+E;IAC/E,IAAI,CAAC,EAAE,gBAAgB,CAAA;IACvB,yFAAyF;IACzF,SAAS,CAAC,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAA;IAC9B,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAA;IAClC,sEAAsE;IACtE,IAAI,CAAC,EAAE,qBAAqB,GAAG,KAAK,CAAA;CACrC,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmChG;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,qBAAqB,GAAG,KAAK,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAA;CAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAWzG;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkF7D;AAED,wBAAsB,oBAAoB,CAAC,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,SAAS,CAAC,CAsE7F"}
|