epicmerch-mcp 1.0.0

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,95 @@
1
+ ---
2
+ name: epicmerch-products
3
+ description: Add an EpicMerch product catalog with search and category filtering to the current project.
4
+ ---
5
+
6
+ # EpicMerch Products Integration
7
+
8
+ **Idempotency rule:** Before writing any file, check if it already exists. If it does, SKIP that file and tell the user "X already exists — keeping it". Never overwrite existing files.
9
+
10
+ ## Step 1: Install SDK
11
+
12
+ Check `package.json`. If `"epicmerch"` is in `dependencies`, skip. Otherwise: `npm install epicmerch`.
13
+
14
+ ## Step 2: SDK initializer
15
+
16
+ If `src/lib/epicmerch.js` already exists, skip. Otherwise write:
17
+
18
+ ```js
19
+ import EpicMerch from 'epicmerch';
20
+
21
+ export const store = new EpicMerch({
22
+ apiKey: import.meta.env.VITE_API_KEY,
23
+ ...(import.meta.env.VITE_API_URL && { baseUrl: import.meta.env.VITE_API_URL }),
24
+ });
25
+ ```
26
+
27
+ ## Step 3: ProductCard
28
+
29
+ If `src/components/ProductCard.jsx` already exists, skip. Otherwise write:
30
+
31
+ ```jsx
32
+ export default function ProductCard({ product, onAddToCart }) {
33
+ return (
34
+ <div className="product-card">
35
+ <img src={product.images?.[0]} alt={product.name} />
36
+ <h3>{product.name}</h3>
37
+ <p>₹{product.price}</p>
38
+ <button onClick={() => onAddToCart(product._id)}>Add to Cart</button>
39
+ </div>
40
+ );
41
+ }
42
+ ```
43
+
44
+ ## Step 4: ProductList
45
+
46
+ If `src/components/ProductList.jsx` already exists, skip. Otherwise write:
47
+
48
+ ```jsx
49
+ import { useEffect, useState } from 'react';
50
+ import { store } from '../lib/epicmerch';
51
+ import ProductCard from './ProductCard';
52
+
53
+ export default function ProductList() {
54
+ const [products, setProducts] = useState([]);
55
+ const [categories, setCategories] = useState([]);
56
+ const [category, setCategory] = useState(undefined);
57
+ const [query, setQuery] = useState('');
58
+
59
+ useEffect(() => { store.categories.list().then(setCategories); }, []);
60
+
61
+ useEffect(() => {
62
+ if (query.trim()) {
63
+ store.products.search(query).then(d => setProducts(d.products ?? d));
64
+ } else {
65
+ store.products.list({ type: category, limit: 12 }).then(d => setProducts(d.products ?? d));
66
+ }
67
+ }, [query, category]);
68
+
69
+ const addToCart = (productId) => store.cart.add(productId, 1);
70
+
71
+ return (
72
+ <div>
73
+ <input
74
+ placeholder="Search products..."
75
+ value={query}
76
+ onChange={e => setQuery(e.target.value)}
77
+ />
78
+ <div>
79
+ {categories.map(c => (
80
+ <button key={c} onClick={() => { setQuery(''); setCategory(c === 'All' ? undefined : c); }}>{c}</button>
81
+ ))}
82
+ </div>
83
+ <div className="product-grid">
84
+ {products.map(p => <ProductCard key={p._id} product={p} onAddToCart={addToCart} />)}
85
+ </div>
86
+ </div>
87
+ );
88
+ }
89
+ ```
90
+
91
+ ## Step 5: Tell the user what's next
92
+
93
+ List CREATED files. Then:
94
+
95
+ > "Product catalog integrated. Set `VITE_API_KEY` in `.env` (ask me to generate one via the MCP if you don't have it). Import `<ProductList />` into your app."
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: epicmerch-stripe
3
+ description: Set up Stripe payments for an EpicMerch store conversationally — get keys, configure webhook, test, and activate.
4
+ ---
5
+
6
+ # EpicMerch Stripe Setup
7
+
8
+ You're helping a merchant configure Stripe as their payment processor. Walk them through these steps.
9
+
10
+ ## Step 1: Confirm they have a Stripe account
11
+
12
+ Ask: "Do you already have a Stripe account at dashboard.stripe.com? If not, sign up first — it's free and takes 5 minutes."
13
+
14
+ ## Step 2: Collect their API keys
15
+
16
+ Ask them to go to https://dashboard.stripe.com/apikeys and copy:
17
+ - Their **publishable key** (starts with `pk_`)
18
+ - Their **secret key** (starts with `sk_`) — remind them this is secret
19
+
20
+ ## Step 3: Set up the webhook
21
+
22
+ Tell them:
23
+ > "In your Stripe Dashboard, go to Developers → Webhooks → Add endpoint.
24
+ >
25
+ > For the endpoint URL, use:
26
+ > `https://api.epicmerch.in/api/webhook/stripe?merchantId=<their-merchant-id>`
27
+ >
28
+ > Subscribe to these events:
29
+ > - `payment_intent.succeeded`
30
+ > - `payment_intent.payment_failed`
31
+ >
32
+ > After creating the webhook, reveal the **Signing secret** (starts with `whsec_`)."
33
+
34
+ You can find their merchant ID by calling `merchant_get_settings()` and reading the `_id` or `id` field.
35
+
36
+ ## Step 4: Save their credentials
37
+
38
+ Call `merchant_configure_stripe` with the three values:
39
+ ```
40
+ merchant_configure_stripe({
41
+ publishableKey: "pk_live_...",
42
+ secretKey: "sk_live_...",
43
+ webhookSecret: "whsec_..."
44
+ })
45
+ ```
46
+
47
+ ## Step 5: Test the connection
48
+
49
+ Call `merchant_test_stripe_connection()`. You should see `{ ok: true, accountId, country, chargesEnabled }`.
50
+
51
+ If `chargesEnabled` is `false`, tell the merchant their Stripe account isn't fully activated yet — they need to complete onboarding in the Stripe Dashboard.
52
+
53
+ ## Step 6: Activate Stripe as their checkout
54
+
55
+ Call `merchant_set_checkout_type({ type: "stripe" })`.
56
+
57
+ Their storefront will now use Stripe Payment Element (cards, Link, Apple Pay, Google Pay) on the next checkout.
58
+
59
+ ## Style
60
+
61
+ - Ask one question at a time. Don't dump all six steps at once.
62
+ - After each step, confirm before moving on ("Got it — paste your publishable key when you have it").
63
+ - Read back the result of each MCP call so the merchant sees progress.
64
+ - If `merchant_test_stripe_connection` fails, diagnose: the most common cause is a wrong secret key (regenerate in Stripe Dashboard) or test/live key mismatch.
@@ -0,0 +1,389 @@
1
+ ---
2
+ name: epicmerch
3
+ description: One-shot EpicMerch integration — installs the SDK and scaffolds auth, product catalog, and order management into the current project.
4
+ ---
5
+
6
+ # EpicMerch Full Integration
7
+
8
+ You are integrating EpicMerch into this project. EpicMerch is a complete e-commerce backend that handles auth, inventory, orders, and payments. Follow every step below in order without asking for confirmation unless a step explicitly says to ask.
9
+
10
+ **Idempotency rule (apply throughout):** Before writing any file in the scaffold steps below, check if it already exists. If it does, SKIP that file and tell the user "X already exists — keeping it". Never overwrite existing files. The merchant may already have a polished version of any of these components; preserving their work is more important than completing the scaffold.
11
+
12
+ ## Checklist
13
+
14
+ - [ ] **Step 0: Survey the project**
15
+
16
+ List which of the scaffold target files already exist in this project:
17
+ - `src/lib/epicmerch.js`
18
+ - `src/components/Login.jsx`
19
+ - `src/components/ProductCard.jsx`
20
+ - `src/components/ProductList.jsx`
21
+ - `src/components/Cart.jsx`
22
+ - `src/components/Checkout.jsx`
23
+ - `src/components/OrderHistory.jsx`
24
+ - `.env.example`
25
+ - `.env`
26
+
27
+ Briefly tell the user which already exist (those will be skipped) and which will be created. Then proceed.
28
+
29
+ - [ ] **Step 1: Detect framework**
30
+
31
+ Read `package.json`. Determine the framework:
32
+ - If `"react"` or `"vite"` in dependencies → framework = `react`, envPrefix = `VITE_`
33
+ - If `"next"` in dependencies → framework = `react`, envPrefix = `NEXT_PUBLIC_`
34
+ - If `"vue"` in dependencies → framework = `react`, envPrefix = `VITE_`; tell the user: "Vue is not yet natively supported — installing React templates instead."
35
+ - Otherwise → framework = `react`, envPrefix = `VITE_`
36
+
37
+ Tell the user: "Detected [framework]. Installing EpicMerch SDK..."
38
+
39
+ - [ ] **Step 2: Install the SDK**
40
+
41
+ Check `package.json` first. If `"epicmerch"` is already in `dependencies`, skip the install and tell the user "epicmerch SDK already installed". Otherwise:
42
+
43
+ Run: `npm install epicmerch`
44
+
45
+ - [ ] **Step 3: Create SDK initializer**
46
+
47
+ **If `src/lib/epicmerch.js` already exists, skip this step entirely** and tell the user "src/lib/epicmerch.js already exists — keeping it". Otherwise, write `src/lib/epicmerch.js` using the `envPrefix` detected in Step 1 to substitute the env var names:
48
+
49
+ - For `VITE_` prefix (Vite / React):
50
+ ```js
51
+ import EpicMerch from 'epicmerch';
52
+
53
+ export const store = new EpicMerch({
54
+ apiKey: import.meta.env.VITE_API_KEY,
55
+ ...(import.meta.env.VITE_API_URL && { baseUrl: import.meta.env.VITE_API_URL }),
56
+ });
57
+ ```
58
+
59
+ - For `NEXT_PUBLIC_` prefix (Next.js):
60
+ ```js
61
+ import EpicMerch from 'epicmerch';
62
+
63
+ export const store = new EpicMerch({
64
+ apiKey: process.env.NEXT_PUBLIC_API_KEY,
65
+ ...(process.env.NEXT_PUBLIC_API_URL && { baseUrl: process.env.NEXT_PUBLIC_API_URL }),
66
+ });
67
+ ```
68
+
69
+ - [ ] **Step 4: Scaffold auth**
70
+
71
+ **If `src/components/Login.jsx` already exists, skip this step** and tell the user "Login.jsx already exists — keeping it". Otherwise write `src/components/Login.jsx`:
72
+
73
+ ```jsx
74
+ import { useState } from 'react';
75
+ import { store } from '../lib/epicmerch';
76
+
77
+ export default function Login({ onLogin }) {
78
+ const [phone, setPhone] = useState('');
79
+ const [otp, setOtp] = useState('');
80
+ const [step, setStep] = useState('phone');
81
+
82
+ const sendOtp = async () => {
83
+ await store.auth.sendOtp(phone, 'phone');
84
+ setStep('otp');
85
+ };
86
+
87
+ const verify = async () => {
88
+ const result = await store.auth.verifyOtp(phone, otp, {});
89
+ if (result.token) {
90
+ store.setCustomerToken(result.token);
91
+ localStorage.setItem('customerInfo', JSON.stringify(result));
92
+ onLogin(result);
93
+ }
94
+ };
95
+
96
+ return (
97
+ <div>
98
+ {step === 'phone' ? (
99
+ <>
100
+ <input value={phone} onChange={e => setPhone(e.target.value)} placeholder="+919876543210" />
101
+ <button onClick={sendOtp}>Send OTP</button>
102
+ </>
103
+ ) : (
104
+ <>
105
+ <input value={otp} onChange={e => setOtp(e.target.value)} placeholder="Enter OTP" />
106
+ <button onClick={verify}>Verify</button>
107
+ </>
108
+ )}
109
+ </div>
110
+ );
111
+ }
112
+ ```
113
+
114
+ - [ ] **Step 5: Scaffold product catalog**
115
+
116
+ For each of the two files in this step, check existence individually. If a file already exists, skip writing it and tell the user "X already exists — keeping it". Otherwise write it.
117
+
118
+ Write `src/components/ProductCard.jsx`:
119
+
120
+ ```jsx
121
+ export default function ProductCard({ product, onAddToCart }) {
122
+ return (
123
+ <div className="product-card">
124
+ <img src={product.images?.[0]} alt={product.name} />
125
+ <h3>{product.name}</h3>
126
+ <p>₹{product.price}</p>
127
+ <button onClick={() => onAddToCart(product._id)}>Add to Cart</button>
128
+ </div>
129
+ );
130
+ }
131
+ ```
132
+
133
+ Write `src/components/ProductList.jsx`:
134
+
135
+ ```jsx
136
+ import { useEffect, useState } from 'react';
137
+ import { store } from '../lib/epicmerch';
138
+ import ProductCard from './ProductCard';
139
+
140
+ export default function ProductList() {
141
+ const [products, setProducts] = useState([]);
142
+ const [categories, setCategories] = useState([]);
143
+ const [category, setCategory] = useState(undefined);
144
+ const [query, setQuery] = useState('');
145
+
146
+ useEffect(() => { store.categories.list().then(setCategories); }, []);
147
+
148
+ useEffect(() => {
149
+ if (query.trim()) {
150
+ store.products.search(query).then(d => setProducts(d.products ?? d));
151
+ } else {
152
+ store.products.list({ type: category, limit: 12 }).then(d => setProducts(d.products ?? d));
153
+ }
154
+ }, [query, category]);
155
+
156
+ const addToCart = (productId) => store.cart.add(productId, 1);
157
+
158
+ return (
159
+ <div>
160
+ <input
161
+ placeholder="Search products..."
162
+ value={query}
163
+ onChange={e => setQuery(e.target.value)}
164
+ />
165
+ <div>
166
+ {categories.map(c => (
167
+ <button key={c} onClick={() => { setQuery(''); setCategory(c === 'All' ? undefined : c); }}>{c}</button>
168
+ ))}
169
+ </div>
170
+ <div className="product-grid">
171
+ {products.map(p => <ProductCard key={p._id} product={p} onAddToCart={addToCart} />)}
172
+ </div>
173
+ </div>
174
+ );
175
+ }
176
+ ```
177
+
178
+ - [ ] **Step 5b: Wire up an existing search bar (only if `ProductList.jsx` was skipped)**
179
+
180
+ If you wrote a fresh `ProductList.jsx` in Step 5, skip this step — the canonical scaffold already has search wired.
181
+
182
+ If `ProductList.jsx` was SKIPPED because it already existed, the merchant has their own UI. They may have a search bar somewhere — in a `Navbar`, a `Header`, a dedicated `SearchBar.jsx`, a search page, etc. — that isn't currently calling EpicMerch's search API. Detect and offer to wire it up.
183
+
184
+ **Detection.** Look through `src/` for files containing any of these signals (case-insensitive):
185
+ - `<input` whose `placeholder` mentions `search`
186
+ - A state variable named `query`, `search`, `searchTerm`, `searchQuery`, or `keyword`
187
+ - A handler named `onSearch`, `handleSearch`, `onQueryChange`, or similar
188
+
189
+ For each match, check whether the file already calls `store.products.search(`. If yes, it's wired — skip silently.
190
+
191
+ **For each unwired match:** show the merchant the file path and the snippet you found, then say:
192
+
193
+ > "I noticed a search input in `<path>` that doesn't call EpicMerch's search yet. The one-line wire-up is:
194
+ >
195
+ > ```js
196
+ > import { store } from '../lib/epicmerch'; // add at top if missing
197
+ >
198
+ > // inside the search handler / useEffect:
199
+ > store.products.search(query).then(d => setProducts(d.products ?? d));
200
+ > ```
201
+ >
202
+ > Want me to apply this to your component? (yes / no — I'll leave it alone if no.)"
203
+
204
+ **WAIT for an explicit yes before editing.** If the merchant declines, move on without changing the file.
205
+
206
+ If you find no search bar anywhere in `src/`, skip this step silently — don't tell the merchant "no search bar found", just continue to Step 6.
207
+
208
+ - [ ] **Step 6: Scaffold cart + orders**
209
+
210
+ For each of the three files in this step (Cart.jsx, Checkout.jsx, OrderHistory.jsx), check existence individually. If a file already exists, skip writing it and tell the user "X already exists — keeping it". Otherwise write it.
211
+
212
+ Write `src/components/Cart.jsx`:
213
+
214
+ ```jsx
215
+ import { useEffect, useState } from 'react';
216
+ import { store } from '../lib/epicmerch';
217
+
218
+ export default function Cart({ onCheckout }) {
219
+ const [cart, setCart] = useState({ items: [] });
220
+
221
+ useEffect(() => { store.cart.get().then(setCart); }, []);
222
+
223
+ const remove = async (productId) => {
224
+ await store.cart.remove(productId);
225
+ store.cart.get().then(setCart);
226
+ };
227
+
228
+ const total = cart.items?.reduce((s, i) => s + i.price * i.qty, 0) ?? 0;
229
+
230
+ return (
231
+ <div>
232
+ {cart.items?.map(item => (
233
+ <div key={item.productId}>
234
+ <span>{item.name} x{item.qty}</span>
235
+ <span>₹{item.price * item.qty}</span>
236
+ <button onClick={() => remove(item.productId)}>Remove</button>
237
+ </div>
238
+ ))}
239
+ <p>Total: ₹{total}</p>
240
+ <button onClick={onCheckout}>Checkout</button>
241
+ </div>
242
+ );
243
+ }
244
+ ```
245
+
246
+ Write `src/components/Checkout.jsx`:
247
+
248
+ ```jsx
249
+ import { useState } from 'react';
250
+ import { store } from '../lib/epicmerch';
251
+
252
+ const loadRazorpay = () =>
253
+ new Promise((resolve) => {
254
+ if (window.Razorpay) return resolve(true);
255
+ const script = document.createElement('script');
256
+ script.src = 'https://checkout.razorpay.com/v1/checkout.js';
257
+ script.onload = () => resolve(true);
258
+ script.onerror = () => resolve(false);
259
+ document.body.appendChild(script);
260
+ });
261
+
262
+ export default function Checkout({ cart, onSuccess }) {
263
+ const [address, setAddress] = useState({ street: '', city: '', postalCode: '', country: 'India' });
264
+
265
+ const placeOrder = async () => {
266
+ await loadRazorpay();
267
+ const total = cart.items.reduce((s, i) => s + i.price * i.qty, 0);
268
+ const { orderId } = await store.orders.create({
269
+ orderItems: cart.items.map(i => ({ productId: i.productId, qty: i.qty, price: i.price })),
270
+ shippingAddress: address,
271
+ paymentMethod: 'Razorpay',
272
+ totalPrice: total,
273
+ });
274
+ const config = await store.payment.getConfig();
275
+ const { razorpayOrderId } = await store.payment.createOrder(total * 100, orderId);
276
+ const rzp = new window.Razorpay({
277
+ key: config.razorpayKeyId,
278
+ order_id: razorpayOrderId,
279
+ amount: total * 100,
280
+ handler: async (response) => {
281
+ await store.payment.verify({ ...response, orderId });
282
+ await store.cart.clear();
283
+ onSuccess(orderId);
284
+ },
285
+ });
286
+ rzp.open();
287
+ };
288
+
289
+ return (
290
+ <div>
291
+ <input placeholder="Street" onChange={e => setAddress(a => ({ ...a, street: e.target.value }))} />
292
+ <input placeholder="City" onChange={e => setAddress(a => ({ ...a, city: e.target.value }))} />
293
+ <input placeholder="Postal Code" onChange={e => setAddress(a => ({ ...a, postalCode: e.target.value }))} />
294
+ <button onClick={placeOrder}>Place Order</button>
295
+ </div>
296
+ );
297
+ }
298
+ ```
299
+
300
+ Do NOT add any Razorpay script tag to `index.html` — the script loads dynamically only when the user clicks Place Order.
301
+
302
+ Write `src/components/OrderHistory.jsx`:
303
+
304
+ ```jsx
305
+ import { useEffect, useState } from 'react';
306
+ import { store } from '../lib/epicmerch';
307
+
308
+ export default function OrderHistory() {
309
+ const [orders, setOrders] = useState([]);
310
+
311
+ useEffect(() => { store.orders.list().then(setOrders); }, []);
312
+
313
+ return (
314
+ <div>
315
+ <h2>Your Orders</h2>
316
+ {orders.map(order => (
317
+ <div key={order._id}>
318
+ <span>Order #{order._id.slice(-6)}</span>
319
+ <span> — ₹{order.totalPrice}</span>
320
+ <span> — {order.status}</span>
321
+ </div>
322
+ ))}
323
+ </div>
324
+ );
325
+ }
326
+ ```
327
+
328
+ - [ ] **Step 7: Generate API key via MCP + write `.env`**
329
+
330
+ This step has two parts: write `.env.example` (the template), then generate a real API key and write a working `.env`.
331
+
332
+ **Part A — `.env.example`:** If `.env.example` already exists, skip writing it and tell the user ".env.example already exists — keeping it". Otherwise write `.env.example`:
333
+
334
+ ```
335
+ VITE_API_KEY=your_epicmerch_api_key_here
336
+ # VITE_API_URL is OPTIONAL — only set it for local development.
337
+ # Production: leave it unset; the SDK defaults to https://api.epicmerch.in/api
338
+ # Local dev: VITE_API_URL=http://localhost:5002/api
339
+ ```
340
+
341
+ Check `.gitignore` — if `.env` is not listed, append `.env` to `.gitignore` before any `.env` file is written.
342
+
343
+ **Part B — generate a real API key + write `.env`:**
344
+
345
+ First, read `.env` if it exists. If it already contains a `VITE_API_KEY=` line whose value is NOT the placeholder string `your_epicmerch_api_key_here` (i.e. there's already a real key), skip Part B entirely and tell the user "Existing API key in .env — keeping it".
346
+
347
+ Otherwise, the merchant is authenticated via the EpicMerch MCP (this skill assumes the MCP is connected — that's how it was invoked). Generate a key by calling the MCP tool:
348
+
349
+ ```
350
+ merchant_generate_api_key({ name: "Storefront" })
351
+ ```
352
+
353
+ The response will include the new key. Extract it (the field is typically `key` or `apiKey` — check the response shape). Then write/update `.env`.
354
+
355
+ **Production (default).** Write only the key — the SDK already knows the prod URL:
356
+
357
+ ```
358
+ VITE_API_KEY=<the generated key from the MCP response>
359
+ ```
360
+
361
+ **Local development.** Only if the merchant is clearly running against a local EpicMerch server (e.g. they explicitly said so, or their existing `.env` already had a `localhost` URL, or the MCP was wired with `EPICMERCH_API_URL=http://localhost:...`), ALSO write the override:
362
+
363
+ ```
364
+ VITE_API_KEY=<the generated key from the MCP response>
365
+ VITE_API_URL=http://localhost:5002/api
366
+ ```
367
+
368
+ When in doubt, default to the production form (no `VITE_API_URL` line). Don't ask the merchant — guess from context.
369
+
370
+ **Fallback if MCP isn't available:** If the `merchant_generate_api_key` tool fails or isn't connected, tell the user: "MCP tool unavailable. Generate a key manually at https://epicmerch.in/dashboard → API Keys and add it to .env." Do NOT fail the whole flow — continue to Step 8.
371
+
372
+ - [ ] **Step 8: Tell the user what to do next**
373
+
374
+ Print a summary using the actual state of the project:
375
+
376
+ - List the files you CREATED in this run (skip the ones that were already present).
377
+ - If you skipped any, briefly mention them as "kept as-is".
378
+ - If Step 5b wired up a search bar in an existing component, mention which file you edited and that it now calls `store.products.search()`.
379
+ - If the API key was generated automatically, mention that ".env now has a working VITE_API_KEY — your storefront should load real product data on next refresh."
380
+ - Then output the next-steps block exactly:
381
+
382
+ ```
383
+ Next steps:
384
+ 1. (API key auto-generated and saved to .env — if not, get one at https://epicmerch.in/dashboard → API Keys)
385
+ 2. Restart your dev server (npm run dev) to pick up the .env changes
386
+ 3. Import the new components into your App.jsx where you need them
387
+
388
+ Need help with payments, analytics, or notifications? Ask me!
389
+ ```
package/smithery.yaml ADDED
@@ -0,0 +1,6 @@
1
+ startCommand:
2
+ type: stdio
3
+ configSchema:
4
+ type: object
5
+ properties: {}
6
+ exampleConfig: {}
@@ -0,0 +1,76 @@
1
+ // src/adapters/mcp.js
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { z } from 'zod';
5
+ import { readFileSync } from 'fs';
6
+ import { fileURLToPath } from 'url';
7
+ import { dirname, join } from 'path';
8
+
9
+ import { sessionTools, sessionToolDefs } from '../tools/session.js';
10
+ import { developerTools, developerToolDefs } from '../tools/developer.js';
11
+ import { scaffoldTools, scaffoldToolDefs } from '../tools/scaffold.js';
12
+ import { merchantTools, merchantToolDefs } from '../tools/merchant.js';
13
+ import { prompts } from '../prompts/index.js';
14
+
15
+ const __dir = dirname(fileURLToPath(import.meta.url));
16
+ const readResource = (rel) => readFileSync(join(__dir, '../resources', rel), 'utf-8');
17
+
18
+ function buildZodSchema(schemaDef) {
19
+ const shape = {};
20
+ for (const [key, def] of Object.entries(schemaDef)) {
21
+ if (def.type === 'string') shape[key] = z.string().optional();
22
+ else if (def.type === 'number') shape[key] = z.number().optional();
23
+ else if (def.type === 'boolean') shape[key] = z.boolean().optional();
24
+ else shape[key] = z.any().optional();
25
+ }
26
+ return shape;
27
+ }
28
+
29
+ export async function startMcpAdapter(session, client) {
30
+ const server = new McpServer({ name: 'epicmerch', version: '1.0.0' });
31
+
32
+ const allToolDefs = [...sessionToolDefs, ...developerToolDefs, ...scaffoldToolDefs, ...merchantToolDefs];
33
+ const allHandlers = {
34
+ ...sessionTools(session),
35
+ ...developerTools(client),
36
+ ...scaffoldTools(),
37
+ ...merchantTools(client, session),
38
+ };
39
+
40
+ // Register all tools
41
+ for (const def of allToolDefs) {
42
+ const zodShape = buildZodSchema(def.schema || {});
43
+ server.tool(def.name, def.description, zodShape, async (args) => {
44
+ return allHandlers[def.name](args);
45
+ });
46
+ }
47
+
48
+ // Register resources
49
+ const resources = [
50
+ { name: 'EpicMerch Overview', uri: 'epicmerch://overview', file: 'overview.md' },
51
+ { name: 'EpicMerch SDK Guide', uri: 'epicmerch://sdk-guide', file: 'sdk-guide.md' },
52
+ { name: 'Auth Examples', uri: 'epicmerch://examples/auth', file: 'examples/auth.md' },
53
+ { name: 'Products Examples', uri: 'epicmerch://examples/products', file: 'examples/products.md' },
54
+ { name: 'Orders Examples', uri: 'epicmerch://examples/orders', file: 'examples/orders.md' },
55
+ { name: 'Payments Examples', uri: 'epicmerch://examples/payments', file: 'examples/payments.md' },
56
+ ];
57
+
58
+ for (const r of resources) {
59
+ server.resource(r.name, r.uri, async () => ({
60
+ contents: [{ uri: r.uri, text: readResource(r.file), mimeType: 'text/markdown' }],
61
+ }));
62
+ }
63
+
64
+ // Register prompts
65
+ for (const p of prompts) {
66
+ const argShape = {};
67
+ for (const arg of (p.arguments || [])) {
68
+ argShape[arg.name] = z.string().optional().describe(arg.description);
69
+ }
70
+ server.prompt(p.name, p.description, argShape, p.handler);
71
+ }
72
+
73
+ const transport = new StdioServerTransport();
74
+ await server.connect(transport);
75
+ console.error('[EpicMerch MCP] Server started (stdio)');
76
+ }