@jini-ai/mcp 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.
Files changed (86) hide show
  1. package/LICENSE +201 -0
  2. package/dist/agent-install/index.d.ts +9 -0
  3. package/dist/agent-install/index.d.ts.map +1 -0
  4. package/dist/agent-install/index.js +9 -0
  5. package/dist/agent-install/index.js.map +1 -0
  6. package/dist/agent-install/install.d.ts +97 -0
  7. package/dist/agent-install/install.d.ts.map +1 -0
  8. package/dist/agent-install/install.js +410 -0
  9. package/dist/agent-install/install.js.map +1 -0
  10. package/dist/bin/serve.d.ts +75 -0
  11. package/dist/bin/serve.d.ts.map +1 -0
  12. package/dist/bin/serve.js +79 -0
  13. package/dist/bin/serve.js.map +1 -0
  14. package/dist/client/client.d.ts +48 -0
  15. package/dist/client/client.d.ts.map +1 -0
  16. package/dist/client/client.js +242 -0
  17. package/dist/client/client.js.map +1 -0
  18. package/dist/client/index.d.ts +8 -0
  19. package/dist/client/index.d.ts.map +1 -0
  20. package/dist/client/index.js +8 -0
  21. package/dist/client/index.js.map +1 -0
  22. package/dist/core/config.d.ts +157 -0
  23. package/dist/core/config.d.ts.map +1 -0
  24. package/dist/core/config.js +475 -0
  25. package/dist/core/config.js.map +1 -0
  26. package/dist/core/index.d.ts +14 -0
  27. package/dist/core/index.d.ts.map +1 -0
  28. package/dist/core/index.js +14 -0
  29. package/dist/core/index.js.map +1 -0
  30. package/dist/core/install-info.d.ts +69 -0
  31. package/dist/core/install-info.d.ts.map +1 -0
  32. package/dist/core/install-info.js +79 -0
  33. package/dist/core/install-info.js.map +1 -0
  34. package/dist/core/oauth.d.ts +274 -0
  35. package/dist/core/oauth.d.ts.map +1 -0
  36. package/dist/core/oauth.js +667 -0
  37. package/dist/core/oauth.js.map +1 -0
  38. package/dist/core/secure-write.d.ts +17 -0
  39. package/dist/core/secure-write.d.ts.map +1 -0
  40. package/dist/core/secure-write.js +72 -0
  41. package/dist/core/secure-write.js.map +1 -0
  42. package/dist/core/tokens.d.ts +72 -0
  43. package/dist/core/tokens.d.ts.map +1 -0
  44. package/dist/core/tokens.js +197 -0
  45. package/dist/core/tokens.js.map +1 -0
  46. package/dist/index.d.ts +28 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +29 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/server/daemon-client.d.ts +21 -0
  51. package/dist/server/daemon-client.d.ts.map +1 -0
  52. package/dist/server/daemon-client.js +176 -0
  53. package/dist/server/daemon-client.js.map +1 -0
  54. package/dist/server/index.d.ts +18 -0
  55. package/dist/server/index.d.ts.map +1 -0
  56. package/dist/server/index.js +18 -0
  57. package/dist/server/index.js.map +1 -0
  58. package/dist/server/resource-protocol.d.ts +38 -0
  59. package/dist/server/resource-protocol.d.ts.map +1 -0
  60. package/dist/server/resource-protocol.js +68 -0
  61. package/dist/server/resource-protocol.js.map +1 -0
  62. package/dist/server/resources/active-resource.d.ts +12 -0
  63. package/dist/server/resources/active-resource.d.ts.map +1 -0
  64. package/dist/server/resources/active-resource.js +46 -0
  65. package/dist/server/resources/active-resource.js.map +1 -0
  66. package/dist/server/tool-protocol.d.ts +45 -0
  67. package/dist/server/tool-protocol.d.ts.map +1 -0
  68. package/dist/server/tool-protocol.js +74 -0
  69. package/dist/server/tool-protocol.js.map +1 -0
  70. package/dist/server/tool-server.d.ts +105 -0
  71. package/dist/server/tool-server.d.ts.map +1 -0
  72. package/dist/server/tool-server.js +96 -0
  73. package/dist/server/tool-server.js.map +1 -0
  74. package/dist/server/tools/delegated-tool.d.ts +15 -0
  75. package/dist/server/tools/delegated-tool.d.ts.map +1 -0
  76. package/dist/server/tools/delegated-tool.js +90 -0
  77. package/dist/server/tools/delegated-tool.js.map +1 -0
  78. package/dist/server/tools/run-tools.d.ts +14 -0
  79. package/dist/server/tools/run-tools.d.ts.map +1 -0
  80. package/dist/server/tools/run-tools.js +142 -0
  81. package/dist/server/tools/run-tools.js.map +1 -0
  82. package/dist/server/tools/tool-catalog-tools.d.ts +8 -0
  83. package/dist/server/tools/tool-catalog-tools.d.ts.map +1 -0
  84. package/dist/server/tools/tool-catalog-tools.js +66 -0
  85. package/dist/server/tools/tool-catalog-tools.js.map +1 -0
  86. package/package.json +50 -0
