mountly-mcp 4.3.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -0
- package/dist/artifact/declaration.d.ts +61 -0
- package/dist/artifact/index.d.ts +2 -42
- package/dist/artifact/index.js +9 -5
- package/dist/chunk-6T5EIQGE.js +104 -0
- package/dist/chunk-6T5EIQGE.js.map +1 -0
- package/dist/chunk-BLT2MK32.js +35 -0
- package/dist/chunk-BLT2MK32.js.map +1 -0
- package/dist/{chunk-HIAIQVK5.js → chunk-QIA76O6M.js} +9 -1
- package/dist/chunk-QIA76O6M.js.map +1 -0
- package/dist/{chunk-DKK26TRS.js → chunk-RLDSMU5M.js} +9 -70
- package/dist/chunk-RLDSMU5M.js.map +1 -0
- package/dist/chunk-UMQ5OULD.js +243 -0
- package/dist/chunk-UMQ5OULD.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/dev/host-entry.js +23 -0
- package/dist/{doctor-4MTC6Y4X.js → doctor-OTOROLPI.js} +118 -9
- package/dist/doctor-OTOROLPI.js.map +1 -0
- package/dist/doctor.d.ts +4 -0
- package/dist/download.d.ts +17 -0
- package/dist/events/index.d.ts +97 -0
- package/dist/events/index.js +215 -0
- package/dist/events/index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -3
- package/dist/json-render/index.js +2 -1
- package/dist/json-render/index.js.map +1 -1
- package/dist/react/hooks.d.ts +7 -0
- package/dist/react/index.js +4 -1
- package/dist/server/core.d.ts +84 -0
- package/dist/server/index.d.ts +2 -72
- package/dist/server/index.js +19 -219
- package/dist/server/index.js.map +1 -1
- package/dist/server/workers.d.ts +7 -0
- package/dist/server/workers.js +14 -0
- package/dist/server/workers.js.map +1 -0
- package/dist/testing/index.js +5 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/vite/index.d.ts +15 -1
- package/dist/vite/index.js +133 -11
- package/dist/vite/index.js.map +1 -1
- package/dist/vue/index.d.ts +6 -0
- package/dist/vue/index.js +8 -0
- package/dist/vue/index.js.map +1 -1
- package/package.json +18 -6
- package/templates/react/README.md.tmpl +3 -0
- package/templates/react/package.json.tmpl +6 -3
- package/templates/svelte/README.md.tmpl +2 -0
- package/templates/svelte/package.json.tmpl +5 -3
- package/templates/svelte/src/env.d.ts.tmpl +10 -0
- package/templates/vanilla/README.md.tmpl +2 -0
- package/templates/vanilla/package.json.tmpl +5 -2
- package/templates/vue/README.md.tmpl +2 -0
- package/templates/vue/package.json.tmpl +6 -4
- package/templates/vue/src/env.d.ts.tmpl +10 -0
- package/dist/chunk-DKK26TRS.js.map +0 -1
- package/dist/chunk-HIAIQVK5.js.map +0 -1
- package/dist/doctor-4MTC6Y4X.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":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from "./types.js";
|
|
|
2
2
|
export * from "./schema.js";
|
|
3
3
|
export * from "./publish.js";
|
|
4
4
|
export { runBridge, App, PostMessageTransport } from "./bridge/index.js";
|
|
5
|
+
export { downloadFile, type DownloadableFile } from "./download.js";
|
|
5
6
|
export type { RunBridgeOptions, RunningBridge } from "./bridge/index.js";
|
|
6
7
|
export { sandboxProxyHtml } from "./sandbox/index.js";
|
|
7
8
|
export type { SandboxProxyHtmlOptions } from "./sandbox/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sandboxProxyHtml
|
|
3
|
+
} from "./chunk-6QHKKFO3.js";
|
|
4
|
+
import {
|
|
5
|
+
downloadFile
|
|
6
|
+
} from "./chunk-BLT2MK32.js";
|
|
1
7
|
import {
|
|
2
8
|
MCP_VIEW_GLOBAL_KEY,
|
|
3
9
|
getPublishedMcpView,
|
|
4
10
|
publishMcpView
|
|
5
11
|
} from "./chunk-5L3QHYWK.js";
|
|
6
|
-
import {
|
|
7
|
-
sandboxProxyHtml
|
|
8
|
-
} from "./chunk-6QHKKFO3.js";
|
|
9
12
|
import {
|
|
10
13
|
App,
|
|
11
14
|
PostMessageTransport,
|
|
@@ -30,6 +33,7 @@ export {
|
|
|
30
33
|
MCP_VIEW_GLOBAL_KEY,
|
|
31
34
|
PostMessageTransport,
|
|
32
35
|
RESOURCE_URI_META_KEY,
|
|
36
|
+
downloadFile,
|
|
33
37
|
getPublishedMcpView,
|
|
34
38
|
publishMcpView,
|
|
35
39
|
runBridge,
|
|
@@ -19,8 +19,9 @@ import {
|
|
|
19
19
|
createMcpView,
|
|
20
20
|
useMcpApp,
|
|
21
21
|
useToolResult
|
|
22
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-QIA76O6M.js";
|
|
23
23
|
import "../chunk-4SZHJ4MK.js";
|
|
24
|
+
import "../chunk-BLT2MK32.js";
|
|
24
25
|
import "../chunk-5L3QHYWK.js";
|
|
25
26
|
import "../chunk-YJA4H6IB.js";
|
|
26
27
|
|
|
@@ -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"]}
|
package/dist/react/hooks.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useAutoResize as useAutoResizeBase } from "@modelcontextprotocol/ext-apps/react";
|
|
2
2
|
import type { App, McpUiDisplayMode, McpUiHostContext, McpUiUpdateModelContextRequest } from "@modelcontextprotocol/ext-apps";
|
|
3
|
+
import { type DownloadableFile } from "../download.js";
|
|
3
4
|
/** The view-side ext-apps App. Use this to call server tools, send messages, open links, etc. */
|
|
4
5
|
export declare function useMcpApp(): App;
|
|
5
6
|
/** Complete notification params; read the tool arguments from `.arguments`. */
|
|
@@ -42,5 +43,11 @@ export declare function useDocumentTheme(): import("@modelcontextprotocol/ext-ap
|
|
|
42
43
|
* triggering an immediate response. Only the last update is kept.
|
|
43
44
|
*/
|
|
44
45
|
export declare function useUpdateModelContext(): (params: McpUiUpdateModelContextRequest["params"]) => Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Return a function that hands the user a file: through the host's
|
|
48
|
+
* `ui/download-file` where it has one, else an ordinary `<a download>`.
|
|
49
|
+
* Resolves `false` when the host declined or the user cancelled.
|
|
50
|
+
*/
|
|
51
|
+
export declare function useDownload(): (file: DownloadableFile) => Promise<boolean>;
|
|
45
52
|
/** Manually control auto-resize (rarely needed — the bridge calls setupSizeChangedNotifications by default). */
|
|
46
53
|
export declare function useAutoResize(...args: Parameters<typeof useAutoResizeBase>): void;
|
package/dist/react/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
useAutoResize,
|
|
4
4
|
useDisplayMode,
|
|
5
5
|
useDocumentTheme,
|
|
6
|
+
useDownload,
|
|
6
7
|
useHostContext,
|
|
7
8
|
useHostFonts,
|
|
8
9
|
useHostStyleVariables,
|
|
@@ -13,10 +14,11 @@ import {
|
|
|
13
14
|
useToolInputPartial,
|
|
14
15
|
useToolResult,
|
|
15
16
|
useUpdateModelContext
|
|
16
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-QIA76O6M.js";
|
|
17
18
|
import {
|
|
18
19
|
McpContext
|
|
19
20
|
} from "../chunk-4SZHJ4MK.js";
|
|
21
|
+
import "../chunk-BLT2MK32.js";
|
|
20
22
|
import "../chunk-5L3QHYWK.js";
|
|
21
23
|
export {
|
|
22
24
|
McpContext,
|
|
@@ -24,6 +26,7 @@ export {
|
|
|
24
26
|
useAutoResize,
|
|
25
27
|
useDisplayMode,
|
|
26
28
|
useDocumentTheme,
|
|
29
|
+
useDownload,
|
|
27
30
|
useHostContext,
|
|
28
31
|
useHostFonts,
|
|
29
32
|
useHostStyleVariables,
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
4
|
+
import { EXTENSION_ID, RESOURCE_MIME_TYPE, type McpUiAppToolConfig } from "@modelcontextprotocol/ext-apps/server";
|
|
5
|
+
import type { McpUiToolVisibility } from "@modelcontextprotocol/ext-apps";
|
|
6
|
+
import { type McpAppArtifact, type McpAppSource } from "../artifact/declaration.js";
|
|
7
|
+
export interface McpAppToolResult {
|
|
8
|
+
structuredContent?: unknown;
|
|
9
|
+
content?: ReadonlyArray<{
|
|
10
|
+
type: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}>;
|
|
13
|
+
isError?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Complete official tool configuration, plus JSON Schema support at Mountly's seam. */
|
|
16
|
+
export interface McpAppToolConfig extends Omit<McpUiAppToolConfig, "_meta" | "inputSchema" | "outputSchema"> {
|
|
17
|
+
inputSchema?: object;
|
|
18
|
+
outputSchema?: object;
|
|
19
|
+
/** Additional metadata is preserved; Mountly only merges `_meta.ui`. */
|
|
20
|
+
_meta?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
/** A View is independent of the tools that reference it. */
|
|
23
|
+
export interface McpAppViewRegistration {
|
|
24
|
+
/**
|
|
25
|
+
* Built artifact or the path to its self-contained HTML — or, on a runtime
|
|
26
|
+
* with no filesystem such as Cloudflare Workers, the View itself, as
|
|
27
|
+
* `mountly-mcp.views.js` exports it.
|
|
28
|
+
*/
|
|
29
|
+
artifact: McpAppArtifact | McpAppSource | string;
|
|
30
|
+
/** Optional assertion used by migration adapters; artifacts already own identity. */
|
|
31
|
+
uri?: string;
|
|
32
|
+
}
|
|
33
|
+
/** A tool links to a separately declared View through the protocol resource URI. */
|
|
34
|
+
export interface McpAppToolRegistration<Args = unknown> {
|
|
35
|
+
name: string;
|
|
36
|
+
resourceUri: string;
|
|
37
|
+
config?: McpAppToolConfig;
|
|
38
|
+
/** Convenience override for `_meta.ui.visibility`. */
|
|
39
|
+
visibility?: McpUiToolVisibility | ReadonlyArray<McpUiToolVisibility>;
|
|
40
|
+
handler: (args: Args, extra?: unknown) => Promise<McpAppToolResult>;
|
|
41
|
+
}
|
|
42
|
+
export interface RegisterMcpAppsOptions {
|
|
43
|
+
views: ReadonlyArray<McpAppViewRegistration>;
|
|
44
|
+
tools?: ReadonlyArray<McpAppToolRegistration>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `createMcpAppServer` declares Views and tools exactly as `registerMcpApps`
|
|
48
|
+
* does; it only owns the `McpServer` for you. One vocabulary, two entry points.
|
|
49
|
+
*/
|
|
50
|
+
export interface CreateMcpAppServerOptions extends RegisterMcpAppsOptions {
|
|
51
|
+
name: string;
|
|
52
|
+
version: string;
|
|
53
|
+
}
|
|
54
|
+
export interface RegisteredMcpApps {
|
|
55
|
+
/** File-backed Views carry `htmlPath`; in-memory ones carry `html`. */
|
|
56
|
+
artifacts: ReadonlyArray<McpAppArtifact | McpAppSource>;
|
|
57
|
+
/** Remove every resource and tool installed by this registration. */
|
|
58
|
+
remove(): void;
|
|
59
|
+
}
|
|
60
|
+
/** Reads a View from disk. Only the Node entry installs one; a Worker has no disk. */
|
|
61
|
+
export type DiskViewLoader = (artifact: McpAppArtifact | string, uri: string | undefined) => Promise<{
|
|
62
|
+
artifact: McpAppArtifact;
|
|
63
|
+
html: string;
|
|
64
|
+
}>;
|
|
65
|
+
/** @internal Called once by the Node entry. */
|
|
66
|
+
export declare function installDiskViewLoader(loader: DiskViewLoader): void;
|
|
67
|
+
/**
|
|
68
|
+
* Install Mountly Views and their independently declared tools into an existing,
|
|
69
|
+
* unconnected MCP server. The caller retains ownership of identity, auth,
|
|
70
|
+
* transports, prompts, ordinary tools, and deployment.
|
|
71
|
+
*/
|
|
72
|
+
export declare function registerMcpApps(server: McpServer, options: RegisterMcpAppsOptions): Promise<RegisteredMcpApps>;
|
|
73
|
+
export interface RunningMcpAppServer {
|
|
74
|
+
connectInProcess(options?: {
|
|
75
|
+
ui?: boolean;
|
|
76
|
+
}): Promise<{
|
|
77
|
+
client: Client;
|
|
78
|
+
}>;
|
|
79
|
+
listen(transport: Transport): Promise<void>;
|
|
80
|
+
close(): Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
/** Convenience adapter that retains the original one-call server experience. */
|
|
83
|
+
export declare function createMcpAppServer(options: CreateMcpAppServerOptions): RunningMcpAppServer;
|
|
84
|
+
export { EXTENSION_ID, RESOURCE_MIME_TYPE };
|