@chrischall/mcp-utils 0.1.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 +235 -0
- package/dist/auth/index.d.ts +223 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +267 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/config/index.d.ts +86 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +121 -0
- package/dist/config/index.js.map +1 -0
- package/dist/errors/index.d.ts +90 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +157 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/fetchproxy/index.d.ts +156 -0
- package/dist/fetchproxy/index.d.ts.map +1 -0
- package/dist/fetchproxy/index.js +197 -0
- package/dist/fetchproxy/index.js.map +1 -0
- package/dist/html/index.d.ts +142 -0
- package/dist/html/index.d.ts.map +1 -0
- package/dist/html/index.js +321 -0
- package/dist/html/index.js.map +1 -0
- package/dist/http/index.d.ts +202 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +341 -0
- package/dist/http/index.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/response/index.d.ts +22 -0
- package/dist/response/index.d.ts.map +1 -0
- package/dist/response/index.js +61 -0
- package/dist/response/index.js.map +1 -0
- package/dist/server/index.d.ts +109 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +95 -0
- package/dist/server/index.js.map +1 -0
- package/dist/session/index.d.ts +233 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +404 -0
- package/dist/session/index.js.map +1 -0
- package/dist/test/index.d.ts +124 -0
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/index.js +181 -0
- package/dist/test/index.js.map +1 -0
- package/dist/zod/index.d.ts +130 -0
- package/dist/zod/index.d.ts.map +1 -0
- package/dist/zod/index.js +184 -0
- package/dist/zod/index.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap any JSON-serialisable value as an MCP tool result. This is the single
|
|
3
|
+
* most duplicated snippet across the fleet:
|
|
4
|
+
* `{ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }`
|
|
5
|
+
*/
|
|
6
|
+
export function textResult(data) {
|
|
7
|
+
return {
|
|
8
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/** Alias for {@link textResult} — pretty-printed JSON tool result. */
|
|
12
|
+
export const jsonResult = textResult;
|
|
13
|
+
/** Return a raw string as a text tool result (no JSON stringify). */
|
|
14
|
+
export function rawTextResult(text) {
|
|
15
|
+
return { content: [{ type: 'text', text }] };
|
|
16
|
+
}
|
|
17
|
+
/** Return a base64 image as an MCP image tool result. */
|
|
18
|
+
export function imageResult(base64, mimeType) {
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: 'image', data: base64, mimeType }],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Return an error tool result (`isError: true`) carrying a message. */
|
|
24
|
+
export function errorResult(message) {
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: 'text', text: message }],
|
|
27
|
+
isError: true,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Collapse a JSON:API-shaped payload (`{ data: { id, type, attributes } }` or an
|
|
32
|
+
* array thereof) into plain objects with `id`/`type` merged into attributes.
|
|
33
|
+
* Used by skylight-mcp; opt-in (callers pass payloads they know are JSON:API).
|
|
34
|
+
*/
|
|
35
|
+
export function flattenJsonApi(payload) {
|
|
36
|
+
const flattenOne = (node) => {
|
|
37
|
+
if (node === null || typeof node !== 'object')
|
|
38
|
+
return node;
|
|
39
|
+
const obj = node;
|
|
40
|
+
const attrs = obj.attributes;
|
|
41
|
+
if (attrs !== null && typeof attrs === 'object') {
|
|
42
|
+
const merged = { ...attrs };
|
|
43
|
+
if ('id' in obj)
|
|
44
|
+
merged.id = obj.id;
|
|
45
|
+
if ('type' in obj)
|
|
46
|
+
merged.type = obj.type;
|
|
47
|
+
return merged;
|
|
48
|
+
}
|
|
49
|
+
return node;
|
|
50
|
+
};
|
|
51
|
+
if (payload === null || typeof payload !== 'object')
|
|
52
|
+
return payload;
|
|
53
|
+
const root = payload;
|
|
54
|
+
if (!('data' in root))
|
|
55
|
+
return payload;
|
|
56
|
+
const data = root.data;
|
|
57
|
+
if (Array.isArray(data))
|
|
58
|
+
return data.map(flattenOne);
|
|
59
|
+
return flattenOne(data);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/response/index.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAAa;IACtC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;KACjE,CAAC;AACJ,CAAC;AAED,sEAAsE;AACtE,MAAM,CAAC,MAAM,UAAU,GAAG,UAAU,CAAC;AAErC,qEAAqE;AACrE,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,QAAgB;IAC1D,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;KACrD,CAAC;AACJ,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QAC1C,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,OAAgB;IAC7C,MAAM,UAAU,GAAG,CAAC,IAAa,EAAW,EAAE;QAC5C,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC3D,MAAM,GAAG,GAAG,IAA+B,CAAC;QAC5C,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,CAAC;QAC7B,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAChD,MAAM,MAAM,GAA4B,EAAE,GAAI,KAAiC,EAAE,CAAC;YAClF,IAAI,IAAI,IAAI,GAAG;gBAAE,MAAM,CAAC,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC;YACpC,IAAI,MAAM,IAAI,GAAG;gBAAE,MAAM,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;YAC1C,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IACpE,MAAM,IAAI,GAAG,OAAkC,CAAC;IAChD,IAAI,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACrD,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `server` — MCP bootstrap & lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* The single biggest dedup win in the fleet: every `src/index.ts` is described
|
|
5
|
+
* as "byte-identical" — construct an {@link McpServer}, run a fixed list of
|
|
6
|
+
* `registerXTools(server, client)` calls, print a stderr banner, wire
|
|
7
|
+
* SIGINT/SIGTERM to tear the transport down, then connect over stdio.
|
|
8
|
+
*
|
|
9
|
+
* This module collapses that 30–120 lines/MCP into three calls:
|
|
10
|
+
* - {@link createMcpServer} — build the server and apply the registrars.
|
|
11
|
+
* - {@link withGracefulShutdown} — SIGINT/SIGTERM → cleanup → exit.
|
|
12
|
+
* - {@link runMcp} — bootstrap + banner + connect + shutdown, the whole boot.
|
|
13
|
+
*
|
|
14
|
+
* It is deliberately transport- and domain-agnostic. The
|
|
15
|
+
* deferred-config-error pattern (server boots before creds exist, so the host's
|
|
16
|
+
* initial `tools/list` always succeeds and the first tool call surfaces the auth
|
|
17
|
+
* error) is preserved by keeping client/transport construction in the caller's
|
|
18
|
+
* `deps`: both Pattern-A (fetchproxy bridge) and Pattern-B (direct/bearer) MCPs
|
|
19
|
+
* build their client themselves and pass it through, so neither is coupled in.
|
|
20
|
+
*/
|
|
21
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
22
|
+
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
23
|
+
/**
|
|
24
|
+
* Registers one or more tools onto a fresh {@link McpServer}. `deps` is whatever
|
|
25
|
+
* the caller threaded through {@link createMcpServer} / {@link runMcp} — an API
|
|
26
|
+
* client, a session registry, an app context, or `undefined` for tools (like
|
|
27
|
+
* pure mortgage/affordability calculators) that need no shared state.
|
|
28
|
+
*
|
|
29
|
+
* May be async; {@link createMcpServer} awaits each registrar in order.
|
|
30
|
+
*/
|
|
31
|
+
export type ToolRegistrar<TDeps = unknown> = (server: McpServer, deps: TDeps) => void | Promise<void>;
|
|
32
|
+
/** Either the literal `'stdio'` (the default) or any SDK {@link Transport}. */
|
|
33
|
+
export type TransportSpec = 'stdio' | Transport;
|
|
34
|
+
/** Options for {@link createMcpServer}. */
|
|
35
|
+
export interface CreateMcpServerOptions<TDeps = unknown> {
|
|
36
|
+
/** Server name advertised to the host (e.g. `'splitwise-mcp'`). */
|
|
37
|
+
name: string;
|
|
38
|
+
/** Server version advertised to the host (the `x-release-please-version`). */
|
|
39
|
+
version: string;
|
|
40
|
+
/** The tool registrars to apply, in order. */
|
|
41
|
+
tools: ToolRegistrar<TDeps>[];
|
|
42
|
+
/**
|
|
43
|
+
* Shared state passed as the second argument to every registrar — the API
|
|
44
|
+
* client, app context, session registry, etc. Build it before calling so the
|
|
45
|
+
* deferred-config-error pattern is preserved. Omit for registrar lists that
|
|
46
|
+
* take no deps.
|
|
47
|
+
*/
|
|
48
|
+
deps?: TDeps;
|
|
49
|
+
/** A one-line startup banner written to stderr (never stdout — stdout is the JSON-RPC channel). */
|
|
50
|
+
banner?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Transport hint. Carried for API symmetry with {@link runMcp}; this function
|
|
53
|
+
* never connects, so it only matters that the value is accepted. Defaults to
|
|
54
|
+
* `'stdio'`.
|
|
55
|
+
*/
|
|
56
|
+
transport?: TransportSpec;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build an {@link McpServer}, print the optional stderr banner, and apply every
|
|
60
|
+
* tool registrar (awaiting async ones) — but do **not** connect a transport.
|
|
61
|
+
* Connecting is {@link runMcp}'s job (or the caller's), which keeps this usable
|
|
62
|
+
* from tests and from custom boot sequences.
|
|
63
|
+
*/
|
|
64
|
+
export declare function createMcpServer<TDeps = unknown>(opts: CreateMcpServerOptions<TDeps>): Promise<McpServer>;
|
|
65
|
+
/** Signals {@link withGracefulShutdown} listens for. */
|
|
66
|
+
export type ShutdownSignal = 'SIGINT' | 'SIGTERM';
|
|
67
|
+
/** Options for {@link withGracefulShutdown}. */
|
|
68
|
+
export interface GracefulShutdownOptions {
|
|
69
|
+
/**
|
|
70
|
+
* Extra cleanup to run on shutdown, before the server is closed — typically
|
|
71
|
+
* `() => client.close()` to release the fetchproxy WebSocket bridge / direct
|
|
72
|
+
* sockets so ports don't leak between host restarts. Receives the signal that
|
|
73
|
+
* triggered shutdown. Errors are logged, never fatal.
|
|
74
|
+
*/
|
|
75
|
+
onSignal?: (signal: ShutdownSignal) => void | Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Call `process.exit(0)` after cleanup completes. Default `true` (matches the
|
|
78
|
+
* fleet's `process.exit(0)`). Set `false` in tests so the process survives.
|
|
79
|
+
*/
|
|
80
|
+
exit?: boolean;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Wire SIGINT/SIGTERM to a one-shot graceful shutdown: run `onSignal` (e.g.
|
|
84
|
+
* close the client/transport), close the server, then `process.exit(0)` (unless
|
|
85
|
+
* `exit: false`). Idempotent — a second signal mid-shutdown is ignored, and a
|
|
86
|
+
* throwing `onSignal`/`close` is logged but still exits cleanly so a wedged
|
|
87
|
+
* cleanup can't hang the host.
|
|
88
|
+
*/
|
|
89
|
+
export declare function withGracefulShutdown(server: Pick<McpServer, 'close'>, opts?: GracefulShutdownOptions): void;
|
|
90
|
+
/** Options for {@link runMcp} — {@link createMcpServer}'s plus lifecycle wiring. */
|
|
91
|
+
export interface RunMcpOptions<TDeps = unknown> extends CreateMcpServerOptions<TDeps> {
|
|
92
|
+
/**
|
|
93
|
+
* Graceful-shutdown wiring. `true` (default) installs SIGINT/SIGTERM handlers
|
|
94
|
+
* that close the server. `false` skips them. An object is passed straight to
|
|
95
|
+
* {@link withGracefulShutdown} (e.g. `{ onSignal: () => client.close() }`).
|
|
96
|
+
*/
|
|
97
|
+
shutdown?: boolean | GracefulShutdownOptions;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The whole boot in one call: build the server, apply registrars, print the
|
|
101
|
+
* banner, install graceful-shutdown handlers, and connect the transport
|
|
102
|
+
* (defaulting to a {@link StdioServerTransport}). Returns the connected server.
|
|
103
|
+
*
|
|
104
|
+
* Pattern-A and Pattern-B MCPs both build their client/transport in `deps` and
|
|
105
|
+
* pass `onSignal: () => client.close()` via `shutdown`, so this stays agnostic
|
|
106
|
+
* to how creds are resolved.
|
|
107
|
+
*/
|
|
108
|
+
export declare function runMcp<TDeps = unknown>(opts: RunMcpOptions<TDeps>): Promise<McpServer>;
|
|
109
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAE/E;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,CAAC,KAAK,GAAG,OAAO,IAAI,CAC3C,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,KAAK,KACR,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B,+EAA+E;AAC/E,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;AAEhD,2CAA2C;AAC3C,MAAM,WAAW,sBAAsB,CAAC,KAAK,GAAG,OAAO;IACrD,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;IAC9B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,mGAAmG;IACnG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,KAAK,GAAG,OAAO,EACnD,IAAI,EAAE,sBAAsB,CAAC,KAAK,CAAC,GAClC,OAAO,CAAC,SAAS,CAAC,CAgBpB;AAED,wDAAwD;AACxD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;AAElD,gDAAgD;AAChD,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAChC,IAAI,GAAE,uBAA4B,GACjC,IAAI,CAyBN;AAED,oFAAoF;AACpF,MAAM,WAAW,aAAa,CAAC,KAAK,GAAG,OAAO,CAAE,SAAQ,sBAAsB,CAAC,KAAK,CAAC;IACnF;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,uBAAuB,CAAC;CAC9C;AAED;;;;;;;;GAQG;AACH,wBAAsB,MAAM,CAAC,KAAK,GAAG,OAAO,EAC1C,IAAI,EAAE,aAAa,CAAC,KAAK,CAAC,GACzB,OAAO,CAAC,SAAS,CAAC,CAapB"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `server` — MCP bootstrap & lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* The single biggest dedup win in the fleet: every `src/index.ts` is described
|
|
5
|
+
* as "byte-identical" — construct an {@link McpServer}, run a fixed list of
|
|
6
|
+
* `registerXTools(server, client)` calls, print a stderr banner, wire
|
|
7
|
+
* SIGINT/SIGTERM to tear the transport down, then connect over stdio.
|
|
8
|
+
*
|
|
9
|
+
* This module collapses that 30–120 lines/MCP into three calls:
|
|
10
|
+
* - {@link createMcpServer} — build the server and apply the registrars.
|
|
11
|
+
* - {@link withGracefulShutdown} — SIGINT/SIGTERM → cleanup → exit.
|
|
12
|
+
* - {@link runMcp} — bootstrap + banner + connect + shutdown, the whole boot.
|
|
13
|
+
*
|
|
14
|
+
* It is deliberately transport- and domain-agnostic. The
|
|
15
|
+
* deferred-config-error pattern (server boots before creds exist, so the host's
|
|
16
|
+
* initial `tools/list` always succeeds and the first tool call surfaces the auth
|
|
17
|
+
* error) is preserved by keeping client/transport construction in the caller's
|
|
18
|
+
* `deps`: both Pattern-A (fetchproxy bridge) and Pattern-B (direct/bearer) MCPs
|
|
19
|
+
* build their client themselves and pass it through, so neither is coupled in.
|
|
20
|
+
*/
|
|
21
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
22
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
23
|
+
/**
|
|
24
|
+
* Build an {@link McpServer}, print the optional stderr banner, and apply every
|
|
25
|
+
* tool registrar (awaiting async ones) — but do **not** connect a transport.
|
|
26
|
+
* Connecting is {@link runMcp}'s job (or the caller's), which keeps this usable
|
|
27
|
+
* from tests and from custom boot sequences.
|
|
28
|
+
*/
|
|
29
|
+
export async function createMcpServer(opts) {
|
|
30
|
+
const server = new McpServer({ name: opts.name, version: opts.version });
|
|
31
|
+
if (opts.banner !== undefined) {
|
|
32
|
+
// stderr only: stdout carries the JSON-RPC frames over stdio transport.
|
|
33
|
+
console.error(opts.banner);
|
|
34
|
+
}
|
|
35
|
+
// `deps` is intentionally cast: when omitted, registrars that declare a deps
|
|
36
|
+
// type are the caller's responsibility (they passed deps if they need it).
|
|
37
|
+
const deps = opts.deps;
|
|
38
|
+
for (const register of opts.tools) {
|
|
39
|
+
await register(server, deps);
|
|
40
|
+
}
|
|
41
|
+
return server;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Wire SIGINT/SIGTERM to a one-shot graceful shutdown: run `onSignal` (e.g.
|
|
45
|
+
* close the client/transport), close the server, then `process.exit(0)` (unless
|
|
46
|
+
* `exit: false`). Idempotent — a second signal mid-shutdown is ignored, and a
|
|
47
|
+
* throwing `onSignal`/`close` is logged but still exits cleanly so a wedged
|
|
48
|
+
* cleanup can't hang the host.
|
|
49
|
+
*/
|
|
50
|
+
export function withGracefulShutdown(server, opts = {}) {
|
|
51
|
+
const shouldExit = opts.exit ?? true;
|
|
52
|
+
let shuttingDown = false;
|
|
53
|
+
const handler = (signal) => {
|
|
54
|
+
if (shuttingDown)
|
|
55
|
+
return;
|
|
56
|
+
shuttingDown = true;
|
|
57
|
+
void (async () => {
|
|
58
|
+
try {
|
|
59
|
+
if (opts.onSignal)
|
|
60
|
+
await opts.onSignal(signal);
|
|
61
|
+
await server.close();
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
if (shouldExit)
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
71
|
+
};
|
|
72
|
+
process.on('SIGINT', () => handler('SIGINT'));
|
|
73
|
+
process.on('SIGTERM', () => handler('SIGTERM'));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The whole boot in one call: build the server, apply registrars, print the
|
|
77
|
+
* banner, install graceful-shutdown handlers, and connect the transport
|
|
78
|
+
* (defaulting to a {@link StdioServerTransport}). Returns the connected server.
|
|
79
|
+
*
|
|
80
|
+
* Pattern-A and Pattern-B MCPs both build their client/transport in `deps` and
|
|
81
|
+
* pass `onSignal: () => client.close()` via `shutdown`, so this stays agnostic
|
|
82
|
+
* to how creds are resolved.
|
|
83
|
+
*/
|
|
84
|
+
export async function runMcp(opts) {
|
|
85
|
+
const server = await createMcpServer(opts);
|
|
86
|
+
const shutdown = opts.shutdown ?? true;
|
|
87
|
+
if (shutdown !== false) {
|
|
88
|
+
withGracefulShutdown(server, shutdown === true ? {} : shutdown);
|
|
89
|
+
}
|
|
90
|
+
const spec = opts.transport ?? 'stdio';
|
|
91
|
+
const transport = spec === 'stdio' ? new StdioServerTransport() : spec;
|
|
92
|
+
await server.connect(transport);
|
|
93
|
+
return server;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session scaffolding for the MCP fleet — three related-but-distinct surfaces
|
|
3
|
+
* consolidated behind one subpath (`@chrischall/mcp-utils/session`):
|
|
4
|
+
*
|
|
5
|
+
* 1. {@link SessionRegistry} — an *ephemeral, in-memory* registry of signed-in
|
|
6
|
+
* sessions keyed by account identity, plus {@link registerSessionTools} for
|
|
7
|
+
* the structurally-identical `*_register_session` / `*_set_active_session` /
|
|
8
|
+
* `*_get_session_context` MCP tool trio. Used by the realty MCPs
|
|
9
|
+
* (zillow/redfin/compass/homes/onehome).
|
|
10
|
+
*
|
|
11
|
+
* 2. {@link SessionStore} — a *disk-persisted* store with hardened file perms
|
|
12
|
+
* (0600 file / 0700 dir), normalized keys, and a most-recently-used "active"
|
|
13
|
+
* pointer. Used by ofw/creditkarma/honeybook.
|
|
14
|
+
*
|
|
15
|
+
* 3. {@link TokenManager} — a bearer-token lifecycle manager: proactive refresh
|
|
16
|
+
* inside a 5-minute skew window, reactive 401-replay, and a single-flight
|
|
17
|
+
* semaphore so concurrent callers coalesce into ONE refresh. Used by
|
|
18
|
+
* skylight/canvas/creditkarma/honeybook/zola.
|
|
19
|
+
*
|
|
20
|
+
* Security-sensitive by design (file perms + token-refresh races), so this is
|
|
21
|
+
* the one audited implementation the fleet shares.
|
|
22
|
+
*/
|
|
23
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
24
|
+
/** How a session was authenticated. Open-ended so per-MCP modes slot in. */
|
|
25
|
+
export type AuthMode = 'browser_session' | 'unknown' | (string & {});
|
|
26
|
+
/** A registered, signed-in session as surfaced to tools and clients. */
|
|
27
|
+
export interface SessionToken {
|
|
28
|
+
/** Opaque label id (`Date.now().toString(36)` + random). Stable across re-registration. */
|
|
29
|
+
session_id: string;
|
|
30
|
+
/**
|
|
31
|
+
* Caller-supplied identity for the signed-in account (usually the saved
|
|
32
|
+
* email). Re-registering the same identity updates the existing entry in
|
|
33
|
+
* place rather than creating a duplicate.
|
|
34
|
+
*/
|
|
35
|
+
account_identity: string;
|
|
36
|
+
auth_mode: AuthMode;
|
|
37
|
+
/** Whether the session is currently usable for tool calls. */
|
|
38
|
+
auth_ready: boolean;
|
|
39
|
+
/** ISO timestamp of when the session was last registered/refreshed. */
|
|
40
|
+
registered_at: string;
|
|
41
|
+
/** ISO expiry, or `null` for "no known expiry". */
|
|
42
|
+
auth_expires_at: string | null;
|
|
43
|
+
}
|
|
44
|
+
/** Snapshot returned by {@link SessionRegistry.getContext}. */
|
|
45
|
+
export interface SessionContext {
|
|
46
|
+
active_session_id: string | null;
|
|
47
|
+
sessions: SessionToken[];
|
|
48
|
+
}
|
|
49
|
+
/** Arguments to {@link SessionRegistry.register}. */
|
|
50
|
+
export interface RegisterArgs {
|
|
51
|
+
account_identity: string;
|
|
52
|
+
auth_mode?: AuthMode;
|
|
53
|
+
/**
|
|
54
|
+
* `undefined` keeps any existing expiry on re-registration; an explicit
|
|
55
|
+
* value (including `null`) replaces it. This distinction matters — coercing
|
|
56
|
+
* with `?? null` would silently wipe a previously-set expiry.
|
|
57
|
+
*/
|
|
58
|
+
auth_expires_at?: string | null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Per-process, in-memory registry of signed-in sessions. The constructor takes
|
|
62
|
+
* no arguments, so it's safe to instantiate per test. Sessions are keyed by
|
|
63
|
+
* `session_id` but de-duplicated by `account_identity`.
|
|
64
|
+
*/
|
|
65
|
+
export declare class SessionRegistry {
|
|
66
|
+
private readonly sessions;
|
|
67
|
+
private activeId;
|
|
68
|
+
/**
|
|
69
|
+
* Register a new session, or refresh the existing one keyed by
|
|
70
|
+
* `account_identity`. The first session registered becomes the active one.
|
|
71
|
+
*/
|
|
72
|
+
register(args: RegisterArgs): SessionToken;
|
|
73
|
+
/** Switch the active session. Returns `false` if the id is unknown. */
|
|
74
|
+
setActive(sessionId: string): boolean;
|
|
75
|
+
/** Look up a session by id (returns a copy, or `null`). */
|
|
76
|
+
get(sessionId: string): SessionToken | null;
|
|
77
|
+
/** Snapshot of the full registry plus the active id. */
|
|
78
|
+
getContext(): SessionContext;
|
|
79
|
+
/** The active session id, if any. */
|
|
80
|
+
activeSessionId(): string | null;
|
|
81
|
+
/** Number of registered sessions. */
|
|
82
|
+
size(): number;
|
|
83
|
+
/**
|
|
84
|
+
* Resolve which session a tool call should route through:
|
|
85
|
+
* - `requested` set and known → it;
|
|
86
|
+
* - `requested` set but unknown → throws;
|
|
87
|
+
* - `requested` undefined → the active session;
|
|
88
|
+
* - no sessions registered → `null` (caller uses the default transport).
|
|
89
|
+
*/
|
|
90
|
+
resolve(requested: string | undefined): string | null;
|
|
91
|
+
/** Clear all state. Test helper. */
|
|
92
|
+
reset(): void;
|
|
93
|
+
}
|
|
94
|
+
/** Construct a fresh in-memory {@link SessionRegistry}. */
|
|
95
|
+
export declare function createSessionRegistry(): SessionRegistry;
|
|
96
|
+
/** Options for {@link registerSessionTools}. */
|
|
97
|
+
export interface RegisterSessionToolsOptions {
|
|
98
|
+
/**
|
|
99
|
+
* Tool-name prefix, e.g. `'zillow'` → `zillow_register_session`. This is the
|
|
100
|
+
* one per-MCP knob; everything else is identical across the fleet.
|
|
101
|
+
*/
|
|
102
|
+
prefix: string;
|
|
103
|
+
/** Human label for the service (defaults to the prefix). */
|
|
104
|
+
serviceLabel?: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Register the structurally-identical session tool trio against `server`,
|
|
108
|
+
* backed by `registry`. Replaces every MCP's hand-rolled `src/tools/sessions.ts`.
|
|
109
|
+
*/
|
|
110
|
+
export declare function registerSessionTools(server: McpServer, registry: SessionRegistry, opts: RegisterSessionToolsOptions): void;
|
|
111
|
+
/**
|
|
112
|
+
* Given a full URL or just an origin, return the origin without a trailing
|
|
113
|
+
* slash. Falls back to trimming a trailing slash for non-URL input.
|
|
114
|
+
*/
|
|
115
|
+
export declare function normalizeOrigin(input: string): string;
|
|
116
|
+
/** Options for {@link SessionStore}. `T` is the persisted record shape. */
|
|
117
|
+
export interface SessionStoreOptions<T> {
|
|
118
|
+
/** Absolute path to the JSON file. Parent dirs are created as needed. */
|
|
119
|
+
filePath: string;
|
|
120
|
+
/** Extract the unique key (e.g. origin / account id) from a record. */
|
|
121
|
+
keyOf: (session: T) => string;
|
|
122
|
+
/**
|
|
123
|
+
* Normalize a key before storing/looking up. Defaults to
|
|
124
|
+
* {@link normalizeOrigin}. The stored record's key field is also normalized.
|
|
125
|
+
*/
|
|
126
|
+
normalizeKey?: (key: string) => string;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Disk-persisted session store with hardened file permissions. Records are kept
|
|
130
|
+
* in a `Map` keyed by their normalized key, persisted as a JSON array (insertion
|
|
131
|
+
* order). The file is written with mode `0600` and its directory `0700` so other
|
|
132
|
+
* users on the machine cannot read captured credentials.
|
|
133
|
+
*
|
|
134
|
+
* `add` marks the record most-recently-used; {@link SessionStore.getActiveSession}
|
|
135
|
+
* (and `get()` with no argument) returns it, so a tool call that omits an explicit
|
|
136
|
+
* key picks up the latest session automatically.
|
|
137
|
+
*/
|
|
138
|
+
export declare class SessionStore<T extends Record<string, unknown>> {
|
|
139
|
+
private sessions;
|
|
140
|
+
private mostRecentKey;
|
|
141
|
+
private readonly filePath;
|
|
142
|
+
private readonly keyOf;
|
|
143
|
+
private readonly normalizeKey;
|
|
144
|
+
constructor(opts: SessionStoreOptions<T>);
|
|
145
|
+
private loadFromDisk;
|
|
146
|
+
/** Serialize the store to its on-disk JSON form (array, insertion order). */
|
|
147
|
+
serialize(): string;
|
|
148
|
+
/** Parse on-disk JSON back into a keyed `Map`; empty map on invalid input. */
|
|
149
|
+
private deserialize;
|
|
150
|
+
private saveToDisk;
|
|
151
|
+
/** Insert or replace a record, normalizing its key and marking it active. */
|
|
152
|
+
add(session: T): void;
|
|
153
|
+
/** Look up by key; with no key, returns the active (most-recent) session. */
|
|
154
|
+
get(key?: string): T | null;
|
|
155
|
+
/** The most-recently-added session, or `null`. */
|
|
156
|
+
getActiveSession(): T | null;
|
|
157
|
+
/** All sessions in insertion order. */
|
|
158
|
+
list(): T[];
|
|
159
|
+
/** Remove a session; fixes up the active pointer. Returns whether it existed. */
|
|
160
|
+
remove(key: string): boolean;
|
|
161
|
+
/** Clear in-memory state without touching disk. Test helper. */
|
|
162
|
+
resetForTest(): void;
|
|
163
|
+
}
|
|
164
|
+
/** Refresh proactively this many ms before the access token expires. */
|
|
165
|
+
export declare const TOKEN_REFRESH_SKEW_MS: number;
|
|
166
|
+
/** A bearer access token + (optional) refresh token + absolute expiry. */
|
|
167
|
+
export interface BearerTokens {
|
|
168
|
+
accessToken: string;
|
|
169
|
+
/** Refresh token, if the flow uses one. */
|
|
170
|
+
refreshToken?: string;
|
|
171
|
+
/** Absolute expiry in epoch milliseconds. */
|
|
172
|
+
expiresAt: number;
|
|
173
|
+
}
|
|
174
|
+
/** Result a {@link TokenManagerOptions.refresh} call must return. */
|
|
175
|
+
export interface RefreshedTokens {
|
|
176
|
+
accessToken: string;
|
|
177
|
+
/** Omit to keep the current refresh token (rotation is optional). */
|
|
178
|
+
refreshToken?: string;
|
|
179
|
+
expiresAt: number;
|
|
180
|
+
}
|
|
181
|
+
/** Options for {@link TokenManager}. */
|
|
182
|
+
export interface TokenManagerOptions {
|
|
183
|
+
/** Initial tokens (typically from env or a one-shot bootstrap). */
|
|
184
|
+
initial: BearerTokens;
|
|
185
|
+
/**
|
|
186
|
+
* Exchange the current refresh token for fresh tokens. Called at most once
|
|
187
|
+
* per concurrent burst (the in-flight promise is shared).
|
|
188
|
+
*/
|
|
189
|
+
refresh: (refreshToken: string) => Promise<RefreshedTokens>;
|
|
190
|
+
/**
|
|
191
|
+
* Override the skew window (ms before expiry that triggers a proactive
|
|
192
|
+
* refresh). Defaults to {@link TOKEN_REFRESH_SKEW_MS} (5 minutes).
|
|
193
|
+
*/
|
|
194
|
+
skewMs?: number;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Manages a bearer access token's lifecycle:
|
|
198
|
+
*
|
|
199
|
+
* - **Proactive:** {@link TokenManager.getAccessToken} refreshes when the token
|
|
200
|
+
* is within `skewMs` (default 5 min) of expiry, returning a still-valid token.
|
|
201
|
+
* - **Reactive:** {@link TokenManager.withAuth} runs a request, and on a `401`
|
|
202
|
+
* refreshes once and replays exactly once (no infinite loop).
|
|
203
|
+
* - **Race-safe:** concurrent refreshes coalesce onto a single in-flight promise
|
|
204
|
+
* (semaphore), so a burst of callers triggers exactly ONE token exchange. The
|
|
205
|
+
* in-flight promise is cleared on settle so a later refresh can run again.
|
|
206
|
+
*/
|
|
207
|
+
export declare class TokenManager {
|
|
208
|
+
private accessToken;
|
|
209
|
+
private refreshToken;
|
|
210
|
+
private expiresAt;
|
|
211
|
+
private readonly refreshFn;
|
|
212
|
+
private readonly skewMs;
|
|
213
|
+
private inFlight;
|
|
214
|
+
constructor(opts: TokenManagerOptions);
|
|
215
|
+
/** Whether the token is within the skew window of (or past) expiry. */
|
|
216
|
+
private needsRefresh;
|
|
217
|
+
/**
|
|
218
|
+
* Single-flight refresh. Concurrent callers share one in-flight promise; it is
|
|
219
|
+
* cleared on settle (success or failure) so a subsequent refresh can proceed.
|
|
220
|
+
*/
|
|
221
|
+
refreshNow(): Promise<void>;
|
|
222
|
+
/** Get a valid access token, refreshing proactively inside the skew window. */
|
|
223
|
+
getAccessToken(): Promise<string>;
|
|
224
|
+
/** Current absolute expiry (epoch ms). */
|
|
225
|
+
getExpiresAt(): number;
|
|
226
|
+
/**
|
|
227
|
+
* Run an authenticated request with reactive 401-replay. `call` receives a
|
|
228
|
+
* valid access token and returns a `Response`. On `401`, the token is
|
|
229
|
+
* refreshed once and `call` is invoked again exactly once.
|
|
230
|
+
*/
|
|
231
|
+
withAuth(call: (accessToken: string) => Promise<Response>): Promise<Response>;
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAYH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAOzE,4EAA4E;AAC5E,MAAM,MAAM,QAAQ,GAAG,iBAAiB,GAAG,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAErE,wEAAwE;AACxE,MAAM,WAAW,YAAY;IAC3B,2FAA2F;IAC3F,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,QAAQ,CAAC;IACpB,8DAA8D;IAC9D,UAAU,EAAE,OAAO,CAAC;IACpB,uEAAuE;IACvE,aAAa,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,qDAAqD;AACrD,MAAM,WAAW,YAAY;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,QAAQ,CAAC;IACrB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAOD;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAC5D,OAAO,CAAC,QAAQ,CAAuB;IAEvC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,YAAY;IA6B1C,uEAAuE;IACvE,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAMrC,2DAA2D;IAC3D,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IAK3C,wDAAwD;IACxD,UAAU,IAAI,cAAc;IAO5B,qCAAqC;IACrC,eAAe,IAAI,MAAM,GAAG,IAAI;IAIhC,qCAAqC;IACrC,IAAI,IAAI,MAAM;IAId;;;;;;OAMG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI;IAYrD,oCAAoC;IACpC,KAAK,IAAI,IAAI;CAId;AAED,2DAA2D;AAC3D,wBAAgB,qBAAqB,IAAI,eAAe,CAEvD;AAED,gDAAgD;AAChD,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,2BAA2B,GAChC,IAAI,CAqFN;AAMD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMrD;AAED,2EAA2E;AAC3E,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,KAAK,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,MAAM,CAAC;IAC9B;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CACxC;AAED;;;;;;;;;GASG;AACH,qBAAa,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACzD,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA0B;gBAE3C,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAOxC,OAAO,CAAC,YAAY;IAYpB,6EAA6E;IAC7E,SAAS,IAAI,MAAM;IAInB,8EAA8E;IAC9E,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,UAAU;IAalB,6EAA6E;IAC7E,GAAG,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI;IAOrB,6EAA6E;IAC7E,GAAG,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI;IAM3B,kDAAkD;IAClD,gBAAgB,IAAI,CAAC,GAAG,IAAI;IAI5B,uCAAuC;IACvC,IAAI,IAAI,CAAC,EAAE;IAIX,iFAAiF;IACjF,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAa5B,gEAAgE;IAChE,YAAY,IAAI,IAAI;CAIrB;AAMD,wEAAwE;AACxE,eAAO,MAAM,qBAAqB,QAAgB,CAAC;AAEnD,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qEAAqE;AACrE,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,mEAAmE;IACnE,OAAO,EAAE,YAAY,CAAC;IACtB;;;OAGG;IACH,OAAO,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IAC5D;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqD;IAC/E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAA4B;gBAEhC,IAAI,EAAE,mBAAmB;IAQrC,uEAAuE;IACvE,OAAO,CAAC,YAAY;IAIpB;;;OAGG;IACH,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB3B,+EAA+E;IACzE,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAKvC,0CAA0C;IAC1C,YAAY,IAAI,MAAM;IAItB;;;;OAIG;IACG,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;CAQpF"}
|