@bintangtimurlangit/shopee-mcp 0.1.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.
Files changed (48) hide show
  1. package/.env.example +25 -0
  2. package/CHANGELOG.md +22 -0
  3. package/CODE_OF_CONDUCT.md +12 -0
  4. package/CONTRIBUTING.md +48 -0
  5. package/LICENSE +21 -0
  6. package/README.md +134 -0
  7. package/SECURITY.md +29 -0
  8. package/build/api/client.d.ts +27 -0
  9. package/build/api/client.d.ts.map +1 -0
  10. package/build/api/client.js +57 -0
  11. package/build/api/client.js.map +1 -0
  12. package/build/api/types.d.ts +92 -0
  13. package/build/api/types.d.ts.map +1 -0
  14. package/build/api/types.js +4 -0
  15. package/build/api/types.js.map +1 -0
  16. package/build/browser/session.d.ts +30 -0
  17. package/build/browser/session.d.ts.map +1 -0
  18. package/build/browser/session.js +109 -0
  19. package/build/browser/session.js.map +1 -0
  20. package/build/index.d.ts +3 -0
  21. package/build/index.d.ts.map +1 -0
  22. package/build/index.js +33 -0
  23. package/build/index.js.map +1 -0
  24. package/build/login.d.ts +12 -0
  25. package/build/login.d.ts.map +1 -0
  26. package/build/login.js +53 -0
  27. package/build/login.js.map +1 -0
  28. package/build/tools/product.d.ts +3 -0
  29. package/build/tools/product.d.ts.map +1 -0
  30. package/build/tools/product.js +129 -0
  31. package/build/tools/product.js.map +1 -0
  32. package/build/tools/search.d.ts +3 -0
  33. package/build/tools/search.d.ts.map +1 -0
  34. package/build/tools/search.js +98 -0
  35. package/build/tools/search.js.map +1 -0
  36. package/build/utils/cache.d.ts +13 -0
  37. package/build/utils/cache.d.ts.map +1 -0
  38. package/build/utils/cache.js +33 -0
  39. package/build/utils/cache.js.map +1 -0
  40. package/build/utils/errors.d.ts +18 -0
  41. package/build/utils/errors.d.ts.map +1 -0
  42. package/build/utils/errors.js +44 -0
  43. package/build/utils/errors.js.map +1 -0
  44. package/docs/CONFIGURATION.md +73 -0
  45. package/docs/DEVELOPMENT.md +52 -0
  46. package/docs/README.md +14 -0
  47. package/docs/RELEASES.md +46 -0
  48. package/package.json +86 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/browser/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC"}
