@getly/mcp 0.1.0 → 0.1.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.
- package/dist/tools.js +73 -1
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Getly MCP tool registry —
|
|
2
|
+
* Getly MCP tool registry — 19 tools.
|
|
3
3
|
*
|
|
4
4
|
* Safety model:
|
|
5
5
|
* - The API key comes ONLY from the GETLY_API_KEY environment variable.
|
|
@@ -108,6 +108,20 @@ const IMAGE_TYPES = {
|
|
|
108
108
|
'.gif': 'image/gif',
|
|
109
109
|
'.avif': 'image/avif',
|
|
110
110
|
};
|
|
111
|
+
/**
|
|
112
|
+
* Build the canonical Pay Widget embed snippet for a product. The pay.js src is
|
|
113
|
+
* UNVERSIONED (the file updates in place) — never add `integrity=` or `?v=`.
|
|
114
|
+
* Buttons default to `auto` (popup on desktop, redirect on mobile); an explicit
|
|
115
|
+
* mode is stamped only for popup/inline/redirect.
|
|
116
|
+
*/
|
|
117
|
+
function payWidgetSnippet(baseUrl, store, product, mode) {
|
|
118
|
+
const script = `<script src="${baseUrl}/pay.js" async></script>`;
|
|
119
|
+
if (mode === 'inline') {
|
|
120
|
+
return `${script}\n<div data-getly-buy data-store="${store}" data-product="${product}" data-mode="inline"></div>`;
|
|
121
|
+
}
|
|
122
|
+
const modeAttr = mode === 'popup' || mode === 'redirect' ? ` data-mode="${mode}"` : '';
|
|
123
|
+
return `${script}\n<button data-getly-buy data-store="${store}" data-product="${product}"${modeAttr}>Buy</button>`;
|
|
124
|
+
}
|
|
111
125
|
// ---------------------------------------------------------------------------
|
|
112
126
|
// Tools
|
|
113
127
|
// ---------------------------------------------------------------------------
|
|
@@ -662,6 +676,64 @@ export const TOOLS = [
|
|
|
662
676
|
});
|
|
663
677
|
}),
|
|
664
678
|
},
|
|
679
|
+
// ---------------------------------------------------------- pay widget --
|
|
680
|
+
{
|
|
681
|
+
name: 'get_pay_widget_code',
|
|
682
|
+
description: 'Generate the Pay Widget embed snippet (a <script> tag + a Buy button/div) that sells ONE of your products from ANY external website — a landing page, quiz funnel, link-in-bio, Webflow/Framer/Carrd site. Buyers pay by card + Apple Pay/Google Pay on a Getly-hosted popup; Getly handles delivery, receipts, refunds and the payout. No API key ever touches the browser. Validates that the product is active and publicly purchasable first. Read-only — it only returns code, it changes nothing.',
|
|
683
|
+
annotations: { title: 'Get Pay Widget embed code', readOnlyHint: true },
|
|
684
|
+
requiresAuth: true,
|
|
685
|
+
inputSchema: {
|
|
686
|
+
productSlug: z.string().min(1)
|
|
687
|
+
.describe('The product SLUG (from list_products → slug, or the product URL) — not the uuid.'),
|
|
688
|
+
mode: z.enum(['auto', 'popup', 'inline', 'redirect']).optional()
|
|
689
|
+
.describe("Embed mode. auto (default): popup on desktop, same-tab redirect on mobile. popup: always a popup. inline: an embedded Stripe form inside a <div>. redirect: same-tab. Apple Pay/Google Pay appear automatically in popup/auto/redirect; inline shows cards + Link until the seller registers the domain in the dashboard."),
|
|
690
|
+
},
|
|
691
|
+
handler: guarded(true, async (args) => {
|
|
692
|
+
const productSlug = String(args.productSlug);
|
|
693
|
+
const mode = args.mode || 'auto';
|
|
694
|
+
// The widget needs data-store — resolve the key's own store slug.
|
|
695
|
+
const storeEnv = await apiRequest('api/v1/store');
|
|
696
|
+
const storeSlug = String(storeEnv.data.slug);
|
|
697
|
+
// Validate the product via the NO-AUTH storefront endpoint (same view the
|
|
698
|
+
// widget itself uses): only active products of non-suspended sellers
|
|
699
|
+
// resolve; everything else (draft/pending/archived/unknown) reads as 404.
|
|
700
|
+
let product = null;
|
|
701
|
+
try {
|
|
702
|
+
const env = await apiRequest(`api/v1/public/stores/${encodeURIComponent(storeSlug)}/products/${encodeURIComponent(productSlug)}`, { apiKey: null });
|
|
703
|
+
product = env.data;
|
|
704
|
+
}
|
|
705
|
+
catch (err) {
|
|
706
|
+
if (err instanceof GetlyApiError && err.status === 404) {
|
|
707
|
+
return refusal(`No ACTIVE public product "${productSlug}" in store "${storeSlug}". The Pay Widget only sells active products. ` +
|
|
708
|
+
'List sellable products with list_products (status: active) and use one of their slugs. ' +
|
|
709
|
+
'If the product exists but is a draft, publish it first with publish_product (needs human confirmation).');
|
|
710
|
+
}
|
|
711
|
+
throw err;
|
|
712
|
+
}
|
|
713
|
+
if (!product || typeof product.priceCents !== 'number' || product.priceCents <= 0) {
|
|
714
|
+
return refusal(`Product "${productSlug}" is not purchasable through the Pay Widget — it must be a fixed-price product above $0. ` +
|
|
715
|
+
'Free or pay-what-you-want products check out on their product page, not the widget.');
|
|
716
|
+
}
|
|
717
|
+
const snippet = payWidgetSnippet(getBaseUrl(), storeSlug, productSlug, mode);
|
|
718
|
+
return json({
|
|
719
|
+
product: {
|
|
720
|
+
name: product.name,
|
|
721
|
+
slug: product.slug,
|
|
722
|
+
priceCents: product.priceCents,
|
|
723
|
+
url: product.urls?.product,
|
|
724
|
+
},
|
|
725
|
+
mode,
|
|
726
|
+
snippet,
|
|
727
|
+
instructions: [
|
|
728
|
+
'Paste the snippet anywhere in the page HTML (the <script> is safe to load once; extra buttons on the same page reuse it).',
|
|
729
|
+
'For a quiz/funnel that picks the product at runtime, render the button with a dynamic data-product and call window.GetlyPay.scan() after inserting it.',
|
|
730
|
+
'Optional attributes: data-success-url="https://…" (where the buyer lands after paying), data-price="show" (append the live price to the button label), data-locale="ru"|"de", and data-i18n-buy / data-i18n-loading / data-i18n-error overrides.',
|
|
731
|
+
],
|
|
732
|
+
security: 'The getly:pay:success browser event is an ADVISORY UI signal only — NEVER unlock files, license keys or paid content on it (a visitor can forge it). Getly delivers the product server-side (buyer email + library) once Stripe confirms payment; verify real sales via the sale.completed / checkout_link.completed webhook.',
|
|
733
|
+
docs: `${getBaseUrl()}/pay-widget`,
|
|
734
|
+
});
|
|
735
|
+
}),
|
|
736
|
+
},
|
|
665
737
|
];
|
|
666
738
|
// Sanity: the registry is the single source of truth for tool names.
|
|
667
739
|
export const TOOL_NAMES = TOOLS.map((t) => t.name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getly/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "MCP server for Getly — let Claude, Cursor and other AI assistants run your digital-products store (products, blog, coupons, checkout links, licenses).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|