@sellhapi/mcp 0.1.2 → 0.1.4
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/resources/capability-patterns.js +57 -0
- package/dist/resources/wireframe-schema.d.ts +1 -1
- package/dist/resources/wireframe-schema.js +1 -1
- package/dist/server.js +2 -0
- package/dist/tools/analytics.d.ts +5 -0
- package/dist/tools/analytics.js +22 -0
- package/dist/tools/commerce.d.ts +2 -5
- package/dist/tools/commerce.js +167 -5
- package/dist/tools/studio.js +8 -4
- package/package.json +1 -1
|
@@ -305,6 +305,63 @@ Anti-patterns:
|
|
|
305
305
|
|
|
306
306
|
---
|
|
307
307
|
|
|
308
|
+
## ENTITY LAYOUT PHILOSOPHY
|
|
309
|
+
|
|
310
|
+
Entities DO NOT have to be displayed as uniform rectangle cards in a grid.
|
|
311
|
+
Choose the layout that fits the business type, visual direction, and the entity's purpose.
|
|
312
|
+
The wireframe is your opportunity to propose the right layout — the user can approve or change it.
|
|
313
|
+
|
|
314
|
+
Layout options by entity type:
|
|
315
|
+
|
|
316
|
+
MEMBERSHIPS — premium tiers deserve premium presentation:
|
|
317
|
+
- Large image tile (full-width image, title + price + CTA below) — ideal for lifestyle/luxury brands
|
|
318
|
+
- Horizontal comparison table — ideal for SaaS-style or tiered feature sets
|
|
319
|
+
- Feature-rich card with expanded benefits checklist — ideal when benefits are the selling point
|
|
320
|
+
- Avoid generic equal-height rectangle cards unless the business explicitly has many tiers
|
|
321
|
+
|
|
322
|
+
SERVICES — editorial presentation conveys quality better than a card grid:
|
|
323
|
+
- Alternating split sections (image left / text + CTA right, alternates per service)
|
|
324
|
+
- Full-width editorial rows with large background image + text overlay
|
|
325
|
+
- Icon + heading + description list for minimalist service studios
|
|
326
|
+
- Grid cards are acceptable only when services are many and uniform (e.g. a menu)
|
|
327
|
+
|
|
328
|
+
EVENTS — presentation should match the energy of the event:
|
|
329
|
+
- Poster-style: large image, event name as headline, date + location below — theatrical feel
|
|
330
|
+
- Horizontal scroll strip: compact cards in a row — good for many events
|
|
331
|
+
- Featured hero card (first event large, rest smaller) — highlights the primary event
|
|
332
|
+
- Calendar or timeline view when date ordering matters most
|
|
333
|
+
|
|
334
|
+
DONATIONS — campaigns are stories, not products:
|
|
335
|
+
- Hero campaign section: full-width image, campaign name, progress bar, donate CTA
|
|
336
|
+
- Stacked editorial rows: image + story side-by-side, alternating
|
|
337
|
+
- Card grid is acceptable when there are multiple parallel campaigns of equal importance
|
|
338
|
+
|
|
339
|
+
APPOINTMENTS — scheduling flows need clarity, not decoration:
|
|
340
|
+
- Service list → select → calendar → time slot: linear step-by-step (most common, best UX)
|
|
341
|
+
- Service cards with inline "Book now" that opens a side panel
|
|
342
|
+
- Minimal: service name + duration + price in a clean list, not a card grid
|
|
343
|
+
|
|
344
|
+
PRODUCTS (homepage teaser) — this is an editorial/marketing section:
|
|
345
|
+
- 2-col editorial layout: large image takes 60% width, product name + price + CTA in remaining 40%
|
|
346
|
+
- 4-col editorial grid with large images and minimal text overlay
|
|
347
|
+
- Featured product hero (one product at full width) + 3 supporting products below
|
|
348
|
+
- NOT the same component or layout as the /products page — it must look visually distinct
|
|
349
|
+
|
|
350
|
+
PRODUCTS (grid/browse page) — standard grid is correct here:
|
|
351
|
+
- 3-col or 4-col responsive grid with image, name, price, Add to Cart
|
|
352
|
+
- Consistent card height, pagination or Load More at bottom
|
|
353
|
+
- This is the browsing context — uniformity and scannability are correct here
|
|
354
|
+
|
|
355
|
+
Key rules:
|
|
356
|
+
- Match layout to visualDirection.layoutPattern if set — it explicitly describes the intended pattern
|
|
357
|
+
- "glassmorphism" or "soft-shadow" cardStyle + premium business → prefer large image tiles, not small cards
|
|
358
|
+
- The wireframe section names you choose (e.g. "featured-membership-tiles", "editorial-services", "campaign-hero")
|
|
359
|
+
signal your layout intent — use descriptive names, not generic ones like "section1"
|
|
360
|
+
- BuildGuard does NOT enforce layout — only data-region attributes and banned JS APIs are checked
|
|
361
|
+
- The visual representation is yours to decide based on brand and context
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
308
365
|
## UNIVERSAL RULES
|
|
309
366
|
|
|
310
367
|
1. Price display: always (priceKobo / 100).toLocaleString() — prefix with ₦
|
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
* apply_wireframe. The AI client generates mid-fidelity wireframes per page,
|
|
6
6
|
* presents each one to the user for review, then submits the approved set.
|
|
7
7
|
*/
|
|
8
|
-
export declare const WIREFRAME_SCHEMA = "\n# SellHapi Wireframe Schema\n\nWireframes are mid-fidelity HTML page layouts \u2014 one per storefront page.\nThey are the USER'S checkpoint before code is written: present each page\nwireframe in the conversation, collect feedback, revise if needed, then\ncall apply_wireframe only after the user approves the full set.\n\n---\n\n## Data shape\n\n```\n{ [pageSlug: string]: htmlString }\n```\n\n- Each key is a page slug that MUST match one of the slugs in designDirection.pages\n- Each value is a valid HTML string for that page's mid-fidelity wireframe\n- All pages from designDirection.pages should be included (missing pages means no wireframe guidance for code generation on that page)\n\n---\n\n## What a wireframe IS\n\nMid-fidelity structural HTML. It communicates:\n- Which sections appear on the page and in what order\n- What content each section contains (headings, CTAs, placeholder data)\n- Navigation structure (links, cart icon, CTA button)\n- Footer structure (columns, links)\n- Which runtime hook each section will use\n\nIt does NOT include:\n- Real CSS (use only inline style for basic layout structure if needed)\n- JavaScript or React\n- Actual product/service data (use descriptive placeholders)\n- Final colour values or fonts (those come from designDirection.visualDirection)\n\n---\n\n## Structure rules\n\n### Navigation \u2014 MANDATORY on every page\n```html\n<nav>\n <div class=\"nav-logo\">[Business Name]</div>\n <ul class=\"nav-links\">\n <li><a href=\"#/home\">Home</a></li>\n <li><a href=\"#/shop\">Shop</a></li>\n <!-- one <li> per page in designDirection.pages -->\n </ul>\n <!-- If physical_products or digital_products in capabilityIds: -->\n <button class=\"nav-cart\">Cart (0)</button>\n <button class=\"nav-cta\">[Primary CTA]</button>\n</nav>\n```\n\n- Navigation HTML must be **identical across all pages** \u2014 copy verbatim after the first page\n- Highlight the current page's link as active (add class=\"active\" to that <li>)\n- Cart icon only when capability includes physical_products or digital_products\n\n### Footer \u2014 MANDATORY on every page\n```html\n<footer>\n <div class=\"footer-col\">\n <div class=\"footer-logo\">[Business Name]</div>\n <p>[tagline]</p>\n </div>\n <div class=\"footer-col\">\n <h4>NAVIGATE</h4>\n <ul><!-- one <li> per page --></ul>\n </div>\n <div class=\"footer-col\">\n <h4>RESOURCES</h4>\n <ul><!-- relevant links for this business type --></ul>\n </div>\n <div class=\"footer-col\">\n <h4>CONNECT</h4>\n <!-- social platform links -->\n </div>\n <div class=\"footer-bottom\">\u00A9 [year] [Business Name]. All rights reserved.</div>\n</footer>\n```\n\n- Footer HTML must be **identical across all pages** \u2014 copy verbatim after the first page\n\n### Sections\nEach section gets a wrapper with a comment and data-hook attribute:\n\n```html\n<!-- Hero Section -->\n<section data-section=\"hero\" data-hook=\"none\">\n <h1>[Headline \u2014 use the business tagline]</h1>\n <p>[Supporting copy]</p>\n <button>[Primary CTA]</button>\n <div class=\"hero-image\">[Hero image placeholder]</div>\n</section>\n\n<!-- Product Grid Section -->\n<section data-section=\"product-grid\" data-hook=\"useProducts\">\n <h2>Our Products</h2>\n <div class=\"product-grid\">\n <div class=\"product-card\">\n <div class=\"product-image\">[Product image]</div>\n <h3>[Product name]</h3>\n <p>[Price in \u20A6]</p>\n <button>Add to Cart</button>\n </div>\n <!-- repeat 2 more placeholder cards -->\n </div>\n</section>\n\n<!-- Testimonials Section -->\n<section data-section=\"testimonials\" data-hook=\"useContentTestimonials\">\n <h2>What Customers Say</h2>\n <div class=\"testimonials-grid\">\n <blockquote>[Testimonial 1]</blockquote>\n <blockquote>[Testimonial 2]</blockquote>\n <blockquote>[Testimonial 3]</blockquote>\n </div>\n</section>\n```\n\n---\n\n## data-hook values by capability\n\nUse these exact hook names in data-hook attributes so code generation\nmatches the correct runtime hook:\n\n| Section type | data-hook |\n|-------------------------------|---------------------------------|\n| Product listing / grid | useProducts |\n| Product collections | useProductCollections |\n| Single collection | useCollection |\n| Category sections | useSections |\n| Service listings | useServiceListings |\n| Appointment booking | useBookAppointment |\n| Available dates | useAvailableDates |\n| Events listing | useEvents |\n| Donation campaigns | useDonationCampaigns |\n| Membership tiers | useMemberships |\n| Content images | useContentImages |\n| Testimonials | useContentTestimonials |\n| FAQs | useContentFaqs |\n| Team members |
|
|
8
|
+
export declare const WIREFRAME_SCHEMA = "\n# SellHapi Wireframe Schema\n\nWireframes are mid-fidelity HTML page layouts \u2014 one per storefront page.\nThey are the USER'S checkpoint before code is written: present each page\nwireframe in the conversation, collect feedback, revise if needed, then\ncall apply_wireframe only after the user approves the full set.\n\n---\n\n## Data shape\n\n```\n{ [pageSlug: string]: htmlString }\n```\n\n- Each key is a page slug that MUST match one of the slugs in designDirection.pages\n- Each value is a valid HTML string for that page's mid-fidelity wireframe\n- All pages from designDirection.pages should be included (missing pages means no wireframe guidance for code generation on that page)\n\n---\n\n## What a wireframe IS\n\nMid-fidelity structural HTML. It communicates:\n- Which sections appear on the page and in what order\n- What content each section contains (headings, CTAs, placeholder data)\n- Navigation structure (links, cart icon, CTA button)\n- Footer structure (columns, links)\n- Which runtime hook each section will use\n\nIt does NOT include:\n- Real CSS (use only inline style for basic layout structure if needed)\n- JavaScript or React\n- Actual product/service data (use descriptive placeholders)\n- Final colour values or fonts (those come from designDirection.visualDirection)\n\n---\n\n## Structure rules\n\n### Navigation \u2014 MANDATORY on every page\n```html\n<nav>\n <div class=\"nav-logo\">[Business Name]</div>\n <ul class=\"nav-links\">\n <li><a href=\"#/home\">Home</a></li>\n <li><a href=\"#/shop\">Shop</a></li>\n <!-- one <li> per page in designDirection.pages -->\n </ul>\n <!-- If physical_products or digital_products in capabilityIds: -->\n <button class=\"nav-cart\">Cart (0)</button>\n <button class=\"nav-cta\">[Primary CTA]</button>\n</nav>\n```\n\n- Navigation HTML must be **identical across all pages** \u2014 copy verbatim after the first page\n- Highlight the current page's link as active (add class=\"active\" to that <li>)\n- Cart icon only when capability includes physical_products or digital_products\n\n### Footer \u2014 MANDATORY on every page\n```html\n<footer>\n <div class=\"footer-col\">\n <div class=\"footer-logo\">[Business Name]</div>\n <p>[tagline]</p>\n </div>\n <div class=\"footer-col\">\n <h4>NAVIGATE</h4>\n <ul><!-- one <li> per page --></ul>\n </div>\n <div class=\"footer-col\">\n <h4>RESOURCES</h4>\n <ul><!-- relevant links for this business type --></ul>\n </div>\n <div class=\"footer-col\">\n <h4>CONNECT</h4>\n <!-- social platform links -->\n </div>\n <div class=\"footer-bottom\">\u00A9 [year] [Business Name]. All rights reserved.</div>\n</footer>\n```\n\n- Footer HTML must be **identical across all pages** \u2014 copy verbatim after the first page\n\n### Sections\nEach section gets a wrapper with a comment and data-hook attribute:\n\n```html\n<!-- Hero Section -->\n<section data-section=\"hero\" data-hook=\"none\">\n <h1>[Headline \u2014 use the business tagline]</h1>\n <p>[Supporting copy]</p>\n <button>[Primary CTA]</button>\n <div class=\"hero-image\">[Hero image placeholder]</div>\n</section>\n\n<!-- Product Grid Section -->\n<section data-section=\"product-grid\" data-hook=\"useProducts\">\n <h2>Our Products</h2>\n <div class=\"product-grid\">\n <div class=\"product-card\">\n <div class=\"product-image\">[Product image]</div>\n <h3>[Product name]</h3>\n <p>[Price in \u20A6]</p>\n <button>Add to Cart</button>\n </div>\n <!-- repeat 2 more placeholder cards -->\n </div>\n</section>\n\n<!-- Testimonials Section -->\n<section data-section=\"testimonials\" data-hook=\"useContentTestimonials\">\n <h2>What Customers Say</h2>\n <div class=\"testimonials-grid\">\n <blockquote>[Testimonial 1]</blockquote>\n <blockquote>[Testimonial 2]</blockquote>\n <blockquote>[Testimonial 3]</blockquote>\n </div>\n</section>\n```\n\n---\n\n## data-hook values by capability\n\nUse these exact hook names in data-hook attributes so code generation\nmatches the correct runtime hook:\n\n| Section type | data-hook |\n|-------------------------------|---------------------------------|\n| Product listing / grid | useProducts |\n| Product collections | useProductCollections |\n| Single collection | useCollection |\n| Category sections | useSections |\n| Service listings | useServiceListings |\n| Appointment booking | useBookAppointment |\n| Available dates | useAvailableDates |\n| Events listing | useEvents |\n| Donation campaigns | useDonationCampaigns |\n| Membership tiers | useMemberships |\n| Content images | useContentImages |\n| Testimonials | useContentTestimonials |\n| FAQs | useContentFaqs |\n| Team members | useContentTeamMembers |\n| Stats / social proof | useContentStats |\n| Feature highlights | useContentFeatures |\n| Contact / enquiry form | useSubmitForm |\n| Listings (classifieds/jobs) | useListings |\n| Non-data sections (hero, about, footer, nav) | none |\n\n---\n\n## Presenting wireframes to the user\n\nBefore calling apply_wireframe, show each page to the user like this:\n\n```\n\uD83D\uDCC4 HOME PAGE\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n[NAV] Logo | Home \u00B7 Shop \u00B7 About \u00B7 Contact [Cart] [Shop Now]\n\n[HERO]\n \"Authentic Nigerian fashion for the bold\"\n Subheading + CTA button\n Full-width hero image\n\n[FEATURED PRODUCTS] (useProducts)\n 3-column product grid \u2014 6 products\n Each card: image, name, price, Add to Cart\n\n[COLLECTIONS] (useProductCollections)\n 4 collection tiles with cover images\n\n[TESTIMONIALS] (useContentTestimonials)\n 3 customer quotes in a row\n\n[NEWSLETTER]\n Email capture form\n\n[FOOTER] 4-column footer\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\nDoes this structure look right? Any sections to add, remove, or reorder?\n```\n\nWait for explicit user approval before proceeding to apply_wireframe.\nIf the user requests changes, update the affected page(s) and show again.\n\n---\n\n## Submission\n\nOnce ALL pages are approved by the user:\n\n```\napply_wireframe(projectId, {\n \"home\": \"<nav>...</nav><main>...</main><footer>...</footer>\",\n \"shop\": \"<nav>...</nav><main>...</main><footer>...</footer>\",\n \"about\": \"<nav>...</nav><main>...</main><footer>...</footer>\",\n \"contact\": \"<nav>...</nav><main>...</main><footer>...</footer>\"\n})\n```\n\nReturns: `{ pageCount: 4, slugs: [\"home\", \"shop\", \"about\", \"contact\"] }`\n\nAfter this, proceed to generate React code using the wireframe as your\nstructural reference. The wireframe defines WHAT to build \u2014 runtime-hooks\nand build-rules define HOW to build it in React.\n";
|
|
@@ -152,7 +152,7 @@ matches the correct runtime hook:
|
|
|
152
152
|
| Content images | useContentImages |
|
|
153
153
|
| Testimonials | useContentTestimonials |
|
|
154
154
|
| FAQs | useContentFaqs |
|
|
155
|
-
| Team members |
|
|
155
|
+
| Team members | useContentTeamMembers |
|
|
156
156
|
| Stats / social proof | useContentStats |
|
|
157
157
|
| Feature highlights | useContentFeatures |
|
|
158
158
|
| Contact / enquiry form | useSubmitForm |
|
package/dist/server.js
CHANGED
|
@@ -12,6 +12,7 @@ const studio_js_1 = require("./tools/studio.js");
|
|
|
12
12
|
const commerce_js_1 = require("./tools/commerce.js");
|
|
13
13
|
const content_js_1 = require("./tools/content.js");
|
|
14
14
|
const intelligence_js_1 = require("./tools/intelligence.js");
|
|
15
|
+
const analytics_js_1 = require("./tools/analytics.js");
|
|
15
16
|
const index_js_1 = require("./resources/index.js");
|
|
16
17
|
function createMcpServer() {
|
|
17
18
|
const server = new mcp_js_1.McpServer({
|
|
@@ -22,6 +23,7 @@ function createMcpServer() {
|
|
|
22
23
|
(0, commerce_js_1.registerCommerceTools)(server);
|
|
23
24
|
(0, content_js_1.registerContentTools)(server);
|
|
24
25
|
(0, intelligence_js_1.registerIntelligenceTools)(server);
|
|
26
|
+
(0, analytics_js_1.registerAnalyticsTools)(server);
|
|
25
27
|
(0, index_js_1.registerResources)(server);
|
|
26
28
|
return server;
|
|
27
29
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Analytics tools — read-only access to pre-computed capability metrics.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerAnalyticsTools = registerAnalyticsTools;
|
|
7
|
+
const v4_1 = require("zod/v4");
|
|
8
|
+
const client_js_1 = require("../client.js");
|
|
9
|
+
const util_js_1 = require("../util.js");
|
|
10
|
+
function registerAnalyticsTools(server) {
|
|
11
|
+
server.registerTool("get_analytics", {
|
|
12
|
+
title: "Get Analytics",
|
|
13
|
+
description: "Get pre-computed analytics for this vendor. Returns revenue, transaction counts, and other metrics per capability for a given period. Use this to answer questions like 'what's my revenue this month', 'which capability earns the most', or 'how many orders have I had'. Metrics are computed on a schedule — call this before making business recommendations.",
|
|
14
|
+
annotations: { readOnlyHint: true },
|
|
15
|
+
inputSchema: {
|
|
16
|
+
period: v4_1.z.enum(["7d", "30d", "90d"]).optional().default("30d").describe("Time period for metrics (default: 30d)"),
|
|
17
|
+
},
|
|
18
|
+
}, async ({ period }) => (0, util_js_1.safeCall)(async () => {
|
|
19
|
+
const data = await (0, client_js_1.get)(`/api/mcp/analytics?period=${period}`);
|
|
20
|
+
return JSON.stringify(data, null, 2);
|
|
21
|
+
}));
|
|
22
|
+
}
|
package/dist/tools/commerce.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Commerce tools — read
|
|
3
|
-
*
|
|
4
|
-
* All URLs point to /api/mcp/* (API key auth). Write endpoints are not yet
|
|
5
|
-
* available on the MCP route layer — create tools will be added when backend
|
|
6
|
-
* write proxies are shipped.
|
|
2
|
+
* Commerce tools — read and write access to products, services, events,
|
|
3
|
+
* memberships, donations, appointments, and forms.
|
|
7
4
|
*/
|
|
8
5
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
6
|
export declare function registerCommerceTools(server: McpServer): void;
|
package/dist/tools/commerce.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Commerce tools — read
|
|
4
|
-
*
|
|
5
|
-
* All URLs point to /api/mcp/* (API key auth). Write endpoints are not yet
|
|
6
|
-
* available on the MCP route layer — create tools will be added when backend
|
|
7
|
-
* write proxies are shipped.
|
|
3
|
+
* Commerce tools — read and write access to products, services, events,
|
|
4
|
+
* memberships, donations, appointments, and forms.
|
|
8
5
|
*/
|
|
9
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
7
|
exports.registerCommerceTools = registerCommerceTools;
|
|
8
|
+
const v4_1 = require("zod/v4");
|
|
11
9
|
const client_js_1 = require("../client.js");
|
|
12
10
|
const util_js_1 = require("../util.js");
|
|
13
11
|
function registerCommerceTools(server) {
|
|
@@ -74,6 +72,170 @@ function registerCommerceTools(server) {
|
|
|
74
72
|
const data = await (0, client_js_1.get)("/api/mcp/appointments/services");
|
|
75
73
|
return JSON.stringify(data.services, null, 2);
|
|
76
74
|
}));
|
|
75
|
+
// ── Categories ────────────────────────────────────────────────────────────
|
|
76
|
+
server.registerTool("list_categories", {
|
|
77
|
+
title: "List Categories",
|
|
78
|
+
description: "List all product categories for this vendor. Call this before create_product or update_product to get valid categoryId values. Returns id, name, description for each category.",
|
|
79
|
+
annotations: { readOnlyHint: true },
|
|
80
|
+
}, async () => (0, util_js_1.safeCall)(async () => {
|
|
81
|
+
const data = await (0, client_js_1.get)("/api/mcp/categories");
|
|
82
|
+
return JSON.stringify(data.categories, null, 2);
|
|
83
|
+
}));
|
|
84
|
+
server.registerTool("create_category", {
|
|
85
|
+
title: "Create Category",
|
|
86
|
+
description: "Create a product category. Returns the new category id — use it immediately in create_product or update_product. Returns a 409 conflict (with the existing id) if a category with the same name already exists — use that id instead of creating a duplicate.",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
name: v4_1.z.string().describe("Category name e.g. 'Clothing', 'Baked Goods'"),
|
|
89
|
+
description: v4_1.z.string().optional().describe("Optional description"),
|
|
90
|
+
},
|
|
91
|
+
}, async (input) => (0, util_js_1.safeCall)(async () => {
|
|
92
|
+
const data = await (0, client_js_1.post)("/api/mcp/categories", input);
|
|
93
|
+
return JSON.stringify(data, null, 2);
|
|
94
|
+
}));
|
|
95
|
+
// ── Write: Create product ─────────────────────────────────────────────────
|
|
96
|
+
server.registerTool("create_product", {
|
|
97
|
+
title: "Create Product",
|
|
98
|
+
description: "Create a physical or digital product. Call list_capabilities first to confirm the relevant capability (physical_products or digital_products) is active. Call list_categories first to get a valid categoryId — supplying an invalid categoryId returns a 400 error. Prices must be in kobo (₦1 = 100 kobo). imageUrl is optional.",
|
|
99
|
+
inputSchema: {
|
|
100
|
+
name: v4_1.z.string().describe("Product name"),
|
|
101
|
+
description: v4_1.z.string().optional().describe("Product description"),
|
|
102
|
+
priceKobo: v4_1.z.number().int().min(0).describe("Price in kobo (₦1 = 100 kobo)"),
|
|
103
|
+
type: v4_1.z.enum(["physical", "digital"]).optional().default("physical").describe("Product type — physical (default) or digital"),
|
|
104
|
+
stock: v4_1.z.number().int().min(0).optional().describe("Initial stock quantity (0 = out of stock). Omit for untracked stock."),
|
|
105
|
+
imageUrl: v4_1.z.string().optional().describe("External image URL for the product"),
|
|
106
|
+
categoryId: v4_1.z.string().uuid().optional().describe("Category ID from list_categories. Omit if no category applies."),
|
|
107
|
+
},
|
|
108
|
+
}, async (input) => (0, util_js_1.safeCall)(async () => {
|
|
109
|
+
const data = await (0, client_js_1.post)("/api/mcp/products", input);
|
|
110
|
+
return JSON.stringify(data, null, 2);
|
|
111
|
+
}));
|
|
112
|
+
// ── Write: Update product ─────────────────────────────────────────────────
|
|
113
|
+
server.registerTool("update_product", {
|
|
114
|
+
title: "Update Product",
|
|
115
|
+
description: "Update an existing product's name, description, category, or image. Call list_products to get the productId, list_categories to get a valid categoryId. Only fields provided are updated — omitted fields stay unchanged. Pass categoryId: null to remove the category.",
|
|
116
|
+
inputSchema: {
|
|
117
|
+
productId: v4_1.z.string().uuid().describe("Product ID from list_products"),
|
|
118
|
+
name: v4_1.z.string().optional().describe("New product name"),
|
|
119
|
+
description: v4_1.z.string().optional().describe("New product description"),
|
|
120
|
+
categoryId: v4_1.z.string().uuid().nullable().optional().describe("Category ID from list_categories, or null to remove category"),
|
|
121
|
+
imageUrl: v4_1.z.string().nullable().optional().describe("New image URL, or null to clear"),
|
|
122
|
+
},
|
|
123
|
+
}, async ({ productId, ...rest }) => (0, util_js_1.safeCall)(async () => {
|
|
124
|
+
const data = await (0, client_js_1.patch)(`/api/mcp/products/${productId}`, rest);
|
|
125
|
+
return JSON.stringify(data, null, 2);
|
|
126
|
+
}));
|
|
127
|
+
// ── Write: Create event ───────────────────────────────────────────────────
|
|
128
|
+
server.registerTool("create_event", {
|
|
129
|
+
title: "Create Event",
|
|
130
|
+
description: "Create an event with optional ticket types in a single call. Events are created as draft — they become visible on the storefront when published from the dashboard. Call list_capabilities first to confirm events capability is active.",
|
|
131
|
+
inputSchema: {
|
|
132
|
+
title: v4_1.z.string().describe("Event title"),
|
|
133
|
+
description: v4_1.z.string().optional().describe("Event description"),
|
|
134
|
+
startsAt: v4_1.z.string().describe("Start datetime — ISO 8601 string e.g. '2025-03-15T10:00:00'"),
|
|
135
|
+
endsAt: v4_1.z.string().describe("End datetime — ISO 8601 string"),
|
|
136
|
+
location: v4_1.z.string().optional().describe("Venue or location name"),
|
|
137
|
+
imageUrl: v4_1.z.string().optional().describe("Event cover image URL"),
|
|
138
|
+
tickets: v4_1.z.array(v4_1.z.object({
|
|
139
|
+
name: v4_1.z.string().describe("Ticket type name e.g. 'General Admission'"),
|
|
140
|
+
priceKobo: v4_1.z.number().int().min(0).describe("Ticket price in kobo (0 = free)"),
|
|
141
|
+
capacity: v4_1.z.number().int().min(1).describe("Total number of tickets available"),
|
|
142
|
+
maxPerOrder: v4_1.z.number().int().optional().describe("Max tickets per order"),
|
|
143
|
+
description: v4_1.z.string().optional(),
|
|
144
|
+
})).optional().default([]).describe("Ticket types to create inline with the event"),
|
|
145
|
+
},
|
|
146
|
+
}, async (input) => (0, util_js_1.safeCall)(async () => {
|
|
147
|
+
const data = await (0, client_js_1.post)("/api/mcp/events", input);
|
|
148
|
+
return JSON.stringify(data, null, 2);
|
|
149
|
+
}));
|
|
150
|
+
// ── Write: Create membership tier ─────────────────────────────────────────
|
|
151
|
+
server.registerTool("create_membership_tier", {
|
|
152
|
+
title: "Create Membership Tier",
|
|
153
|
+
description: "Create a membership tier with benefits list and billing period. Call list_capabilities first to confirm memberships capability is active. Prices in kobo.",
|
|
154
|
+
inputSchema: {
|
|
155
|
+
name: v4_1.z.string().describe("Tier name e.g. 'Gold Member'"),
|
|
156
|
+
description: v4_1.z.string().optional().describe("Tier description"),
|
|
157
|
+
priceKobo: v4_1.z.number().int().min(0).describe("Price in kobo per billing period"),
|
|
158
|
+
billingPeriod: v4_1.z.enum(["monthly", "yearly", "one_time"]).optional().default("monthly"),
|
|
159
|
+
benefits: v4_1.z.array(v4_1.z.string()).optional().describe("List of benefits e.g. ['Free delivery', 'Early access to drops']"),
|
|
160
|
+
imageUrl: v4_1.z.string().optional().describe("Tier cover image URL"),
|
|
161
|
+
maxMembers: v4_1.z.number().int().optional().describe("Maximum members for this tier (null = unlimited)"),
|
|
162
|
+
},
|
|
163
|
+
}, async (input) => (0, util_js_1.safeCall)(async () => {
|
|
164
|
+
const data = await (0, client_js_1.post)("/api/mcp/memberships/tiers", input);
|
|
165
|
+
return JSON.stringify(data, null, 2);
|
|
166
|
+
}));
|
|
167
|
+
// ── Write: Create donation campaign ───────────────────────────────────────
|
|
168
|
+
server.registerTool("create_donation_campaign", {
|
|
169
|
+
title: "Create Donation Campaign",
|
|
170
|
+
description: "Create a fundraising campaign. Campaigns are live immediately after creation. Call list_capabilities first to confirm donations capability is active.",
|
|
171
|
+
inputSchema: {
|
|
172
|
+
title: v4_1.z.string().describe("Campaign title"),
|
|
173
|
+
description: v4_1.z.string().optional().describe("Campaign story / description"),
|
|
174
|
+
goalAmountKobo: v4_1.z.number().int().optional().describe("Fundraising goal in kobo (omit for open-ended campaigns)"),
|
|
175
|
+
imageUrl: v4_1.z.string().optional().describe("Campaign cover image URL"),
|
|
176
|
+
endsAt: v4_1.z.string().optional().describe("Campaign end date — ISO 8601 string (omit for no end date)"),
|
|
177
|
+
presetAmounts: v4_1.z.array(v4_1.z.number().int().min(0)).optional().describe("Preset donation amount options in kobo e.g. [500000, 1000000, 2000000]"),
|
|
178
|
+
},
|
|
179
|
+
}, async (input) => (0, util_js_1.safeCall)(async () => {
|
|
180
|
+
const data = await (0, client_js_1.post)("/api/mcp/donations/campaigns", input);
|
|
181
|
+
return JSON.stringify(data, null, 2);
|
|
182
|
+
}));
|
|
183
|
+
// ── Write: Create appointment service ────────────────────────────────────
|
|
184
|
+
server.registerTool("create_appointment_service", {
|
|
185
|
+
title: "Create Appointment Service",
|
|
186
|
+
description: "Create a bookable appointment service. Note: staff members and their weekly schedules must be set up in the Appointments dashboard before customers can book. Call list_capabilities first to confirm appointments capability is active.",
|
|
187
|
+
inputSchema: {
|
|
188
|
+
name: v4_1.z.string().describe("Service name e.g. 'Hair Consultation'"),
|
|
189
|
+
description: v4_1.z.string().optional(),
|
|
190
|
+
durationMinutes: v4_1.z.number().int().min(1).describe("Service duration in minutes"),
|
|
191
|
+
priceKobo: v4_1.z.number().int().min(0).describe("Price in kobo"),
|
|
192
|
+
imageUrl: v4_1.z.string().optional(),
|
|
193
|
+
bufferMinutes: v4_1.z.number().int().min(0).optional().describe("Buffer time between bookings in minutes (default 0)"),
|
|
194
|
+
maxConcurrentBookings: v4_1.z.number().int().min(1).optional().describe("Max simultaneous bookings for this service (default 1)"),
|
|
195
|
+
},
|
|
196
|
+
}, async (input) => (0, util_js_1.safeCall)(async () => {
|
|
197
|
+
const data = await (0, client_js_1.post)("/api/mcp/appointments/services", input);
|
|
198
|
+
return JSON.stringify(data, null, 2);
|
|
199
|
+
}));
|
|
200
|
+
// ── Write: Create service listing ────────────────────────────────────────
|
|
201
|
+
server.registerTool("create_service_listing", {
|
|
202
|
+
title: "Create Service Listing",
|
|
203
|
+
description: "Create a service listing (freelance/agency-style services with a coverImage, description, and purchase flow). Different from appointment services — service listings are for deliverable-based work like design, photography, consulting. Call list_capabilities first to confirm services capability is active.",
|
|
204
|
+
inputSchema: {
|
|
205
|
+
title: v4_1.z.string().describe("Listing title e.g. 'Brand Identity Package'"),
|
|
206
|
+
description: v4_1.z.string().optional(),
|
|
207
|
+
priceKobo: v4_1.z.number().int().min(0).describe("Price in kobo"),
|
|
208
|
+
category: v4_1.z.string().optional().describe("Service category e.g. 'Design', 'Photography'"),
|
|
209
|
+
coverImageUrl: v4_1.z.string().optional(),
|
|
210
|
+
duration: v4_1.z.string().optional().describe("Delivery timeframe label e.g. '3–5 days'"),
|
|
211
|
+
deliveryTime: v4_1.z.string().optional().describe("Delivery time e.g. '7 days'"),
|
|
212
|
+
},
|
|
213
|
+
}, async (input) => (0, util_js_1.safeCall)(async () => {
|
|
214
|
+
const data = await (0, client_js_1.post)("/api/mcp/service-listings", input);
|
|
215
|
+
return JSON.stringify(data, null, 2);
|
|
216
|
+
}));
|
|
217
|
+
// ── Write: Create form ───────────────────────────────────────────────────
|
|
218
|
+
server.registerTool("create_form", {
|
|
219
|
+
title: "Create Form",
|
|
220
|
+
description: "Create a published storefront form. Returns the form ID — use it in useContentForm(formId) and useSubmitForm(formId) in storefront code. Call list_capabilities first to confirm content capability is active. Field types: text, email, phone, textarea, select, radio, checkbox, date, number, file.",
|
|
221
|
+
inputSchema: {
|
|
222
|
+
title: v4_1.z.string().describe("Form name e.g. 'Contact Us'"),
|
|
223
|
+
description: v4_1.z.string().optional(),
|
|
224
|
+
fields: v4_1.z.array(v4_1.z.object({
|
|
225
|
+
label: v4_1.z.string().describe("Field label shown to the user"),
|
|
226
|
+
type: v4_1.z.enum(["text", "email", "phone", "textarea", "select", "radio", "checkbox", "date", "number", "file"]).describe("Field input type"),
|
|
227
|
+
required: v4_1.z.boolean().optional().default(false),
|
|
228
|
+
placeholder: v4_1.z.string().optional(),
|
|
229
|
+
options: v4_1.z.array(v4_1.z.string()).optional().describe("Options for select fields"),
|
|
230
|
+
})).describe("Form fields in order"),
|
|
231
|
+
submitButtonText: v4_1.z.string().optional().describe("Submit button label (default: 'Submit')"),
|
|
232
|
+
successMessage: v4_1.z.string().optional().describe("Message shown after successful submission"),
|
|
233
|
+
storefrontPurpose: v4_1.z.enum(["contact", "waitlist", "enquiry", "feedback", "application"]).optional().describe("Semantic purpose — helps the storefront and Studio identify this form"),
|
|
234
|
+
},
|
|
235
|
+
}, async (input) => (0, util_js_1.safeCall)(async () => {
|
|
236
|
+
const data = await (0, client_js_1.post)("/api/mcp/content/forms", input);
|
|
237
|
+
return JSON.stringify(data, null, 2);
|
|
238
|
+
}));
|
|
77
239
|
// ── Capabilities ───────────────────────────────────────────────────────────
|
|
78
240
|
server.registerTool("list_capabilities", {
|
|
79
241
|
title: "List Capabilities",
|
package/dist/tools/studio.js
CHANGED
|
@@ -9,6 +9,8 @@ const client_js_1 = require("../client.js");
|
|
|
9
9
|
const util_js_1 = require("../util.js");
|
|
10
10
|
const runtime_hooks_js_1 = require("../resources/runtime-hooks.js");
|
|
11
11
|
const build_rules_js_1 = require("../resources/build-rules.js");
|
|
12
|
+
const design_direction_schema_js_1 = require("../resources/design-direction-schema.js");
|
|
13
|
+
const wireframe_schema_js_1 = require("../resources/wireframe-schema.js");
|
|
12
14
|
function registerStudioTools(server) {
|
|
13
15
|
// ── List projects ──────────────────────────────────────────────────────────
|
|
14
16
|
server.registerTool("list_projects", {
|
|
@@ -52,13 +54,13 @@ function registerStudioTools(server) {
|
|
|
52
54
|
step: 1,
|
|
53
55
|
tool: "apply_design_direction",
|
|
54
56
|
required: true,
|
|
55
|
-
description: "
|
|
57
|
+
description: "BEFORE generating any direction JSON, have a SHORT design conversation with the user. Ask: (1) preferred brand colors — or let the AI suggest based on the business type; (2) font style — modern/minimal, editorial/serif, warm/handcrafted, bold/display, playful; (3) if physical_products is active: which display modes to use (product grid / collections / bundles / category sections); (4) overall mood / reference brands they like. Then generate the DesignDirection JSON and present a summary (palette, fonts, pages, mood) for user confirmation before calling apply_design_direction. The design_direction_schema in required_reading has the full shape. Alternatively use set_design_direction to let SellHapi's Claude generate it (costs credits).",
|
|
56
58
|
},
|
|
57
59
|
{
|
|
58
60
|
step: 2,
|
|
59
61
|
tool: "apply_wireframe",
|
|
60
62
|
required: false,
|
|
61
|
-
description: "Optional but recommended. Generate mid-fidelity HTML wireframes for each page, present them section-by-section to the user, collect feedback, then store the approved wireframes.
|
|
63
|
+
description: "Optional but recommended. Generate mid-fidelity HTML wireframes for each page, present them section-by-section to the user, collect feedback, then store the approved wireframes. The wireframe_schema in required_reading has full conventions and data-hook reference.",
|
|
62
64
|
},
|
|
63
65
|
{
|
|
64
66
|
step: 3,
|
|
@@ -75,7 +77,9 @@ function registerStudioTools(server) {
|
|
|
75
77
|
],
|
|
76
78
|
},
|
|
77
79
|
required_reading: {
|
|
78
|
-
instruction: "Read
|
|
80
|
+
instruction: "Read ALL four documents below before generating any storefront code. design_direction_schema and wireframe_schema must be read before Step 1. build_rules and runtime_hooks must be read before Step 3 (push_code). Violating build_rules causes push_code to fail with a BuildGuard error.",
|
|
81
|
+
design_direction_schema: design_direction_schema_js_1.DESIGN_DIRECTION_SCHEMA,
|
|
82
|
+
wireframe_schema: wireframe_schema_js_1.WIREFRAME_SCHEMA,
|
|
79
83
|
build_rules: build_rules_js_1.BUILD_RULES,
|
|
80
84
|
runtime_hooks: runtime_hooks_js_1.RUNTIME_HOOKS,
|
|
81
85
|
},
|
|
@@ -120,7 +124,7 @@ function registerStudioTools(server) {
|
|
|
120
124
|
// ── Push code (zero credits) ───────────────────────────────────────────────
|
|
121
125
|
server.registerTool("push_code", {
|
|
122
126
|
title: "Push Code",
|
|
123
|
-
description: "Push client-generated React code to a project. Runs BuildGuard validation + esbuild bundling and returns a preview URL. ZERO AI credit cost — you generate the code, SellHapi only builds it. CRITICAL RULES: (1) import ONLY from @sellhapi/studio-runtime and react — NO external packages; (2) every top-level JSX section must have data-region, data-label, data-scope attributes; (3)
|
|
127
|
+
description: "Push client-generated React code to a project. Runs BuildGuard validation + esbuild bundling and returns a preview URL. ZERO AI credit cost — you generate the code, SellHapi only builds it. CRITICAL RULES: (1) import ONLY from @sellhapi/studio-runtime and react — NO external packages; (2) every top-level JSX section must have data-region, data-label, data-scope attributes; (3) use inline React style objects for ALL styling — Tailwind is NOT available; (4) NO localStorage/sessionStorage for state management — EXCEPTION: window.location.href IS required and allowed for payment redirects (all commerce hooks return { paymentLink } — redirect with window.location.href = result.paymentLink); (5) prices via priceKobo / 100 — never hardcode currency. Call get_build_rules or get_runtime_hooks for full reference.",
|
|
124
128
|
inputSchema: {
|
|
125
129
|
projectId: v4_1.z.string().describe("Studio project ID"),
|
|
126
130
|
files: v4_1.z.record(v4_1.z.string(), v4_1.z.string()).describe("Map of filename → code content. Must include App.tsx as entry point."),
|