package/build/index.js ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import { registerSearchTools } from './tools/search.js';
6
+ import { registerProductTools } from './tools/product.js';
7
+ import { closeContext } from './browser/session.js';
8
+ async function main() {
9
+ const server = new McpServer({
10
+ name: 'shopee-mcp',
11
+ version: '0.1.0',
12
+ });
13
+ // Register tool groups. Every tool runs through the shared, logged-in browser
14
+ // session (see src/browser/session.ts) — sign in once with `npm run login`.
15
+ registerSearchTools(server);
16
+ registerProductTools(server);
17
+ const transport = new StdioServerTransport();
18
+ await server.connect(transport);
19
+ if (process.env.DEBUG === 'true') {
20
+ process.stderr.write('[shopee-mcp] Server started via stdio (browser-backed discovery)\n');
21
+ }
22
+ }
23
+ // Tidy up the browser on shutdown.
24
+ for (const sig of ['SIGINT', 'SIGTERM']) {
25
+ process.on(sig, () => {
26
+ void closeContext().finally(() => process.exit(0));
27
+ });
28
+ }
29
+ main().catch((err) => {
30
+ process.stderr.write(`[shopee-mcp] Fatal error: ${err}\n`);
31
+ process.exit(1);
32
+ });
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,8EAA8E;IAC9E,4EAA4E;IAC5E,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAE7B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;QACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oEAAoE,CAAC,CAAC;IAC7F,CAAC;AACH,CAAC;AAED,mCAAmC;AACnC,KAAK,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAU,EAAE,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE;QACnB,KAAK,YAAY,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,6BAA6B,GAAG,IAAI,CAAC,CAAC;IAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * One-time Shopee login.
4
+ *
5
+ * Opens a visible Chromium window bound to the persistent profile, lets you sign
6
+ * in by hand (password, OTP, whatever Shopee asks), then saves the session so the
7
+ * MCP server can reuse it. Run once; re-run only if the session expires.
8
+ *
9
+ * npm run login (dev) or shopee-mcp-login (installed)
10
+ */
11
+ import 'dotenv/config';
12
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AACH,OAAO,eAAe,CAAC"}
package/build/login.js ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * One-time Shopee login.
4
+ *
5
+ * Opens a visible Chromium window bound to the persistent profile, lets you sign
6
+ * in by hand (password, OTP, whatever Shopee asks), then saves the session so the
7
+ * MCP server can reuse it. Run once; re-run only if the session expires.
8
+ *
9
+ * npm run login (dev) or shopee-mcp-login (installed)
10
+ */
11
+ import 'dotenv/config';
12
+ import readline from 'node:readline';
13
+ import { getContext, isLoggedIn, closeContext, BASE_URL, PROFILE_DIR } from './browser/session.js';
14
+ function prompt(question) {
15
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
16
+ return new Promise((resolve) => rl.question(question, () => {
17
+ rl.close();
18
+ resolve();
19
+ }));
20
+ }
21
+ async function main() {
22
+ console.log('\nšŸ›’ Shopee MCP — login\n');
23
+ console.log(`Profile directory: ${PROFILE_DIR}`);
24
+ console.log('Opening a Chromium window…\n');
25
+ // Force a visible window regardless of SHOPEE_HEADLESS.
26
+ const ctx = await getContext(false);
27
+ const page = ctx.pages().find((p) => !p.isClosed()) ?? (await ctx.newPage());
28
+ await page.goto(`${BASE_URL}/buyer/login`, { waitUntil: 'domcontentloaded', timeout: 60000 });
29
+ if (await isLoggedIn()) {
30
+ console.log('āœ… This profile is already logged in. Nothing to do.\n');
31
+ await closeContext();
32
+ return;
33
+ }
34
+ console.log('šŸ‘‰ In the Chromium window: log into your Shopee account.');
35
+ console.log(' Complete any OTP / captcha Shopee shows.');
36
+ await prompt('\nWhen you see your account is logged in, press Enter here to save… ');
37
+ const ok = await isLoggedIn();
38
+ if (ok) {
39
+ console.log('\nāœ… Login detected and saved. You can close this and start the MCP server.\n');
40
+ }
41
+ else {
42
+ console.log('\nāš ļø Could not detect a logged-in session (no SPC_U/SPC_EC cookie).\n' +
43
+ ' If you did log in, the session is still saved to the profile — try the server anyway.\n' +
44
+ ' Otherwise re-run `npm run login`.\n');
45
+ }
46
+ await closeContext();
47
+ }
48
+ main().catch(async (err) => {
49
+ console.error('Login failed:', err);
50
+ await closeContext().catch(() => { });
51
+ process.exit(1);
52
+ });
53
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AACH,OAAO,eAAe,CAAC;AACvB,OAAO,QAAQ,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnG,SAAS,MAAM,CAAC,QAAgB;IAC9B,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACtF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAC7B,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;QACzB,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CACH,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;IACzC,OAAO,CAAC,GAAG,CAAC,sBAAsB,WAAW,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;IAE5C,wDAAwD;IACxD,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAE7E,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,QAAQ,cAAc,EAAE,EAAE,SAAS,EAAE,kBAAkB,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAE9F,IAAI,MAAM,UAAU,EAAE,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;QACrE,MAAM,YAAY,EAAE,CAAC;QACrB,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,0DAA0D,CAAC,CAAC;IACxE,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;IAC3D,MAAM,MAAM,CAAC,sEAAsE,CAAC,CAAC;IAErF,MAAM,EAAE,GAAG,MAAM,UAAU,EAAE,CAAC;IAC9B,IAAI,EAAE,EAAE,CAAC;QACP,OAAO,CAAC,GAAG,CAAC,8EAA8E,CAAC,CAAC;IAC9F,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CACT,wEAAwE;YACtE,4FAA4F;YAC5F,wCAAwC,CAC3C,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,EAAE,CAAC;AACvB,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IACzB,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;IACpC,MAAM,YAAY,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerProductTools(server: McpServer): void;
3
+ //# sourceMappingURL=product.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product.d.ts","sourceRoot":"","sources":["../../src/tools/product.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAgCpE,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAoH5D"}
@@ -0,0 +1,129 @@
1
+ import { z } from 'zod';
2
+ import { shopeeCapture, shopeeUrl } from '../api/client.js';
3
+ import { BASE_URL } from '../browser/session.js';
4
+ import { cache } from '../utils/cache.js';
5
+ import { withErrorHandling, truncate } from '../utils/errors.js';
6
+ // Shopee stores prices as the real amount Ɨ 100000.
7
+ function fmt(raw, currency = 'IDR') {
8
+ const amount = raw / 100000;
9
+ if (currency === 'IDR')
10
+ return `Rp${Math.round(amount).toLocaleString('id-ID')}`;
11
+ return `${currency} ${amount.toLocaleString('id-ID')}`;
12
+ }
13
+ function priceText(p, currency) {
14
+ if (p.range_min >= 0 && p.range_max >= 0 && p.range_min !== p.range_max) {
15
+ return `${fmt(p.range_min, currency)} – ${fmt(p.range_max, currency)}`;
16
+ }
17
+ return fmt(p.single_value, currency);
18
+ }
19
+ /** Parse "shopId/itemId" out of a Shopee product URL, if present. */
20
+ function parseProductUrl(url) {
21
+ // /product/<shopid>/<itemid> OR ...-i.<shopid>.<itemid>
22
+ const m1 = url.match(/\/product\/(\d+)\/(\d+)/);
23
+ if (m1)
24
+ return { shopId: m1[1], itemId: m1[2] };
25
+ const m2 = url.match(/-i\.(\d+)\.(\d+)/);
26
+ if (m2)
27
+ return { shopId: m2[1], itemId: m2[2] };
28
+ return null;
29
+ }
30
+ export function registerProductTools(server) {
31
+ server.tool('get_product_detail', 'Get details for a Shopee product: title, price (and discount), brand, condition, category, rating, stock, seller location, and description. ' +
32
+ 'Provide the numeric shopId + itemId (from search_products), or a full product URL.', {
33
+ shopId: z
34
+ .string()
35
+ .optional()
36
+ .describe('Numeric shop ID (from search_products URL / results)'),
37
+ itemId: z.string().optional().describe('Numeric item/product ID (the šŸ†” in search_products)'),
38
+ url: z
39
+ .string()
40
+ .url()
41
+ .optional()
42
+ .describe('Full product URL, e.g. https://shopee.co.id/product/78730497/47060432055'),
43
+ }, async ({ shopId, itemId, url }) => {
44
+ return withErrorHandling(async () => {
45
+ let sid = shopId;
46
+ let iid = itemId;
47
+ if ((!sid || !iid) && url) {
48
+ const parsed = parseProductUrl(url);
49
+ if (parsed) {
50
+ sid = parsed.shopId;
51
+ iid = parsed.itemId;
52
+ }
53
+ }
54
+ if (!sid || !iid) {
55
+ return {
56
+ content: [
57
+ {
58
+ type: 'text',
59
+ text: 'āŒ Please provide both `shopId` and `itemId`, or a full product `url`.',
60
+ },
61
+ ],
62
+ };
63
+ }
64
+ const cacheKey = cache.key('product', sid, iid);
65
+ const cached = cache.get(cacheKey);
66
+ if (cached)
67
+ return { content: [{ type: 'text', text: cached }] };
68
+ const pageUrl = shopeeUrl(`/product/${sid}/${iid}`);
69
+ const data = await shopeeCapture(pageUrl, 'pdp/get_pc');
70
+ const item = data.data?.item;
71
+ const pp = data.data?.product_price;
72
+ if (!item || !pp) {
73
+ return {
74
+ content: [
75
+ {
76
+ type: 'text',
77
+ text: 'āŒ Could not read product data. Check the shopId/itemId or URL.',
78
+ },
79
+ ],
80
+ };
81
+ }
82
+ const currency = item.currency || 'IDR';
83
+ const price = priceText(pp.price, currency);
84
+ const before = pp.price_before_discount && pp.price_before_discount.single_value > pp.price.single_value
85
+ ? pp.price_before_discount
86
+ : undefined;
87
+ const discountPct = before
88
+ ? Math.round((1 - pp.price.single_value / before.single_value) * 100)
89
+ : 0;
90
+ const review = data.data?.product_review;
91
+ const conditionLabel = item.condition === 1 ? 'New' : item.condition ? 'Used' : 'Unknown';
92
+ const rating = item.item_rating?.rating_star;
93
+ const ratingCount = review?.total_rating_count ?? review?.cmt_count ?? 0;
94
+ const soldText = review?.sold_count_display ??
95
+ review?.historical_sold_display ??
96
+ review?.global_sold_display;
97
+ const breadcrumb = (item.categories ?? []).map((c) => c.display_name).join(' › ');
98
+ const stock = item.stock ?? item.normal_stock ?? undefined;
99
+ const lines = [
100
+ `šŸ“¦ **${item.title}**`,
101
+ '',
102
+ `šŸ’° **Price:** ${price}${before ? ` ~~${priceText(before, currency)}~~ (-${discountPct}%)` : ''}`,
103
+ '',
104
+ `šŸ“Š **Stats:**`,
105
+ ` ⭐ Rating: ${rating ? rating.toFixed(2) : 'N/A'}${ratingCount ? ` (${ratingCount.toLocaleString('id-ID')} reviews)` : ''}`,
106
+ soldText ? ` āœ… Sold: ${soldText}` : '',
107
+ '',
108
+ `šŸ“‹ **Details:**`,
109
+ item.brand ? ` šŸ· Brand: ${item.brand}` : '',
110
+ ` šŸ†• Condition: ${conditionLabel}`,
111
+ breadcrumb ? ` šŸ—‚ Category: ${breadcrumb}` : '',
112
+ stock !== undefined && stock !== null
113
+ ? ` šŸ“¦ Stock: ${stock.toLocaleString('id-ID')}`
114
+ : '',
115
+ item.is_free_shipping ? ` 🚚 Free shipping` : '',
116
+ ` šŸ“ Location: ${item.shop_location || 'N/A'}`,
117
+ ` šŸ†” Item ID: \`${item.item_id}\` | Shop ID: \`${item.shop_id}\``,
118
+ ].filter((l) => l !== '');
119
+ if (item.description) {
120
+ lines.push('', 'šŸ“ **Description:**', truncate(item.description.replace(/\n+/g, ' ').trim(), 400));
121
+ }
122
+ lines.push('', `šŸ”— ${BASE_URL}/product/${item.shop_id}/${item.item_id}`);
123
+ const text = lines.join('\n');
124
+ cache.set(cacheKey, text);
125
+ return { content: [{ type: 'text', text }] };
126
+ });
127
+ });
128
+ }
129
+ //# sourceMappingURL=product.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product.js","sourceRoot":"","sources":["../../src/tools/product.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerSearchTools(server: McpServer): void;
3
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA+BpE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAmF3D"}
@@ -0,0 +1,98 @@
1
+ import { z } from 'zod';
2
+ import { shopeeCapture, shopeeUrl } from '../api/client.js';
3
+ import { BASE_URL } from '../browser/session.js';
4
+ import { cache } from '../utils/cache.js';
5
+ import { withErrorHandling } from '../utils/errors.js';
6
+ // Shopee stores prices as the real amount Ɨ 100000.
7
+ function formatPrice(raw, currency = 'IDR') {
8
+ const amount = raw / 100000;
9
+ if (currency === 'IDR')
10
+ return `Rp${Math.round(amount).toLocaleString('id-ID')}`;
11
+ return `${currency} ${amount.toLocaleString('id-ID')}`;
12
+ }
13
+ function priceText(b) {
14
+ if (b.price_min && b.price_max && b.price_min !== b.price_max) {
15
+ return `${formatPrice(b.price_min, b.currency)} – ${formatPrice(b.price_max, b.currency)}`;
16
+ }
17
+ return formatPrice(b.price, b.currency);
18
+ }
19
+ // Sort option → Shopee search-URL params.
20
+ const SORT_MAP = {
21
+ relevance: { sortBy: 'relevancy' },
22
+ newest: { sortBy: 'ctime' },
23
+ top_sales: { sortBy: 'sales' },
24
+ price_low: { sortBy: 'price', order: 'asc' },
25
+ price_high: { sortBy: 'price', order: 'desc' },
26
+ };
27
+ export function registerSearchTools(server) {
28
+ server.tool('search_products', 'Search for products on Shopee by keyword, with sorting and pagination. ' +
29
+ 'Returns product names, prices, sold counts, ratings, seller location, product IDs, and direct URLs. ' +
30
+ 'Requires a one-time login (run `npm run login`) because Shopee blocks anonymous requests.', {
31
+ query: z.string().min(1).describe('The search query, e.g. "laptop gaming", "sepatu nike"'),
32
+ page: z.number().int().min(1).default(1).describe('Page number (default: 1)'),
33
+ limit: z
34
+ .number()
35
+ .int()
36
+ .min(1)
37
+ .max(60)
38
+ .default(20)
39
+ .describe('Max results to show from the page, 1-60 (default: 20)'),
40
+ sort: z
41
+ .enum(['relevance', 'newest', 'top_sales', 'price_low', 'price_high'])
42
+ .default('relevance')
43
+ .describe('Sort order (default: relevance)'),
44
+ }, async ({ query, page, limit, sort }) => {
45
+ return withErrorHandling(async () => {
46
+ const cacheKey = cache.key('search', query, page, limit, sort);
47
+ const cached = cache.get(cacheKey);
48
+ if (cached)
49
+ return { content: [{ type: 'text', text: cached }] };
50
+ const { sortBy, order } = SORT_MAP[sort] ?? SORT_MAP.relevance;
51
+ const qs = new URLSearchParams({ keyword: query, page: String(page - 1), sortBy });
52
+ if (order)
53
+ qs.set('order', order);
54
+ const searchUrl = shopeeUrl(`/search?${qs.toString()}`);
55
+ const data = await shopeeCapture(searchUrl, 'search/search_items');
56
+ const items = data.items ?? [];
57
+ if (items.length === 0) {
58
+ return {
59
+ content: [
60
+ { type: 'text', text: `No products found for "${query}". Try a different keyword.` },
61
+ ],
62
+ };
63
+ }
64
+ const shown = items.slice(0, limit);
65
+ const totalCount = data.total_count ?? 0;
66
+ const totalPages = totalCount > 0 ? Math.ceil(totalCount / items.length) : page;
67
+ const lines = [
68
+ `šŸ›’ Search Results for "${query}"`,
69
+ `šŸ“Š ${totalCount.toLocaleString('id-ID')} total products | Page ${page}${totalPages > 1 ? `/${totalPages}` : ''}`,
70
+ ``,
71
+ ];
72
+ shown.forEach((it, i) => {
73
+ const b = it.item_basic;
74
+ const rank = (page - 1) * limit + i + 1;
75
+ const rating = b.item_rating?.rating_star
76
+ ? `⭐ ${b.item_rating.rating_star.toFixed(1)}`
77
+ : '⭐ N/A';
78
+ const sold = b.historical_sold || b.sold || 0;
79
+ const soldText = sold > 0 ? ` | šŸ“¦ ${sold.toLocaleString('id-ID')} sold` : '';
80
+ const official = b.is_official_shop ? ' [Shopee Mall]' : '';
81
+ const url = `${BASE_URL}/product/${b.shopid}/${b.itemid}`;
82
+ lines.push(`${rank}. **${b.name}**`);
83
+ lines.push(` šŸ’° ${priceText(b)}`);
84
+ lines.push(` ${rating}${soldText} | šŸŖ ${b.shop_location || 'N/A'}${official} | šŸ†” ${b.itemid}`);
85
+ lines.push(` šŸ”— ${url}`);
86
+ if (i < shown.length - 1)
87
+ lines.push('');
88
+ });
89
+ if (!data.nomore) {
90
+ lines.push(``, `šŸ“„ Use page=${page + 1} to see more results.`);
91
+ }
92
+ const text = lines.join('\n');
93
+ cache.set(cacheKey, text);
94
+ return { content: [{ type: 'text', text }] };
95
+ });
96
+ });
97
+ }
98
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ declare class Cache {
2
+ private store;
3
+ private ttl;
4
+ constructor(ttlMs?: number);
5
+ get<T>(key: string): T | undefined;
6
+ set<T>(key: string, data: T): void;
7
+ key(...parts: (string | number | boolean | undefined)[]): string;
8
+ clear(): void;
9
+ size(): number;
10
+ }
11
+ export declare const cache: Cache;
12
+ export {};
13
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/utils/cache.ts"],"names":[],"mappings":"AAQA,cAAM,KAAK;IACT,OAAO,CAAC,KAAK,CAA0C;IACvD,OAAO,CAAC,GAAG,CAAS;gBAER,KAAK,CAAC,EAAE,MAAM;IAI1B,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAUlC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI;IAIlC,GAAG,CAAC,GAAG,KAAK,EAAE,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,EAAE,GAAG,MAAM;IAIhE,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,MAAM;CAGf;AAED,eAAO,MAAM,KAAK,OAAc,CAAC"}
@@ -0,0 +1,33 @@
1
+ // Lightweight in-memory TTL cache.
2
+ // Prevents re-issuing identical Shopee queries in a short window.
3
+ class Cache {
4
+ store = new Map();
5
+ ttl;
6
+ constructor(ttlMs) {
7
+ this.ttl = ttlMs ?? parseInt(process.env.CACHE_TTL_MS ?? '30000', 10);
8
+ }
9
+ get(key) {
10
+ const entry = this.store.get(key);
11
+ if (!entry)
12
+ return undefined;
13
+ if (Date.now() > entry.expiresAt) {
14
+ this.store.delete(key);
15
+ return undefined;
16
+ }
17
+ return entry.data;
18
+ }
19
+ set(key, data) {
20
+ this.store.set(key, { data, expiresAt: Date.now() + this.ttl });
21
+ }
22
+ key(...parts) {
23
+ return parts.map(String).join(':');
24
+ }
25
+ clear() {
26
+ this.store.clear();
27
+ }
28
+ size() {
29
+ return this.store.size;
30
+ }
31
+ }
32
+ export const cache = new Cache();
33
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/utils/cache.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,kEAAkE;AAOlE,MAAM,KAAK;IACD,KAAK,GAAG,IAAI,GAAG,EAA+B,CAAC;IAC/C,GAAG,CAAS;IAEpB,YAAY,KAAc;QACxB,IAAI,CAAC,GAAG,GAAG,KAAK,IAAI,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,OAAO,EAAE,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,GAAG,CAAI,GAAW;QAChB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAA8B,CAAC;QAC/D,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAC7B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC;IACpB,CAAC;IAED,GAAG,CAAI,GAAW,EAAE,IAAO;QACzB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,GAAG,CAAC,GAAG,KAAgD;QACrD,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;CACF;AAED,MAAM,CAAC,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Wraps a tool handler to return a clean MCP error content block
3
+ * instead of throwing and crashing the server.
4
+ */
5
+ export declare function withErrorHandling(fn: () => Promise<{
6
+ content: Array<{
7
+ type: 'text';
8
+ text: string;
9
+ }>;
10
+ }>): Promise<{
11
+ content: Array<{
12
+ type: 'text';
13
+ text: string;
14
+ }>;
15
+ }>;
16
+ /** Truncate text to max chars. */
17
+ export declare function truncate(text: string, maxLen?: number): string;
18
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,MAAM,OAAO,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,GACpE,OAAO,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,CA4B7D;AAED,kCAAkC;AAClC,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAM,GAAG,MAAM,CAG3D"}
@@ -0,0 +1,44 @@
1
+ import { ShopeeAPIError, ShopeeAuthRequiredError } from '../api/client.js';
2
+ /**
3
+ * Wraps a tool handler to return a clean MCP error content block
4
+ * instead of throwing and crashing the server.
5
+ */
6
+ export async function withErrorHandling(fn) {
7
+ try {
8
+ return await fn();
9
+ }
10
+ catch (err) {
11
+ let message;
12
+ if (err instanceof ShopeeAuthRequiredError) {
13
+ message =
14
+ 'šŸ”’ Not signed in to Shopee.\n\n' +
15
+ 'Shopee blocks anonymous product requests. Run this once in the project folder:\n' +
16
+ '`npm run login`\n\n' +
17
+ 'A Chromium window opens — log into your Shopee account, then come back and press Enter. ' +
18
+ 'The session is saved and reused; retry your request afterwards.';
19
+ }
20
+ else if (err instanceof ShopeeAPIError) {
21
+ message = `āŒ Shopee API Error: ${err.message}`;
22
+ if (err.statusCode === 429) {
23
+ message += '\n\nšŸ’” Tip: Rate limited by Shopee. Please wait a moment before retrying.';
24
+ }
25
+ else if (err.statusCode && err.statusCode >= 500) {
26
+ message += '\n\nšŸ’” Tip: Shopee is having a temporary issue. Please retry shortly.';
27
+ }
28
+ }
29
+ else if (err instanceof Error) {
30
+ message = `āŒ Error: ${err.message}`;
31
+ }
32
+ else {
33
+ message = `āŒ Unknown error occurred`;
34
+ }
35
+ return { content: [{ type: 'text', text: message }] };
36
+ }
37
+ }
38
+ /** Truncate text to max chars. */
39
+ export function truncate(text, maxLen = 200) {
40
+ if (text.length <= maxLen)
41
+ return text;
42
+ return text.slice(0, maxLen).trimEnd() + '…';
43
+ }
44
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAE3E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,EAAqE;IAErE,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,OAAe,CAAC;QAEpB,IAAI,GAAG,YAAY,uBAAuB,EAAE,CAAC;YAC3C,OAAO;gBACL,iCAAiC;oBACjC,kFAAkF;oBAClF,qBAAqB;oBACrB,0FAA0F;oBAC1F,iEAAiE,CAAC;QACtE,CAAC;aAAM,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;YACzC,OAAO,GAAG,uBAAuB,GAAG,CAAC,OAAO,EAAE,CAAC;YAC/C,IAAI,GAAG,CAAC,UAAU,KAAK,GAAG,EAAE,CAAC;gBAC3B,OAAO,IAAI,2EAA2E,CAAC;YACzF,CAAC;iBAAM,IAAI,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,UAAU,IAAI,GAAG,EAAE,CAAC;gBACnD,OAAO,IAAI,uEAAuE,CAAC;YACrF,CAAC;QACH,CAAC;aAAM,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;YAChC,OAAO,GAAG,YAAY,GAAG,CAAC,OAAO,EAAE,CAAC;QACtC,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,0BAA0B,CAAC;QACvC,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IACxD,CAAC;AACH,CAAC;AAED,kCAAkC;AAClC,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,MAAM,GAAG,GAAG;IACjD,IAAI,IAAI,CAAC,MAAM,IAAI,MAAM;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,73 @@
1
+ # Configuration
2
+
3
+ For installing the package or cloning the repo, see **[Installation](../README.md#installation)** in the README.
4
+
5
+ > **Login required.** Shopee blocks anonymous requests, so this server reads public data through a saved browser session. Run `npm run login` once before use. There are no API keys — authentication is the browser session under `~/.shopee-mcp/chrome-profile`.
6
+
7
+ ---
8
+
9
+ ## Environment variables
10
+
11
+ All optional. Set them in your MCP client's **`env`** block, or copy `.env.example` to `.env` when developing from a checkout.
12
+
13
+ | Variable | Default | Description |
14
+ | -------------------- | ------------------------------ | ------------------------------------ |
15
+ | `SHOPEE_DOMAIN` | `shopee.co.id` | Regional Shopee domain. |
16
+ | `SHOPEE_PROFILE_DIR` | `~/.shopee-mcp/chrome-profile` | Where the saved login lives. |
17
+ | `SHOPEE_HEADLESS` | `false` | Keep `false` — headless is detected. |
18
+ | `CACHE_TTL_MS` | `30000` | In-memory cache lifetime. |
19
+ | `DEBUG` | `false` | Log startup/debug info to stderr. |
20
+
21
+ ---
22
+
23
+ ## MCP configuration (all clients)
24
+
25
+ This server uses **stdio** and launches a **headed** browser, so it needs a display. On a headless machine, wrap the command in `xvfb-run`.
26
+
27
+ ### With a virtual display (servers)
28
+
29
+ ```json
30
+ {
31
+ "mcpServers": {
32
+ "shopee": {
33
+ "command": "xvfb-run",
34
+ "args": ["-a", "node", "/absolute/path/to/shopee-mcp/build/index.js"]
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ ### With a real display (desktop / WSLg)
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "shopee": {
46
+ "command": "node",
47
+ "args": ["/absolute/path/to/shopee-mcp/build/index.js"]
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ Use an **absolute** path to `build/index.js`.
54
+
55
+ ---
56
+
57
+ ## Claude Code
58
+
59
+ - **CLI:** `claude mcp add shopee -- xvfb-run -a node /absolute/path/to/shopee-mcp/build/index.js` (drop `xvfb-run -a` on a machine with a display).
60
+ - **Project scope:** `.mcp.json` in the repo root. **User scope:** `~/.claude.json`.
61
+ - **Restart** or reload so the new server is registered.
62
+
63
+ ## Claude Desktop
64
+
65
+ - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
66
+ - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
67
+ - **Linux:** `~/.config/Claude/claude_desktop_config.json`
68
+
69
+ Use the same **`mcpServers`** JSON as above.
70
+
71
+ ## Other editors
72
+
73
+ Cursor, Zed, Windsurf, and any other **stdio MCP host** use the same pattern: a server whose command is `node` (or `xvfb-run … node`) plus the path to `build/index.js`.