@sellhapi/mcp 0.1.3 → 0.1.5

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/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,5 @@
1
+ /**
2
+ * Analytics tools — read-only access to pre-computed capability metrics.
3
+ */
4
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ export declare function registerAnalyticsTools(server: McpServer): void;
@@ -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
+ }
@@ -72,10 +72,58 @@ function registerCommerceTools(server) {
72
72
  const data = await (0, client_js_1.get)("/api/mcp/appointments/services");
73
73
  return JSON.stringify(data.services, null, 2);
74
74
  }));
75
+ // ── Discounts ─────────────────────────────────────────────────────────────
76
+ server.registerTool("list_discounts", {
77
+ title: "List Discounts",
78
+ description: "List all active discounts for this vendor. Returns id, name, code, discountType, value, scope, usage count, and expiry. Use this to check existing discounts before creating new ones.",
79
+ annotations: { readOnlyHint: true },
80
+ }, async () => (0, util_js_1.safeCall)(async () => {
81
+ const data = await (0, client_js_1.get)("/api/mcp/discounts");
82
+ return JSON.stringify(data.discounts, null, 2);
83
+ }));
84
+ server.registerTool("create_discount", {
85
+ title: "Create Discount",
86
+ description: "Create a discount code or automatic discount. For percentage discounts, value is 1–100 (e.g. 20 = 20% off). For fixed discounts, value is in kobo (₦1 = 100 kobo) — the API converts to Naira internally. minimumOrderKobo is also in kobo. scope 'all_products' applies sitewide. 'specific_products' and 'specific_categories' require editing via the dashboard after creation. code is optional — omit for automatic (no-code) discounts.",
87
+ inputSchema: {
88
+ name: v4_1.z.string().describe("Internal discount name e.g. 'Lagos Launch 20% Off'"),
89
+ discountType: v4_1.z.enum(["percentage", "fixed"]).describe("percentage = % off, fixed = fixed Naira amount off"),
90
+ value: v4_1.z.number().describe("For percentage: 1–100. For fixed: amount in kobo (₦1 = 100 kobo)."),
91
+ code: v4_1.z.string().optional().describe("Discount code customers enter at checkout (e.g. 'LAUNCH20'). Omit for automatic discount."),
92
+ description: v4_1.z.string().optional().describe("Internal description"),
93
+ scope: v4_1.z.enum(["all_products", "specific_products", "specific_categories"]).optional().default("all_products").describe("Which products this applies to. specific_products/specific_categories require assigning products in the dashboard after creation."),
94
+ usageLimit: v4_1.z.number().int().min(1).optional().describe("Maximum total uses. Omit for unlimited."),
95
+ minimumOrderKobo: v4_1.z.number().int().min(0).optional().describe("Minimum cart total in kobo to qualify for this discount."),
96
+ expiresAt: v4_1.z.string().optional().describe("Expiry datetime — ISO 8601 string. Omit for no expiry."),
97
+ startsAt: v4_1.z.string().optional().describe("Start datetime — ISO 8601 string. Omit to activate immediately."),
98
+ },
99
+ }, async (input) => (0, util_js_1.safeCall)(async () => {
100
+ const data = await (0, client_js_1.post)("/api/mcp/discounts", input);
101
+ return JSON.stringify(data, null, 2);
102
+ }));
103
+ // ── Categories ────────────────────────────────────────────────────────────
104
+ server.registerTool("list_categories", {
105
+ title: "List Categories",
106
+ 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.",
107
+ annotations: { readOnlyHint: true },
108
+ }, async () => (0, util_js_1.safeCall)(async () => {
109
+ const data = await (0, client_js_1.get)("/api/mcp/categories");
110
+ return JSON.stringify(data.categories, null, 2);
111
+ }));
112
+ server.registerTool("create_category", {
113
+ title: "Create Category",
114
+ 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.",
115
+ inputSchema: {
116
+ name: v4_1.z.string().describe("Category name e.g. 'Clothing', 'Baked Goods'"),
117
+ description: v4_1.z.string().optional().describe("Optional description"),
118
+ },
119
+ }, async (input) => (0, util_js_1.safeCall)(async () => {
120
+ const data = await (0, client_js_1.post)("/api/mcp/categories", input);
121
+ return JSON.stringify(data, null, 2);
122
+ }));
75
123
  // ── Write: Create product ─────────────────────────────────────────────────
