@particle-academy/agent-integrations 0.20.0 → 0.21.1

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.
@@ -0,0 +1,308 @@
1
+ import { ensureUndoToolsRegistered, pushUndoEntry } from './chunk-KJ5AOOV7.js';
2
+ import { wrapToolWithActivity } from './chunk-ULJL53DL.js';
3
+ import { textResult, errorResult } from './chunk-4KAIV6OD.js';
4
+
5
+ // src/bridges/catalog.ts
6
+ var DEFAULT_AGENT = { id: "agent", name: "Agent", color: "#a855f7" };
7
+ var str = (v, fallback = "") => typeof v === "string" ? v : fallback;
8
+ var num = (v, fallback = 0) => typeof v === "number" && Number.isFinite(v) ? v : fallback;
9
+ function registerCatalogBridge(host, options) {
10
+ const { adapter } = options;
11
+ const agent = { ...DEFAULT_AGENT, ...options.agent ?? {} };
12
+ const pendingMode = options.pendingMode ?? true;
13
+ const catalogId = adapter.id ?? "catalog";
14
+ const disposers = [];
15
+ ensureUndoToolsRegistered(host, { defaultAgentId: agent.id });
16
+ const target = (elementId, label) => ({
17
+ kind: "custom",
18
+ screenId: adapter.screenId,
19
+ elementId,
20
+ label: label ?? catalogId
21
+ });
22
+ const reg = (name, description, properties, required, handler, resolveTarget) => {
23
+ const wrapped = async (args) => {
24
+ try {
25
+ return await handler(args);
26
+ } catch (e) {
27
+ return errorResult(e instanceof Error ? e.message : String(e));
28
+ }
29
+ };
30
+ const final = resolveTarget ? wrapToolWithActivity(wrapped, {
31
+ toolName: name,
32
+ agent,
33
+ kind: "custom",
34
+ screenId: adapter.screenId,
35
+ resolveTarget: ({ args }) => resolveTarget(args)
36
+ }) : wrapped;
37
+ disposers.push(
38
+ host.registerTool(
39
+ { name, description, inputSchema: { type: "object", properties, required, additionalProperties: false } },
40
+ final
41
+ )
42
+ );
43
+ };
44
+ const guardDestructive = async (action, id, label, hasConfirmArg) => {
45
+ if (!pendingMode) return null;
46
+ if (options.confirm) {
47
+ const ok = await options.confirm({ action, id, label });
48
+ return ok ? null : errorResult(`Declined: ${action} ${id} (human did not confirm).`);
49
+ }
50
+ if (!hasConfirmArg) {
51
+ return errorResult(`${action} is staged (pendingMode). Re-call with confirm:true to apply, or wire a host confirm hook.`);
52
+ }
53
+ return null;
54
+ };
55
+ reg(
56
+ "catalog_list_products",
57
+ "List products: { id, name, active, lookupKey, priceCount }. Pass withTrashed:true to include soft-deleted.",
58
+ { withTrashed: { type: "boolean" } },
59
+ [],
60
+ async (args) => {
61
+ const products = await adapter.products.all({ withTrashed: args.withTrashed === true });
62
+ const list = await Promise.all(
63
+ products.map(async (p) => ({
64
+ id: p.id,
65
+ name: p.name,
66
+ active: p.active ?? true,
67
+ lookupKey: p.lookupKey ?? null,
68
+ priceCount: (await adapter.prices.forProduct(p.id)).length
69
+ }))
70
+ );
71
+ return textResult(JSON.stringify(list, null, 2), list);
72
+ },
73
+ false
74
+ );
75
+ reg(
76
+ "catalog_get_product",
77
+ "Read a product's full JSON plus its prices.",
78
+ { id: { type: "string" }, withTrashed: { type: "boolean" } },
79
+ ["id"],
80
+ async (args) => {
81
+ const id = str(args.id);
82
+ const product = await adapter.products.find(id, { withTrashed: args.withTrashed === true });
83
+ if (!product) return errorResult(`No product with id ${id}`);
84
+ const prices = await adapter.prices.forProduct(id, { withTrashed: args.withTrashed === true });
85
+ const out = { product, prices };
86
+ return textResult(JSON.stringify(out, null, 2), out);
87
+ },
88
+ false
89
+ );
90
+ reg(
91
+ "catalog_list_prices",
92
+ "List prices for a product (pass productId), or every price (omit it).",
93
+ { productId: { type: "string" }, withTrashed: { type: "boolean" } },
94
+ [],
95
+ async (args) => {
96
+ const withTrashed = args.withTrashed === true;
97
+ const prices = args.productId !== void 0 ? await adapter.prices.forProduct(str(args.productId), { withTrashed }) : await adapter.prices.all({ withTrashed });
98
+ const list = prices.map((p) => ({
99
+ id: p.id,
100
+ productId: p.productId,
101
+ currency: p.currency,
102
+ unitAmount: p.unitAmount,
103
+ type: p.type,
104
+ active: p.active ?? true,
105
+ externalId: p.externalId ?? null
106
+ }));
107
+ return textResult(JSON.stringify(list, null, 2), list);
108
+ },
109
+ false
110
+ );
111
+ reg(
112
+ "catalog_create_product",
113
+ "Create a product. `name` is required; id (ULID) is auto-assigned. Returns the created product.",
114
+ {
115
+ name: { type: "string" },
116
+ description: { type: "string" },
117
+ active: { type: "boolean" },
118
+ lookupKey: { type: "string" },
119
+ metadata: { type: "object" }
120
+ },
121
+ ["name"],
122
+ async (args) => {
123
+ const name = str(args.name);
124
+ if (!name) return errorResult("name is required.");
125
+ const product = await adapter.createProduct({
126
+ name,
127
+ ...args.description !== void 0 ? { description: str(args.description) } : {},
128
+ ...args.active !== void 0 ? { active: args.active === true } : {},
129
+ ...args.lookupKey !== void 0 ? { lookupKey: str(args.lookupKey) } : {},
130
+ ...args.metadata && typeof args.metadata === "object" ? { metadata: args.metadata } : {}
131
+ });
132
+ pushUndoEntry(agent.id, {
133
+ timestamp: Date.now(),
134
+ bridgeId: catalogId,
135
+ action: "catalog_create_product",
136
+ label: `Created product ${product.id} (${product.name})`,
137
+ undo: () => void adapter.products.remove(product.id),
138
+ redo: () => void adapter.createProduct({ ...product })
139
+ });
140
+ return textResult(`Created product ${product.id} (${product.name})`, product);
141
+ },
142
+ (args) => target(void 0, `create product ${str(args.name)}`)
143
+ );
144
+ reg(
145
+ "catalog_create_price",
146
+ "Create a price under a product. unitAmount is integer minor units (cents). type is 'recurring' | 'one_time'.",
147
+ {
148
+ productId: { type: "string" },
149
+ currency: { type: "string", description: "ISO-4217, e.g. 'USD'." },
150
+ unitAmount: { type: "number", description: "Price in cents." },
151
+ type: { type: "string", enum: ["recurring", "one_time"] },
152
+ recurringInterval: { type: "string", description: "'month' | 'year' | \u2026 (recurring only)." },
153
+ nickname: { type: "string" },
154
+ lookupKey: { type: "string" },
155
+ metadata: { type: "object" }
156
+ },
157
+ ["productId", "currency", "unitAmount", "type"],
158
+ async (args) => {
159
+ const productId = str(args.productId);
160
+ const product = await adapter.products.find(productId);
161
+ if (!product) return errorResult(`No product with id ${productId}`);
162
+ const type = str(args.type);
163
+ if (type !== "recurring" && type !== "one_time") return errorResult("type must be 'recurring' or 'one_time'.");
164
+ const price = await adapter.createPrice({
165
+ productId,
166
+ currency: str(args.currency),
167
+ unitAmount: num(args.unitAmount),
168
+ type,
169
+ ...args.recurringInterval !== void 0 ? { recurringInterval: str(args.recurringInterval) } : {},
170
+ ...args.nickname !== void 0 ? { nickname: str(args.nickname) } : {},
171
+ ...args.lookupKey !== void 0 ? { lookupKey: str(args.lookupKey) } : {},
172
+ ...args.metadata && typeof args.metadata === "object" ? { metadata: args.metadata } : {}
173
+ });
174
+ pushUndoEntry(agent.id, {
175
+ timestamp: Date.now(),
176
+ bridgeId: catalogId,
177
+ action: "catalog_create_price",
178
+ label: `Created price ${price.id} on ${productId}`,
179
+ undo: () => void adapter.prices.remove(price.id),
180
+ redo: () => void adapter.createPrice({ ...price })
181
+ });
182
+ return textResult(`Created ${type} price ${price.id} on ${productId}`, price);
183
+ },
184
+ (args) => target(str(args.productId), `create price on ${str(args.productId)}`)
185
+ );
186
+ reg(
187
+ "catalog_delete_product",
188
+ "Soft-delete a product (preserves financial history). Staged in pendingMode \u2014 pass confirm:true or wire a host confirm hook.",
189
+ { id: { type: "string" }, confirm: { type: "boolean" } },
190
+ ["id"],
191
+ async (args) => {
192
+ const id = str(args.id);
193
+ const product = await adapter.products.find(id, { withTrashed: true });
194
+ if (!product) return errorResult(`No product with id ${id}`);
195
+ const blocked = await guardDestructive("catalog_delete_product", id, product.name, args.confirm === true);
196
+ if (blocked) return blocked;
197
+ const snapshot = { ...product };
198
+ await adapter.products.remove(id);
199
+ pushUndoEntry(agent.id, {
200
+ timestamp: Date.now(),
201
+ bridgeId: catalogId,
202
+ action: "catalog_delete_product",
203
+ label: `Deleted product ${id} (${product.name})`,
204
+ undo: () => void adapter.createProduct({ ...snapshot }),
205
+ redo: () => void adapter.products.remove(id)
206
+ });
207
+ return textResult(`Deleted product ${id}`, { id });
208
+ },
209
+ (args) => target(str(args.id), `delete product ${str(args.id)}`)
210
+ );
211
+ reg(
212
+ "catalog_delete_price",
213
+ "Soft-delete a price (mirrors Stripe archiving). Staged in pendingMode \u2014 pass confirm:true or wire a host confirm hook.",
214
+ { id: { type: "string" }, confirm: { type: "boolean" } },
215
+ ["id"],
216
+ async (args) => {
217
+ const id = str(args.id);
218
+ const price = await adapter.prices.find(id, { withTrashed: true });
219
+ if (!price) return errorResult(`No price with id ${id}`);
220
+ const blocked = await guardDestructive("catalog_delete_price", id, id, args.confirm === true);
221
+ if (blocked) return blocked;
222
+ const snapshot = { ...price };
223
+ await adapter.prices.remove(id);
224
+ pushUndoEntry(agent.id, {
225
+ timestamp: Date.now(),
226
+ bridgeId: catalogId,
227
+ action: "catalog_delete_price",
228
+ label: `Deleted price ${id}`,
229
+ undo: () => void adapter.createPrice({ ...snapshot }),
230
+ redo: () => void adapter.prices.remove(id)
231
+ });
232
+ return textResult(`Deleted price ${id}`, { id });
233
+ },
234
+ (args) => target(str(args.id), `delete price ${str(args.id)}`)
235
+ );
236
+ reg(
237
+ "catalog_sync_product",
238
+ "Push a product + its prices to Stripe (creates/updates the Stripe Product + Prices, stamps external ids).",
239
+ { id: { type: "string" } },
240
+ ["id"],
241
+ async (args) => {
242
+ if (!adapter.syncProductAndPrices) return errorResult("Host did not wire Stripe sync.");
243
+ const id = str(args.id);
244
+ const product = await adapter.products.find(id);
245
+ if (!product) return errorResult(`No product with id ${id}`);
246
+ const synced = await adapter.syncProductAndPrices(product);
247
+ return textResult(`Synced product ${id} to Stripe (${synced.externalId ?? "no external id"})`, synced);
248
+ },
249
+ (args) => target(str(args.id), `sync product ${str(args.id)}`)
250
+ );
251
+ reg(
252
+ "catalog_test_connection",
253
+ "Verify the Stripe connection \u2014 returns { success, message, productCount? }.",
254
+ {},
255
+ [],
256
+ async () => {
257
+ if (!adapter.testConnection) return errorResult("Host did not wire a Stripe connection test.");
258
+ const result = await adapter.testConnection();
259
+ return textResult(JSON.stringify(result, null, 2), result);
260
+ },
261
+ false
262
+ );
263
+ reg(
264
+ "catalog_create_checkout",
265
+ "Build a hosted Stripe Checkout URL for a price. Recurring prices \u2192 subscription; one-time \u2192 payment (pass quantity). Requires the price be synced to Stripe first.",
266
+ {
267
+ priceId: { type: "string" },
268
+ successUrl: { type: "string" },
269
+ cancelUrl: { type: "string" },
270
+ customer: { type: "string", description: "Stripe customer id (optional \u2014 Checkout collects one if omitted)." },
271
+ quantity: { type: "number", description: "One-time checkouts only. Default 1." },
272
+ metadata: { type: "object" }
273
+ },
274
+ ["priceId", "successUrl", "cancelUrl"],
275
+ async (args) => {
276
+ const priceId = str(args.priceId);
277
+ const price = await adapter.prices.find(priceId);
278
+ if (!price) return errorResult(`No price with id ${priceId}`);
279
+ const checkoutArgs = {
280
+ successUrl: str(args.successUrl),
281
+ cancelUrl: str(args.cancelUrl),
282
+ ...args.customer !== void 0 ? { customer: str(args.customer) } : {},
283
+ ...args.metadata && typeof args.metadata === "object" ? { metadata: args.metadata } : {}
284
+ };
285
+ let url;
286
+ if (price.type === "recurring") {
287
+ if (!adapter.getSubscriptionCheckoutUrl) return errorResult("Host did not wire subscription checkout.");
288
+ url = await adapter.getSubscriptionCheckoutUrl(price, checkoutArgs);
289
+ } else {
290
+ if (!adapter.getOneTimeCheckoutUrl) return errorResult("Host did not wire one-time checkout.");
291
+ url = await adapter.getOneTimeCheckoutUrl(price, { ...checkoutArgs, quantity: num(args.quantity, 1) });
292
+ }
293
+ return textResult(url || "(no url)", { priceId, type: price.type, url });
294
+ },
295
+ (args) => target(str(args.priceId), `checkout ${str(args.priceId)}`)
296
+ );
297
+ return {
298
+ id: `catalog:${catalogId}`,
299
+ title: adapter.title ?? "Catalog",
300
+ dispose: () => {
301
+ for (const d of disposers.splice(0)) d();
302
+ }
303
+ };
304
+ }
305
+
306
+ export { registerCatalogBridge };
307
+ //# sourceMappingURL=chunk-267JS64O.js.map
308
+ //# sourceMappingURL=chunk-267JS64O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bridges/catalog.ts"],"names":[],"mappings":"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","file":"chunk-267JS64O.js","sourcesContent":["import { textResult, errorResult } from \"../mcp/server\";\nimport type { ToolHost } from \"../mcp/tool-host\";\nimport type { JsonObject } from \"../mcp/types\";\nimport type { Bridge } from \"./types\";\nimport { wrapToolWithActivity } from \"../presence/wrap-tool-with-activity\";\nimport type { AgentTarget } from \"../presence/types\";\nimport { pushUndoEntry } from \"../undo/undo-stack\";\nimport { ensureUndoToolsRegistered } from \"../undo/undo-tools\";\n\n/**\n * Headless bridge for `@particle-academy/fancy-catalog` — a Stripe catalog with\n * NO UI surface. The adapter exposes the catalog's STORES + operations (not UI\n * getters/setters), and the tools are CRUD over products / prices + checkout +\n * Stripe sync. Mutations still funnel through `wrapToolWithActivity` so presence\n * + undo compose, and the destructive delete is `pendingMode`-gated.\n *\n * The adapter shapes below are STRUCTURAL mirrors of fancy-catalog's public\n * `Catalog` + store interfaces, defined LOCALLY so this bridge builds with the\n * sibling package absent (it's an optional peer). A real `Catalog` instance is\n * assignable to `CatalogBridgeAdapter` with no import — TypeScript is structural.\n */\n\n/** Minimal product shape crossing the MCP wire (mirror of fancy-catalog `Product`). */\nexport type CatalogProduct = {\n id: string;\n name: string;\n description?: string | null;\n active?: boolean;\n lookupKey?: string | null;\n externalId?: string | null;\n metadata?: Record<string, unknown> | null;\n [k: string]: unknown;\n};\n\n/** Minimal price shape crossing the MCP wire (mirror of fancy-catalog `Price`). */\nexport type CatalogPrice = {\n id: string;\n productId: string;\n active?: boolean;\n currency: string;\n unitAmount: number;\n type: \"recurring\" | \"one_time\";\n externalId?: string | null;\n [k: string]: unknown;\n};\n\n/** Common checkout args (mirror of fancy-catalog `CheckoutArgs`). */\nexport type CatalogCheckoutArgs = {\n customer?: string;\n successUrl: string;\n cancelUrl: string;\n metadata?: Record<string, string>;\n /** One-time checkouts only. */\n quantity?: number;\n};\n\n/**\n * Adapter = the catalog instance / stores. A live `Catalog` from fancy-catalog\n * satisfies this directly (`catalog.products`, `catalog.createProduct`, …); a\n * host can also hand-roll one over its own persistence.\n */\nexport type CatalogBridgeAdapter = {\n /** Stable id for this catalog instance (multiple catalogs ⇒ distinct ids). */\n id?: string;\n /** Display label for logs / presence. */\n title?: string;\n /** Optional fancy-screens screen id (usually unset — this is headless). */\n screenId?: string;\n\n /** Product store — `catalog.products`. */\n products: {\n find(id: string, opts?: { withTrashed?: boolean }): CatalogProduct | null | Promise<CatalogProduct | null>;\n all(opts?: { withTrashed?: boolean }): CatalogProduct[] | Promise<CatalogProduct[]>;\n remove(id: string): void | Promise<void>;\n };\n /** Price store — `catalog.prices`. */\n prices: {\n find(id: string, opts?: { withTrashed?: boolean }): CatalogPrice | null | Promise<CatalogPrice | null>;\n forProduct(productId: string, opts?: { withTrashed?: boolean }): CatalogPrice[] | Promise<CatalogPrice[]>;\n all(opts?: { withTrashed?: boolean }): CatalogPrice[] | Promise<CatalogPrice[]>;\n remove(id: string): void | Promise<void>;\n };\n\n /** Authoring helpers — `catalog.createProduct` / `catalog.createPrice` (ULIDs auto-assigned). */\n createProduct(input: Partial<CatalogProduct> & { name: string }): Promise<CatalogProduct> | CatalogProduct;\n createPrice(\n input: Partial<CatalogPrice> & { productId: string; currency: string; unitAmount: number; type: \"recurring\" | \"one_time\" },\n ): Promise<CatalogPrice> | CatalogPrice;\n\n /** Stripe sync — `catalog.syncProductAndPrices`. Optional (no-op when absent). */\n syncProductAndPrices?(product: CatalogProduct): Promise<CatalogProduct> | CatalogProduct;\n /** Connection test — `catalog.testConnection`. Optional. */\n testConnection?(): Promise<{ success: boolean; message: string; productCount?: number }> | { success: boolean; message: string; productCount?: number };\n\n /** Hosted Checkout — `catalog.getSubscriptionCheckoutUrl` / `getOneTimeCheckoutUrl`. Optional. */\n getSubscriptionCheckoutUrl?(price: CatalogPrice, args: CatalogCheckoutArgs): Promise<string> | string;\n getOneTimeCheckoutUrl?(price: CatalogPrice, args: CatalogCheckoutArgs): Promise<string> | string;\n};\n\nexport type CatalogBridgeOptions = {\n adapter: CatalogBridgeAdapter;\n /** Identity stamped on activity + undo entries. */\n agent?: { id: string; name?: string; color?: string };\n /**\n * Trust-but-verify. When on (default), destructive ops (`catalog_delete_product`,\n * `catalog_delete_price`) route through `adapter`-less staging: they require an\n * explicit `confirm: true` arg OR a host `confirm` callback. Read + create +\n * checkout-url tools are unaffected.\n */\n pendingMode?: boolean;\n /** Host confirm hook for destructive ops (pendingMode). Resolves true to proceed. */\n confirm?: (req: { action: string; id: string; label: string }) => Promise<boolean> | boolean;\n};\n\nconst DEFAULT_AGENT = { id: \"agent\", name: \"Agent\", color: \"#a855f7\" };\n\nconst str = (v: unknown, fallback = \"\"): string => (typeof v === \"string\" ? v : fallback);\nconst num = (v: unknown, fallback = 0): number => (typeof v === \"number\" && Number.isFinite(v) ? v : fallback);\n\n/**\n * registerCatalogBridge — full MCP tool set over a headless Stripe catalog.\n *\n * catalog_list_products list products ({ id, name, active, priceCount })\n * catalog_get_product full product JSON + its prices\n * catalog_create_product create a product (ULID auto-assigned)\n * catalog_delete_product soft-delete a product (pendingMode-gated, undoable*)\n * catalog_list_prices list a product's prices (or all)\n * catalog_create_price create a price under a product\n * catalog_delete_price soft-delete a price (pendingMode-gated)\n * catalog_sync_product push a product + its prices to Stripe\n * catalog_test_connection verify the Stripe connection\n * catalog_create_checkout build a hosted Checkout URL for a price\n *\n * *delete undo is best-effort: stores expose soft-delete (`remove`) but no\n * un-delete, so the undo closure re-creates from the captured snapshot.\n */\nexport function registerCatalogBridge(host: ToolHost, options: CatalogBridgeOptions): Bridge {\n const { adapter } = options;\n const agent = { ...DEFAULT_AGENT, ...(options.agent ?? {}) };\n const pendingMode = options.pendingMode ?? true;\n const catalogId = adapter.id ?? \"catalog\";\n const disposers: Array<() => void> = [];\n\n ensureUndoToolsRegistered(host, { defaultAgentId: agent.id });\n\n const target = (elementId?: string, label?: string): AgentTarget => ({\n kind: \"custom\",\n screenId: adapter.screenId,\n elementId,\n label: label ?? catalogId,\n });\n\n const reg = (\n name: string,\n description: string,\n properties: Record<string, unknown>,\n required: string[],\n handler: (args: JsonObject) => Promise<unknown> | unknown,\n resolveTarget: false | ((args: JsonObject) => AgentTarget),\n ) => {\n const wrapped = async (args: JsonObject) => {\n try {\n return await handler(args);\n } catch (e) {\n return errorResult(e instanceof Error ? e.message : String(e));\n }\n };\n const final = resolveTarget\n ? wrapToolWithActivity(wrapped as never, {\n toolName: name,\n agent,\n kind: \"custom\",\n screenId: adapter.screenId,\n resolveTarget: ({ args }) => resolveTarget(args as JsonObject),\n })\n : wrapped;\n disposers.push(\n host.registerTool(\n { name, description, inputSchema: { type: \"object\", properties: properties as Record<string, never>, required, additionalProperties: false } },\n final as never,\n ),\n );\n };\n\n /** Stage a destructive op behind the human (pendingMode). Returns null to proceed, or an errorResult to block. */\n const guardDestructive = async (action: string, id: string, label: string, hasConfirmArg: boolean) => {\n if (!pendingMode) return null;\n if (options.confirm) {\n const ok = await options.confirm({ action, id, label });\n return ok ? null : errorResult(`Declined: ${action} ${id} (human did not confirm).`);\n }\n // No host confirm hook → require an explicit confirm:true arg as the staged ack.\n if (!hasConfirmArg) {\n return errorResult(`${action} is staged (pendingMode). Re-call with confirm:true to apply, or wire a host confirm hook.`);\n }\n return null;\n };\n\n // ─── Read tools ──────────────────────────────────────────────────────────\n\n reg(\n \"catalog_list_products\",\n \"List products: { id, name, active, lookupKey, priceCount }. Pass withTrashed:true to include soft-deleted.\",\n { withTrashed: { type: \"boolean\" } },\n [],\n async (args) => {\n const products = await adapter.products.all({ withTrashed: args.withTrashed === true });\n const list = await Promise.all(\n products.map(async (p) => ({\n id: p.id,\n name: p.name,\n active: p.active ?? true,\n lookupKey: p.lookupKey ?? null,\n priceCount: (await adapter.prices.forProduct(p.id)).length,\n })),\n );\n return textResult(JSON.stringify(list, null, 2), list);\n },\n false,\n );\n\n reg(\n \"catalog_get_product\",\n \"Read a product's full JSON plus its prices.\",\n { id: { type: \"string\" }, withTrashed: { type: \"boolean\" } },\n [\"id\"],\n async (args) => {\n const id = str(args.id);\n const product = await adapter.products.find(id, { withTrashed: args.withTrashed === true });\n if (!product) return errorResult(`No product with id ${id}`);\n const prices = await adapter.prices.forProduct(id, { withTrashed: args.withTrashed === true });\n const out = { product, prices };\n return textResult(JSON.stringify(out, null, 2), out);\n },\n false,\n );\n\n reg(\n \"catalog_list_prices\",\n \"List prices for a product (pass productId), or every price (omit it).\",\n { productId: { type: \"string\" }, withTrashed: { type: \"boolean\" } },\n [],\n async (args) => {\n const withTrashed = args.withTrashed === true;\n const prices = args.productId !== undefined\n ? await adapter.prices.forProduct(str(args.productId), { withTrashed })\n : await adapter.prices.all({ withTrashed });\n const list = prices.map((p) => ({\n id: p.id,\n productId: p.productId,\n currency: p.currency,\n unitAmount: p.unitAmount,\n type: p.type,\n active: p.active ?? true,\n externalId: p.externalId ?? null,\n }));\n return textResult(JSON.stringify(list, null, 2), list);\n },\n false,\n );\n\n // ─── Create tools ────────────────────────────────────────────────────────\n\n reg(\n \"catalog_create_product\",\n \"Create a product. `name` is required; id (ULID) is auto-assigned. Returns the created product.\",\n {\n name: { type: \"string\" },\n description: { type: \"string\" },\n active: { type: \"boolean\" },\n lookupKey: { type: \"string\" },\n metadata: { type: \"object\" },\n },\n [\"name\"],\n async (args) => {\n const name = str(args.name);\n if (!name) return errorResult(\"name is required.\");\n const product = await adapter.createProduct({\n name,\n ...(args.description !== undefined ? { description: str(args.description) } : {}),\n ...(args.active !== undefined ? { active: args.active === true } : {}),\n ...(args.lookupKey !== undefined ? { lookupKey: str(args.lookupKey) } : {}),\n ...(args.metadata && typeof args.metadata === \"object\" ? { metadata: args.metadata as Record<string, unknown> } : {}),\n });\n pushUndoEntry(agent.id, {\n timestamp: Date.now(),\n bridgeId: catalogId,\n action: \"catalog_create_product\",\n label: `Created product ${product.id} (${product.name})`,\n undo: () => void adapter.products.remove(product.id),\n redo: () => void adapter.createProduct({ ...product }),\n });\n return textResult(`Created product ${product.id} (${product.name})`, product);\n },\n (args) => target(undefined, `create product ${str(args.name)}`),\n );\n\n reg(\n \"catalog_create_price\",\n \"Create a price under a product. unitAmount is integer minor units (cents). type is 'recurring' | 'one_time'.\",\n {\n productId: { type: \"string\" },\n currency: { type: \"string\", description: \"ISO-4217, e.g. 'USD'.\" },\n unitAmount: { type: \"number\", description: \"Price in cents.\" },\n type: { type: \"string\", enum: [\"recurring\", \"one_time\"] },\n recurringInterval: { type: \"string\", description: \"'month' | 'year' | … (recurring only).\" },\n nickname: { type: \"string\" },\n lookupKey: { type: \"string\" },\n metadata: { type: \"object\" },\n },\n [\"productId\", \"currency\", \"unitAmount\", \"type\"],\n async (args) => {\n const productId = str(args.productId);\n const product = await adapter.products.find(productId);\n if (!product) return errorResult(`No product with id ${productId}`);\n const type = str(args.type) as \"recurring\" | \"one_time\";\n if (type !== \"recurring\" && type !== \"one_time\") return errorResult(\"type must be 'recurring' or 'one_time'.\");\n const price = await adapter.createPrice({\n productId,\n currency: str(args.currency),\n unitAmount: num(args.unitAmount),\n type,\n ...(args.recurringInterval !== undefined ? { recurringInterval: str(args.recurringInterval) } : {}),\n ...(args.nickname !== undefined ? { nickname: str(args.nickname) } : {}),\n ...(args.lookupKey !== undefined ? { lookupKey: str(args.lookupKey) } : {}),\n ...(args.metadata && typeof args.metadata === \"object\" ? { metadata: args.metadata as Record<string, unknown> } : {}),\n });\n pushUndoEntry(agent.id, {\n timestamp: Date.now(),\n bridgeId: catalogId,\n action: \"catalog_create_price\",\n label: `Created price ${price.id} on ${productId}`,\n undo: () => void adapter.prices.remove(price.id),\n redo: () => void adapter.createPrice({ ...price }),\n });\n return textResult(`Created ${type} price ${price.id} on ${productId}`, price);\n },\n (args) => target(str(args.productId), `create price on ${str(args.productId)}`),\n );\n\n // ─── Destructive tools (pendingMode-gated) ───────────────────────────────\n\n reg(\n \"catalog_delete_product\",\n \"Soft-delete a product (preserves financial history). Staged in pendingMode — pass confirm:true or wire a host confirm hook.\",\n { id: { type: \"string\" }, confirm: { type: \"boolean\" } },\n [\"id\"],\n async (args) => {\n const id = str(args.id);\n const product = await adapter.products.find(id, { withTrashed: true });\n if (!product) return errorResult(`No product with id ${id}`);\n const blocked = await guardDestructive(\"catalog_delete_product\", id, product.name, args.confirm === true);\n if (blocked) return blocked;\n const snapshot = { ...product };\n await adapter.products.remove(id);\n pushUndoEntry(agent.id, {\n timestamp: Date.now(),\n bridgeId: catalogId,\n action: \"catalog_delete_product\",\n label: `Deleted product ${id} (${product.name})`,\n undo: () => void adapter.createProduct({ ...snapshot }),\n redo: () => void adapter.products.remove(id),\n });\n return textResult(`Deleted product ${id}`, { id });\n },\n (args) => target(str(args.id), `delete product ${str(args.id)}`),\n );\n\n reg(\n \"catalog_delete_price\",\n \"Soft-delete a price (mirrors Stripe archiving). Staged in pendingMode — pass confirm:true or wire a host confirm hook.\",\n { id: { type: \"string\" }, confirm: { type: \"boolean\" } },\n [\"id\"],\n async (args) => {\n const id = str(args.id);\n const price = await adapter.prices.find(id, { withTrashed: true });\n if (!price) return errorResult(`No price with id ${id}`);\n const blocked = await guardDestructive(\"catalog_delete_price\", id, id, args.confirm === true);\n if (blocked) return blocked;\n const snapshot = { ...price };\n await adapter.prices.remove(id);\n pushUndoEntry(agent.id, {\n timestamp: Date.now(),\n bridgeId: catalogId,\n action: \"catalog_delete_price\",\n label: `Deleted price ${id}`,\n undo: () => void adapter.createPrice({ ...snapshot }),\n redo: () => void adapter.prices.remove(id),\n });\n return textResult(`Deleted price ${id}`, { id });\n },\n (args) => target(str(args.id), `delete price ${str(args.id)}`),\n );\n\n // ─── Stripe sync ─────────────────────────────────────────────────────────\n\n reg(\n \"catalog_sync_product\",\n \"Push a product + its prices to Stripe (creates/updates the Stripe Product + Prices, stamps external ids).\",\n { id: { type: \"string\" } },\n [\"id\"],\n async (args) => {\n if (!adapter.syncProductAndPrices) return errorResult(\"Host did not wire Stripe sync.\");\n const id = str(args.id);\n const product = await adapter.products.find(id);\n if (!product) return errorResult(`No product with id ${id}`);\n const synced = await adapter.syncProductAndPrices(product);\n return textResult(`Synced product ${id} to Stripe (${synced.externalId ?? \"no external id\"})`, synced);\n },\n (args) => target(str(args.id), `sync product ${str(args.id)}`),\n );\n\n reg(\n \"catalog_test_connection\",\n \"Verify the Stripe connection — returns { success, message, productCount? }.\",\n {},\n [],\n async () => {\n if (!adapter.testConnection) return errorResult(\"Host did not wire a Stripe connection test.\");\n const result = await adapter.testConnection();\n return textResult(JSON.stringify(result, null, 2), result);\n },\n false,\n );\n\n // ─── Checkout ────────────────────────────────────────────────────────────\n\n reg(\n \"catalog_create_checkout\",\n \"Build a hosted Stripe Checkout URL for a price. Recurring prices → subscription; one-time → payment (pass quantity). Requires the price be synced to Stripe first.\",\n {\n priceId: { type: \"string\" },\n successUrl: { type: \"string\" },\n cancelUrl: { type: \"string\" },\n customer: { type: \"string\", description: \"Stripe customer id (optional — Checkout collects one if omitted).\" },\n quantity: { type: \"number\", description: \"One-time checkouts only. Default 1.\" },\n metadata: { type: \"object\" },\n },\n [\"priceId\", \"successUrl\", \"cancelUrl\"],\n async (args) => {\n const priceId = str(args.priceId);\n const price = await adapter.prices.find(priceId);\n if (!price) return errorResult(`No price with id ${priceId}`);\n const checkoutArgs: CatalogCheckoutArgs = {\n successUrl: str(args.successUrl),\n cancelUrl: str(args.cancelUrl),\n ...(args.customer !== undefined ? { customer: str(args.customer) } : {}),\n ...(args.metadata && typeof args.metadata === \"object\" ? { metadata: args.metadata as Record<string, string> } : {}),\n };\n let url: string;\n if (price.type === \"recurring\") {\n if (!adapter.getSubscriptionCheckoutUrl) return errorResult(\"Host did not wire subscription checkout.\");\n url = await adapter.getSubscriptionCheckoutUrl(price, checkoutArgs);\n } else {\n if (!adapter.getOneTimeCheckoutUrl) return errorResult(\"Host did not wire one-time checkout.\");\n url = await adapter.getOneTimeCheckoutUrl(price, { ...checkoutArgs, quantity: num(args.quantity, 1) });\n }\n return textResult(url || \"(no url)\", { priceId, type: price.type, url });\n },\n (args) => target(str(args.priceId), `checkout ${str(args.priceId)}`),\n );\n\n return {\n id: `catalog:${catalogId}`,\n title: adapter.title ?? \"Catalog\",\n dispose: () => {\n for (const d of disposers.splice(0)) d();\n },\n };\n}\n"]}
@@ -26,7 +26,7 @@ function buildVscodeDeeplink(server, opts = {}) {
26
26
  return `${scheme}://mcp/install?${payload}`;
27
27
  }
