byclaw-mcp 0.4.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.
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +50 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +296 -0
- package/dist/setup.d.ts +8 -0
- package/dist/setup.js +209 -0
- package/package.json +36 -0
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* byclaw-mcp CLI
|
|
5
|
+
*
|
|
6
|
+
* npx byclaw-mcp setup --key byclaw_xxx → Configure AI tools
|
|
7
|
+
* npx byclaw-mcp → Start MCP server (called by AI tools)
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
const command = process.argv[2];
|
|
43
|
+
if (command === 'setup') {
|
|
44
|
+
// Run setup wizard
|
|
45
|
+
Promise.resolve().then(() => __importStar(require('./setup.js')));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Start MCP server (this is what AI tools call)
|
|
49
|
+
Promise.resolve().then(() => __importStar(require('./index.js')));
|
|
50
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
5
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
7
|
+
const API_BASE = process.env.BYCLAW_API_URL || 'https://byclaw.io';
|
|
8
|
+
const API_KEY = process.env.BYCLAW_API_KEY || '';
|
|
9
|
+
async function fetchImageBase64(url) {
|
|
10
|
+
if (!url || url.includes('picsum.photos'))
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch(url, {
|
|
14
|
+
signal: AbortSignal.timeout(8000),
|
|
15
|
+
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; byclaw-mcp/0.4.0)' },
|
|
16
|
+
redirect: 'follow',
|
|
17
|
+
});
|
|
18
|
+
if (!res.ok)
|
|
19
|
+
return null;
|
|
20
|
+
const contentType = res.headers.get('content-type') || 'image/jpeg';
|
|
21
|
+
if (!contentType.startsWith('image/'))
|
|
22
|
+
return null;
|
|
23
|
+
const buffer = await res.arrayBuffer();
|
|
24
|
+
if (buffer.byteLength < 100 || buffer.byteLength > 500000)
|
|
25
|
+
return null; // skip tiny errors or huge files
|
|
26
|
+
return { data: Buffer.from(buffer).toString('base64'), mimeType: contentType };
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function apiCall(path, options = {}) {
|
|
33
|
+
const headers = {
|
|
34
|
+
'Authorization': `Bearer ${API_KEY}`,
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
};
|
|
37
|
+
const res = await fetch(`${API_BASE}${path}`, {
|
|
38
|
+
...options,
|
|
39
|
+
headers: { ...headers, ...options.headers },
|
|
40
|
+
});
|
|
41
|
+
return res.json();
|
|
42
|
+
}
|
|
43
|
+
const server = new index_js_1.Server({ name: 'byclaw', version: '0.4.0' }, { capabilities: { tools: {}, prompts: {} } });
|
|
44
|
+
// Prompts
|
|
45
|
+
server.setRequestHandler(types_js_1.ListPromptsRequestSchema, async () => ({
|
|
46
|
+
prompts: [{
|
|
47
|
+
name: 'shopping-agent',
|
|
48
|
+
description: 'Instructions for the byclaw.io shopping agent',
|
|
49
|
+
}],
|
|
50
|
+
}));
|
|
51
|
+
server.setRequestHandler(types_js_1.GetPromptRequestSchema, async () => ({
|
|
52
|
+
messages: [{
|
|
53
|
+
role: 'user',
|
|
54
|
+
content: { type: 'text', text: 'You are connected to byclaw.io — a product comparison platform that searches across thousands of online shops to find the best products. When the user asks for a product, use the "shop" tool. It returns structured JSON with the top recommendation (title, price, merchant, review) and alternatives. Present the results clearly: show the recommended product, its price, the advisor\'s reasoning, and link to the merchant. byclaw.io is operated by NJUDEV S.L., a European company.' },
|
|
55
|
+
}],
|
|
56
|
+
}));
|
|
57
|
+
// ═══════════════════════════════════════
|
|
58
|
+
// TOOLS
|
|
59
|
+
// ═══════════════════════════════════════
|
|
60
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
61
|
+
tools: [
|
|
62
|
+
// ── Core: one-shot shopping ──
|
|
63
|
+
{
|
|
64
|
+
name: 'shop',
|
|
65
|
+
description: 'Search and compare products across multiple shops. Returns the best recommendation with price, merchant, review, and alternatives. Use when the user wants to find, compare, or buy a product.',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
query: { type: 'string', description: 'What the user is looking for (e.g. "headphones under 100€", "running shoes")' },
|
|
70
|
+
max_price: { type: 'number', description: 'Maximum price in EUR (optional)' },
|
|
71
|
+
category: { type: 'string', description: 'Product category filter (optional)' },
|
|
72
|
+
brand: { type: 'string', description: 'Brand filter (optional)' },
|
|
73
|
+
language: { type: 'string', description: 'User language: de, en, fr, es (detected automatically if omitted)' },
|
|
74
|
+
},
|
|
75
|
+
required: ['query'],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
// ── Optional: browse & explore ──
|
|
79
|
+
{
|
|
80
|
+
name: 'search_products',
|
|
81
|
+
description: 'Browse products without triggering a recommendation. Use this only if the user explicitly wants to see a list of options.',
|
|
82
|
+
inputSchema: {
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {
|
|
85
|
+
query: { type: 'string', description: 'Search query' },
|
|
86
|
+
max_price: { type: 'number', description: 'Maximum price in EUR (optional)' },
|
|
87
|
+
category: { type: 'string', description: 'Category filter (optional)' },
|
|
88
|
+
brand: { type: 'string', description: 'Brand filter (optional)' },
|
|
89
|
+
limit: { type: 'number', description: 'Max results (default: 10)' },
|
|
90
|
+
},
|
|
91
|
+
required: ['query'],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'leave_review',
|
|
96
|
+
description: 'Write a product review. Only call if the user explicitly asks to leave a review. Only de and en allowed.',
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: {
|
|
100
|
+
product_id: { type: 'string', description: 'Product ID' },
|
|
101
|
+
review_text: { type: 'string', description: 'Review text (1-2 sentences)' },
|
|
102
|
+
rating: { type: 'number', description: 'Rating 1.0-5.0' },
|
|
103
|
+
action: { type: 'string', enum: ['recommended', 'purchased', 'declined'] },
|
|
104
|
+
language: { type: 'string', enum: ['de', 'en'] },
|
|
105
|
+
},
|
|
106
|
+
required: ['product_id', 'review_text', 'rating', 'action', 'language'],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
// ── Discovery ──
|
|
110
|
+
{
|
|
111
|
+
name: 'list_categories',
|
|
112
|
+
description: 'Browse all available product categories.',
|
|
113
|
+
inputSchema: { type: 'object', properties: {} },
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'get_trending',
|
|
117
|
+
description: 'Get trending products that agents are buying right now.',
|
|
118
|
+
inputSchema: { type: 'object', properties: {} },
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'get_product_reviews',
|
|
122
|
+
description: 'Get agent reviews for a product.',
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: 'object',
|
|
125
|
+
properties: {
|
|
126
|
+
product_id: { type: 'string', description: 'Product ID (optional — omit for latest)' },
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'get_platform_stats',
|
|
132
|
+
description: 'Get platform statistics: products compared, reviews written, recommendations sent.',
|
|
133
|
+
inputSchema: { type: 'object', properties: {} },
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
}));
|
|
137
|
+
// ═══════════════════════════════════════
|
|
138
|
+
// TOOL HANDLERS
|
|
139
|
+
// ═══════════════════════════════════════
|
|
140
|
+
let reviewLeftThisSession = false;
|
|
141
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
142
|
+
const { name, arguments: args } = request.params;
|
|
143
|
+
try {
|
|
144
|
+
switch (name) {
|
|
145
|
+
case 'shop': {
|
|
146
|
+
const result = await apiCall('/api/mcp/shop', {
|
|
147
|
+
method: 'POST',
|
|
148
|
+
body: JSON.stringify({
|
|
149
|
+
query: args?.query,
|
|
150
|
+
max_price: args?.max_price,
|
|
151
|
+
category: args?.category,
|
|
152
|
+
brand: args?.brand,
|
|
153
|
+
language: args?.language,
|
|
154
|
+
source: 'mcp',
|
|
155
|
+
}),
|
|
156
|
+
});
|
|
157
|
+
if (result?.ok) {
|
|
158
|
+
const d = result.data;
|
|
159
|
+
const disclosure = d.disclosure || 'ℹ️ Affiliate-Link. byclaw earns a small commission — no extra cost to you.';
|
|
160
|
+
const personalityName = d.agent?.personality || '';
|
|
161
|
+
const personalityLabels = { dealmaker: 'Dealmaker', luxpicker: 'Luxpicker', smartpicker: 'Smartpicker', vibechecker: 'Vibechecker', quickshooter: 'Quickshooter' };
|
|
162
|
+
const advisorLabel = personalityLabels[personalityName] || personalityName;
|
|
163
|
+
// ── GIFT MODE ──
|
|
164
|
+
if (d.status === 'gift_found' && Array.isArray(d.gift_picks) && d.gift_picks.length > 0) {
|
|
165
|
+
const intent = d.gift_intent || {};
|
|
166
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
167
|
+
type: 'gift_recommendation',
|
|
168
|
+
recipient: intent.summary || '',
|
|
169
|
+
picks: d.gift_picks.map((p) => ({
|
|
170
|
+
title: p.title,
|
|
171
|
+
price: p.price,
|
|
172
|
+
merchant: p.merchant || '',
|
|
173
|
+
reasoning: p.reasoning || '',
|
|
174
|
+
url: p.affiliate_url || '',
|
|
175
|
+
})),
|
|
176
|
+
}, null, 2) }] };
|
|
177
|
+
}
|
|
178
|
+
// ── NORMAL / FOLLOW-UP MODE ──
|
|
179
|
+
const content = [];
|
|
180
|
+
// Fetch all images in parallel (picked + up to 3 alternatives)
|
|
181
|
+
const allProducts = [d.picked, ...(d.alternatives || []).slice(0, 3)];
|
|
182
|
+
const allImages = await Promise.all(allProducts.map(async (p) => {
|
|
183
|
+
if (!p?.image_url)
|
|
184
|
+
return null;
|
|
185
|
+
return fetchImageBase64(p.image_url);
|
|
186
|
+
}));
|
|
187
|
+
// Add picked image
|
|
188
|
+
if (allImages[0]) {
|
|
189
|
+
content.push({ type: 'image', data: allImages[0].data, mimeType: allImages[0].mimeType });
|
|
190
|
+
}
|
|
191
|
+
// Add structured data
|
|
192
|
+
content.push({ type: 'text', text: JSON.stringify({
|
|
193
|
+
type: 'product_recommendation',
|
|
194
|
+
advisor: advisorLabel,
|
|
195
|
+
advisor_profile: d.advisor_profile || null,
|
|
196
|
+
recommendation: {
|
|
197
|
+
title: d.picked?.title || '',
|
|
198
|
+
price: d.picked?.price ?? '',
|
|
199
|
+
brand: d.picked?.brand || '',
|
|
200
|
+
merchant: d.picked?.merchant || '',
|
|
201
|
+
url: d.picked?.affiliate_url || '',
|
|
202
|
+
review: d.review || d.reasoning || '',
|
|
203
|
+
},
|
|
204
|
+
alternatives: (d.alternatives || []).slice(0, 3).map((a) => ({
|
|
205
|
+
title: a.title,
|
|
206
|
+
price: a.price,
|
|
207
|
+
brand: a.brand || '',
|
|
208
|
+
category: a.category || '',
|
|
209
|
+
merchant: a.merchant || '',
|
|
210
|
+
url: a.affiliate_url || '',
|
|
211
|
+
})),
|
|
212
|
+
compared: d.compared || 0,
|
|
213
|
+
reasoning: d.reasoning || '',
|
|
214
|
+
affiliate_notice: 'Links are affiliate links. byclaw.io earns a small commission at no extra cost to you.',
|
|
215
|
+
}, null, 2) });
|
|
216
|
+
// Add alternative images
|
|
217
|
+
allImages.slice(1).forEach((img) => {
|
|
218
|
+
if (img)
|
|
219
|
+
content.push({ type: 'image', data: img.data, mimeType: img.mimeType });
|
|
220
|
+
});
|
|
221
|
+
return { content };
|
|
222
|
+
}
|
|
223
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
224
|
+
}
|
|
225
|
+
case 'search_products': {
|
|
226
|
+
const params = new URLSearchParams();
|
|
227
|
+
if (args?.query)
|
|
228
|
+
params.set('q', String(args.query));
|
|
229
|
+
if (args?.max_price)
|
|
230
|
+
params.set('max_price', String(args.max_price));
|
|
231
|
+
if (args?.category)
|
|
232
|
+
params.set('category', String(args.category));
|
|
233
|
+
if (args?.brand)
|
|
234
|
+
params.set('brand', String(args.brand));
|
|
235
|
+
if (args?.limit)
|
|
236
|
+
params.set('limit', String(args.limit));
|
|
237
|
+
else
|
|
238
|
+
params.set('limit', '10');
|
|
239
|
+
const result = await apiCall(`/api/products/search?${params}`);
|
|
240
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
241
|
+
}
|
|
242
|
+
case 'leave_review': {
|
|
243
|
+
const lang = args?.language;
|
|
244
|
+
if (lang !== 'de' && lang !== 'en') {
|
|
245
|
+
return { content: [{ type: 'text', text: 'Reviews are only allowed in German (de) or English (en).' }] };
|
|
246
|
+
}
|
|
247
|
+
if (reviewLeftThisSession) {
|
|
248
|
+
return { content: [{ type: 'text', text: 'You already left a review this session.' }] };
|
|
249
|
+
}
|
|
250
|
+
const result = await apiCall('/api/products/review', {
|
|
251
|
+
method: 'POST',
|
|
252
|
+
body: JSON.stringify({
|
|
253
|
+
product_id: args?.product_id,
|
|
254
|
+
review_text: args?.review_text,
|
|
255
|
+
rating: args?.rating,
|
|
256
|
+
action: args?.action,
|
|
257
|
+
language: lang,
|
|
258
|
+
}),
|
|
259
|
+
});
|
|
260
|
+
reviewLeftThisSession = true;
|
|
261
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
262
|
+
}
|
|
263
|
+
case 'list_categories': {
|
|
264
|
+
const result = await apiCall('/api/products/categories');
|
|
265
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
266
|
+
}
|
|
267
|
+
case 'get_trending': {
|
|
268
|
+
const result = await apiCall('/api/products/trending');
|
|
269
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
270
|
+
}
|
|
271
|
+
case 'get_product_reviews': {
|
|
272
|
+
const params = new URLSearchParams();
|
|
273
|
+
if (args?.product_id)
|
|
274
|
+
params.set('product_id', String(args.product_id));
|
|
275
|
+
const result = await apiCall(`/api/products/reviews?${params}`);
|
|
276
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
277
|
+
}
|
|
278
|
+
case 'get_platform_stats': {
|
|
279
|
+
const result = await apiCall('/api/stats');
|
|
280
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
281
|
+
}
|
|
282
|
+
default:
|
|
283
|
+
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
// Start server
|
|
291
|
+
async function main() {
|
|
292
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
293
|
+
await server.connect(transport);
|
|
294
|
+
console.error('byclaw MCP server v0.4.0 running on stdio');
|
|
295
|
+
}
|
|
296
|
+
main().catch(console.error);
|
package/dist/setup.d.ts
ADDED
package/dist/setup.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* byclaw-mcp setup --key byclaw_xxx
|
|
5
|
+
*
|
|
6
|
+
* Auto-detects installed AI tools and configures MCP.
|
|
7
|
+
* Supports: Claude Desktop, Claude Code, OpenClaw
|
|
8
|
+
*/
|
|
9
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const os_1 = __importDefault(require("os"));
|
|
16
|
+
const BG_BLACK = '\x1b[40m';
|
|
17
|
+
const ORANGE = '\x1b[38;5;208m';
|
|
18
|
+
const DIM = '\x1b[2m';
|
|
19
|
+
const BOLD = '\x1b[1m';
|
|
20
|
+
const RESET = '\x1b[0m';
|
|
21
|
+
const GREEN = '\x1b[32m';
|
|
22
|
+
const RED = '\x1b[31m';
|
|
23
|
+
const WHITE = '\x1b[97m';
|
|
24
|
+
const CLEAR_SCREEN = '\x1b[2J\x1b[H';
|
|
25
|
+
function log(msg) { console.log(msg); }
|
|
26
|
+
function success(msg) { console.log(`${GREEN}✓${RESET} ${msg}`); }
|
|
27
|
+
function fail(msg) { console.log(`${RED}✗${RESET} ${msg}`); }
|
|
28
|
+
function getTargets() {
|
|
29
|
+
const home = os_1.default.homedir();
|
|
30
|
+
const platform = process.platform;
|
|
31
|
+
const targets = [];
|
|
32
|
+
// Claude Desktop
|
|
33
|
+
if (platform === 'darwin') {
|
|
34
|
+
targets.push({
|
|
35
|
+
name: 'Claude Desktop',
|
|
36
|
+
configPath: path_1.default.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
|
|
37
|
+
exists: false,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
else if (platform === 'win32') {
|
|
41
|
+
targets.push({
|
|
42
|
+
name: 'Claude Desktop',
|
|
43
|
+
configPath: path_1.default.join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json'),
|
|
44
|
+
exists: false,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
targets.push({
|
|
49
|
+
name: 'Claude Desktop',
|
|
50
|
+
configPath: path_1.default.join(home, '.config', 'claude', 'claude_desktop_config.json'),
|
|
51
|
+
exists: false,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
// Claude Code (global)
|
|
55
|
+
targets.push({
|
|
56
|
+
name: 'Claude Code',
|
|
57
|
+
configPath: path_1.default.join(home, '.claude', 'mcp.json'),
|
|
58
|
+
exists: false,
|
|
59
|
+
});
|
|
60
|
+
// OpenClaw
|
|
61
|
+
if (platform === 'darwin') {
|
|
62
|
+
targets.push({
|
|
63
|
+
name: 'OpenClaw',
|
|
64
|
+
configPath: path_1.default.join(home, '.openclaw', 'config.json'),
|
|
65
|
+
exists: false,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
else if (platform === 'win32') {
|
|
69
|
+
targets.push({
|
|
70
|
+
name: 'OpenClaw',
|
|
71
|
+
configPath: path_1.default.join(home, 'AppData', 'Roaming', 'openclaw', 'config.json'),
|
|
72
|
+
exists: false,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
targets.push({
|
|
77
|
+
name: 'OpenClaw',
|
|
78
|
+
configPath: path_1.default.join(home, '.config', 'openclaw', 'config.json'),
|
|
79
|
+
exists: false,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// Check which ones exist (config dir exists = tool is installed)
|
|
83
|
+
for (const t of targets) {
|
|
84
|
+
const dir = path_1.default.dirname(t.configPath);
|
|
85
|
+
t.exists = fs_1.default.existsSync(dir);
|
|
86
|
+
}
|
|
87
|
+
return targets;
|
|
88
|
+
}
|
|
89
|
+
function getMcpEntry(apiKey) {
|
|
90
|
+
return {
|
|
91
|
+
command: 'npx',
|
|
92
|
+
args: ['byclaw-mcp'],
|
|
93
|
+
env: {
|
|
94
|
+
BYCLAW_API_KEY: apiKey,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function writeConfig(target, apiKey) {
|
|
99
|
+
try {
|
|
100
|
+
const dir = path_1.default.dirname(target.configPath);
|
|
101
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
102
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
let config = {};
|
|
105
|
+
if (fs_1.default.existsSync(target.configPath)) {
|
|
106
|
+
try {
|
|
107
|
+
config = JSON.parse(fs_1.default.readFileSync(target.configPath, 'utf-8'));
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Corrupted config — start fresh
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!config.mcpServers)
|
|
114
|
+
config.mcpServers = {};
|
|
115
|
+
config.mcpServers.byclaw = getMcpEntry(apiKey);
|
|
116
|
+
fs_1.default.writeFileSync(target.configPath, JSON.stringify(config, null, 2) + '\n');
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
fail(`Could not write to ${target.configPath}: ${err.message}`);
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function printBanner() {
|
|
125
|
+
log('');
|
|
126
|
+
log(`${ORANGE}${BOLD} ██████╗ ██╗ ██╗ ██████╗██╗ █████╗ ██╗ ██╗${RESET}`);
|
|
127
|
+
log(`${ORANGE}${BOLD} ██╔══██╗╚██╗ ██╔╝██╔════╝██║ ██╔══██╗██║ ██║${RESET}`);
|
|
128
|
+
log(`${ORANGE}${BOLD} ██████╔╝ ╚████╔╝ ██║ ██║ ███████║██║ █╗ ██║${RESET}`);
|
|
129
|
+
log(`${ORANGE}${BOLD} ██╔══██╗ ╚██╔╝ ██║ ██║ ██╔══██║██║███╗██║${RESET}`);
|
|
130
|
+
log(`${ORANGE}${BOLD} ██████╔╝ ██║ ╚██████╗███████╗██║ ██║╚███╔███╔╝${RESET}`);
|
|
131
|
+
log(`${ORANGE}${BOLD} ╚═════╝ ╚═╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝${RESET}`);
|
|
132
|
+
log(`${WHITE}${BOLD} Your personal AI shopping advisor ${RESET}`);
|
|
133
|
+
log('');
|
|
134
|
+
}
|
|
135
|
+
async function main() {
|
|
136
|
+
const args = process.argv.slice(2);
|
|
137
|
+
// Parse --key
|
|
138
|
+
let apiKey = '';
|
|
139
|
+
for (let i = 0; i < args.length; i++) {
|
|
140
|
+
if (args[i] === '--key' && args[i + 1]) {
|
|
141
|
+
apiKey = args[i + 1];
|
|
142
|
+
}
|
|
143
|
+
else if (args[i].startsWith('--key=')) {
|
|
144
|
+
apiKey = args[i].split('=')[1];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (args[0] !== 'setup') {
|
|
148
|
+
log(`Usage: npx byclaw-mcp setup --key <your_api_key>`);
|
|
149
|
+
log(` Get your key at https://byclaw.io/dashboard`);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
// Black background for the entire output
|
|
153
|
+
process.stdout.write(CLEAR_SCREEN + BG_BLACK);
|
|
154
|
+
printBanner();
|
|
155
|
+
if (!apiKey || !apiKey.startsWith('byclaw_')) {
|
|
156
|
+
fail('Missing or invalid API key.');
|
|
157
|
+
log('');
|
|
158
|
+
log(` Usage: ${BOLD}npx byclaw-mcp setup --key byclaw_xxx${RESET}`);
|
|
159
|
+
log(` Get your key: ${ORANGE}https://byclaw.io/dashboard${RESET}`);
|
|
160
|
+
log('');
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
log(` Scanning for AI tools...`);
|
|
164
|
+
log('');
|
|
165
|
+
const targets = getTargets();
|
|
166
|
+
const installed = targets.filter(t => t.exists);
|
|
167
|
+
const notInstalled = targets.filter(t => !t.exists);
|
|
168
|
+
if (installed.length === 0) {
|
|
169
|
+
fail('No supported AI tools found.');
|
|
170
|
+
log('');
|
|
171
|
+
log(' Supported:');
|
|
172
|
+
log(' • Claude Desktop — claude.ai/download');
|
|
173
|
+
log(' • Claude Code — npm install -g @anthropic-ai/claude-code');
|
|
174
|
+
log(' • OpenClaw — openclaw.ai');
|
|
175
|
+
log('');
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
// Show detected tools
|
|
179
|
+
for (const t of installed) {
|
|
180
|
+
log(` ${GREEN}●${RESET} ${t.name}`);
|
|
181
|
+
}
|
|
182
|
+
for (const t of notInstalled) {
|
|
183
|
+
log(` ${DIM}○ ${t.name}${RESET}`);
|
|
184
|
+
}
|
|
185
|
+
log('');
|
|
186
|
+
// Write config to all detected tools
|
|
187
|
+
let configured = 0;
|
|
188
|
+
for (const t of installed) {
|
|
189
|
+
if (writeConfig(t, apiKey)) {
|
|
190
|
+
success(`${t.name} configured`);
|
|
191
|
+
configured++;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
log('');
|
|
195
|
+
if (configured > 0) {
|
|
196
|
+
log(`${GREEN} Done.${RESET} Connected to ${configured} tool${configured > 1 ? 's' : ''}.`);
|
|
197
|
+
log('');
|
|
198
|
+
log(` Open ${installed[0].name} and say:`);
|
|
199
|
+
log(` ${ORANGE}"Find me headphones under 100€"${RESET}`);
|
|
200
|
+
log('');
|
|
201
|
+
log(` ${DIM}Restart your AI tool if it was already open.${RESET}`);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
fail('Could not configure any tools.');
|
|
205
|
+
}
|
|
206
|
+
log('');
|
|
207
|
+
process.stdout.write(RESET);
|
|
208
|
+
}
|
|
209
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "byclaw-mcp",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "MCP Server for byclaw.io — Your AI shopping advisor. Connects Claude Desktop and other MCP clients to the byclaw.io product catalog.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"byclaw-mcp": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsx src/index.ts",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"keywords": ["mcp", "model-context-protocol", "ai-shopping", "claude", "byclaw"],
|
|
15
|
+
"author": "NJUDEV S.L.",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/dichtare/byclaw"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://byclaw.io",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist/**/*.js",
|
|
27
|
+
"dist/**/*.d.ts"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"typescript": "^5.0.0",
|
|
34
|
+
"tsx": "^4.0.0"
|
|
35
|
+
}
|
|
36
|
+
}
|