mountly-mcp 4.2.0 → 4.4.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 (46) hide show
  1. package/GLOSSARY.md +1 -0
  2. package/README.md +165 -0
  3. package/dist/bridge/iframe-entry.js +7 -7
  4. package/dist/build/emit-meta.d.ts +3 -0
  5. package/dist/build/index.d.ts +3 -0
  6. package/dist/build/index.js +1 -1
  7. package/dist/chunk-4SZHJ4MK.js +9 -0
  8. package/dist/chunk-4SZHJ4MK.js.map +1 -0
  9. package/dist/{chunk-SXF23BZD.js → chunk-HIAIQVK5.js} +4 -7
  10. package/dist/chunk-HIAIQVK5.js.map +1 -0
  11. package/dist/{chunk-MTJDRWGD.js → chunk-IDGTP2NI.js} +4 -2
  12. package/dist/chunk-IDGTP2NI.js.map +1 -0
  13. package/dist/cli.js +1 -1
  14. package/dist/dev/host-entry.js +6 -6
  15. package/dist/{doctor-B54H6CRN.js → doctor-NB3DE4CG.js} +45 -3
  16. package/dist/doctor-NB3DE4CG.js.map +1 -0
  17. package/dist/events/index.d.ts +97 -0
  18. package/dist/events/index.js +215 -0
  19. package/dist/events/index.js.map +1 -0
  20. package/dist/json-render/index.js +2 -1
  21. package/dist/json-render/index.js.map +1 -1
  22. package/dist/openai/server.d.ts +16 -0
  23. package/dist/openai/server.js +20 -0
  24. package/dist/openai/server.js.map +1 -0
  25. package/dist/react/index.js +4 -2
  26. package/dist/react/openai-extensions.d.ts +6 -0
  27. package/dist/react/openai-extensions.js +25 -0
  28. package/dist/react/openai-extensions.js.map +1 -0
  29. package/dist/server/index.js +11 -6
  30. package/dist/server/index.js.map +1 -1
  31. package/dist/types.d.ts +3 -0
  32. package/dist/vite/index.js +1 -1
  33. package/package.json +20 -3
  34. package/templates/react/README.md.tmpl +3 -0
  35. package/templates/react/package.json.tmpl +6 -3
  36. package/templates/svelte/README.md.tmpl +2 -0
  37. package/templates/svelte/package.json.tmpl +5 -3
  38. package/templates/svelte/src/env.d.ts.tmpl +10 -0
  39. package/templates/vanilla/README.md.tmpl +2 -0
  40. package/templates/vanilla/package.json.tmpl +5 -2
  41. package/templates/vue/README.md.tmpl +2 -0
  42. package/templates/vue/package.json.tmpl +6 -4
  43. package/templates/vue/src/env.d.ts.tmpl +10 -0
  44. package/dist/chunk-MTJDRWGD.js.map +0 -1
  45. package/dist/chunk-SXF23BZD.js.map +0 -1
  46. package/dist/doctor-B54H6CRN.js.map +0 -1