@@ -0,0 +1,176 @@
1
+ /**
2
+ * @module @jini-ai/mcp/server/daemon-client
3
+ *
4
+ * Bounded-I/O JSON GET/POST against a trusted daemon base URL — the transport
5
+ * primitive every proxy tool `createMcpToolServer` hosts is built on
6
+ * (`../client/client.js`'s `createMcpIdleExitController` is the other half of
7
+ * the mechanism; this module is the network half).
8
+ *
9
+ * Deliberately NOT `@jini-ai/cli`'s `getJsonFromDaemon`/`postJsonToDaemon`
10
+ * (`packages/cli/src/http.ts`): those map a failure onto `process.exit`,
11
+ * which is the right contract for a one-shot CLI invocation but wrong here —
12
+ * a stdio MCP server is a long-lived process serving many tool calls, and one
13
+ * failed call must return an MCP `{isError:true}` result and keep serving the
14
+ * next call, not terminate the process. This module ports the same
15
+ * bounded-read / timeout / redaction posture `http.ts` established
16
+ * (CR-004/SEC-RB-009 — see `ADS-memory/reports/code-review/CR-remaining-backend-audit-2026-07-21.md`,
17
+ * `ADS-memory/reports/security/SEC-remaining-backend-audit-2026-07-21.md`) as
18
+ * a throw-based primitive instead: a failed request throws a plain,
19
+ * already-redacted `Error` whose message a tool handler's caller
20
+ * (`handleToolCall` in `./tool-protocol.js`) turns into that MCP error result.
21
+ *
22
+ * No SSRF hardening here (unlike `../core/oauth.ts`'s `safeOAuthFetch`): the
23
+ * target is a caller-resolved, typically-loopback daemon the user already
24
+ * trusts enough to run — not an attacker- or server-metadata-controlled
25
+ * remote URL the way a configured external MCP server's OAuth endpoints are.
26
+ * This mirrors `@jini-ai/cli/http.ts`'s own posture for the identical "fetch my
27
+ * own daemon" concern (no `assertSafePublicUrl` there either).
28
+ */
29
+ import { sanitizeUntrustedText } from '@jini-ai/cli';
30
+ /** Request deadline. Generous enough for a slow tool call, short enough that a stalled daemon doesn't hang a stdio server turn forever. */
31
+ const DEFAULT_TIMEOUT_MS = 15_000;
32
+ /** Cap on a daemon response body. Comfortably larger than any real status/run envelope, small enough to bound worst-case memory from a hostile/broken daemon. */
33
+ const DEFAULT_MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
34
+ /** Thrown internally when a response exceeds its byte cap; always translated to a plain `Error` before it crosses this module's public functions. */
35
+ export class DaemonResponseTooLargeError extends Error {
36
+ limitBytes;
37
+ constructor(limitBytes) {
38
+ super(`daemon response exceeded the ${limitBytes}-byte limit`);
39
+ this.limitBytes = limitBytes;
40
+ this.name = 'DaemonResponseTooLargeError';
41
+ }
42
+ }
43
+ function parseJsonLoose(text) {
44
+ if (text.length === 0)
45
+ return {};
46
+ try {
47
+ return JSON.parse(text);
48
+ }
49
+ catch {
50
+ return {};
51
+ }
52
+ }
53
+ /**
54
+ * Read `resp`'s body as JSON without ever buffering past `maxBytes`. Prefers streaming via
55
+ * `resp.body`'s reader (what a real `fetch()` `Response` provides), rejecting mid-stream once the
56
+ * cap is exceeded rather than buffering a huge response whole first; falls back to `resp.text()`
57
+ * (still byte-checked before parsing) for response-like test doubles that expose no reader. A body
58
+ * that isn't valid JSON resolves to `{}` rather than throwing — a malformed daemon response should
59
+ * surface as a normal non-2xx/shape mismatch to the caller, not an opaque parse exception.
60
+ */
61
+ async function readJsonWithLimit(resp, maxBytes) {
62
+ const contentLength = resp.headers?.get?.('content-length');
63
+ if (contentLength !== null && contentLength !== undefined) {
64
+ const declared = Number(contentLength);
65
+ if (Number.isFinite(declared) && declared > maxBytes)
66
+ throw new DaemonResponseTooLargeError(maxBytes);
67
+ }
68
+ const body = resp.body;
69
+ if (body != null && typeof body.getReader === 'function') {
70
+ const reader = body.getReader();
71
+ const decoder = new TextDecoder();
72
+ let text = '';
73
+ let total = 0;
74
+ try {
75
+ for (;;) {
76
+ const { done, value } = await reader.read();
77
+ if (done)
78
+ break;
79
+ if (value !== undefined) {
80
+ total += value.byteLength;
81
+ if (total > maxBytes)
82
+ throw new DaemonResponseTooLargeError(maxBytes);
83
+ text += decoder.decode(value, { stream: true });
84
+ }
85
+ }
86
+ text += decoder.decode();
87
+ }
88
+ finally {
89
+ reader.releaseLock();
90
+ }
91
+ return parseJsonLoose(text);
92
+ }
93
+ const text = await resp.text();
94
+ if (Buffer.byteLength(text, 'utf8') > maxBytes)
95
+ throw new DaemonResponseTooLargeError(maxBytes);
96
+ return parseJsonLoose(text);
97
+ }
98
+ /** Formats a `fetch()` rejection (network-unreachable) into a message safe to surface as an MCP tool error. */
99
+ function formatConnectionFailure(err, baseUrl) {
100
+ const cause = err !== null && typeof err === 'object' ? err.cause : null;
101
+ const code = cause !== null && typeof cause === 'object' && typeof cause.code === 'string'
102
+ ? cause.code
103
+ : null;
104
+ if (code === 'ECONNREFUSED' || code === 'ENOTFOUND') {
105
+ return `cannot reach the daemon at ${baseUrl}. Is it running?`;
106
+ }
107
+ const message = err instanceof Error ? err.message : String(err);
108
+ return sanitizeUntrustedText(message);
109
+ }
110
+ /** Formats a non-2xx daemon response into a message safe to surface as an MCP tool error. */
111
+ function formatDaemonHttpError(status, url, data) {
112
+ const envelope = data;
113
+ const code = typeof envelope.error?.code === 'string' ? envelope.error.code : undefined;
114
+ const rawMessage = typeof envelope.error?.message === 'string' ? envelope.error.message : undefined;
115
+ const detail = rawMessage !== undefined ? sanitizeUntrustedText(rawMessage) : `HTTP ${status}`;
116
+ return code !== undefined ? `daemon ${status} on ${url}: ${code}: ${detail}` : `daemon ${status} on ${url}: ${detail}`;
117
+ }
118
+ /**
119
+ * Shared transport core behind {@link getDaemonJson} and {@link postDaemonJson}: fetch
120
+ * `<baseUrl><route>` with a bounded timeout and response-size cap, and turn a network failure or a
121
+ * non-2xx daemon response into a thrown, already-redacted `Error` — this function either resolves
122
+ * with the parsed body or throws. Never calls `process.exit`.
123
+ */
124
+ async function requestDaemonJson(baseUrl, route, init, options = {}) {
125
+ const fetchImpl = options.fetchImpl ?? fetch;
126
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
127
+ const maxResponseBytes = options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
128
+ const url = `${baseUrl}${route}`;
129
+ const timeoutController = new AbortController();
130
+ const timeoutHandle = setTimeout(() => {
131
+ timeoutController.abort(new Error(`request to ${route} timed out after ${timeoutMs}ms`));
132
+ }, timeoutMs);
133
+ // A stdio server turn shouldn't be kept alive purely by this timer.
134
+ timeoutHandle.unref?.();
135
+ const signal = options.signal !== undefined ? AbortSignal.any([options.signal, timeoutController.signal]) : timeoutController.signal;
136
+ try {
137
+ let resp;
138
+ try {
139
+ resp = await fetchImpl(url, {
140
+ method: init.method,
141
+ headers: init.method === 'POST' ? { 'content-type': 'application/json', ...options.headers } : { ...options.headers },
142
+ ...(init.method === 'POST' ? { body: JSON.stringify(init.body ?? {}) } : {}),
143
+ signal,
144
+ });
145
+ }
146
+ catch (err) {
147
+ throw new Error(formatConnectionFailure(err, baseUrl));
148
+ }
149
+ let data;
150
+ try {
151
+ data = await readJsonWithLimit(resp, maxResponseBytes);
152
+ }
153
+ catch (err) {
154
+ if (err instanceof DaemonResponseTooLargeError) {
155
+ throw new Error(`response from ${url} exceeded the ${err.limitBytes}-byte limit`);
156
+ }
157
+ throw err;
158
+ }
159
+ if (!resp.ok) {
160
+ throw new Error(formatDaemonHttpError(resp.status, url, data));
161
+ }
162
+ return data;
163
+ }
164
+ finally {
165
+ clearTimeout(timeoutHandle);
166
+ }
167
+ }
168
+ /** `GET <baseUrl><route>` and return the parsed JSON response, throwing on network failure or a non-2xx status. */
169
+ export async function getDaemonJson(baseUrl, route, options) {
170
+ return requestDaemonJson(baseUrl, route, { method: 'GET' }, options);
171
+ }
172
+ /** `POST body` (JSON-serialized) to `<baseUrl><route>` and return the parsed JSON response, throwing on network failure or a non-2xx status. */
173
+ export async function postDaemonJson(baseUrl, route, body, options) {
174
+ return requestDaemonJson(baseUrl, route, { method: 'POST', body }, options);
175
+ }
176
+ //# sourceMappingURL=daemon-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-client.js","sourceRoot":"","sources":["../../src/server/daemon-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @module @jini-ai/mcp/server
3
+ * Sub-barrel for the MCP tool-hosting mechanism: the pure tool protocol
4
+ * layer (`tool-protocol.ts`), the pure resource protocol layer
5
+ * (`resource-protocol.ts`), the daemon transport (`daemon-client.ts`), the
6
+ * `Server`/`StdioServerTransport` wiring (`tool-server.ts`), the concrete
7
+ * kernel-run tool defs (`tools/run-tools.ts`), the concrete kernel
8
+ * resource defs (`resources/active-resource.ts`), and the delegated-tool
9
+ * execution def (`tools/delegated-tool.ts`).
10
+ */
11
+ export * from './tool-protocol.js';
12
+ export * from './resource-protocol.js';
13
+ export * from './daemon-client.js';
14
+ export * from './tool-server.js';
15
+ export * from './tools/run-tools.js';
16
+ export * from './tools/delegated-tool.js';
17
+ export * from './resources/active-resource.js';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gCAAgC,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @module @jini-ai/mcp/server
3
+ * Sub-barrel for the MCP tool-hosting mechanism: the pure tool protocol
4
+ * layer (`tool-protocol.ts`), the pure resource protocol layer
5
+ * (`resource-protocol.ts`), the daemon transport (`daemon-client.ts`), the
6
+ * `Server`/`StdioServerTransport` wiring (`tool-server.ts`), the concrete
7
+ * kernel-run tool defs (`tools/run-tools.ts`), the concrete kernel
8
+ * resource defs (`resources/active-resource.ts`), and the delegated-tool
9
+ * execution def (`tools/delegated-tool.ts`).
10
+ */
11
+ export * from './tool-protocol.js';
12
+ export * from './resource-protocol.js';
13
+ export * from './daemon-client.js';
14
+ export * from './tool-server.js';
15
+ export * from './tools/run-tools.js';
16
+ export * from './tools/delegated-tool.js';
17
+ export * from './resources/active-resource.js';
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gCAAgC,CAAC"}
@@ -0,0 +1,38 @@
1
+ import type { ReadResourceResult, Resource } from '@modelcontextprotocol/sdk/types.js';
2
+ import type { McpToolContext } from './tool-protocol.js';
3
+ /** What one resource's `read` returns before this module wraps it into the MCP wire shape. */
4
+ export interface McpResourceReadResult {
5
+ readonly text: string;
6
+ /** Overrides the resource def's own `mimeType` for this particular read. Rarely needed — most resources have a fixed content type. */
7
+ readonly mimeType?: string;
8
+ }
9
+ /**
10
+ * One read-only MCP resource a `createMcpToolServer` caller registers. `uri` must be unique within
11
+ * a given resource list (`buildResourceIndex` throws otherwise, mirroring `buildToolIndex`'s
12
+ * duplicate-name guard). Unlike a tool, a resource takes no caller-supplied arguments — it is
13
+ * addressed purely by its static `uri`.
14
+ */
15
+ export interface McpResourceDef {
16
+ readonly uri: string;
17
+ readonly name: string;
18
+ readonly description?: string;
19
+ readonly mimeType?: string;
20
+ readonly read: (ctx: McpToolContext) => Promise<McpResourceReadResult> | McpResourceReadResult;
21
+ }
22
+ /** Projects a resource list into the `Resource[]` shape `resources/list` returns. */
23
+ export declare function resourcesToList(resources: readonly McpResourceDef[]): Resource[];
24
+ /** Builds a uri -> def lookup, throwing if two resources in `resources` share a uri (a caller-configuration bug, surfaced eagerly at server-construction time rather than letting the second registration shadow the first). */
25
+ export declare function buildResourceIndex(resources: readonly McpResourceDef[]): Map<string, McpResourceDef>;
26
+ /**
27
+ * `resources/read` dispatch: looks up `uri` in `resources` and runs its `read`. Unlike
28
+ * {@link import('./tool-protocol.js').handleToolCall}, a failure here *throws* rather than
29
+ * returning an `{isError:true}` result — MCP resources have no such protocol-level "soft failure"
30
+ * content shape; an unknown uri or a read failure is meant to surface as a JSON-RPC error response,
31
+ * matching both the MCP spec's `resources/read` contract and the OD origin's own
32
+ * `ReadResourceRequestSchema` handler (which likewise just threw a plain `Error` on an unrecognized
33
+ * uri). The thrown message is passed through {@link sanitizeUntrustedText} unconditionally before
34
+ * it leaves this function, the same conservative posture `handleToolCall` uses — a `read`
35
+ * implementation may re-throw text that traces back to an untrusted daemon response.
36
+ */
37
+ export declare function handleResourceRead(uri: string, resources: ReadonlyMap<string, McpResourceDef>, ctx: McpToolContext): Promise<ReadResourceResult>;
38
+ //# sourceMappingURL=resource-protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-protocol.d.ts","sourceRoot":"","sources":["../../src/server/resource-protocol.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AACvF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,8FAA8F;AAC9F,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sIAAsI;IACtI,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,qBAAqB,CAAC,GAAG,qBAAqB,CAAC;CAChG;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE,GAAG,QAAQ,EAAE,CAOhF;AAED,gOAAgO;AAChO,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CASpG;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,EAC9C,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,kBAAkB,CAAC,CAsB7B"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @module @jini-ai/mcp/server/resource-protocol
3
+ *
4
+ * The pure, SDK-connection-free half of the MCP *resource* surface —
5
+ * mirrors `./tool-protocol.js` for `resources/list` and `resources/read`.
6
+ * A resource is read-only, addressed by a static `uri` with no arguments,
7
+ * and returns text content; `./tool-server.js` is the thin layer that wires
8
+ * this to a real `Server`'s `ListResourcesRequestSchema`/
9
+ * `ReadResourceRequestSchema` handlers, exactly as `tool-protocol.js` is
10
+ * wired to `ListToolsRequestSchema`/`CallToolRequestSchema`.
11
+ */
12
+ import { sanitizeUntrustedText } from '@jini-ai/cli';
13
+ /** Projects a resource list into the `Resource[]` shape `resources/list` returns. */
14
+ export function resourcesToList(resources) {
15
+ return resources.map((resource) => ({
16
+ uri: resource.uri,
17
+ name: resource.name,
18
+ ...(resource.description !== undefined ? { description: resource.description } : {}),
19
+ ...(resource.mimeType !== undefined ? { mimeType: resource.mimeType } : {}),
20
+ }));
21
+ }
22
+ /** Builds a uri -> def lookup, throwing if two resources in `resources` share a uri (a caller-configuration bug, surfaced eagerly at server-construction time rather than letting the second registration shadow the first). */
23
+ export function buildResourceIndex(resources) {
24
+ const index = new Map();
25
+ for (const resource of resources) {
26
+ if (index.has(resource.uri)) {
27
+ throw new Error(`createMcpToolServer: duplicate resource uri "${resource.uri}"`);
28
+ }
29
+ index.set(resource.uri, resource);
30
+ }
31
+ return index;
32
+ }
33
+ /**
34
+ * `resources/read` dispatch: looks up `uri` in `resources` and runs its `read`. Unlike
35
+ * {@link import('./tool-protocol.js').handleToolCall}, a failure here *throws* rather than
36
+ * returning an `{isError:true}` result — MCP resources have no such protocol-level "soft failure"
37
+ * content shape; an unknown uri or a read failure is meant to surface as a JSON-RPC error response,
38
+ * matching both the MCP spec's `resources/read` contract and the OD origin's own
39
+ * `ReadResourceRequestSchema` handler (which likewise just threw a plain `Error` on an unrecognized
40
+ * uri). The thrown message is passed through {@link sanitizeUntrustedText} unconditionally before
41
+ * it leaves this function, the same conservative posture `handleToolCall` uses — a `read`
42
+ * implementation may re-throw text that traces back to an untrusted daemon response.
43
+ */
44
+ export async function handleResourceRead(uri, resources, ctx) {
45
+ const resource = resources.get(uri);
46
+ if (resource === undefined) {
47
+ throw new Error(`unsupported resource URI: ${uri}`);
48
+ }
49
+ let result;
50
+ try {
51
+ result = await resource.read(ctx);
52
+ }
53
+ catch (err) {
54
+ const message = err instanceof Error ? err.message : String(err);
55
+ throw new Error(sanitizeUntrustedText(message));
56
+ }
57
+ const mimeType = result.mimeType ?? resource.mimeType;
58
+ return {
59
+ contents: [
60
+ {
61
+ uri,
62
+ text: result.text,
63
+ ...(mimeType !== undefined ? { mimeType } : {}),
64
+ },
65
+ ],
66
+ };
67
+ }
68
+ //# sourceMappingURL=resource-protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-protocol.js","sourceRoot":"","sources":["../../src/server/resource-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAyBrD,qFAAqF;AACrF,MAAM,UAAU,eAAe,CAAC,SAAoC;IAClE,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAClC,GAAG,EAAE,QAAQ,CAAC,GAAG;QACjB,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,GAAG,CAAC,QAAQ,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpF,GAAG,CAAC,QAAQ,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5E,CAAC,CAAC,CAAC;AACN,CAAC;AAED,gOAAgO;AAChO,MAAM,UAAU,kBAAkB,CAAC,SAAoC;IACrE,MAAM,KAAK,GAAG,IAAI,GAAG,EAA0B,CAAC;IAChD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,IAAI,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,gDAAgD,QAAQ,CAAC,GAAG,GAAG,CAAC,CAAC;QACnF,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,GAAW,EACX,SAA8C,EAC9C,GAAmB;IAEnB,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,6BAA6B,GAAG,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,MAA6B,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;IAClD,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC;IACtD,OAAO;QACL,QAAQ,EAAE;YACR;gBACE,GAAG;gBACH,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAChD;SACF;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { McpResourceDef } from '../resource-protocol.js';
2
+ /**
3
+ * `jini://active` -> `GET /api/active`. Returns the raw daemon payload as formatted JSON text,
4
+ * unchanged — unlike `getActiveContextTool`, this does not add a conversational hint when
5
+ * `active:false`; a resource is meant to be raw structured data a client attaches to context, not
6
+ * a model-facing tool result, matching the OD origin's own `od://focus/active` handler (which also
7
+ * returned the raw `/api/active` body with no special-casing).
8
+ */
9
+ export declare const activeContextResource: McpResourceDef;
10
+ /** The full set of kernel resource defs this package ships, ready to pass as `createMcpToolServer`'s `resources` option. */
11
+ export declare const KERNEL_RESOURCES: readonly McpResourceDef[];
12
+ //# sourceMappingURL=active-resource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-resource.d.ts","sourceRoot":"","sources":["../../../src/server/resources/active-resource.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAO9D;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAUnC,CAAC;AAEF,4HAA4H;AAC5H,eAAO,MAAM,gBAAgB,EAAE,SAAS,cAAc,EAA4B,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @module @jini-ai/mcp/server/resources/active-resource
3
+ *
4
+ * The one MCP *resource* this package ships: a read-only pointer to
5
+ * `GET /api/active` (`packages/http/src/active-context.ts`'s `getActiveRoute`)
6
+ * — the same underlying data `../tools/run-tools.js`'s `getActiveContextTool`
7
+ * already proxies as a *tool*. This is not redundant: OD's origin
8
+ * (`apps/daemon/src/mcp.ts`) exposed the identical `/api/active` payload
9
+ * both ways — as the `get_active_context` tool (already ported, see
10
+ * `../tools/run-tools.js`) AND as the `od://focus/active` resource (its
11
+ * `ListResourcesRequestSchema`/`ReadResourceRequestSchema` handlers) —
12
+ * because tools and resources serve different MCP client affordances: a
13
+ * tool is invoked by the model mid-conversation, while a resource can be
14
+ * listed and attached to context by the user/client without any tool call.
15
+ * This module is that second affordance for the same primitive. See
16
+ * `source-map.md`'s 2026-07-21 addition for why the rest of OD's resource
17
+ * surface (`od://skills/...`, `od://design-systems/...`) was NOT ported the
18
+ * same way (both require a Skill/DesignSystem noun this kernel doesn't have).
19
+ *
20
+ * Security posture matches every other tool/resource in this package: no
21
+ * separate authorization mechanism here — whatever `@jini-ai/http`'s
22
+ * same-origin guard / bearer-auth middleware already enforces on
23
+ * `GET /api/active` is the only gate a read of this resource passes
24
+ * through.
25
+ */
26
+ import { getDaemonJson } from '../daemon-client.js';
27
+ /**
28
+ * `jini://active` -> `GET /api/active`. Returns the raw daemon payload as formatted JSON text,
29
+ * unchanged — unlike `getActiveContextTool`, this does not add a conversational hint when
30
+ * `active:false`; a resource is meant to be raw structured data a client attaches to context, not
31
+ * a model-facing tool result, matching the OD origin's own `od://focus/active` handler (which also
32
+ * returned the raw `/api/active` body with no special-casing).
33
+ */
34
+ export const activeContextResource = {
35
+ uri: 'jini://active',
36
+ name: 'Active context',
37
+ description: 'The resource (resourceRef) plus optional detail the caller last recorded as its current focus via POST /api/active — the same generic, product-neutral pointer the get_active_context tool proxies, exposed here as an attachable MCP resource instead of a tool call.',
38
+ mimeType: 'application/json',
39
+ read: async (ctx) => {
40
+ const data = await getDaemonJson(ctx.baseUrl, '/api/active', { fetchImpl: ctx.fetchImpl });
41
+ return { text: JSON.stringify(data, null, 2) };
42
+ },
43
+ };
44
+ /** The full set of kernel resource defs this package ships, ready to pass as `createMcpToolServer`'s `resources` option. */
45
+ export const KERNEL_RESOURCES = [activeContextResource];
46
+ //# sourceMappingURL=active-resource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-resource.js","sourceRoot":"","sources":["../../../src/server/resources/active-resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAQpD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAmB;IACnD,GAAG,EAAE,eAAe;IACpB,IAAI,EAAE,gBAAgB;IACtB,WAAW,EACT,wQAAwQ;IAC1Q,QAAQ,EAAE,kBAAkB;IAC5B,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QAClB,MAAM,IAAI,GAAG,MAAM,aAAa,CAAuB,GAAG,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC;QACjH,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;IACjD,CAAC;CACF,CAAC;AAEF,4HAA4H;AAC5H,MAAM,CAAC,MAAM,gBAAgB,GAA8B,CAAC,qBAAqB,CAAC,CAAC"}
@@ -0,0 +1,45 @@
1
+ import type { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ /** What every tool handler receives alongside its parsed arguments. */
3
+ export interface McpToolContext {
4
+ /** The resolved daemon HTTP base URL (no trailing slash), fixed for the lifetime of one server run. */
5
+ readonly baseUrl: string;
6
+ /** Defaults to the global `fetch`; threaded through so a host can inject its own (e.g. for tests). */
7
+ readonly fetchImpl: typeof fetch;
8
+ }
9
+ /**
10
+ * One MCP tool a `createMcpToolServer` caller registers. `name` must be unique within a given
11
+ * tool list (`buildToolIndex` throws otherwise — a caller bug, not something to silently drop).
12
+ * `handler` returns a JSON-serializable payload on success or throws an `Error` (or any value —
13
+ * non-`Error` throws are stringified) on failure; both are converted to the matching MCP
14
+ * `CallToolResult` shape by {@link handleToolCall}, so individual tools never construct MCP
15
+ * protocol objects themselves.
16
+ */
17
+ export interface McpToolDef<Args extends Record<string, unknown> = Record<string, unknown>> {
18
+ readonly name: string;
19
+ readonly description: string;
20
+ readonly inputSchema: Tool['inputSchema'];
21
+ readonly annotations?: Tool['annotations'];
22
+ readonly handler: (args: Args, ctx: McpToolContext) => Promise<unknown> | unknown;
23
+ }
24
+ /** Wraps a successful tool result as MCP `text` content, JSON-stringifying anything that isn't already a string. */
25
+ export declare function okResult(payload: unknown): CallToolResult;
26
+ /** Wraps a tool failure as an MCP `isError` result. */
27
+ export declare function errorResult(message: string): CallToolResult;
28
+ /** Throws a caller-facing validation error unless `value` is a non-empty string. Mirrors the OD origin's `requireString` — a convenience for tool authors, not part of the MCP protocol itself. */
29
+ export declare function requireString(value: unknown, name: string): asserts value is string;
30
+ /** Projects a tool list into the `Tool[]` shape `tools/list` returns. */
31
+ export declare function toolsToList(tools: readonly McpToolDef[]): Tool[];
32
+ /** Builds a name -> def lookup, throwing if two tools in `tools` share a name (a caller-configuration bug, surfaced eagerly at server-construction time rather than silently letting the second registration shadow the first). */
33
+ export declare function buildToolIndex(tools: readonly McpToolDef[]): Map<string, McpToolDef>;
34
+ /**
35
+ * `tools/call` dispatch: looks up `name` in `tools`, runs its handler against `rawArgs`, and
36
+ * converts the outcome to a `CallToolResult` — an unknown tool name or a thrown error both
37
+ * produce an `{isError:true}` result rather than rejecting, matching MCP's convention that tool
38
+ * failures are protocol-level results, not JSON-RPC errors. A thrown error's message is passed
39
+ * through {@link sanitizeUntrustedText} before it reaches the result: `handler` may re-throw text
40
+ * that ultimately traces back to the daemon (untrusted network peer), and a caller-thrown
41
+ * validation error is cheap to sanitize unconditionally rather than trying to prove which case
42
+ * applies at each call site.
43
+ */
44
+ export declare function handleToolCall(name: string, rawArgs: Record<string, unknown> | undefined, tools: ReadonlyMap<string, McpToolDef>, ctx: McpToolContext): Promise<CallToolResult>;
45
+ //# sourceMappingURL=tool-protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-protocol.d.ts","sourceRoot":"","sources":["../../src/server/tool-protocol.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAE/E,uEAAuE;AACvE,MAAM,WAAW,cAAc;IAC7B,uGAAuG;IACvG,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sGAAsG;IACtG,QAAQ,CAAC,SAAS,EAAE,OAAO,KAAK,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU,CAAC,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACxF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;IAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CACnF;AAED,oHAAoH;AACpH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,cAAc,CAGzD;AAED,uDAAuD;AACvD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAE3D;AAED,mMAAmM;AACnM,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAInF;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,IAAI,EAAE,CAOhE;AAED,mOAAmO;AACnO,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CASpF;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC5C,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,EACtC,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,cAAc,CAAC,CAYzB"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @module @jini-ai/mcp/server/tool-protocol
3
+ *
4
+ * The pure, SDK-connection-free half of the MCP tool-hosting mechanism: the
5
+ * `McpToolDef` contract a caller registers tools against, `tools/list`
6
+ * projection, and `tools/call` dispatch (look up a tool by name, run its
7
+ * handler, wrap the result). None of this touches `@modelcontextprotocol/sdk`'s
8
+ * `Server`/transport classes or the network — a tool `handler` is just
9
+ * `(args, ctx) => value | Promise<value>` that either returns a
10
+ * JSON-serializable payload (wrapped as a successful MCP result) or throws
11
+ * (wrapped as an `{isError:true}` MCP result). `./tool-server.js` is the thin
12
+ * layer that wires this to a real `Server` + `StdioServerTransport`.
13
+ */
14
+ import { sanitizeUntrustedText } from '@jini-ai/cli';
15
+ /** Wraps a successful tool result as MCP `text` content, JSON-stringifying anything that isn't already a string. */
16
+ export function okResult(payload) {
17
+ const text = typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2);
18
+ return { content: [{ type: 'text', text }] };
19
+ }
20
+ /** Wraps a tool failure as an MCP `isError` result. */
21
+ export function errorResult(message) {
22
+ return { isError: true, content: [{ type: 'text', text: message }] };
23
+ }
24
+ /** Throws a caller-facing validation error unless `value` is a non-empty string. Mirrors the OD origin's `requireString` — a convenience for tool authors, not part of the MCP protocol itself. */
25
+ export function requireString(value, name) {
26
+ if (typeof value !== 'string' || value.length === 0) {
27
+ throw new Error(`${name} is required (string).`);
28
+ }
29
+ }
30
+ /** Projects a tool list into the `Tool[]` shape `tools/list` returns. */
31
+ export function toolsToList(tools) {
32
+ return tools.map((tool) => ({
33
+ name: tool.name,
34
+ description: tool.description,
35
+ inputSchema: tool.inputSchema,
36
+ ...(tool.annotations !== undefined ? { annotations: tool.annotations } : {}),
37
+ }));
38
+ }
39
+ /** Builds a name -> def lookup, throwing if two tools in `tools` share a name (a caller-configuration bug, surfaced eagerly at server-construction time rather than silently letting the second registration shadow the first). */
40
+ export function buildToolIndex(tools) {
41
+ const index = new Map();
42
+ for (const tool of tools) {
43
+ if (index.has(tool.name)) {
44
+ throw new Error(`createMcpToolServer: duplicate tool name "${tool.name}"`);
45
+ }
46
+ index.set(tool.name, tool);
47
+ }
48
+ return index;
49
+ }
50
+ /**
51
+ * `tools/call` dispatch: looks up `name` in `tools`, runs its handler against `rawArgs`, and
52
+ * converts the outcome to a `CallToolResult` — an unknown tool name or a thrown error both
53
+ * produce an `{isError:true}` result rather than rejecting, matching MCP's convention that tool
54
+ * failures are protocol-level results, not JSON-RPC errors. A thrown error's message is passed
55
+ * through {@link sanitizeUntrustedText} before it reaches the result: `handler` may re-throw text
56
+ * that ultimately traces back to the daemon (untrusted network peer), and a caller-thrown
57
+ * validation error is cheap to sanitize unconditionally rather than trying to prove which case
58
+ * applies at each call site.
59
+ */
60
+ export async function handleToolCall(name, rawArgs, tools, ctx) {
61
+ const tool = tools.get(name);
62
+ if (tool === undefined) {
63
+ return errorResult(`unknown tool: ${name}`);
64
+ }
65
+ try {
66
+ const result = await tool.handler(rawArgs ?? {}, ctx);
67
+ return okResult(result);
68
+ }
69
+ catch (err) {
70
+ const message = err instanceof Error ? err.message : String(err);
71
+ return errorResult(sanitizeUntrustedText(message));
72
+ }
73
+ }
74
+ //# sourceMappingURL=tool-protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-protocol.js","sourceRoot":"","sources":["../../src/server/tool-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AA2BrD,oHAAoH;AACpH,MAAM,UAAU,QAAQ,CAAC,OAAgB;IACvC,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACtF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;AACvE,CAAC;AAED,mMAAmM;AACnM,MAAM,UAAU,aAAa,CAAC,KAAc,EAAE,IAAY;IACxD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,wBAAwB,CAAC,CAAC;IACnD,CAAC;AACH,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,WAAW,CAAC,KAA4B;IACtD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC1B,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7E,CAAC,CAAC,CAAC;AACN,CAAC;AAED,mOAAmO;AACnO,MAAM,UAAU,cAAc,CAAC,KAA4B;IACzD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,6CAA6C,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QAC7E,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAY,EACZ,OAA4C,EAC5C,KAAsC,EACtC,GAAmB;IAEnB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,WAAW,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;QACtD,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,WAAW,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,CAAC;AACH,CAAC"}