28
28
  function slugifyServerName(name) {
29
- const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
29
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
30
30
  return slug || "mcp-server";
31
31
  }
32
32
  function buildManualConfig(server) {
@@ -281,5 +281,5 @@ function ManualPopover({
281
281
  }
282
282
 
283
283
  export { CLAUDE_CONNECTORS_URL, CONNECTOR_GLYPHS, CONNECTOR_TARGETS, ClaudeMark, ConnectorButtons, CursorMark, DesktopMark, VscodeMark, WrenchMark, buildCursorDeeplink, buildManualConfig, buildManualConfigSnippet, buildVscodeDeeplink, connectorHref, encodeBase64Json, slugifyServerName };
284
- //# sourceMappingURL=chunk-54QEFRMS.js.map
285
- //# sourceMappingURL=chunk-54QEFRMS.js.map
284
+ //# sourceMappingURL=chunk-5HNTNIWY.js.map
285
+ //# sourceMappingURL=chunk-5HNTNIWY.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/connectors/targets.ts","../src/connectors/glyphs.tsx","../src/connectors/ConnectorButtons.tsx"],"names":["jsx"],"mappings":"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","file":"chunk-54QEFRMS.js","sourcesContent":["// Per-client MCP \"install\" affordances — the single source of truth for the\n// subtly-different way each MCP host wants a remote server handed to it.\n//\n// These are PURE, framework-agnostic builders (no React, no DOM): a host can\n// call them to render its own buttons, and <ConnectorButtons> is a thin UI\n// over them. Every quirk below is a real one rediscovered the hard way:\n//\n// - Claude has NO install deeplink (web or Desktop). The best a button can do\n// is copy the URL and open the Connectors page for a manual paste; a\n// `.mcpb` bundle is the only \"double-click\" path (Desktop only).\n// - Cursor's deeplink wants base64-encoded JSON; for an HTTP server the\n// payload is just `{\"url\":\"...\"}` — no `type`, no `transport`.\n// - VS Code wants URL-ENCODED JSON (not base64), a different scheme handler.\n// - The manual path is a `claude_desktop_config.json` snippet that wraps the\n// remote URL with `npx -y mcp-remote` (MCPB/stdio can't take an HTTP URL).\n\n/** The MCP hosts we know how to generate an install affordance for. */\nexport type ConnectorClient =\n | \"claude-web\"\n | \"claude-desktop\"\n | \"cursor\"\n | \"vscode\"\n | \"manual\";\n\n/** A remote MCP server to generate install artifacts for. */\nexport interface ConnectorServer {\n /** Human-readable server name, e.g. `\"Decksmith\"`. */\n name: string;\n /** The remote MCP endpoint, e.g. `\"https://decksmith.dev/mcp\"`. */\n url: string;\n}\n\n/**\n * Claude's Connectors page — the manual \"Add custom connector\" flow.\n *\n * Claude exposes no install deeplink, so a button can only copy the URL and\n * open this page for a paste. Override per-app if Claude moves it (it has\n * historically lived at both `/settings/connectors` and `/customize/connectors`).\n */\nexport const CLAUDE_CONNECTORS_URL = \"https://claude.ai/settings/connectors\";\n\n/** Base64-encode a JSON value, working in both the browser and Node. */\nexport function encodeBase64Json(value: unknown): string {\n const json = JSON.stringify(value);\n if (typeof btoa === \"function\") {\n // utf8-safe: collapse multibyte → latin1 before btoa. For ASCII (URLs) this\n // is a no-op and matches a plain `btoa(JSON.stringify(...))`.\n return btoa(unescape(encodeURIComponent(json)));\n }\n // Node without a global btoa.\n return Buffer.from(json, \"utf8\").toString(\"base64\");\n}\n\n/**\n * Cursor install deeplink for a remote (HTTP) MCP server.\n *\n * `cursor://anysphere.cursor-deeplink/mcp/install?name=<name>&config=<base64>`,\n * where `config` is base64 of `{\"url\":\"<mcpUrl>\"}` — the HTTP shape, with no\n * `type`/`transport` keys (those are for the stdio examples in the docs). The\n * base64 is intentionally NOT percent-encoded, matching Cursor's own install\n * links. Docs: https://cursor.com/docs/context/mcp/install-links\n */\nexport function buildCursorDeeplink(server: ConnectorServer): string {\n const config = encodeBase64Json({ url: server.url });\n return `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(\n server.name,\n )}&config=${config}`;\n}\n\n/**\n * VS Code install deeplink for a remote (HTTP) MCP server.\n *\n * `vscode://mcp/install?<urlencoded-json>` — URL-ENCODED JSON (not base64), the\n * opposite encoding from Cursor and an easy one to mix up. The payload is\n * `{ \"name\", \"url\" }`; for VS Code Insiders pass `{ insiders: true }`.\n */\nexport function buildVscodeDeeplink(\n server: ConnectorServer,\n opts: { insiders?: boolean } = {},\n): string {\n const scheme = opts.insiders ? \"vscode-insiders\" : \"vscode\";\n const payload = encodeURIComponent(\n JSON.stringify({ name: server.name, url: server.url }),\n );\n return `${scheme}://mcp/install?${payload}`;\n}\n\n/** A normalized server key for a config file (`My App` → `my-app`). */\nexport function slugifyServerName(name: string): string {\n const slug = name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return slug || \"mcp-server\";\n}\n\n/** The `claude_desktop_config.json` object for a manual install. */\nexport interface ManualMcpConfig {\n mcpServers: Record<string, { command: string; args: string[] }>;\n}\n\n/**\n * The `claude_desktop_config.json` (or any stdio MCP client config) entry for a\n * remote server, wrapping it with `npx -y mcp-remote <url>` — the standard\n * stdio→HTTP bridge, since stdio clients can't take an HTTP URL directly.\n * Requires Node 18+ on the user's machine.\n */\nexport function buildManualConfig(server: ConnectorServer): ManualMcpConfig {\n return {\n mcpServers: {\n [slugifyServerName(server.name)]: {\n command: \"npx\",\n args: [\"-y\", \"mcp-remote\", server.url],\n },\n },\n };\n}\n\n/** Pretty-printed JSON snippet of {@link buildManualConfig}, for a copy box. */\nexport function buildManualConfigSnippet(server: ConnectorServer): string {\n return JSON.stringify(buildManualConfig(server), null, 2);\n}\n\n/** How a given client's button behaves — drives the default UI. */\nexport type ConnectorMechanism =\n | \"copy-open\" // copy URL + open a web page (claude-web)\n | \"download\" // download a .mcpb bundle (claude-desktop)\n | \"deeplink\" // navigate to a custom-scheme URL (cursor / vscode)\n | \"snippet\"; // reveal a copy-paste JSON snippet (manual)\n\n/** Display metadata for a client, so consumers don't redraw the marks. */\nexport interface ConnectorTargetMeta {\n id: ConnectorClient;\n /** Default button label. */\n label: string;\n mechanism: ConnectorMechanism;\n /** One-line tooltip explaining what the button does. */\n hint: string;\n}\n\nexport const CONNECTOR_TARGETS: Record<ConnectorClient, ConnectorTargetMeta> = {\n \"claude-web\": {\n id: \"claude-web\",\n label: \"Add to Claude\",\n mechanism: \"copy-open\",\n hint: \"Copy the MCP URL and open Claude's Connectors page — click 'Add custom connector' and paste.\",\n },\n \"claude-desktop\": {\n id: \"claude-desktop\",\n label: \"Claude Desktop\",\n mechanism: \"download\",\n hint: \"Download a .mcpb bundle and double-click it to install in Claude Desktop.\",\n },\n cursor: {\n id: \"cursor\",\n label: \"Add to Cursor\",\n mechanism: \"deeplink\",\n hint: \"Open Cursor with this MCP server pre-filled — confirm to install.\",\n },\n vscode: {\n id: \"vscode\",\n label: \"Add to VS Code\",\n mechanism: \"deeplink\",\n hint: \"Open VS Code with this MCP server pre-filled — confirm to install.\",\n },\n manual: {\n id: \"manual\",\n label: \"Manual setup\",\n mechanism: \"snippet\",\n hint: \"Show a config snippet to paste into any stdio MCP client.\",\n },\n};\n\n/**\n * Resolve the navigable href for a deeplink client (cursor / vscode), or null\n * for clients whose mechanism isn't a plain navigation.\n */\nexport function connectorHref(\n client: ConnectorClient,\n server: ConnectorServer,\n opts: { insiders?: boolean } = {},\n): string | null {\n switch (client) {\n case \"cursor\":\n return buildCursorDeeplink(server);\n case \"vscode\":\n return buildVscodeDeeplink(server, opts);\n default:\n return null;\n }\n}\n","// Brand-ish glyph marks for each MCP host, so consumers don't have to redraw\n// them. Deliberately simple, single-path, `currentColor` SVGs — they inherit\n// the button's text color and stay crisp at 14px.\n\nimport type { SVGProps } from \"react\";\n\ntype GlyphProps = SVGProps<SVGSVGElement>;\n\nexport function ClaudeMark(props: GlyphProps) {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden {...props}>\n <path d=\"M12 2 L 14 10 L 22 12 L 14 14 L 12 22 L 10 14 L 2 12 L 10 10 Z\" />\n </svg>\n );\n}\n\nexport function CursorMark(props: GlyphProps) {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden {...props}>\n <path d=\"M3 3 L 20 11 L 12 13 L 9 21 Z\" />\n </svg>\n );\n}\n\nexport function VscodeMark(props: GlyphProps) {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden {...props}>\n <path d=\"M17 2 L 22 4.5 V 19.5 L 17 22 L 6.5 13.2 L 3 16 L 1.5 15 V 9 L 3 8 L 6.5 10.8 Z M 17 6.5 L 10 12 L 17 17.5 Z\" />\n </svg>\n );\n}\n\nexport function DesktopMark(props: GlyphProps) {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden {...props}>\n <path d=\"M3 4 H 21 A 1 1 0 0 1 22 5 V 16 A 1 1 0 0 1 21 17 H 14 V 19 H 16 V 21 H 8 V 19 H 10 V 17 H 3 A 1 1 0 0 1 2 16 V 5 A 1 1 0 0 1 3 4 Z\" />\n </svg>\n );\n}\n\nexport function WrenchMark(props: GlyphProps) {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden {...props}>\n <path d=\"M21 4 a 5 5 0 0 1 -6.5 6.5 L 6 19 l -3 -3 l 8.5 -8.5 A 5 5 0 0 1 17 1 l -2.5 2.5 l 1.5 3 l 3 1.5 Z\" />\n </svg>\n );\n}\n\nimport type { ComponentType } from \"react\";\nimport type { ConnectorClient } from \"./targets\";\n\n/** Glyph component for a given client. */\nexport const CONNECTOR_GLYPHS: Record<\n ConnectorClient,\n ComponentType<GlyphProps>\n> = {\n \"claude-web\": ClaudeMark,\n \"claude-desktop\": DesktopMark,\n cursor: CursorMark,\n vscode: VscodeMark,\n manual: WrenchMark,\n};\n","import { type CSSProperties, type ReactNode, useId, useState } from \"react\";\nimport {\n CLAUDE_CONNECTORS_URL,\n CONNECTOR_TARGETS,\n type ConnectorClient,\n type ConnectorServer,\n buildManualConfigSnippet,\n connectorHref,\n} from \"./targets\";\nimport { CONNECTOR_GLYPHS } from \"./glyphs\";\n\nexport interface ConnectorButtonsProps {\n /** Human-readable server name, e.g. `\"Decksmith\"`. */\n serverName: string;\n /** The remote MCP endpoint, e.g. `\"https://decksmith.dev/mcp\"`. */\n mcpUrl: string;\n /**\n * Which client buttons to render, in order. Defaults to\n * `[\"claude-web\", \"cursor\", \"vscode\", \"manual\"]` — plus `\"claude-desktop\"`\n * when {@link mcpbDownloadUrl} is set. A `\"claude-desktop\"` entry with no\n * `mcpbDownloadUrl` is skipped (there's nothing to download).\n */\n clients?: ConnectorClient[];\n /** URL of a prebuilt `.mcpb` bundle; enables the Claude Desktop button. */\n mcpbDownloadUrl?: string;\n /** Override Claude's Connectors page (it has moved before). */\n claudeConnectorsUrl?: string;\n /** Target VS Code Insiders instead of stable. */\n vscodeInsiders?: boolean;\n /** Fired when a value is copied to the clipboard (URL or snippet). */\n onCopy?: (target: ConnectorClient) => void;\n /** Fired when any button is activated (after its side effect). */\n onAction?: (target: ConnectorClient) => void;\n /** Per-client label override. */\n labels?: Partial<Record<ConnectorClient, ReactNode>>;\n className?: string;\n style?: CSSProperties;\n}\n\nconst DEFAULT_CLIENTS: ConnectorClient[] = [\n \"claude-web\",\n \"cursor\",\n \"vscode\",\n \"manual\",\n];\n\n/**\n * Per-client \"Add to <host>\" buttons for a remote MCP server, each with the\n * right (and subtly different) install behavior baked in — see {@link\n * ./targets}. Brand glyphs, copy/feedback states, and the manual-config popover\n * are owned here so a consumer just passes a name + URL.\n *\n * Needs the package stylesheet for its default look:\n * `import \"@particle-academy/agent-integrations/styles.css\"`.\n */\nexport function ConnectorButtons({\n serverName,\n mcpUrl,\n clients,\n mcpbDownloadUrl,\n claudeConnectorsUrl = CLAUDE_CONNECTORS_URL,\n vscodeInsiders,\n onCopy,\n onAction,\n labels,\n className,\n style,\n}: ConnectorButtonsProps) {\n const server: ConnectorServer = { name: serverName, url: mcpUrl };\n const [copied, setCopied] = useState<ConnectorClient | null>(null);\n const [manualOpen, setManualOpen] = useState(false);\n const manualId = useId();\n\n const list = (clients ?? defaultClients(mcpbDownloadUrl)).filter((c) =>\n c === \"claude-desktop\" ? !!mcpbDownloadUrl : true,\n );\n\n const flashCopied = (target: ConnectorClient) => {\n setCopied(target);\n window.setTimeout(() => setCopied((c) => (c === target ? null : c)), 2000);\n };\n\n const copy = async (value: string, target: ConnectorClient) => {\n try {\n await navigator.clipboard?.writeText(value);\n flashCopied(target);\n onCopy?.(target);\n } catch {\n /* clipboard blocked — the popover/URL is still visible */\n }\n };\n\n const labelFor = (c: ConnectorClient): ReactNode =>\n labels?.[c] ?? CONNECTOR_TARGETS[c].label;\n\n return (\n <div\n className={[\"fai-connect\", className].filter(Boolean).join(\" \")}\n style={style}\n >\n {list.map((client) => {\n const meta = CONNECTOR_TARGETS[client];\n const Glyph = CONNECTOR_GLYPHS[client];\n const base = `fai-connect__btn fai-connect__btn--${client}`;\n\n // Deeplink clients (cursor / vscode) are plain navigations.\n const href = connectorHref(client, server, { insiders: vscodeInsiders });\n if (href) {\n return (\n <a\n key={client}\n href={href}\n className={base}\n title={meta.hint}\n onClick={() => onAction?.(client)}\n >\n <Glyph className=\"fai-connect__glyph\" />\n {labelFor(client)}\n </a>\n );\n }\n\n if (client === \"claude-desktop\") {\n return (\n <a\n key={client}\n href={mcpbDownloadUrl}\n download\n className={base}\n title={meta.hint}\n onClick={() => onAction?.(client)}\n >\n <Glyph className=\"fai-connect__glyph\" />\n {labelFor(client)}\n </a>\n );\n }\n\n if (client === \"claude-web\") {\n return (\n <button\n key={client}\n type=\"button\"\n className={base}\n title={meta.hint}\n onClick={() => {\n void copy(mcpUrl, client);\n window.open(\n claudeConnectorsUrl,\n \"_blank\",\n \"noopener,noreferrer\",\n );\n onAction?.(client);\n }}\n >\n <Glyph className=\"fai-connect__glyph\" />\n {copied === client ? \"Copied — paste in Claude\" : labelFor(client)}\n </button>\n );\n }\n\n // manual\n return (\n <div key={client} className=\"fai-connect__manual-wrap\">\n <button\n type=\"button\"\n className={base}\n title={meta.hint}\n aria-expanded={manualOpen}\n aria-controls={manualId}\n onClick={() => {\n setManualOpen((o) => !o);\n onAction?.(client);\n }}\n >\n <Glyph className=\"fai-connect__glyph\" />\n {labelFor(client)}\n </button>\n {manualOpen && (\n <ManualPopover\n id={manualId}\n snippet={buildManualConfigSnippet(server)}\n copied={copied === client}\n onCopy={() => copy(buildManualConfigSnippet(server), client)}\n onClose={() => setManualOpen(false)}\n />\n )}\n </div>\n );\n })}\n </div>\n );\n}\n\nfunction defaultClients(mcpbDownloadUrl?: string): ConnectorClient[] {\n return mcpbDownloadUrl\n ? [\"claude-web\", \"claude-desktop\", \"cursor\", \"vscode\", \"manual\"]\n : DEFAULT_CLIENTS;\n}\n\nfunction ManualPopover({\n id,\n snippet,\n copied,\n onCopy,\n onClose,\n}: {\n id: string;\n snippet: string;\n copied: boolean;\n onCopy: () => void;\n onClose: () => void;\n}) {\n return (\n <div id={id} className=\"fai-connect__popover\" role=\"dialog\">\n <div className=\"fai-connect__popover-head\">\n <span>Add to any stdio MCP client</span>\n <button\n type=\"button\"\n className=\"fai-connect__popover-close\"\n aria-label=\"Close\"\n onClick={onClose}\n >\n ×\n </button>\n </div>\n <p className=\"fai-connect__popover-hint\">\n Paste into <code>claude_desktop_config.json</code> (or any stdio MCP\n client config). Needs Node 18+.\n </p>\n <pre className=\"fai-connect__snippet\">{snippet}</pre>\n <button type=\"button\" className=\"fai-connect__copy-btn\" onClick={onCopy}>\n {copied ? \"Copied\" : \"Copy snippet\"}\n </button>\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/connectors/targets.ts","../src/connectors/glyphs.tsx","../src/connectors/ConnectorButtons.tsx"],"names":["jsx"],"mappings":"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","file":"chunk-5HNTNIWY.js","sourcesContent":["// Per-client MCP \"install\" affordances — the single source of truth for the\n// subtly-different way each MCP host wants a remote server handed to it.\n//\n// These are PURE, framework-agnostic builders (no React, no DOM): a host can\n// call them to render its own buttons, and <ConnectorButtons> is a thin UI\n// over them. Every quirk below is a real one rediscovered the hard way:\n//\n// - Claude has NO install deeplink (web or Desktop). The best a button can do\n// is copy the URL and open the Connectors page for a manual paste; a\n// `.mcpb` bundle is the only \"double-click\" path (Desktop only).\n// - Cursor's deeplink wants base64-encoded JSON; for an HTTP server the\n// payload is just `{\"url\":\"...\"}` — no `type`, no `transport`.\n// - VS Code wants URL-ENCODED JSON (not base64), a different scheme handler.\n// - The manual path is a `claude_desktop_config.json` snippet that wraps the\n// remote URL with `npx -y mcp-remote` (MCPB/stdio can't take an HTTP URL).\n\n/** The MCP hosts we know how to generate an install affordance for. */\nexport type ConnectorClient =\n | \"claude-web\"\n | \"claude-desktop\"\n | \"cursor\"\n | \"vscode\"\n | \"manual\";\n\n/** A remote MCP server to generate install artifacts for. */\nexport interface ConnectorServer {\n /** Human-readable server name, e.g. `\"Decksmith\"`. */\n name: string;\n /** The remote MCP endpoint, e.g. `\"https://decksmith.dev/mcp\"`. */\n url: string;\n}\n\n/**\n * Claude's Connectors page — the manual \"Add custom connector\" flow.\n *\n * Claude exposes no install deeplink, so a button can only copy the URL and\n * open this page for a paste. Override per-app if Claude moves it (it has\n * historically lived at both `/settings/connectors` and `/customize/connectors`).\n */\nexport const CLAUDE_CONNECTORS_URL = \"https://claude.ai/settings/connectors\";\n\n/** Base64-encode a JSON value, working in both the browser and Node. */\nexport function encodeBase64Json(value: unknown): string {\n const json = JSON.stringify(value);\n if (typeof btoa === \"function\") {\n // utf8-safe: collapse multibyte → latin1 before btoa. For ASCII (URLs) this\n // is a no-op and matches a plain `btoa(JSON.stringify(...))`.\n return btoa(unescape(encodeURIComponent(json)));\n }\n // Node without a global btoa.\n return Buffer.from(json, \"utf8\").toString(\"base64\");\n}\n\n/**\n * Cursor install deeplink for a remote (HTTP) MCP server.\n *\n * `cursor://anysphere.cursor-deeplink/mcp/install?name=<name>&config=<base64>`,\n * where `config` is base64 of `{\"url\":\"<mcpUrl>\"}` — the HTTP shape, with no\n * `type`/`transport` keys (those are for the stdio examples in the docs). The\n * base64 is intentionally NOT percent-encoded, matching Cursor's own install\n * links. Docs: https://cursor.com/docs/context/mcp/install-links\n */\nexport function buildCursorDeeplink(server: ConnectorServer): string {\n const config = encodeBase64Json({ url: server.url });\n return `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(\n server.name,\n )}&config=${config}`;\n}\n\n/**\n * VS Code install deeplink for a remote (HTTP) MCP server.\n *\n * `vscode://mcp/install?<urlencoded-json>` — URL-ENCODED JSON (not base64), the\n * opposite encoding from Cursor and an easy one to mix up. The payload is\n * `{ \"name\", \"url\" }`; for VS Code Insiders pass `{ insiders: true }`.\n */\nexport function buildVscodeDeeplink(\n server: ConnectorServer,\n opts: { insiders?: boolean } = {},\n): string {\n const scheme = opts.insiders ? \"vscode-insiders\" : \"vscode\";\n const payload = encodeURIComponent(\n JSON.stringify({ name: server.name, url: server.url }),\n );\n return `${scheme}://mcp/install?${payload}`;\n}\n\n/** A normalized server key for a config file (`My App` → `my-app`). */\nexport function slugifyServerName(name: string): string {\n const slug = name\n .toLowerCase()\n // Collapse every run of non-alphanumerics to a single dash, so the slug can\n // never contain two consecutive dashes…\n .replace(/[^a-z0-9]+/g, \"-\")\n // …which leaves at most ONE leading/trailing dash to strip. A single-char\n // strip (no `+` quantifier) is equivalent here and avoids a\n // polynomial-backtracking trim regex.\n .replace(/^-|-$/g, \"\");\n return slug || \"mcp-server\";\n}\n\n/** The `claude_desktop_config.json` object for a manual install. */\nexport interface ManualMcpConfig {\n mcpServers: Record<string, { command: string; args: string[] }>;\n}\n\n/**\n * The `claude_desktop_config.json` (or any stdio MCP client config) entry for a\n * remote server, wrapping it with `npx -y mcp-remote <url>` — the standard\n * stdio→HTTP bridge, since stdio clients can't take an HTTP URL directly.\n * Requires Node 18+ on the user's machine.\n */\nexport function buildManualConfig(server: ConnectorServer): ManualMcpConfig {\n return {\n mcpServers: {\n [slugifyServerName(server.name)]: {\n command: \"npx\",\n args: [\"-y\", \"mcp-remote\", server.url],\n },\n },\n };\n}\n\n/** Pretty-printed JSON snippet of {@link buildManualConfig}, for a copy box. */\nexport function buildManualConfigSnippet(server: ConnectorServer): string {\n return JSON.stringify(buildManualConfig(server), null, 2);\n}\n\n/** How a given client's button behaves — drives the default UI. */\nexport type ConnectorMechanism =\n | \"copy-open\" // copy URL + open a web page (claude-web)\n | \"download\" // download a .mcpb bundle (claude-desktop)\n | \"deeplink\" // navigate to a custom-scheme URL (cursor / vscode)\n | \"snippet\"; // reveal a copy-paste JSON snippet (manual)\n\n/** Display metadata for a client, so consumers don't redraw the marks. */\nexport interface ConnectorTargetMeta {\n id: ConnectorClient;\n /** Default button label. */\n label: string;\n mechanism: ConnectorMechanism;\n /** One-line tooltip explaining what the button does. */\n hint: string;\n}\n\nexport const CONNECTOR_TARGETS: Record<ConnectorClient, ConnectorTargetMeta> = {\n \"claude-web\": {\n id: \"claude-web\",\n label: \"Add to Claude\",\n mechanism: \"copy-open\",\n hint: \"Copy the MCP URL and open Claude's Connectors page — click 'Add custom connector' and paste.\",\n },\n \"claude-desktop\": {\n id: \"claude-desktop\",\n label: \"Claude Desktop\",\n mechanism: \"download\",\n hint: \"Download a .mcpb bundle and double-click it to install in Claude Desktop.\",\n },\n cursor: {\n id: \"cursor\",\n label: \"Add to Cursor\",\n mechanism: \"deeplink\",\n hint: \"Open Cursor with this MCP server pre-filled — confirm to install.\",\n },\n vscode: {\n id: \"vscode\",\n label: \"Add to VS Code\",\n mechanism: \"deeplink\",\n hint: \"Open VS Code with this MCP server pre-filled — confirm to install.\",\n },\n manual: {\n id: \"manual\",\n label: \"Manual setup\",\n mechanism: \"snippet\",\n hint: \"Show a config snippet to paste into any stdio MCP client.\",\n },\n};\n\n/**\n * Resolve the navigable href for a deeplink client (cursor / vscode), or null\n * for clients whose mechanism isn't a plain navigation.\n */\nexport function connectorHref(\n client: ConnectorClient,\n server: ConnectorServer,\n opts: { insiders?: boolean } = {},\n): string | null {\n switch (client) {\n case \"cursor\":\n return buildCursorDeeplink(server);\n case \"vscode\":\n return buildVscodeDeeplink(server, opts);\n default:\n return null;\n }\n}\n","// Brand-ish glyph marks for each MCP host, so consumers don't have to redraw\n// them. Deliberately simple, single-path, `currentColor` SVGs — they inherit\n// the button's text color and stay crisp at 14px.\n\nimport type { SVGProps } from \"react\";\n\ntype GlyphProps = SVGProps<SVGSVGElement>;\n\nexport function ClaudeMark(props: GlyphProps) {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden {...props}>\n <path d=\"M12 2 L 14 10 L 22 12 L 14 14 L 12 22 L 10 14 L 2 12 L 10 10 Z\" />\n </svg>\n );\n}\n\nexport function CursorMark(props: GlyphProps) {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden {...props}>\n <path d=\"M3 3 L 20 11 L 12 13 L 9 21 Z\" />\n </svg>\n );\n}\n\nexport function VscodeMark(props: GlyphProps) {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden {...props}>\n <path d=\"M17 2 L 22 4.5 V 19.5 L 17 22 L 6.5 13.2 L 3 16 L 1.5 15 V 9 L 3 8 L 6.5 10.8 Z M 17 6.5 L 10 12 L 17 17.5 Z\" />\n </svg>\n );\n}\n\nexport function DesktopMark(props: GlyphProps) {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden {...props}>\n <path d=\"M3 4 H 21 A 1 1 0 0 1 22 5 V 16 A 1 1 0 0 1 21 17 H 14 V 19 H 16 V 21 H 8 V 19 H 10 V 17 H 3 A 1 1 0 0 1 2 16 V 5 A 1 1 0 0 1 3 4 Z\" />\n </svg>\n );\n}\n\nexport function WrenchMark(props: GlyphProps) {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden {...props}>\n <path d=\"M21 4 a 5 5 0 0 1 -6.5 6.5 L 6 19 l -3 -3 l 8.5 -8.5 A 5 5 0 0 1 17 1 l -2.5 2.5 l 1.5 3 l 3 1.5 Z\" />\n </svg>\n );\n}\n\nimport type { ComponentType } from \"react\";\nimport type { ConnectorClient } from \"./targets\";\n\n/** Glyph component for a given client. */\nexport const CONNECTOR_GLYPHS: Record<\n ConnectorClient,\n ComponentType<GlyphProps>\n> = {\n \"claude-web\": ClaudeMark,\n \"claude-desktop\": DesktopMark,\n cursor: CursorMark,\n vscode: VscodeMark,\n manual: WrenchMark,\n};\n","import { type CSSProperties, type ReactNode, useId, useState } from \"react\";\nimport {\n CLAUDE_CONNECTORS_URL,\n CONNECTOR_TARGETS,\n type ConnectorClient,\n type ConnectorServer,\n buildManualConfigSnippet,\n connectorHref,\n} from \"./targets\";\nimport { CONNECTOR_GLYPHS } from \"./glyphs\";\n\nexport interface ConnectorButtonsProps {\n /** Human-readable server name, e.g. `\"Decksmith\"`. */\n serverName: string;\n /** The remote MCP endpoint, e.g. `\"https://decksmith.dev/mcp\"`. */\n mcpUrl: string;\n /**\n * Which client buttons to render, in order. Defaults to\n * `[\"claude-web\", \"cursor\", \"vscode\", \"manual\"]` — plus `\"claude-desktop\"`\n * when {@link mcpbDownloadUrl} is set. A `\"claude-desktop\"` entry with no\n * `mcpbDownloadUrl` is skipped (there's nothing to download).\n */\n clients?: ConnectorClient[];\n /** URL of a prebuilt `.mcpb` bundle; enables the Claude Desktop button. */\n mcpbDownloadUrl?: string;\n /** Override Claude's Connectors page (it has moved before). */\n claudeConnectorsUrl?: string;\n /** Target VS Code Insiders instead of stable. */\n vscodeInsiders?: boolean;\n /** Fired when a value is copied to the clipboard (URL or snippet). */\n onCopy?: (target: ConnectorClient) => void;\n /** Fired when any button is activated (after its side effect). */\n onAction?: (target: ConnectorClient) => void;\n /** Per-client label override. */\n labels?: Partial<Record<ConnectorClient, ReactNode>>;\n className?: string;\n style?: CSSProperties;\n}\n\nconst DEFAULT_CLIENTS: ConnectorClient[] = [\n \"claude-web\",\n \"cursor\",\n \"vscode\",\n \"manual\",\n];\n\n/**\n * Per-client \"Add to <host>\" buttons for a remote MCP server, each with the\n * right (and subtly different) install behavior baked in — see {@link\n * ./targets}. Brand glyphs, copy/feedback states, and the manual-config popover\n * are owned here so a consumer just passes a name + URL.\n *\n * Needs the package stylesheet for its default look:\n * `import \"@particle-academy/agent-integrations/styles.css\"`.\n */\nexport function ConnectorButtons({\n serverName,\n mcpUrl,\n clients,\n mcpbDownloadUrl,\n claudeConnectorsUrl = CLAUDE_CONNECTORS_URL,\n vscodeInsiders,\n onCopy,\n onAction,\n labels,\n className,\n style,\n}: ConnectorButtonsProps) {\n const server: ConnectorServer = { name: serverName, url: mcpUrl };\n const [copied, setCopied] = useState<ConnectorClient | null>(null);\n const [manualOpen, setManualOpen] = useState(false);\n const manualId = useId();\n\n const list = (clients ?? defaultClients(mcpbDownloadUrl)).filter((c) =>\n c === \"claude-desktop\" ? !!mcpbDownloadUrl : true,\n );\n\n const flashCopied = (target: ConnectorClient) => {\n setCopied(target);\n window.setTimeout(() => setCopied((c) => (c === target ? null : c)), 2000);\n };\n\n const copy = async (value: string, target: ConnectorClient) => {\n try {\n await navigator.clipboard?.writeText(value);\n flashCopied(target);\n onCopy?.(target);\n } catch {\n /* clipboard blocked — the popover/URL is still visible */\n }\n };\n\n const labelFor = (c: ConnectorClient): ReactNode =>\n labels?.[c] ?? CONNECTOR_TARGETS[c].label;\n\n return (\n <div\n className={[\"fai-connect\", className].filter(Boolean).join(\" \")}\n style={style}\n >\n {list.map((client) => {\n const meta = CONNECTOR_TARGETS[client];\n const Glyph = CONNECTOR_GLYPHS[client];\n const base = `fai-connect__btn fai-connect__btn--${client}`;\n\n // Deeplink clients (cursor / vscode) are plain navigations.\n const href = connectorHref(client, server, { insiders: vscodeInsiders });\n if (href) {\n return (\n <a\n key={client}\n href={href}\n className={base}\n title={meta.hint}\n onClick={() => onAction?.(client)}\n >\n <Glyph className=\"fai-connect__glyph\" />\n {labelFor(client)}\n </a>\n );\n }\n\n if (client === \"claude-desktop\") {\n return (\n <a\n key={client}\n href={mcpbDownloadUrl}\n download\n className={base}\n title={meta.hint}\n onClick={() => onAction?.(client)}\n >\n <Glyph className=\"fai-connect__glyph\" />\n {labelFor(client)}\n </a>\n );\n }\n\n if (client === \"claude-web\") {\n return (\n <button\n key={client}\n type=\"button\"\n className={base}\n title={meta.hint}\n onClick={() => {\n void copy(mcpUrl, client);\n window.open(\n claudeConnectorsUrl,\n \"_blank\",\n \"noopener,noreferrer\",\n );\n onAction?.(client);\n }}\n >\n <Glyph className=\"fai-connect__glyph\" />\n {copied === client ? \"Copied — paste in Claude\" : labelFor(client)}\n </button>\n );\n }\n\n // manual\n return (\n <div key={client} className=\"fai-connect__manual-wrap\">\n <button\n type=\"button\"\n className={base}\n title={meta.hint}\n aria-expanded={manualOpen}\n aria-controls={manualId}\n onClick={() => {\n setManualOpen((o) => !o);\n onAction?.(client);\n }}\n >\n <Glyph className=\"fai-connect__glyph\" />\n {labelFor(client)}\n </button>\n {manualOpen && (\n <ManualPopover\n id={manualId}\n snippet={buildManualConfigSnippet(server)}\n copied={copied === client}\n onCopy={() => copy(buildManualConfigSnippet(server), client)}\n onClose={() => setManualOpen(false)}\n />\n )}\n </div>\n );\n })}\n </div>\n );\n}\n\nfunction defaultClients(mcpbDownloadUrl?: string): ConnectorClient[] {\n return mcpbDownloadUrl\n ? [\"claude-web\", \"claude-desktop\", \"cursor\", \"vscode\", \"manual\"]\n : DEFAULT_CLIENTS;\n}\n\nfunction ManualPopover({\n id,\n snippet,\n copied,\n onCopy,\n onClose,\n}: {\n id: string;\n snippet: string;\n copied: boolean;\n onCopy: () => void;\n onClose: () => void;\n}) {\n return (\n <div id={id} className=\"fai-connect__popover\" role=\"dialog\">\n <div className=\"fai-connect__popover-head\">\n <span>Add to any stdio MCP client</span>\n <button\n type=\"button\"\n className=\"fai-connect__popover-close\"\n aria-label=\"Close\"\n onClick={onClose}\n >\n ×\n </button>\n </div>\n <p className=\"fai-connect__popover-hint\">\n Paste into <code>claude_desktop_config.json</code> (or any stdio MCP\n client config). Needs Node 18+.\n </p>\n <pre className=\"fai-connect__snippet\">{snippet}</pre>\n <button type=\"button\" className=\"fai-connect__copy-btn\" onClick={onCopy}>\n {copied ? \"Copied\" : \"Copy snippet\"}\n </button>\n </div>\n );\n}\n"]}