@@ -0,0 +1,97 @@
1
+ import { z } from "zod";
2
+ type MaybePromise<T> = T | Promise<T>;
3
+ export interface EventDefinition<I extends z.ZodType = z.ZodType, P extends z.ZodType = z.ZodType> {
4
+ description?: string;
5
+ /** Subscription arguments (e.g. `{ document_id }`). */
6
+ input: I;
7
+ /** Event `data`. Validated on `emit` when set. */
8
+ payload?: P;
9
+ }
10
+ export interface EventSubscription {
11
+ id: string;
12
+ name: string;
13
+ arguments: Record<string, unknown>;
14
+ url: string;
15
+ secret: string;
16
+ /** Epoch ms. */
17
+ expiresAt: number;
18
+ /** Principal captured at subscribe time; re-checked before every delivery. */
19
+ authInfo?: unknown;
20
+ }
21
+ /** A `Map` works. Persist across restarts in production (the spec requires it). */
22
+ export interface SubscriptionStore {
23
+ get(key: string): MaybePromise<EventSubscription | undefined>;
24
+ set(key: string, subscription: EventSubscription): unknown;
25
+ delete(key: string): unknown;
26
+ values(): MaybePromise<Iterable<EventSubscription>>;
27
+ }
28
+ export interface AuthorizeRequest {
29
+ name: string;
30
+ arguments: Record<string, unknown>;
31
+ authInfo?: unknown;
32
+ }
33
+ export interface DefineEventsOptions<E extends Record<string, EventDefinition>> {
34
+ events: E;
35
+ store?: SubscriptionStore;
36
+ /**
37
+ * Called on subscribe/unsubscribe with the caller, and before each delivery with
38
+ * the principal stored at subscribe time. Return `false` to deny; a denied
39
+ * delivery deletes the subscription.
40
+ */
41
+ authorize?: (request: AuthorizeRequest) => MaybePromise<boolean>;
42
+ /** Default: `https:` only. Narrow further to block SSRF to internal hosts. */
43
+ allowCallbackUrl?: (url: URL) => boolean;
44
+ /** Default and maximum subscription lifetime. Default 30 days. */
45
+ maxTtlMs?: number;
46
+ /** Delivery attempts per event. Default 5. */
47
+ maxAttempts?: number;
48
+ /** First retry delay; doubles each attempt. Default 1000. */
49
+ retryBaseMs?: number;
50
+ fetch?: typeof fetch;
51
+ }
52
+ export interface EmitResult {
53
+ subscriptionId: string;
54
+ status: number;
55
+ delivered: boolean;
56
+ }
57
+ /** JSON-RPC error; `code` is honoured by the MCP SDK when thrown from a handler. */
58
+ export declare class McpEventsError extends Error {
59
+ readonly code: number;
60
+ constructor(code: number, message: string);
61
+ }
62
+ /** Standard Webhooks `v1` signature (what ChatGPT verifies). */
63
+ export declare function signWebhook(input: {
64
+ secret: string;
65
+ id: string;
66
+ timestamp: number;
67
+ body: string;
68
+ }): string;
69
+ export declare function defineEvents<E extends Record<string, EventDefinition>>(options: DefineEventsOptions<E>): {
70
+ /** `events/list` result. */
71
+ list(): {
72
+ events: {
73
+ name: string;
74
+ description?: string | undefined;
75
+ delivery: string[];
76
+ inputSchema: z.core.ZodStandardJSONSchemaPayload<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
77
+ payloadSchema?: z.core.ZodStandardJSONSchemaPayload<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>> | undefined;
78
+ }[];
79
+ nextCursor: null;
80
+ };
81
+ /** `events/subscribe`, idempotent per (name, arguments, url). */
82
+ subscribe(params: unknown, ctx?: {
83
+ authInfo?: unknown;
84
+ }): Promise<{
85
+ id: string;
86
+ refreshBefore: string;
87
+ cursor: null;
88
+ truncated: boolean;
89
+ }>;
90
+ /** `events/unsubscribe`, idempotent. */
91
+ unsubscribe(params: unknown, ctx?: {
92
+ authInfo?: unknown;
93
+ }): Promise<{}>;
94
+ /** Deliver one event to every live subscription for `name` whose arguments `match`. */
95
+ emit<N extends keyof E & string>(name: N, data: E[N]["payload"] extends z.ZodType ? z.input<E[N]["payload"]> : unknown, match?: (args: z.output<E[N]["input"]>) => boolean): Promise<EmitResult[]>;
96
+ };
97
+ export {};
@@ -0,0 +1,215 @@
1
+ // src/events/index.ts
2
+ import { createHmac, randomBytes, timingSafeEqual } from "crypto";
3
+ import { z } from "zod";
4
+ var McpEventsError = class extends Error {
5
+ constructor(code, message) {
6
+ super(message);
7
+ this.name = "McpEventsError";
8
+ this.code = code;
9
+ }
10
+ };
11
+ var INVALID_PARAMS = -32602;
12
+ var MAX_BODY_BYTES = 262144;
13
+ var SECRET = /^whsec_([A-Za-z0-9+/]+={0,2})$/;
14
+ var SubscribeParams = z.object({
15
+ name: z.string(),
16
+ arguments: z.record(z.string(), z.unknown()).default({}),
17
+ delivery: z.object({ mode: z.literal("webhook"), url: z.string(), secret: z.string() }),
18
+ cursor: z.string().nullish(),
19
+ ttlMs: z.number().int().positive().nullish()
20
+ });
21
+ var UnsubscribeParams = z.object({
22
+ name: z.string(),
23
+ arguments: z.record(z.string(), z.unknown()).default({}),
24
+ delivery: z.object({ mode: z.literal("webhook"), url: z.string() })
25
+ });
26
+ function signWebhook(input) {
27
+ const key = Buffer.from(input.secret.replace(/^whsec_/, ""), "base64");
28
+ const mac = createHmac("sha256", key).update(`${input.id}.${input.timestamp}.${input.body}`);
29
+ return `v1,${mac.digest("base64")}`;
30
+ }
31
+ function canonical(value) {
32
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
33
+ if (value && typeof value === "object") {
34
+ const obj = value;
35
+ return `{${Object.keys(obj).sort().map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
36
+ }
37
+ return JSON.stringify(value);
38
+ }
39
+ function parse(schema, value, what) {
40
+ const result = schema.safeParse(value);
41
+ if (!result.success)
42
+ throw new McpEventsError(INVALID_PARAMS, `Invalid ${what}: ${z.prettifyError(result.error)}`);
43
+ return result.data;
44
+ }
45
+ var id = (prefix) => `${prefix}_${randomBytes(12).toString("hex")}`;
46
+ var keyOf = (name, args, url) => `${name}
47
+ ${canonical(args)}
48
+ ${url}`;
49
+ function defineEvents(options) {
50
+ const {
51
+ events,
52
+ store = /* @__PURE__ */ new Map(),
53
+ authorize,
54
+ allowCallbackUrl = (url) => url.protocol === "https:",
55
+ maxTtlMs = 30 * 24 * 60 * 60 * 1e3,
56
+ maxAttempts = 5,
57
+ retryBaseMs = 1e3,
58
+ fetch: fetchFn = fetch
59
+ } = options;
60
+ function definition(name) {
61
+ const def = Object.prototype.hasOwnProperty.call(events, name) ? events[name] : void 0;
62
+ if (!def) throw new McpEventsError(INVALID_PARAMS, `Unknown event: ${name}`);
63
+ return def;
64
+ }
65
+ async function allowed(request) {
66
+ return !authorize || await authorize(request);
67
+ }
68
+ function post(sub, webhookId, body) {
69
+ const timestamp = Math.floor(Date.now() / 1e3);
70
+ return fetchFn(sub.url, {
71
+ method: "POST",
72
+ redirect: "error",
73
+ signal: AbortSignal.timeout(1e4),
74
+ headers: {
75
+ "content-type": "application/json",
76
+ "webhook-id": webhookId,
77
+ "webhook-timestamp": String(timestamp),
78
+ "webhook-signature": signWebhook({ secret: sub.secret, id: webhookId, timestamp, body }),
79
+ "x-mcp-subscription-id": sub.id
80
+ },
81
+ body
82
+ });
83
+ }
84
+ async function verify(sub) {
85
+ const challenge = randomBytes(32).toString("base64url");
86
+ const res = await post(
87
+ sub,
88
+ id("msg_verification"),
89
+ JSON.stringify({ type: "verification", challenge })
90
+ ).catch(() => void 0);
91
+ const echoed = res?.ok ? (await res.json().catch(() => ({}))).challenge : void 0;
92
+ const ok = typeof echoed === "string" && echoed.length === challenge.length && timingSafeEqual(Buffer.from(echoed), Buffer.from(challenge));
93
+ if (!ok) throw new McpEventsError(INVALID_PARAMS, "Callback verification failed");
94
+ }
95
+ async function deliver(key, sub, eventId, body) {
96
+ let status = 0;
97
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
98
+ status = (await post(sub, eventId, body).catch(() => void 0))?.status ?? 0;
99
+ if (status >= 200 && status < 300) return { subscriptionId: sub.id, status, delivered: true };
100
+ if (status === 410) await store.delete(key);
101
+ const transient = status === 0 || status === 408 || status === 429 || status >= 500;
102
+ if (!transient) break;
103
+ if (attempt < maxAttempts)
104
+ await new Promise((r) => setTimeout(r, retryBaseMs * 2 ** (attempt - 1)));
105
+ }
106
+ return { subscriptionId: sub.id, status, delivered: false };
107
+ }
108
+ return {
109
+ /** `events/list` result. */
110
+ list() {
111
+ return {
112
+ events: Object.entries(events).map(([name, def]) => ({
113
+ name,
114
+ ...def.description ? { description: def.description } : {},
115
+ delivery: ["webhook"],
116
+ inputSchema: z.toJSONSchema(def.input),
117
+ ...def.payload ? { payloadSchema: z.toJSONSchema(def.payload) } : {}
118
+ })),
119
+ nextCursor: null
120
+ };
121
+ },
122
+ /** `events/subscribe`, idempotent per (name, arguments, url). */
123
+ async subscribe(params, ctx = {}) {
124
+ const p = parse(SubscribeParams, params, "events/subscribe params");
125
+ const args = parse(
126
+ definition(p.name).input,
127
+ p.arguments,
128
+ `arguments for ${p.name}`
129
+ );
130
+ const url = URL.parse(p.delivery.url);
131
+ if (!url || !allowCallbackUrl(url))
132
+ throw new McpEventsError(INVALID_PARAMS, "Callback URL not allowed");
133
+ const secret = SECRET.exec(p.delivery.secret)?.[1];
134
+ const keyBytes = secret ? Buffer.from(secret, "base64").length : 0;
135
+ if (keyBytes < 24 || keyBytes > 64)
136
+ throw new McpEventsError(INVALID_PARAMS, "Secret must be whsec_ + base64 of 24\u201364 bytes");
137
+ if (!await allowed({ name: p.name, arguments: args, authInfo: ctx.authInfo })) {
138
+ throw new McpEventsError(INVALID_PARAMS, "Not authorized to subscribe");
139
+ }
140
+ const key = keyOf(p.name, args, url.href);
141
+ const existing = await store.get(key);
142
+ const ttl = Math.min(p.ttlMs ?? maxTtlMs, maxTtlMs);
143
+ const sub = {
144
+ id: existing?.id ?? id("sub"),
145
+ name: p.name,
146
+ arguments: args,
147
+ url: url.href,
148
+ secret: p.delivery.secret,
149
+ expiresAt: Date.now() + ttl,
150
+ authInfo: ctx.authInfo
151
+ };
152
+ if (existing?.secret !== sub.secret) await verify(sub);
153
+ await store.set(key, sub);
154
+ return {
155
+ id: sub.id,
156
+ refreshBefore: new Date(sub.expiresAt).toISOString(),
157
+ cursor: null,
158
+ truncated: false
159
+ };
160
+ },
161
+ /** `events/unsubscribe`, idempotent. */
162
+ async unsubscribe(params, ctx = {}) {
163
+ const p = parse(UnsubscribeParams, params, "events/unsubscribe params");
164
+ const args = parse(
165
+ definition(p.name).input,
166
+ p.arguments,
167
+ `arguments for ${p.name}`
168
+ );
169
+ if (!await allowed({ name: p.name, arguments: args, authInfo: ctx.authInfo })) {
170
+ throw new McpEventsError(INVALID_PARAMS, "Not authorized to unsubscribe");
171
+ }
172
+ const url = URL.parse(p.delivery.url);
173
+ if (url) await store.delete(keyOf(p.name, args, url.href));
174
+ return {};
175
+ },
176
+ /** Deliver one event to every live subscription for `name` whose arguments `match`. */
177
+ async emit(name, data, match = () => true) {
178
+ const def = definition(name);
179
+ const payload = def.payload ? parse(def.payload, data, `payload for ${name}`) : data;
180
+ const eventId = id("evt");
181
+ const body = JSON.stringify({
182
+ eventId,
183
+ name,
184
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
185
+ data: payload,
186
+ cursor: null
187
+ });
188
+ if (Buffer.byteLength(body) > MAX_BODY_BYTES)
189
+ throw new McpEventsError(INVALID_PARAMS, "Event body exceeds 256 KiB");
190
+ const now = Date.now();
191
+ const deliveries = [];
192
+ for (const sub of [...await store.values()]) {
193
+ if (sub.name !== name) continue;
194
+ const key = keyOf(sub.name, sub.arguments, sub.url);
195
+ if (sub.expiresAt <= now) {
196
+ await store.delete(key);
197
+ continue;
198
+ }
199
+ if (!match(sub.arguments)) continue;
200
+ if (!await allowed({ name, arguments: sub.arguments, authInfo: sub.authInfo })) {
201
+ await store.delete(key);
202
+ continue;
203
+ }
204
+ deliveries.push(deliver(key, sub, eventId, body));
205
+ }
206
+ return Promise.all(deliveries);
207
+ }
208
+ };
209
+ }
210
+ export {
211
+ McpEventsError,
212
+ defineEvents,
213
+ signWebhook
214
+ };
215
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/events/index.ts"],"sourcesContent":["/**\n * MCP Events over webhooks (protocol `2026-07-28`), the delivery mode ChatGPT uses.\n *\n * Wire `list`, `subscribe` and `unsubscribe` into any MCP 2.0 server as `events/*`\n * request handlers, then call `emit` when something changes. This entry tracks the\n * draft spec and stays experimental until the spec settles.\n */\nimport { createHmac, randomBytes, timingSafeEqual } from \"node:crypto\";\nimport { z } from \"zod\";\n\ntype MaybePromise<T> = T | Promise<T>;\n\nexport interface EventDefinition<I extends z.ZodType = z.ZodType, P extends z.ZodType = z.ZodType> {\n description?: string;\n /** Subscription arguments (e.g. `{ document_id }`). */\n input: I;\n /** Event `data`. Validated on `emit` when set. */\n payload?: P;\n}\n\nexport interface EventSubscription {\n id: string;\n name: string;\n arguments: Record<string, unknown>;\n url: string;\n secret: string;\n /** Epoch ms. */\n expiresAt: number;\n /** Principal captured at subscribe time; re-checked before every delivery. */\n authInfo?: unknown;\n}\n\n/** A `Map` works. Persist across restarts in production (the spec requires it). */\nexport interface SubscriptionStore {\n get(key: string): MaybePromise<EventSubscription | undefined>;\n set(key: string, subscription: EventSubscription): unknown;\n delete(key: string): unknown;\n values(): MaybePromise<Iterable<EventSubscription>>;\n}\n\nexport interface AuthorizeRequest {\n name: string;\n arguments: Record<string, unknown>;\n authInfo?: unknown;\n}\n\nexport interface DefineEventsOptions<E extends Record<string, EventDefinition>> {\n events: E;\n store?: SubscriptionStore;\n /**\n * Called on subscribe/unsubscribe with the caller, and before each delivery with\n * the principal stored at subscribe time. Return `false` to deny; a denied\n * delivery deletes the subscription.\n */\n authorize?: (request: AuthorizeRequest) => MaybePromise<boolean>;\n /** Default: `https:` only. Narrow further to block SSRF to internal hosts. */\n allowCallbackUrl?: (url: URL) => boolean;\n /** Default and maximum subscription lifetime. Default 30 days. */\n maxTtlMs?: number;\n /** Delivery attempts per event. Default 5. */\n maxAttempts?: number;\n /** First retry delay; doubles each attempt. Default 1000. */\n retryBaseMs?: number;\n fetch?: typeof fetch;\n}\n\nexport interface EmitResult {\n subscriptionId: string;\n status: number;\n delivered: boolean;\n}\n\n/** JSON-RPC error; `code` is honoured by the MCP SDK when thrown from a handler. */\nexport class McpEventsError extends Error {\n readonly code: number;\n constructor(code: number, message: string) {\n super(message);\n this.name = \"McpEventsError\";\n this.code = code;\n }\n}\n\nconst INVALID_PARAMS = -32602;\nconst MAX_BODY_BYTES = 262_144;\nconst SECRET = /^whsec_([A-Za-z0-9+/]+={0,2})$/;\n\nconst SubscribeParams = z.object({\n name: z.string(),\n arguments: z.record(z.string(), z.unknown()).default({}),\n delivery: z.object({ mode: z.literal(\"webhook\"), url: z.string(), secret: z.string() }),\n cursor: z.string().nullish(),\n ttlMs: z.number().int().positive().nullish(),\n});\n\nconst UnsubscribeParams = z.object({\n name: z.string(),\n arguments: z.record(z.string(), z.unknown()).default({}),\n delivery: z.object({ mode: z.literal(\"webhook\"), url: z.string() }),\n});\n\n/** Standard Webhooks `v1` signature (what ChatGPT verifies). */\nexport function signWebhook(input: {\n secret: string;\n id: string;\n timestamp: number;\n body: string;\n}): string {\n const key = Buffer.from(input.secret.replace(/^whsec_/, \"\"), \"base64\");\n const mac = createHmac(\"sha256\", key).update(`${input.id}.${input.timestamp}.${input.body}`);\n return `v1,${mac.digest(\"base64\")}`;\n}\n\nfunction canonical(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n const obj = value as Record<string, unknown>;\n return `{${Object.keys(obj)\n .sort()\n .map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`)\n .join(\",\")}}`;\n }\n return JSON.stringify(value);\n}\n\nfunction parse<S extends z.ZodType>(schema: S, value: unknown, what: string): z.output<S> {\n const result = schema.safeParse(value);\n if (!result.success)\n throw new McpEventsError(INVALID_PARAMS, `Invalid ${what}: ${z.prettifyError(result.error)}`);\n return result.data;\n}\n\nconst id = (prefix: string) => `${prefix}_${randomBytes(12).toString(\"hex\")}`;\n\nconst keyOf = (name: string, args: unknown, url: string) => `${name}\\n${canonical(args)}\\n${url}`;\n\nexport function defineEvents<E extends Record<string, EventDefinition>>(\n options: DefineEventsOptions<E>,\n) {\n const {\n events,\n store = new Map<string, EventSubscription>(),\n authorize,\n allowCallbackUrl = (url) => url.protocol === \"https:\",\n maxTtlMs = 30 * 24 * 60 * 60 * 1000,\n maxAttempts = 5,\n retryBaseMs = 1000,\n fetch: fetchFn = fetch,\n } = options;\n\n function definition(name: string): EventDefinition {\n const def = Object.prototype.hasOwnProperty.call(events, name) ? events[name] : undefined;\n if (!def) throw new McpEventsError(INVALID_PARAMS, `Unknown event: ${name}`);\n return def;\n }\n\n async function allowed(request: AuthorizeRequest) {\n return !authorize || (await authorize(request));\n }\n\n function post(sub: EventSubscription, webhookId: string, body: string) {\n const timestamp = Math.floor(Date.now() / 1000);\n return fetchFn(sub.url, {\n method: \"POST\",\n redirect: \"error\",\n signal: AbortSignal.timeout(10_000),\n headers: {\n \"content-type\": \"application/json\",\n \"webhook-id\": webhookId,\n \"webhook-timestamp\": String(timestamp),\n \"webhook-signature\": signWebhook({ secret: sub.secret, id: webhookId, timestamp, body }),\n \"x-mcp-subscription-id\": sub.id,\n },\n body,\n });\n }\n\n async function verify(sub: EventSubscription) {\n const challenge = randomBytes(32).toString(\"base64url\");\n const res = await post(\n sub,\n id(\"msg_verification\"),\n JSON.stringify({ type: \"verification\", challenge }),\n ).catch(() => undefined);\n const echoed = res?.ok\n ? ((await res.json().catch(() => ({}))) as { challenge?: unknown }).challenge\n : undefined;\n const ok =\n typeof echoed === \"string\" &&\n echoed.length === challenge.length &&\n timingSafeEqual(Buffer.from(echoed), Buffer.from(challenge));\n if (!ok) throw new McpEventsError(INVALID_PARAMS, \"Callback verification failed\");\n }\n\n async function deliver(\n key: string,\n sub: EventSubscription,\n eventId: string,\n body: string,\n ): Promise<EmitResult> {\n let status = 0;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n // Fresh timestamp + signature per attempt; same event id and body bytes.\n status = (await post(sub, eventId, body).catch(() => undefined))?.status ?? 0;\n if (status >= 200 && status < 300) return { subscriptionId: sub.id, status, delivered: true };\n if (status === 410) await store.delete(key);\n const transient = status === 0 || status === 408 || status === 429 || status >= 500;\n if (!transient) break;\n if (attempt < maxAttempts)\n await new Promise((r) => setTimeout(r, retryBaseMs * 2 ** (attempt - 1)));\n }\n return { subscriptionId: sub.id, status, delivered: false };\n }\n\n return {\n /** `events/list` result. */\n list() {\n return {\n events: Object.entries(events).map(([name, def]) => ({\n name,\n ...(def.description ? { description: def.description } : {}),\n delivery: [\"webhook\"],\n inputSchema: z.toJSONSchema(def.input),\n ...(def.payload ? { payloadSchema: z.toJSONSchema(def.payload) } : {}),\n })),\n nextCursor: null,\n };\n },\n\n /** `events/subscribe`, idempotent per (name, arguments, url). */\n async subscribe(params: unknown, ctx: { authInfo?: unknown } = {}) {\n const p = parse(SubscribeParams, params, \"events/subscribe params\");\n const args = parse(\n definition(p.name).input,\n p.arguments,\n `arguments for ${p.name}`,\n ) as Record<string, unknown>;\n\n const url = URL.parse(p.delivery.url);\n if (!url || !allowCallbackUrl(url))\n throw new McpEventsError(INVALID_PARAMS, \"Callback URL not allowed\");\n const secret = SECRET.exec(p.delivery.secret)?.[1];\n const keyBytes = secret ? Buffer.from(secret, \"base64\").length : 0;\n if (keyBytes < 24 || keyBytes > 64)\n throw new McpEventsError(INVALID_PARAMS, \"Secret must be whsec_ + base64 of 24–64 bytes\");\n if (!(await allowed({ name: p.name, arguments: args, authInfo: ctx.authInfo }))) {\n throw new McpEventsError(INVALID_PARAMS, \"Not authorized to subscribe\");\n }\n\n const key = keyOf(p.name, args, url.href);\n const existing = await store.get(key);\n const ttl = Math.min(p.ttlMs ?? maxTtlMs, maxTtlMs);\n const sub: EventSubscription = {\n id: existing?.id ?? id(\"sub\"),\n name: p.name,\n arguments: args,\n url: url.href,\n secret: p.delivery.secret,\n expiresAt: Date.now() + ttl,\n authInfo: ctx.authInfo,\n };\n if (existing?.secret !== sub.secret) await verify(sub);\n await store.set(key, sub);\n return {\n id: sub.id,\n refreshBefore: new Date(sub.expiresAt).toISOString(),\n cursor: null,\n truncated: false,\n };\n },\n\n /** `events/unsubscribe`, idempotent. */\n async unsubscribe(params: unknown, ctx: { authInfo?: unknown } = {}) {\n const p = parse(UnsubscribeParams, params, \"events/unsubscribe params\");\n const args = parse(\n definition(p.name).input,\n p.arguments,\n `arguments for ${p.name}`,\n ) as Record<string, unknown>;\n if (!(await allowed({ name: p.name, arguments: args, authInfo: ctx.authInfo }))) {\n throw new McpEventsError(INVALID_PARAMS, \"Not authorized to unsubscribe\");\n }\n const url = URL.parse(p.delivery.url);\n if (url) await store.delete(keyOf(p.name, args, url.href));\n return {};\n },\n\n /** Deliver one event to every live subscription for `name` whose arguments `match`. */\n async emit<N extends keyof E & string>(\n name: N,\n data: E[N][\"payload\"] extends z.ZodType ? z.input<E[N][\"payload\"]> : unknown,\n match: (args: z.output<E[N][\"input\"]>) => boolean = () => true,\n ): Promise<EmitResult[]> {\n const def = definition(name);\n const payload = def.payload ? parse(def.payload, data, `payload for ${name}`) : data;\n const eventId = id(\"evt\");\n const body = JSON.stringify({\n eventId,\n name,\n timestamp: new Date().toISOString(),\n data: payload,\n cursor: null,\n });\n if (Buffer.byteLength(body) > MAX_BODY_BYTES)\n throw new McpEventsError(INVALID_PARAMS, \"Event body exceeds 256 KiB\");\n\n const now = Date.now();\n const deliveries: Promise<EmitResult>[] = [];\n for (const sub of [...(await store.values())]) {\n if (sub.name !== name) continue;\n const key = keyOf(sub.name, sub.arguments, sub.url);\n if (sub.expiresAt <= now) {\n await store.delete(key);\n continue;\n }\n if (!match(sub.arguments as z.output<E[N][\"input\"]>)) continue;\n if (!(await allowed({ name, arguments: sub.arguments, authInfo: sub.authInfo }))) {\n await store.delete(key);\n continue;\n }\n deliveries.push(deliver(key, sub, eventId, body));\n }\n return Promise.all(deliveries);\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -19,7 +19,8 @@ import {
19
19
  createMcpView,
20
20
  useMcpApp,
21
21
  useToolResult
22
- } from "../chunk-SXF23BZD.js";
22
+ } from "../chunk-HIAIQVK5.js";
23
+ import "../chunk-4SZHJ4MK.js";
23
24
  import "../chunk-5L3QHYWK.js";
