webcake-storefront-mcp 1.0.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,158 @@
1
+ import { z } from "zod";
2
+ import { getConfig, setConfig } from "../db.js";
3
+ /** Read all saved credentials from SQLite for startup */
4
+ export function getSavedConfig() {
5
+ return {
6
+ token: getConfig("token") || "",
7
+ session_id: getConfig("session_id") || "",
8
+ site_id: getConfig("site_id") || "",
9
+ api_url: getConfig("api_url") || "",
10
+ };
11
+ }
12
+ /**
13
+ * Get confirm mode for update operations.
14
+ * - "always_confirm" (default): dry_run=true, AI must show diff and ask user before applying
15
+ * - "auto_apply": dry_run=false, changes apply immediately without confirmation
16
+ */
17
+ export function getConfirmMode() {
18
+ return getConfig("confirm_mode") || "always_confirm";
19
+ }
20
+ // ── Tools ──
21
+ export function registerContextTools(server, api, handle) {
22
+ server.tool("get_current_context", "Show current connection context: which site_id, API URL, session, and account info. Call this first to confirm you're working on the right site", {}, () => handle(async () => {
23
+ const [me, site] = await Promise.all([
24
+ api.getMe().catch(() => null),
25
+ api.getSiteInfo().catch(() => null),
26
+ ]);
27
+ return {
28
+ api_url: api.baseUrl,
29
+ site_id: api.siteId,
30
+ session_id: api.sessionId || null,
31
+ site_name: site?.data?.name || null,
32
+ site_domain: site?.data?.domain || site?.data?.sub_domain || null,
33
+ account: me?.data
34
+ ? {
35
+ id: me.data.id,
36
+ email: me.data.email,
37
+ name: [me.data.first_name, me.data.last_name].filter(Boolean).join(" ") || null,
38
+ }
39
+ : null,
40
+ confirm_mode: getConfirmMode(),
41
+ hint: "Use list_my_sites to see all sites, switch_site to change site. Use toggle_confirm_mode to switch between 'always_confirm' (safe) and 'auto_apply' (fast).",
42
+ };
43
+ }));
44
+ server.tool("list_my_sites", "List all sites accessible by the current account. Use this to find a site_id before switching", {
45
+ page: z.number().default(1).describe("Page number"),
46
+ limit: z.number().default(20).describe("Items per page"),
47
+ term: z.string().optional().describe("Search by site name"),
48
+ }, ({ page, limit, term }) => handle(async () => {
49
+ const res = await api.listMySites({ page, limit, ...(term && { term }) });
50
+ const raw = res?.data?.sites || res?.data || [];
51
+ const list = Array.isArray(raw) ? raw : [];
52
+ const sites = list.map((s) => ({
53
+ id: s.id,
54
+ name: s.name,
55
+ domain: s.domain || s.sub_domain || null,
56
+ is_current: s.id === api.siteId,
57
+ }));
58
+ return {
59
+ current_site_id: api.siteId,
60
+ sites,
61
+ total: res?.data?.total_entries || sites.length,
62
+ page,
63
+ };
64
+ }));
65
+ server.tool("switch_site", `Switch to a different site by site_id. All subsequent tool calls will target the new site.
66
+ The choice is saved to local database — next session will auto-connect to this site.
67
+ Use list_my_sites first to find the site_id`, {
68
+ site_id: z.string().describe("The site ID to switch to"),
69
+ }, ({ site_id }) => handle(async () => {
70
+ const oldSiteId = api.siteId;
71
+ api.switchSite(site_id);
72
+ // Verify the new site is accessible
73
+ const site = await api.getSiteInfo().catch(() => null);
74
+ if (!site?.data) {
75
+ api.switchSite(oldSiteId);
76
+ throw new Error(`Cannot access site "${site_id}". Check the ID or your permissions.`);
77
+ }
78
+ // Persist for next session
79
+ setConfig("site_id", api.siteId);
80
+ setConfig("site_name", site.data.name || "");
81
+ setConfig("site_domain", site.data.domain || site.data.sub_domain || "");
82
+ return {
83
+ switched: true,
84
+ saved: true,
85
+ previous_site_id: oldSiteId,
86
+ current_site_id: api.siteId,
87
+ site_name: site.data.name,
88
+ site_domain: site.data.domain || site.data.sub_domain || null,
89
+ };
90
+ }));
91
+ server.tool("update_auth", `Update authentication credentials. All values are saved to local database — next session auto-restores them.
92
+ Get token and session_id from browser DevTools → Network tab → copy from any API request headers`, {
93
+ token: z.string().optional().describe("JWT Bearer token (from Authorization header)"),
94
+ session_id: z.string().optional().describe("Session ID (from x-session-id header)"),
95
+ api_url: z.string().optional().describe("API base URL (e.g. https://api.storecake.io)"),
96
+ }, ({ token, session_id, api_url }) => handle(async () => {
97
+ if (!token && !session_id && !api_url) {
98
+ throw new Error("Provide at least one of: token, session_id, api_url");
99
+ }
100
+ const oldToken = api.token;
101
+ const oldSessionId = api.sessionId;
102
+ const oldBaseUrl = api.baseUrl;
103
+ if (api_url)
104
+ api.baseUrl = api_url.replace(/\/$/, "");
105
+ if (token)
106
+ api.switchToken(token);
107
+ if (session_id)
108
+ api.switchSession(session_id);
109
+ // Verify credentials work
110
+ const me = await api.getMe().catch(() => null);
111
+ if (!me?.data) {
112
+ // Rollback
113
+ if (token)
114
+ api.switchToken(oldToken);
115
+ if (session_id)
116
+ api.switchSession(oldSessionId);
117
+ if (api_url)
118
+ api.baseUrl = oldBaseUrl;
119
+ throw new Error("Authentication failed — credentials were NOT changed. Make sure token and session_id are both correct.");
120
+ }
121
+ // Persist all to SQLite
122
+ if (token)
123
+ setConfig("token", token);
124
+ if (session_id)
125
+ setConfig("session_id", session_id);
126
+ if (api_url)
127
+ setConfig("api_url", api.baseUrl);
128
+ return {
129
+ updated: true,
130
+ saved: true,
131
+ account: {
132
+ id: me.data.id,
133
+ email: me.data.email,
134
+ name: [me.data.first_name, me.data.last_name].filter(Boolean).join(" ") || null,
135
+ },
136
+ current_site_id: api.siteId,
137
+ };
138
+ }));
139
+ server.tool("toggle_confirm_mode", `Toggle update confirmation mode. Controls whether update tools ask for user confirmation before saving.
140
+ - "always_confirm" (default): Shows diff first, requires user approval before saving. Safer.
141
+ - "auto_apply": Applies changes immediately without preview. Faster but riskier.
142
+ Current mode is saved to database and persists across sessions.
143
+
144
+ Call this tool when the user says things like:
145
+ → "tự động xác nhận" / "auto confirm" / "không cần hỏi" / "don't ask" / "apply directly" → mode: "auto_apply"
146
+ → "hỏi trước khi lưu" / "luôn hỏi" / "always ask" / "confirm before saving" / "xác nhận trước" → mode: "always_confirm"`, {
147
+ mode: z.enum(["always_confirm", "auto_apply"]).describe('Set to "always_confirm" (safe) or "auto_apply" (fast). Map user intent: "tự động"/"auto"/"không cần hỏi" → auto_apply, "hỏi lại"/"confirm"/"luôn hỏi" → always_confirm'),
148
+ }, ({ mode }) => handle(async () => {
149
+ setConfig("confirm_mode", mode);
150
+ return {
151
+ confirm_mode: mode,
152
+ saved: true,
153
+ description: mode === "always_confirm"
154
+ ? "Update tools will now preview changes (dry_run) and require your confirmation before saving."
155
+ : "Update tools will now apply changes immediately without preview. Use with caution.",
156
+ };
157
+ }));
158
+ }
@@ -0,0 +1,13 @@
1
+ import { z } from "zod";
2
+ export function registerCustomerTools(server, api, handle) {
3
+ server.tool("find_customer", "Find a customer by ID, phone number, or email", {
4
+ by: z.enum(["id", "phone", "email"]).describe("Search field"),
5
+ value: z.string().describe("Search value"),
6
+ }, ({ by, value }) => handle(() => {
7
+ switch (by) {
8
+ case "id": return api.findCustomerById(value);
9
+ case "phone": return api.findCustomerByPhone(value);
10
+ case "email": return api.findCustomerByEmail(value);
11
+ }
12
+ }));
13
+ }