@fdkey/mcp 0.2.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/CHANGELOG.md ADDED
@@ -0,0 +1,47 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@fdkey/mcp` will be documented in this file.
4
+
5
+ ## 0.2.0 — 2026-05-09
6
+
7
+ ### Added
8
+
9
+ - **Cloudflare Workers / Bun / Deno support** on the default routing path.
10
+ The single-VPS `StaticRouter` now uses the runtime's global `fetch` and
11
+ imports zero Node-only dependencies. Multi-VPS routing (set via
12
+ `discoveryUrl`) still uses `undici` for IP-pinning and is lazy-loaded
13
+ via dynamic `import()`, so Workers bundles never pull undici unless the
14
+ integrator explicitly opts in. `undici` moved from `dependencies` to
15
+ `optionalDependencies`.
16
+ - **`score` and `tier` as first-class fields on `FdkeyContext`.**
17
+ Previously available only inside `FdkeyContext.claims`. The wire shape
18
+ reserves `score` as a 0..1 float for forward-compat with graduated
19
+ capability scoring (today the value is binary 1.0/0.0).
20
+ - **Bounded session store** — sessions now evict on a 1h idle TTL with a
21
+ hard 10k LRU cap (~2 MB max). Long-lived shared MCP servers no longer
22
+ leak per-session memory.
23
+ - **Actionable error message** when `discoveryUrl` is set but `undici`
24
+ is not installed.
25
+ - Internal `index.test.ts` covering: lazy-router contract, score/tier
26
+ shape, SDK_VERSION sync, and SessionStore eviction semantics.
27
+
28
+ ### Changed
29
+
30
+ - `withFdkey()` no longer reaches into a static `import './vps-router.js'`.
31
+ The default URL is now `https://api.fdkey.com` when neither `vpsUrl`
32
+ nor `discoveryUrl` is set.
33
+ - `getFdkeyContext()` reads via `store.peek()` — querying context no
34
+ longer extends a session's lifetime.
35
+
36
+ ### Migration from 0.1.0
37
+
38
+ No public API breaks. If you currently relied on `FdkeyContext.claims.score`
39
+ you can keep doing that, or migrate to the first-class `ctx.score` /
40
+ `ctx.tier` fields. If you use multi-VPS routing (`discoveryUrl`), make
41
+ sure `undici` is in your dependencies — it's no longer pulled in by
42
+ default.
43
+
44
+ ## 0.1.0 — 2026-04-XX
45
+
46
+ Initial pre-publish release. MCP middleware: tool injection, policy gating,
47
+ Ed25519 JWT verify, IP-pinned multi-VPS routing.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FDKEY
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,201 @@
1
+ # @fdkey/mcp
2
+
3
+ > **FDKEY verification middleware for MCP servers.** Gate AI-agent access to
4
+ > your tools behind LLM-only puzzles. Drop-in for any
5
+ > [Model Context Protocol](https://modelcontextprotocol.io) server.
6
+
7
+ ## What it does
8
+
9
+ - Injects two MCP tools into your server: `fdkey_get_challenge` and
10
+ `fdkey_submit_challenge`.
11
+ - Wraps the tools you want to protect — they return
12
+ `fdkey_verification_required` until the connecting agent has solved a
13
+ challenge.
14
+ - Talks to `https://api.fdkey.com` for challenge issuance and scoring.
15
+ - Verifies the Ed25519 JWT response **offline** using the public key from
16
+ `https://api.fdkey.com/.well-known/fdkey.json`.
17
+
18
+ The agent never handles a token. The connection itself becomes verified —
19
+ verification state lives server-side in the integrator's process, keyed by
20
+ the MCP session id. Every agent verifies for itself; verification doesn't
21
+ transfer between agents.
22
+
23
+ ## Runtime support
24
+
25
+ | Runtime | Default (single-VPS) | `discoveryUrl` set (multi-VPS) |
26
+ | -------------------- | :------------------: | :----------------------------: |
27
+ | Node 18+ | ✅ | ✅ |
28
+ | Cloudflare Workers | ✅ | ❌ (requires `undici`, Node-only) |
29
+ | Bun | ✅ | ✅ |
30
+ | Deno | ✅ | ⚠️ untested |
31
+
32
+ By default the SDK runs on the global `fetch` and pulls in zero
33
+ Node-only dependencies — works on edge runtimes out of the box. The
34
+ multi-VPS routing path (set via `discoveryUrl`) is lazy-loaded and
35
+ requires `undici` (declared as an `optionalDependency`).
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ npm install @fdkey/mcp
41
+ ```
42
+
43
+ You also need the official MCP server SDK (`@modelcontextprotocol/sdk`) — it's
44
+ declared as a `peerDependency`, so install your own version:
45
+
46
+ ```bash
47
+ npm install @modelcontextprotocol/sdk
48
+ ```
49
+
50
+ Get an API key at [app.fdkey.com](https://app.fdkey.com).
51
+
52
+ ## Usage
53
+
54
+ ```ts
55
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
56
+ import { withFdkey } from '@fdkey/mcp';
57
+
58
+ const server = new McpServer({ name: 'my-server', version: '1.0.0' });
59
+
60
+ // Wrap the server. `protect` lists tool names that require verification.
61
+ withFdkey(server, {
62
+ apiKey: process.env.FDKEY_API_KEY!,
63
+ protect: {
64
+ sensitive_action: { policy: 'each_call' },
65
+ register: { policy: 'once_per_session' },
66
+ },
67
+ });
68
+
69
+ // Register your tools as normal.
70
+ server.registerTool('sensitive_action', {
71
+ description: 'Does something that needs verification',
72
+ inputSchema: { /* ... */ },
73
+ }, async (args, extra) => {
74
+ // Reaches here only if the agent has solved a challenge first.
75
+ return { content: [{ type: 'text', text: 'verified' }] };
76
+ });
77
+
78
+ // Serve over your transport of choice (stdio, HTTP, etc.)
79
+ ```
80
+
81
+ ## Policies
82
+
83
+ Per-tool gating policy — passed as `{ policy: ... }` in the `protect` map:
84
+
85
+ - `'each_call'` — verification required for every invocation. Use for
86
+ irreversible actions (payments, deletes).
87
+ - `'once_per_session'` — verification required once per connection. Use
88
+ for account creation, signup-style flows.
89
+ - `{ type: 'every_minutes', minutes: N }` — verification good for N
90
+ minutes after the puzzle was solved. Use as a middle ground when
91
+ "every call" is too aggressive but "once forever" is too loose. Note
92
+ the timer does NOT extend on calls — it expires `minutes` after the
93
+ puzzle solve, regardless of activity.
94
+
95
+ ```ts
96
+ protect: {
97
+ delete_account: { policy: 'each_call' },
98
+ register: { policy: 'once_per_session' },
99
+ refresh_dashboard: { policy: { type: 'every_minutes', minutes: 15 } },
100
+ }
101
+ ```
102
+
103
+ ## Configuration reference
104
+
105
+ ```ts
106
+ withFdkey(server, {
107
+ apiKey: 'fdk_...', // required
108
+ protect: { ... }, // tool name → policy (above)
109
+ vpsUrl?: 'https://api.fdkey.com', // override for self-hosted
110
+ discoveryUrl?: 'https://...endpoints.json', // multi-VPS routing (Node only; lazy-loaded)
111
+ difficulty?: 'easy' | 'medium' | 'hard', // default 'medium'
112
+ onFail?: 'block' | 'allow', // default 'block' — what happens when puzzle is failed
113
+ onVpsError?: 'block' | 'allow', // default 'allow' (see below)
114
+ inlineChallenge?: boolean, // default false — embed puzzle JSON in blocked-tool errors
115
+ // so the agent can submit without a separate
116
+ // `fdkey_get_challenge` round-trip
117
+ tags?: { env: 'prod', region: 'eu' }, // free-form non-PII labels forwarded to FDKEY for analytics
118
+ });
119
+ ```
120
+
121
+ ### Failure-mode defaults
122
+
123
+ `onVpsError: 'allow'` is the default — if the FDKEY scoring service is
124
+ unreachable, the protected tool falls through to your handler instead of
125
+ blocking. We chose this so an FDKEY outage doesn't brick your workflow
126
+ in the worst case (think: we shut down, your DNS can't resolve api.fdkey.com,
127
+ etc.). FDKEY is verification, not gating — your service still serves traffic
128
+ when our service is down.
129
+
130
+ If your threat model is the opposite — you'd rather drop traffic than admit
131
+ unverified callers during an outage — set `onVpsError: 'block'` and you
132
+ get HTTP-503-style errors instead.
133
+
134
+ ## What FDKEY sees
135
+
136
+ - The MCP `clientInfo` your agent reports (name, version, protocol version,
137
+ transport).
138
+ - Challenge IDs, scores, timestamps.
139
+ - Your integrator-supplied `tags`.
140
+
141
+ ## Security notes
142
+
143
+ - **JWT `aud` is not validated by the SDK.** The audience claim binds the
144
+ JWT to the integrator's `vps_users.id`, which the SDK doesn't know at
145
+ verify time. The VPS already binds `aud` to the API key that requested
146
+ the challenge — defense in depth — but in principle, a JWT issued for
147
+ one FDKEY-protected service could be replayed against a different
148
+ one within the JWT lifetime (~5 min default). Keep the JWT lifetime
149
+ short on the VPS side if your threat model includes cross-integrator
150
+ replay.
151
+
152
+ ## What FDKEY does NOT see
153
+
154
+ - Your prompts.
155
+ - Tool inputs or outputs.
156
+ - Your end users' identities or PII.
157
+ - Anything about the agent beyond the MCP `clientInfo` it self-reports.
158
+
159
+ ## Reading verification context
160
+
161
+ ```ts
162
+ import { getFdkeyContext, type FdkeyContext } from '@fdkey/mcp';
163
+
164
+ server.registerTool('whoami', { /*...*/ }, async (args, extra) => {
165
+ const ctx = getFdkeyContext(server, extra);
166
+ if (ctx?.verified) {
167
+ return {
168
+ content: [{
169
+ type: 'text',
170
+ text: `Verified at ${ctx.verifiedAt} (score=${ctx.score}, tier=${ctx.tier})`,
171
+ }],
172
+ };
173
+ }
174
+ return { content: [{ type: 'text', text: 'Not verified yet' }] };
175
+ });
176
+ ```
177
+
178
+ `FdkeyContext` shape:
179
+
180
+ ```ts
181
+ interface FdkeyContext {
182
+ verified: boolean; // true once the agent has solved a challenge
183
+ verifiedAt: number | null; // ms epoch of the most recent successful verify
184
+ score: number | null; // 0..1 capability score (today binary 1.0/0.0)
185
+ tier: string | null; // VPS-issued tier label (e.g. "free", "gold")
186
+ claims: Record<string, unknown> | null; // raw decoded JWT, for power users
187
+ }
188
+ ```
189
+
190
+ `score` is reserved as a 0..1 float for graduated capability scoring (combined T1 correctness + T3 tau + future T4-T6 frequency); today the value is effectively binary. The field shape will not change when the internal scoring grows.
191
+
192
+ ## Links
193
+
194
+ - Marketing + docs: <https://fdkey.com>
195
+ - Dashboard (sign up + manage keys): <https://app.fdkey.com>
196
+ - Source: <https://github.com/fdkey/sdks>
197
+ - Issues: <https://github.com/fdkey/sdks/issues>
198
+
199
+ ## License
200
+
201
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,17 @@
1
+ import type { Policy, SessionState } from './types.js';
2
+ export declare function newSession(): SessionState;
3
+ /**
4
+ * Decides whether a protected tool call passes given the current policy and session state.
5
+ *
6
+ * once_per_session: pass if the session has ever been verified.
7
+ * each_call: pass only if there is an unconsumed fresh verification ticket.
8
+ * every_minutes: N: pass if (now - verifiedAt) < N minutes — the timer does NOT
9
+ * extend on calls; it expires N minutes after the puzzle was solved.
10
+ */
11
+ export declare function canCall(policy: Policy, _toolName: string, session: SessionState): boolean;
12
+ /** Called only when fdkey_submit_challenge succeeds. Replenishes session state. */
13
+ export declare function markVerified(session: SessionState): void;
14
+ /** Called after a protected tool call completes. Consumes the fresh-verification
15
+ * ticket for each_call policies. once_per_session and every_minutes do nothing. */
16
+ export declare function consumePolicy(policy: Policy, session: SessionState): void;
17
+ //# sourceMappingURL=guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEvD,wBAAgB,UAAU,IAAI,YAAY,CAazC;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAWzF;AAED,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAIxD;AAED;oFACoF;AACpF,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI,CAIzE"}
package/dist/guard.js ADDED
@@ -0,0 +1,49 @@
1
+ export function newSession() {
2
+ return {
3
+ verified: false,
4
+ verifiedAt: null,
5
+ lastTouchedAt: Date.now(),
6
+ freshVerificationAvailable: false,
7
+ pendingChallengeId: null,
8
+ lastClaims: null,
9
+ clientInfo: null,
10
+ protocolVersion: null,
11
+ mcpSessionId: null,
12
+ transport: 'unknown',
13
+ };
14
+ }
15
+ /**
16
+ * Decides whether a protected tool call passes given the current policy and session state.
17
+ *
18
+ * once_per_session: pass if the session has ever been verified.
19
+ * each_call: pass only if there is an unconsumed fresh verification ticket.
20
+ * every_minutes: N: pass if (now - verifiedAt) < N minutes — the timer does NOT
21
+ * extend on calls; it expires N minutes after the puzzle was solved.
22
+ */
23
+ export function canCall(policy, _toolName, session) {
24
+ switch (policy.type) {
25
+ case 'once_per_session':
26
+ return session.verified;
27
+ case 'each_call':
28
+ return session.verified && session.freshVerificationAvailable;
29
+ case 'every_minutes': {
30
+ if (session.verifiedAt === null)
31
+ return false;
32
+ return Date.now() - session.verifiedAt < policy.minutes * 60 * 1000;
33
+ }
34
+ }
35
+ }
36
+ /** Called only when fdkey_submit_challenge succeeds. Replenishes session state. */
37
+ export function markVerified(session) {
38
+ session.verified = true;
39
+ session.verifiedAt = Date.now();
40
+ session.freshVerificationAvailable = true;
41
+ }
42
+ /** Called after a protected tool call completes. Consumes the fresh-verification
43
+ * ticket for each_call policies. once_per_session and every_minutes do nothing. */
44
+ export function consumePolicy(policy, session) {
45
+ if (policy.type === 'each_call') {
46
+ session.freshVerificationAvailable = false;
47
+ }
48
+ }
49
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,UAAU;IACxB,OAAO;QACL,QAAQ,EAAE,KAAK;QACf,UAAU,EAAE,IAAI;QAChB,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE;QACzB,0BAA0B,EAAE,KAAK;QACjC,kBAAkB,EAAE,IAAI;QACxB,UAAU,EAAE,IAAI;QAChB,UAAU,EAAE,IAAI;QAChB,eAAe,EAAE,IAAI;QACrB,YAAY,EAAE,IAAI;QAClB,SAAS,EAAE,SAAS;KACrB,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,OAAO,CAAC,MAAc,EAAE,SAAiB,EAAE,OAAqB;IAC9E,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,kBAAkB;YACrB,OAAO,OAAO,CAAC,QAAQ,CAAC;QAC1B,KAAK,WAAW;YACd,OAAO,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,0BAA0B,CAAC;QAChE,KAAK,eAAe,CAAC,CAAC,CAAC;YACrB,IAAI,OAAO,CAAC,UAAU,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;YAC9C,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,UAAU,GAAG,MAAM,CAAC,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC;QACtE,CAAC;IACH,CAAC;AACH,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,YAAY,CAAC,OAAqB;IAChD,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAChC,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC;AAC5C,CAAC;AAED;oFACoF;AACpF,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,OAAqB;IACjE,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAChC,OAAO,CAAC,0BAA0B,GAAG,KAAK,CAAC;IAC7C,CAAC;AACH,CAAC"}
@@ -0,0 +1,64 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { FdkeyConfig, IVpsRouter, RoutingTarget } from './types.js';
3
+ export type { FdkeyConfig, Policy, AgentMeta, IntegratorMeta, ChallengeMeta } from './types.js';
4
+ /** @internal Test-only export. Not part of the supported public API;
5
+ * may change or disappear without notice. Used by `index.test.ts` to
6
+ * verify the lazy-import contract directly. */
7
+ export { LazyVpsRouter as __LazyVpsRouterForTesting };
8
+ /** Read-only context surfaced to integrator tool handlers. */
9
+ export interface FdkeyContext {
10
+ verified: boolean;
11
+ verifiedAt: number | null;
12
+ /** Capability score from the most recent verification, in [0, 1].
13
+ * Today this is effectively binary (1.0 = passed, 0.0 = failed); the
14
+ * type signature reserves the float so future capability scoring
15
+ * (combined T1 correctness + T3 tau + T4-T6 frequency) can land
16
+ * without an API change. Null until first successful verify. */
17
+ score: number | null;
18
+ /** VPS-issued tier label from the most recent verification (e.g.
19
+ * capability bucket the agent fell into). Null until first verify. */
20
+ tier: string | null;
21
+ /** Decoded JWT payload from the most recent verification. Includes
22
+ * `score`, `tier`, `puzzle_summary`, etc. Null until first successful verify. */
23
+ claims: Record<string, unknown> | null;
24
+ }
25
+ /** Lazy wrapper around the multi-VPS `VpsRouter`. Defers `await import('./vps-router.js')`
26
+ * until the first `getTarget()` call so Workers/Bun/Deno builds — which never hit this
27
+ * path unless `discoveryUrl` is set — don't pull undici into the bundle.
28
+ *
29
+ * If `undici` is not installed (it's an `optionalDependency`), the dynamic
30
+ * import will fail. We catch that and rethrow with a clear, actionable
31
+ * error so the integrator doesn't have to debug a `Cannot find module
32
+ * 'undici'` stack trace. */
33
+ declare class LazyVpsRouter implements IVpsRouter {
34
+ private readonly discoveryUrl;
35
+ private inner;
36
+ constructor(discoveryUrl: string | undefined);
37
+ getTarget(): Promise<RoutingTarget>;
38
+ recordFailure(ip: string | undefined): void;
39
+ }
40
+ /** Read the current FDKEY context for a given session. Pass either the `extra`
41
+ * argument from a tool handler or a session ID string. Returns null if the server
42
+ * was not wrapped with withFdkey() or the session doesn't exist yet.
43
+ *
44
+ * Reads use `store.peek()` which does NOT slide the LRU position — querying
45
+ * the context shouldn't extend a session's lifetime; only actual tool calls
46
+ * do that. */
47
+ export declare function getFdkeyContext(server: McpServer, extraOrSessionId: {
48
+ sessionId?: string;
49
+ } | string | undefined): FdkeyContext | null;
50
+ /**
51
+ * Wraps an MCP server with FDKEY verification middleware.
52
+ *
53
+ * @example
54
+ * const server = withFdkey(new McpServer({ name: 'my-server', version: '1.0.0' }), {
55
+ * apiKey: process.env.FDKEY_API_KEY!,
56
+ * protect: {
57
+ * login: { policy: 'each_call' },
58
+ * register: { policy: 'once_per_session' },
59
+ * },
60
+ * });
61
+ * server.registerTool('login', { inputSchema: { username: z.string() } }, async (args) => { ... });
62
+ */
63
+ export declare function withFdkey(server: McpServer, config: FdkeyConfig): McpServer;
64
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EACV,WAAW,EAKX,UAAU,EACV,aAAa,EACd,MAAM,YAAY,CAAC;AAQpB,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhG;;gDAEgD;AAChD,OAAO,EAAE,aAAa,IAAI,yBAAyB,EAAE,CAAC;AAWtD,8DAA8D;AAC9D,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;qEAIiE;IACjE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;2EACuE;IACvE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB;sFACkF;IAClF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACxC;AAED;;;;;;;6BAO6B;AAC7B,cAAM,aAAc,YAAW,UAAU;IAE3B,OAAO,CAAC,QAAQ,CAAC,YAAY;IADzC,OAAO,CAAC,KAAK,CAA2B;gBACX,YAAY,EAAE,MAAM,GAAG,SAAS;IACvD,SAAS,IAAI,OAAO,CAAC,aAAa,CAAC;IA4BzC,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;CAG5C;AAMD;;;;;;eAMe;AACf,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,EACjB,gBAAgB,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,SAAS,GAC5D,YAAY,GAAG,IAAI,CAgBrB;AA+CD;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,GAAG,SAAS,CA+U3E"}