24
25
  import "../chunk-YJA4H6IB.js";
25
26
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/json-render/generative-view.tsx","../../src/json-render/index.ts","../../src/json-render/use-spec-stream.ts"],"sourcesContent":["import { createElement } from \"react\";\nimport { type ComponentMap, createRenderer } from \"@json-render/react\";\nimport type { Catalog, SchemaDefinition, Spec } from \"@json-render/core\";\nimport { createMcpView, useMcpApp, useToolResult } from \"../react/index.js\";\nimport type { App } from \"@modelcontextprotocol/ext-apps\";\nimport type { AdapterOptions } from \"mountly/adapter\";\nimport type { McpView } from \"../types.js\";\n\n/** The catalog-data constraint createRenderer infers against. */\ntype CatalogData = { components: Record<string, { props: unknown }> };\n\n/**\n * Route a generated UI's actions back to the MCP host. Called with the action\n * name, its (resolved) params, and the view-side `App` handle.\n */\nexport type ActionRouter = (\n name: string,\n params: Record<string, unknown> | undefined,\n mcp: App,\n) => void;\n\n/**\n * Default router: an `ask` action with a string `prompt` param sends a\n * follow-up user turn to the model via `App.sendMessage`. This is the agent\n * loop json-render alone cannot do — it has no concept of an MCP host.\n */\nexport const defaultActionRouter: ActionRouter = (name, params, mcp) => {\n if (name !== \"ask\") return;\n const prompt = params?.prompt;\n const text = typeof prompt === \"string\" ? prompt : \"\";\n if (text) {\n void mcp.sendMessage({ role: \"user\", content: [{ type: \"text\", text }] });\n }\n};\n\n/**\n * Identity helper that types a components map against a catalog (so it can be\n * defined once and reused by both `createGenerativeView` and `createRenderer`)\n * without losing per-component prop inference.\n *\n * ```ts\n * export const components = defineComponents(catalog, {\n * Metric: ({ element }) => <span>{element.props.value}</span>,\n * });\n * ```\n */\nexport function defineComponents<TDef extends SchemaDefinition, TCatalog extends CatalogData>(\n _catalog: Catalog<TDef, TCatalog>,\n components: ComponentMap<TCatalog[\"components\"]>,\n): ComponentMap<TCatalog[\"components\"]> {\n return components;\n}\n\nexport interface GenerativeViewOptions<\n TDef extends SchemaDefinition,\n TCatalog extends CatalogData,\n> extends AdapterOptions {\n /** The json-render catalog — the vocabulary the agent may compose. */\n catalog: Catalog<TDef, TCatalog>;\n /** Native component implementations for each catalog type. */\n components: ComponentMap<TCatalog[\"components\"]>;\n /**\n * How a generated UI's actions reach the agent. Defaults to\n * {@link defaultActionRouter} (`ask` → `App.sendMessage`).\n */\n onAction?: ActionRouter;\n}\n\ninterface ToolResultShape {\n structuredContent?: { spec?: Spec };\n}\n\n/**\n * Turn a json-render catalog + components into a mountly MCP Apps View.\n *\n * Reads the spec from the tool result, renders it natively (resolving `$state`\n * from `spec.state`), and routes actions back to the MCP host.\n *\n * ```ts\n * createGenerativeView({ catalog, components, styles, shadow: true });\n * ```\n */\nexport function createGenerativeView<TDef extends SchemaDefinition, TCatalog extends CatalogData>(\n options: GenerativeViewOptions<TDef, TCatalog>,\n): McpView {\n const { catalog, components, onAction = defaultActionRouter, ...adapter } = options;\n const Rendered = createRenderer(catalog, components);\n\n function GenerativeView() {\n const mcp = useMcpApp();\n const result = useToolResult<ToolResultShape>();\n const spec = result?.structuredContent?.spec ?? null;\n if (!spec) return null;\n return createElement(Rendered, {\n spec,\n state: spec.state,\n onAction: (name: string, params?: Record<string, unknown>) => onAction(name, params, mcp),\n });\n }\n\n return createMcpView(GenerativeView, adapter);\n}\n","export {\n type ActionRouter,\n type GenerativeViewOptions,\n createGenerativeView,\n defaultActionRouter,\n defineComponents,\n} from \"./generative-view.js\";\nexport {\n type JsonRenderToolOptions,\n type RegisterJsonRenderResourceOptions,\n type RegisterJsonRenderToolOptions,\n type CreateJsonRenderMcpAppOptions,\n createJsonRenderMcpApp,\n registerJsonRenderTool,\n registerJsonRenderResource,\n} from \"./mcp.js\";\nexport {\n type BuildAppHtmlOptions,\n type UseJsonRenderAppOptions,\n type UseJsonRenderAppReturn,\n buildAppHtml,\n useJsonRenderApp,\n} from \"./app.js\";\n\n// Convenience re-exports for rendering the same catalog natively (non-MCP) —\n// previews, tests, or a plain mountly feature. Pair with `mountly-react`'s\n// `createWidget` to mount a spec outside an MCP host.\nexport { createRenderer } from \"@json-render/react\";\nexport type { ComponentMap, ComponentRenderProps } from \"@json-render/react\";\n\n// Streaming primitives (json-render's own): replay a known spec as a patch\n// stream, compile patches back into a progressively-built spec, and the AI-SDK\n// transform that classifies a model's UI-message stream into spec patches.\nexport {\n compileSpecStream,\n compileTextStreamToSpecs,\n createJsonRenderTransform,\n createSpecStreamCompiler,\n parseSpecStreamLine,\n specToPatchLines,\n} from \"./spec-stream.js\";\nexport type { Spec } from \"@json-render/core\";\n\n// --- Two ways to stream ---\n//\n// Replay (known spec): `useSpecStream` takes a finished spec — e.g. the one an\n// MCP tool delivered as `structuredContent` — and replays it as a live build,\n// with the compiler, loading flag, and cancellation included.\nexport {\n type SpecStreamState,\n type UseSpecStreamOptions,\n useSpecStream,\n} from \"./use-spec-stream.js\";\n\n// Live (from a model): `useUIStream` / `useChatUI` are json-render's own client\n// hooks — POST a prompt to an endpoint that streams JSONL patches (see\n// `streamSpec` in `mountly-mcp/json-render/server`) and render the spec as it\n// builds. Re-exported so the live path is one import away.\nexport { useChatUI, useUIStream } from \"@json-render/react\";\nexport type { UseUIStreamOptions, UseUIStreamReturn } from \"@json-render/react\";\n","import { useEffect, useState } from \"react\";\nimport type { Spec } from \"@json-render/core\";\nimport { createSpecStreamCompiler } from \"@json-render/core\";\nimport { specToPatchLines } from \"./spec-stream.js\";\n\nexport interface UseSpecStreamOptions {\n /** Delay between patches, ms. Default 90. Set 0 to render instantly. */\n intervalMs?: number;\n}\n\nexport interface SpecStreamState {\n /** The progressively-built spec (partial while streaming, full when done). */\n spec: Spec | null;\n /** Convenience: `spec?.state`, to pass straight to the renderer. */\n state: Record<string, unknown> | undefined;\n /** True while patches are still being applied. */\n loading: boolean;\n}\n\n/**\n * Replay a finished spec as a live json-render stream — and own all the\n * ceremony. Give it the target spec; it scaffolds a `createSpecStreamCompiler`,\n * replays the patches with a delay, exposes the partial spec + `loading` after\n * each step, and cancels cleanly when the target changes (so a self-driving UI\n * that swaps specs mid-stream just works). Re-streams whenever `target` changes.\n *\n * ```tsx\n * const { spec, state, loading } = useSpecStream(specs[view]);\n * return <Renderer spec={spec} state={state} loading={loading} onAction={...} />;\n * ```\n */\nexport function useSpecStream(\n target: Spec | null | undefined,\n options: UseSpecStreamOptions = {},\n): SpecStreamState {\n const { intervalMs = 90 } = options;\n const [spec, setSpec] = useState<Spec | null>(null);\n const [loading, setLoading] = useState(false);\n\n useEffect(() => {\n if (!target) {\n setSpec(null);\n setLoading(false);\n return;\n }\n let cancelled = false;\n const compiler = createSpecStreamCompiler<Spec>();\n const lines = specToPatchLines(target);\n setLoading(true);\n\n void (async () => {\n for (const line of lines) {\n if (cancelled) return;\n const { result } = compiler.push(`${line}\\n`);\n setSpec({ ...result });\n if (intervalMs > 0) {\n await new Promise((r) => setTimeout(r, intervalMs));\n }\n }\n if (cancelled) return;\n setSpec(compiler.getResult());\n setLoading(false);\n })();\n\n return () => {\n cancelled = true;\n };\n }, [target, intervalMs]);\n\n return { spec, state: spec?.state, loading };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,qBAAqB;AAC9B,SAA4B,sBAAsB;AAyB3C,IAAM,sBAAoC,CAAC,MAAM,QAAQ,QAAQ;AACtE,MAAI,SAAS,MAAO;AACpB,QAAM,SAAS,QAAQ;AACvB,QAAM,OAAO,OAAO,WAAW,WAAW,SAAS;AACnD,MAAI,MAAM;AACR,SAAK,IAAI,YAAY,EAAE,MAAM,QAAQ,SAAS,CAAC,EAAE,MAAM,QAAQ,KAAK,CAAC,EAAE,CAAC;AAAA,EAC1E;AACF;AAaO,SAAS,iBACd,UACA,YACsC;AACtC,SAAO;AACT;AA+BO,SAAS,qBACd,SACS;AACT,QAAM,EAAE,SAAS,YAAY,WAAW,qBAAqB,GAAG,QAAQ,IAAI;AAC5E,QAAM,WAAW,eAAe,SAAS,UAAU;AAEnD,WAAS,iBAAiB;AACxB,UAAM,MAAM,UAAU;AACtB,UAAM,SAAS,cAA+B;AAC9C,UAAM,OAAO,QAAQ,mBAAmB,QAAQ;AAChD,QAAI,CAAC,KAAM,QAAO;AAClB,WAAO,cAAc,UAAU;AAAA,MAC7B;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,UAAU,CAAC,MAAc,WAAqC,SAAS,MAAM,QAAQ,GAAG;AAAA,IAC1F,CAAC;AAAA,EACH;AAEA,SAAO,cAAc,gBAAgB,OAAO;AAC9C;;;AC1EA,SAAS,kBAAAA,uBAAsB;;;AC3B/B,SAAS,WAAW,gBAAgB;AAEpC,SAAS,4BAAAC,iCAAgC;AA6BlC,SAAS,cACd,QACA,UAAgC,CAAC,GAChB;AACjB,QAAM,EAAE,aAAa,GAAG,IAAI;AAC5B,QAAM,CAAC,MAAM,OAAO,IAAI,SAAsB,IAAI;AAClD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAE5C,YAAU,MAAM;AACd,QAAI,CAAC,QAAQ;AACX,cAAQ,IAAI;AACZ,iBAAW,KAAK;AAChB;AAAA,IACF;AACA,QAAI,YAAY;AAChB,UAAM,WAAWC,0BAA+B;AAChD,UAAM,QAAQ,iBAAiB,MAAM;AACrC,eAAW,IAAI;AAEf,UAAM,YAAY;AAChB,iBAAW,QAAQ,OAAO;AACxB,YAAI,UAAW;AACf,cAAM,EAAE,OAAO,IAAI,SAAS,KAAK,GAAG,IAAI;AAAA,CAAI;AAC5C,gBAAQ,EAAE,GAAG,OAAO,CAAC;AACrB,YAAI,aAAa,GAAG;AAClB,gBAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,UAAU,CAAC;AAAA,QACpD;AAAA,MACF;AACA,UAAI,UAAW;AACf,cAAQ,SAAS,UAAU,CAAC;AAC5B,iBAAW,KAAK;AAAA,IAClB,GAAG;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,QAAQ,UAAU,CAAC;AAEvB,SAAO,EAAE,MAAM,OAAO,MAAM,OAAO,QAAQ;AAC7C;;;ADZA,SAAS,WAAW,mBAAmB;","names":["createRenderer","createSpecStreamCompiler","createSpecStreamCompiler"]}
1
+ {"version":3,"sources":["../../src/json-render/generative-view.tsx","../../src/json-render/index.ts","../../src/json-render/use-spec-stream.ts"],"sourcesContent":["import { createElement } from \"react\";\nimport { type ComponentMap, createRenderer } from \"@json-render/react\";\nimport type { Catalog, SchemaDefinition, Spec } from \"@json-render/core\";\nimport { createMcpView, useMcpApp, useToolResult } from \"../react/index.js\";\nimport type { App } from \"@modelcontextprotocol/ext-apps\";\nimport type { AdapterOptions } from \"mountly/adapter\";\nimport type { McpView } from \"../types.js\";\n\n/** The catalog-data constraint createRenderer infers against. */\ntype CatalogData = { components: Record<string, { props: unknown }> };\n\n/**\n * Route a generated UI's actions back to the MCP host. Called with the action\n * name, its (resolved) params, and the view-side `App` handle.\n */\nexport type ActionRouter = (\n name: string,\n params: Record<string, unknown> | undefined,\n mcp: App,\n) => void;\n\n/**\n * Default router: an `ask` action with a string `prompt` param sends a\n * follow-up user turn to the model via `App.sendMessage`. This is the agent\n * loop json-render alone cannot do — it has no concept of an MCP host.\n */\nexport const defaultActionRouter: ActionRouter = (name, params, mcp) => {\n if (name !== \"ask\") return;\n const prompt = params?.prompt;\n const text = typeof prompt === \"string\" ? prompt : \"\";\n if (text) {\n void mcp.sendMessage({ role: \"user\", content: [{ type: \"text\", text }] });\n }\n};\n\n/**\n * Identity helper that types a components map against a catalog (so it can be\n * defined once and reused by both `createGenerativeView` and `createRenderer`)\n * without losing per-component prop inference.\n *\n * ```ts\n * export const components = defineComponents(catalog, {\n * Metric: ({ element }) => <span>{element.props.value}</span>,\n * });\n * ```\n */\nexport function defineComponents<TDef extends SchemaDefinition, TCatalog extends CatalogData>(\n _catalog: Catalog<TDef, TCatalog>,\n components: ComponentMap<TCatalog[\"components\"]>,\n): ComponentMap<TCatalog[\"components\"]> {\n return components;\n}\n\nexport interface GenerativeViewOptions<\n TDef extends SchemaDefinition,\n TCatalog extends CatalogData,\n> extends AdapterOptions {\n /** The json-render catalog — the vocabulary the agent may compose. */\n catalog: Catalog<TDef, TCatalog>;\n /** Native component implementations for each catalog type. */\n components: ComponentMap<TCatalog[\"components\"]>;\n /**\n * How a generated UI's actions reach the agent. Defaults to\n * {@link defaultActionRouter} (`ask` → `App.sendMessage`).\n */\n onAction?: ActionRouter;\n}\n\ninterface ToolResultShape {\n structuredContent?: { spec?: Spec };\n}\n\n/**\n * Turn a json-render catalog + components into a mountly MCP Apps View.\n *\n * Reads the spec from the tool result, renders it natively (resolving `$state`\n * from `spec.state`), and routes actions back to the MCP host.\n *\n * ```ts\n * createGenerativeView({ catalog, components, styles, shadow: true });\n * ```\n */\nexport function createGenerativeView<TDef extends SchemaDefinition, TCatalog extends CatalogData>(\n options: GenerativeViewOptions<TDef, TCatalog>,\n): McpView {\n const { catalog, components, onAction = defaultActionRouter, ...adapter } = options;\n const Rendered = createRenderer(catalog, components);\n\n function GenerativeView() {\n const mcp = useMcpApp();\n const result = useToolResult<ToolResultShape>();\n const spec = result?.structuredContent?.spec ?? null;\n if (!spec) return null;\n return createElement(Rendered, {\n spec,\n state: spec.state,\n onAction: (name: string, params?: Record<string, unknown>) => onAction(name, params, mcp),\n });\n }\n\n return createMcpView(GenerativeView, adapter);\n}\n","export {\n type ActionRouter,\n type GenerativeViewOptions,\n createGenerativeView,\n defaultActionRouter,\n defineComponents,\n} from \"./generative-view.js\";\nexport {\n type JsonRenderToolOptions,\n type RegisterJsonRenderResourceOptions,\n type RegisterJsonRenderToolOptions,\n type CreateJsonRenderMcpAppOptions,\n createJsonRenderMcpApp,\n registerJsonRenderTool,\n registerJsonRenderResource,\n} from \"./mcp.js\";\nexport {\n type BuildAppHtmlOptions,\n type UseJsonRenderAppOptions,\n type UseJsonRenderAppReturn,\n buildAppHtml,\n useJsonRenderApp,\n} from \"./app.js\";\n\n// Convenience re-exports for rendering the same catalog natively (non-MCP) —\n// previews, tests, or a plain mountly feature. Pair with `mountly-react`'s\n// `createWidget` to mount a spec outside an MCP host.\nexport { createRenderer } from \"@json-render/react\";\nexport type { ComponentMap, ComponentRenderProps } from \"@json-render/react\";\n\n// Streaming primitives (json-render's own): replay a known spec as a patch\n// stream, compile patches back into a progressively-built spec, and the AI-SDK\n// transform that classifies a model's UI-message stream into spec patches.\nexport {\n compileSpecStream,\n compileTextStreamToSpecs,\n createJsonRenderTransform,\n createSpecStreamCompiler,\n parseSpecStreamLine,\n specToPatchLines,\n} from \"./spec-stream.js\";\nexport type { Spec } from \"@json-render/core\";\n\n// --- Two ways to stream ---\n//\n// Replay (known spec): `useSpecStream` takes a finished spec — e.g. the one an\n// MCP tool delivered as `structuredContent` — and replays it as a live build,\n// with the compiler, loading flag, and cancellation included.\nexport {\n type SpecStreamState,\n type UseSpecStreamOptions,\n useSpecStream,\n} from \"./use-spec-stream.js\";\n\n// Live (from a model): `useUIStream` / `useChatUI` are json-render's own client\n// hooks — POST a prompt to an endpoint that streams JSONL patches (see\n// `streamSpec` in `mountly-mcp/json-render/server`) and render the spec as it\n// builds. Re-exported so the live path is one import away.\nexport { useChatUI, useUIStream } from \"@json-render/react\";\nexport type { UseUIStreamOptions, UseUIStreamReturn } from \"@json-render/react\";\n","import { useEffect, useState } from \"react\";\nimport type { Spec } from \"@json-render/core\";\nimport { createSpecStreamCompiler } from \"@json-render/core\";\nimport { specToPatchLines } from \"./spec-stream.js\";\n\nexport interface UseSpecStreamOptions {\n /** Delay between patches, ms. Default 90. Set 0 to render instantly. */\n intervalMs?: number;\n}\n\nexport interface SpecStreamState {\n /** The progressively-built spec (partial while streaming, full when done). */\n spec: Spec | null;\n /** Convenience: `spec?.state`, to pass straight to the renderer. */\n state: Record<string, unknown> | undefined;\n /** True while patches are still being applied. */\n loading: boolean;\n}\n\n/**\n * Replay a finished spec as a live json-render stream — and own all the\n * ceremony. Give it the target spec; it scaffolds a `createSpecStreamCompiler`,\n * replays the patches with a delay, exposes the partial spec + `loading` after\n * each step, and cancels cleanly when the target changes (so a self-driving UI\n * that swaps specs mid-stream just works). Re-streams whenever `target` changes.\n *\n * ```tsx\n * const { spec, state, loading } = useSpecStream(specs[view]);\n * return <Renderer spec={spec} state={state} loading={loading} onAction={...} />;\n * ```\n */\nexport function useSpecStream(\n target: Spec | null | undefined,\n options: UseSpecStreamOptions = {},\n): SpecStreamState {\n const { intervalMs = 90 } = options;\n const [spec, setSpec] = useState<Spec | null>(null);\n const [loading, setLoading] = useState(false);\n\n useEffect(() => {\n if (!target) {\n setSpec(null);\n setLoading(false);\n return;\n }\n let cancelled = false;\n const compiler = createSpecStreamCompiler<Spec>();\n const lines = specToPatchLines(target);\n setLoading(true);\n\n void (async () => {\n for (const line of lines) {\n if (cancelled) return;\n const { result } = compiler.push(`${line}\\n`);\n setSpec({ ...result });\n if (intervalMs > 0) {\n await new Promise((r) => setTimeout(r, intervalMs));\n }\n }\n if (cancelled) return;\n setSpec(compiler.getResult());\n setLoading(false);\n })();\n\n return () => {\n cancelled = true;\n };\n }, [target, intervalMs]);\n\n return { spec, state: spec?.state, loading };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,qBAAqB;AAC9B,SAA4B,sBAAsB;AAyB3C,IAAM,sBAAoC,CAAC,MAAM,QAAQ,QAAQ;AACtE,MAAI,SAAS,MAAO;AACpB,QAAM,SAAS,QAAQ;AACvB,QAAM,OAAO,OAAO,WAAW,WAAW,SAAS;AACnD,MAAI,MAAM;AACR,SAAK,IAAI,YAAY,EAAE,MAAM,QAAQ,SAAS,CAAC,EAAE,MAAM,QAAQ,KAAK,CAAC,EAAE,CAAC;AAAA,EAC1E;AACF;AAaO,SAAS,iBACd,UACA,YACsC;AACtC,SAAO;AACT;AA+BO,SAAS,qBACd,SACS;AACT,QAAM,EAAE,SAAS,YAAY,WAAW,qBAAqB,GAAG,QAAQ,IAAI;AAC5E,QAAM,WAAW,eAAe,SAAS,UAAU;AAEnD,WAAS,iBAAiB;AACxB,UAAM,MAAM,UAAU;AACtB,UAAM,SAAS,cAA+B;AAC9C,UAAM,OAAO,QAAQ,mBAAmB,QAAQ;AAChD,QAAI,CAAC,KAAM,QAAO;AAClB,WAAO,cAAc,UAAU;AAAA,MAC7B;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,UAAU,CAAC,MAAc,WAAqC,SAAS,MAAM,QAAQ,GAAG;AAAA,IAC1F,CAAC;AAAA,EACH;AAEA,SAAO,cAAc,gBAAgB,OAAO;AAC9C;;;AC1EA,SAAS,kBAAAA,uBAAsB;;;AC3B/B,SAAS,WAAW,gBAAgB;AAEpC,SAAS,4BAAAC,iCAAgC;AA6BlC,SAAS,cACd,QACA,UAAgC,CAAC,GAChB;AACjB,QAAM,EAAE,aAAa,GAAG,IAAI;AAC5B,QAAM,CAAC,MAAM,OAAO,IAAI,SAAsB,IAAI;AAClD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAE5C,YAAU,MAAM;AACd,QAAI,CAAC,QAAQ;AACX,cAAQ,IAAI;AACZ,iBAAW,KAAK;AAChB;AAAA,IACF;AACA,QAAI,YAAY;AAChB,UAAM,WAAWC,0BAA+B;AAChD,UAAM,QAAQ,iBAAiB,MAAM;AACrC,eAAW,IAAI;AAEf,UAAM,YAAY;AAChB,iBAAW,QAAQ,OAAO;AACxB,YAAI,UAAW;AACf,cAAM,EAAE,OAAO,IAAI,SAAS,KAAK,GAAG,IAAI;AAAA,CAAI;AAC5C,gBAAQ,EAAE,GAAG,OAAO,CAAC;AACrB,YAAI,aAAa,GAAG;AAClB,gBAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,UAAU,CAAC;AAAA,QACpD;AAAA,MACF;AACA,UAAI,UAAW;AACf,cAAQ,SAAS,UAAU,CAAC;AAC5B,iBAAW,KAAK;AAAA,IAClB,GAAG;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,QAAQ,UAAU,CAAC;AAEvB,SAAO,EAAE,MAAM,OAAO,MAAM,OAAO,QAAQ;AAC7C;;;ADZA,SAAS,WAAW,mBAAmB;","names":["createRenderer","createSpecStreamCompiler","createSpecStreamCompiler"]}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * ChatGPT helpers that wrap `@openai/mcp-extensions/server`.
3
+ */
4
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { OpenAIExtensions, type OpenAIUiEntrypoint, type OpenAIUiResourceMetadata, type OpenAIUiToolMetadata } from "@openai/mcp-extensions/server";
6
+ /** Return the official OpenAI extensions surface for this MCP server. */
7
+ export declare function enableOpenAiExtensions(server: McpServer): OpenAIExtensions;
8
+ /** Build a `_meta` fragment with typed `"openai/ui"` tool metadata. */
9
+ export declare function openaiUiToolMeta(meta: OpenAIUiToolMetadata): {
10
+ "openai/ui": OpenAIUiToolMetadata;
11
+ };
12
+ /** Build a `_meta` fragment with typed `"openai/ui"` resource metadata. */
13
+ export declare function openaiUiResourceMeta(meta: OpenAIUiResourceMetadata): {
14
+ "openai/ui": OpenAIUiResourceMetadata;
15
+ };
16
+ export { OpenAIExtensions, type OpenAIUiEntrypoint, type OpenAIUiResourceMetadata, type OpenAIUiToolMetadata, };
@@ -0,0 +1,20 @@
1
+ // src/openai/server.ts
2
+ import {
3
+ OpenAIExtensions
4
+ } from "@openai/mcp-extensions/server";
5
+ function enableOpenAiExtensions(server) {
6
+ return new OpenAIExtensions(server);
7
+ }
8
+ function openaiUiToolMeta(meta) {
9
+ return { "openai/ui": meta };
10
+ }
11
+ function openaiUiResourceMeta(meta) {
12
+ return { "openai/ui": meta };
13
+ }
14
+ export {
15
+ OpenAIExtensions,
16
+ enableOpenAiExtensions,
17
+ openaiUiResourceMeta,
18
+ openaiUiToolMeta
19
+ };
20
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/openai/server.ts"],"sourcesContent":["/**\n * ChatGPT helpers that wrap `@openai/mcp-extensions/server`.\n */\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport {\n OpenAIExtensions,\n type OpenAIUiEntrypoint,\n type OpenAIUiResourceMetadata,\n type OpenAIUiToolMetadata,\n} from \"@openai/mcp-extensions/server\";\n\n/** Return the official OpenAI extensions surface for this MCP server. */\nexport function enableOpenAiExtensions(server: McpServer): OpenAIExtensions {\n return new OpenAIExtensions(server);\n}\n\n/** Build a `_meta` fragment with typed `\"openai/ui\"` tool metadata. */\nexport function openaiUiToolMeta(\n meta: OpenAIUiToolMetadata,\n): { \"openai/ui\": OpenAIUiToolMetadata } {\n return { \"openai/ui\": meta };\n}\n\n/** Build a `_meta` fragment with typed `\"openai/ui\"` resource metadata. */\nexport function openaiUiResourceMeta(\n meta: OpenAIUiResourceMetadata,\n): { \"openai/ui\": OpenAIUiResourceMetadata } {\n return { \"openai/ui\": meta };\n}\n\nexport {\n OpenAIExtensions,\n type OpenAIUiEntrypoint,\n type OpenAIUiResourceMetadata,\n type OpenAIUiToolMetadata,\n};\n"],"mappings":";AAIA;AAAA,EACE;AAAA,OAIK;AAGA,SAAS,uBAAuB,QAAqC;AAC1E,SAAO,IAAI,iBAAiB,MAAM;AACpC;AAGO,SAAS,iBACd,MACuC;AACvC,SAAO,EAAE,aAAa,KAAK;AAC7B;AAGO,SAAS,qBACd,MAC2C;AAC3C,SAAO,EAAE,aAAa,KAAK;AAC7B;","names":[]}
@@ -1,5 +1,4 @@
1
1
  import {
2
- McpContext,
3
2
  createMcpView,
4
3
  useAutoResize,
5
4
  useDisplayMode,
@@ -14,7 +13,10 @@ import {
14
13
  useToolInputPartial,
15
14
  useToolResult,
16
15
  useUpdateModelContext
17
- } from "../chunk-SXF23BZD.js";
16
+ } from "../chunk-HIAIQVK5.js";
17
+ import {
18
+ McpContext
19
+ } from "../chunk-4SZHJ4MK.js";
18
20
  import "../chunk-5L3QHYWK.js";