76
124
  server.registerTool("create_product", {
77
125
  title: "Create Product",
78
- description: "Create a physical or digital product. Call list_capabilities first to confirm the relevant capability (physical_products or digital_products) is active. Prices must be in kobo (₦1 = 100 kobo). imageUrl is optional — use an external URL if the vendor has one, or omit it.",
126
+ 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.",
79
127
  inputSchema: {
80
128
  name: v4_1.z.string().describe("Product name"),
81
129
  description: v4_1.z.string().optional().describe("Product description"),
@@ -83,11 +131,27 @@ function registerCommerceTools(server) {
83
131
  type: v4_1.z.enum(["physical", "digital"]).optional().default("physical").describe("Product type — physical (default) or digital"),
84
132
  stock: v4_1.z.number().int().min(0).optional().describe("Initial stock quantity (0 = out of stock). Omit for untracked stock."),
85
133
  imageUrl: v4_1.z.string().optional().describe("External image URL for the product"),
134
+ categoryId: v4_1.z.string().uuid().optional().describe("Category ID from list_categories. Omit if no category applies."),
86
135
  },
87
136
  }, async (input) => (0, util_js_1.safeCall)(async () => {
88
137
  const data = await (0, client_js_1.post)("/api/mcp/products", input);
89
138
  return JSON.stringify(data, null, 2);
90
139
  }));
140
+ // ── Write: Update product ─────────────────────────────────────────────────
141
+ server.registerTool("update_product", {
142
+ title: "Update Product",
143
+ 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.",
144
+ inputSchema: {
145
+ productId: v4_1.z.string().uuid().describe("Product ID from list_products"),
146
+ name: v4_1.z.string().optional().describe("New product name"),
147
+ description: v4_1.z.string().optional().describe("New product description"),
148
+ categoryId: v4_1.z.string().uuid().nullable().optional().describe("Category ID from list_categories, or null to remove category"),
149
+ imageUrl: v4_1.z.string().nullable().optional().describe("New image URL, or null to clear"),
150
+ },
151
+ }, async ({ productId, ...rest }) => (0, util_js_1.safeCall)(async () => {
152
+ const data = await (0, client_js_1.patch)(`/api/mcp/products/${productId}`, rest);
153
+ return JSON.stringify(data, null, 2);
154
+ }));
91
155
  // ── Write: Create event ───────────────────────────────────────────────────
92
156
  server.registerTool("create_event", {
93
157
  title: "Create Event",
@@ -181,13 +245,13 @@ function registerCommerceTools(server) {
181
245
  // ── Write: Create form ───────────────────────────────────────────────────
182
246
  server.registerTool("create_form", {
183
247
  title: "Create Form",
184
- 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, checkbox, date, number.",
248
+ 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.",
185
249
  inputSchema: {
186
250
  title: v4_1.z.string().describe("Form name e.g. 'Contact Us'"),
187
251
  description: v4_1.z.string().optional(),
188
252
  fields: v4_1.z.array(v4_1.z.object({
189
253
  label: v4_1.z.string().describe("Field label shown to the user"),
190
- type: v4_1.z.enum(["text", "email", "phone", "textarea", "select", "checkbox", "date", "number"]).describe("Field input type"),
254
+ type: v4_1.z.enum(["text", "email", "phone", "textarea", "select", "radio", "checkbox", "date", "number", "file"]).describe("Field input type"),
191
255
  required: v4_1.z.boolean().optional().default(false),
192
256
  placeholder: v4_1.z.string().optional(),
193
257
  options: v4_1.z.array(v4_1.z.string()).optional().describe("Options for select fields"),
@@ -124,7 +124,7 @@ function registerStudioTools(server) {
124
124
  // ── Push code (zero credits) ───────────────────────────────────────────────
125
125
  server.registerTool("push_code", {
126
126
  title: "Push Code",
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) NO inline style={{}} — use Tailwind only; (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.",
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.",
128
128
  inputSchema: {
129
129
  projectId: v4_1.z.string().describe("Studio project ID"),
130
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."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellhapi/mcp",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "SellHapi MCP server — build and manage SellHapi storefronts from AI tools",
5
5
  "bin": {
6
6
  "sellhapi-mcp": "./dist/index.js"