19
21
  export {
20
22
  McpContext,
@@ -0,0 +1,6 @@
1
+ import { OpenAIExtensions } from "@openai/mcp-extensions/app";
2
+ /**
3
+ * Official ChatGPT extension surface for this View.
4
+ * One `OpenAIExtensions` per App handle. Call `message`, `files`, `deepLink`, and the rest on the returned instance.
5
+ */
6
+ export declare function useOpenAIExtensions(): OpenAIExtensions;
@@ -0,0 +1,25 @@
1
+ import {
2
+ McpContext
3
+ } from "../chunk-4SZHJ4MK.js";
4
+
5
+ // src/react/openai-extensions.ts
6
+ import { useContext, useRef } from "react";
7
+ import { OpenAIExtensions } from "@openai/mcp-extensions/app";
8
+ function useOpenAIExtensions() {
9
+ const ctx = useContext(McpContext);
10
+ if (!ctx) {
11
+ throw new Error(
12
+ "mountly-mcp/react: useOpenAIExtensions must be used inside a View wrapped with createMcpView()."
13
+ );
14
+ }
15
+ const app = ctx.app;
16
+ const ref = useRef(null);
17
+ if (ref.current?.app !== app) {
18
+ ref.current = { app, extensions: new OpenAIExtensions(app) };
19
+ }
20
+ return ref.current.extensions;
21
+ }
22
+ export {
23
+ useOpenAIExtensions
24
+ };
25
+ //# sourceMappingURL=openai-extensions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/openai-extensions.ts"],"sourcesContent":["/**\n * ChatGPT extension hook backed by `@openai/mcp-extensions/app`.\n * Separate entry (`mountly-mcp/react/openai`) keeps the peer out of ordinary Views.\n */\nimport { useContext, useRef } from \"react\";\nimport type { App } from \"@modelcontextprotocol/ext-apps\";\nimport { OpenAIExtensions } from \"@openai/mcp-extensions/app\";\nimport { McpContext } from \"./context.js\";\n\n/**\n * Official ChatGPT extension surface for this View.\n * One `OpenAIExtensions` per App handle. Call `message`, `files`, `deepLink`, and the rest on the returned instance.\n */\nexport function useOpenAIExtensions(): OpenAIExtensions {\n const ctx = useContext(McpContext);\n if (!ctx) {\n throw new Error(\n \"mountly-mcp/react: useOpenAIExtensions must be used inside a View wrapped with createMcpView().\",\n );\n }\n const app = ctx.app;\n const ref = useRef<{ app: App; extensions: OpenAIExtensions } | null>(null);\n if (ref.current?.app !== app) {\n ref.current = { app, extensions: new OpenAIExtensions(app) };\n }\n return ref.current.extensions;\n}\n"],"mappings":";;;;;AAIA,SAAS,YAAY,cAAc;AAEnC,SAAS,wBAAwB;AAO1B,SAAS,sBAAwC;AACtD,QAAM,MAAM,WAAW,UAAU;AACjC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,MAAM,IAAI;AAChB,QAAM,MAAM,OAA0D,IAAI;AAC1E,MAAI,IAAI,SAAS,QAAQ,KAAK;AAC5B,QAAI,UAAU,EAAE,KAAK,YAAY,IAAI,iBAAiB,GAAG,EAAE;AAAA,EAC7D;AACA,SAAO,IAAI,QAAQ;AACrB;","names":[]}
@@ -116,15 +116,20 @@ async function registerMcpApps(server, options) {
116
116
  server.server.registerCapabilities({
117
117
  extensions: { [EXTENSION_ID]: { mimeTypes: [RESOURCE_MIME_TYPE] } }
118
118
  });
119
- const resources = views.map(
120
- ({ artifact, html }) => registerAppResource(
119
+ const resources = views.map(({ artifact, html }) => {
120
+ const resourceMeta = {
121
+ ui: artifact.declaration._meta.ui
122
+ };
123
+ const openaiUi = artifact.declaration._meta["openai/ui"];
124
+ if (openaiUi !== void 0) resourceMeta["openai/ui"] = openaiUi;
125
+ return registerAppResource(
121
126
  server,
122
127
  artifact.name,
123
128
  artifact.uri,
124
129
  {
125
130
  mimeType: artifact.declaration.mimeType,
126
131
  description: artifact.declaration.description,
127
- _meta: { ui: artifact.declaration._meta.ui }
132
+ _meta: resourceMeta
128
133
  },
129
134
  async () => ({
130
135
  contents: [
@@ -132,12 +137,12 @@ async function registerMcpApps(server, options) {
132
137
  uri: artifact.uri,
133
138
  mimeType: artifact.declaration.mimeType,
134
139
  text: html,
135
- _meta: { ui: artifact.declaration._meta.ui }
140
+ _meta: resourceMeta
136
141
  }
137
142
  ]
138
143
  })
139
- )
140
- );
144
+ );
145
+ });
141
146
  const tools = (options.tools ?? []).map((registration) => {
142
147
  const { meta, withoutUi, visibility } = mergedToolMetadata(registration);
143
148
  const config = registration.config ?? {};