byclaw-mcp 0.4.5 → 0.4.7
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/index.js +198 -5
- package/package.json +8 -2
package/dist/index.js
CHANGED
|
@@ -132,22 +132,116 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
|
132
132
|
description: 'Get platform statistics: products compared, reviews written, recommendations sent.',
|
|
133
133
|
inputSchema: { type: 'object', properties: {} },
|
|
134
134
|
},
|
|
135
|
+
// ── QA / Testing ──
|
|
136
|
+
{
|
|
137
|
+
name: 'test_web_search',
|
|
138
|
+
description: 'Test the web agent search endpoint (same as dashboard search). Use for QA to verify web search behavior vs MCP shop behavior.',
|
|
139
|
+
inputSchema: {
|
|
140
|
+
type: 'object',
|
|
141
|
+
properties: {
|
|
142
|
+
query: { type: 'string', description: 'Search query' },
|
|
143
|
+
max_price: { type: 'number', description: 'Maximum price in EUR (optional)' },
|
|
144
|
+
brand: { type: 'string', description: 'Brand filter (optional)' },
|
|
145
|
+
language: { type: 'string', description: 'Language: de, en (optional)' },
|
|
146
|
+
},
|
|
147
|
+
required: ['query'],
|
|
148
|
+
},
|
|
149
|
+
},
|
|
135
150
|
],
|
|
136
151
|
}));
|
|
137
152
|
// ═══════════════════════════════════════
|
|
138
153
|
// TOOL HANDLERS
|
|
139
154
|
// ═══════════════════════════════════════
|
|
140
155
|
let reviewLeftThisSession = false;
|
|
156
|
+
// ═══ Session state for follow-up queries ═══
|
|
157
|
+
// originalQuery = the first real product search (category anchor, never overwritten by modifiers)
|
|
158
|
+
// query = the effective query sent last (may be a reconstructed follow-up)
|
|
159
|
+
let lastShopContext = null;
|
|
160
|
+
// Patterns that indicate a follow-up to the previous search (no standalone product intent)
|
|
161
|
+
const FOLLOWUP_PATTERNS = [
|
|
162
|
+
// DE
|
|
163
|
+
/^(hast du |gibt es |gibt's |zeig mir |zeig |noch |was anderes|was billigeres|was teureres)/i,
|
|
164
|
+
/^(günstiger|teurer|billiger|andere|alternative|statt dessen|weiteres|mehr davon)/i,
|
|
165
|
+
/^(das erste|den ersten|die erste|das zweite|das letzte|das nehme ich|das nehm ich)/i,
|
|
166
|
+
/^(in größe|in farbe|in schwarz|in weiß|in blau|in rot|in grün)/i,
|
|
167
|
+
/^(nochmal|nochmals|lieber|besser|schlechter|kleiner|größer)/i,
|
|
168
|
+
// EN
|
|
169
|
+
/^(show me|anything else|something else|something cheaper|something better)/i,
|
|
170
|
+
/^(instead|cheaper|more expensive|different|other options|alternatives)/i,
|
|
171
|
+
/^(the first|the second|the last|i'll take|in size|in color|in black|in white)/i,
|
|
172
|
+
/^(do you have|is there|can you|what about|how about|any other)/i,
|
|
173
|
+
// Short contextual (only match if very short — <30 chars)
|
|
174
|
+
];
|
|
175
|
+
// Patterns that should go to the clarification flow, NOT follow-up reconstruction
|
|
176
|
+
const CLARIFICATION_PATTERNS = [
|
|
177
|
+
/^(hi|hallo|hey|hello|moin|servus|grüß)/i,
|
|
178
|
+
/^(hilf|help|ich weiß nicht|i don't know|bin unsicher|not sure)/i,
|
|
179
|
+
/^(erzähl|tell me more|was meinst|what do you mean|ich verstehe)/i,
|
|
180
|
+
/^(was |irgendwas|irgendwelche|something|anything)$/i,
|
|
181
|
+
/^(danke|thanks|ok|okay|ja|nein|yes|no)$/i,
|
|
182
|
+
];
|
|
183
|
+
function isFollowUp(q) {
|
|
184
|
+
if (!lastShopContext)
|
|
185
|
+
return false;
|
|
186
|
+
const trimmed = q.trim();
|
|
187
|
+
// Clarification queries should NOT be reconstructed as follow-ups
|
|
188
|
+
if (CLARIFICATION_PATTERNS.some(p => p.test(trimmed)))
|
|
189
|
+
return false;
|
|
190
|
+
// Very short queries (<30 chars) without a product noun are likely follow-ups
|
|
191
|
+
if (trimmed.length < 30 && !/\b(kopfhörer|headphones|schuhe|shoes|tasche|bag|uhr|watch|laptop|phone|tablet|kamera|camera|rucksack|jacke|jacket)\b/i.test(trimmed)) {
|
|
192
|
+
if (FOLLOWUP_PATTERNS.some(p => p.test(trimmed)))
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
// Longer queries: only match if they start with a clear follow-up pattern
|
|
196
|
+
if (trimmed.length <= 80 && FOLLOWUP_PATTERNS.some(p => p.test(trimmed)))
|
|
197
|
+
return true;
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
function buildFollowUpQuery(q) {
|
|
201
|
+
if (!lastShopContext)
|
|
202
|
+
return q;
|
|
203
|
+
const trimmed = q.trim().toLowerCase();
|
|
204
|
+
// "das erste nehmen" / "the first one" → return the picked product directly
|
|
205
|
+
if (/das erste|den ersten|die erste|the first|i'll take|das nehm/i.test(trimmed)) {
|
|
206
|
+
return lastShopContext.picked;
|
|
207
|
+
}
|
|
208
|
+
// "in Größe 42" / "in size 42" → can't fulfill (no size data), let API handle
|
|
209
|
+
if (/in größe|in size|in farbe|in color/i.test(trimmed)) {
|
|
210
|
+
return `${lastShopContext.picked} ${q}`;
|
|
211
|
+
}
|
|
212
|
+
// Price modifiers → use ORIGINAL category anchor + price constraint
|
|
213
|
+
const anchor = lastShopContext.originalQuery;
|
|
214
|
+
if (/günstiger|billiger|cheaper|was billigeres|noch günstiger/i.test(trimmed)) {
|
|
215
|
+
return `${anchor} unter ${Math.floor(lastShopContext.price * 0.8)}€`;
|
|
216
|
+
}
|
|
217
|
+
if (/teurer|more expensive|was teureres/i.test(trimmed)) {
|
|
218
|
+
return `${anchor} über ${Math.ceil(lastShopContext.price * 1.3)}€`;
|
|
219
|
+
}
|
|
220
|
+
// Generic "something else" / "andere" → use ORIGINAL category anchor
|
|
221
|
+
return `${anchor} — ${q}`;
|
|
222
|
+
}
|
|
141
223
|
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
142
224
|
const { name, arguments: args } = request.params;
|
|
143
225
|
try {
|
|
144
226
|
switch (name) {
|
|
145
227
|
case 'shop': {
|
|
228
|
+
// Follow-up detection: reconstruct query with context from last search
|
|
229
|
+
let effectiveQuery = String(args?.query || '');
|
|
230
|
+
const wasFollowUp = isFollowUp(effectiveQuery);
|
|
231
|
+
if (wasFollowUp) {
|
|
232
|
+
effectiveQuery = buildFollowUpQuery(effectiveQuery);
|
|
233
|
+
console.error(`[mcp] Follow-up detected: "${args?.query}" → "${effectiveQuery}"`);
|
|
234
|
+
}
|
|
146
235
|
const result = await apiCall('/api/mcp/shop', {
|
|
147
236
|
method: 'POST',
|
|
148
237
|
body: JSON.stringify({
|
|
149
|
-
query:
|
|
238
|
+
query: effectiveQuery,
|
|
150
239
|
max_price: args?.max_price,
|
|
240
|
+
previous_context: lastShopContext ? {
|
|
241
|
+
original_query: lastShopContext.originalQuery,
|
|
242
|
+
picked: lastShopContext.picked,
|
|
243
|
+
price: lastShopContext.price,
|
|
244
|
+
} : undefined,
|
|
151
245
|
category: args?.category,
|
|
152
246
|
brand: args?.brand,
|
|
153
247
|
language: args?.language,
|
|
@@ -162,16 +256,45 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
162
256
|
// ── GIFT MODE ──
|
|
163
257
|
if (d.status === 'gift_found' && Array.isArray(d.gift_picks) && d.gift_picks.length > 0) {
|
|
164
258
|
const intent = d.gift_intent || {};
|
|
259
|
+
// Track session for follow-ups (keep originalQuery stable across chains)
|
|
260
|
+
lastShopContext = {
|
|
261
|
+
originalQuery: wasFollowUp ? (lastShopContext?.originalQuery || String(args?.query || '')) : String(args?.query || ''),
|
|
262
|
+
query: effectiveQuery,
|
|
263
|
+
picked: d.gift_picks[0]?.title || '',
|
|
264
|
+
price: d.gift_picks[0]?.price || 0,
|
|
265
|
+
alternatives: d.gift_picks.slice(1).map((p) => p.title),
|
|
266
|
+
};
|
|
165
267
|
return { content: [{ type: 'text', text: JSON.stringify({
|
|
166
|
-
type: '
|
|
167
|
-
|
|
168
|
-
|
|
268
|
+
type: 'gift_found',
|
|
269
|
+
advisor: advisorLabel,
|
|
270
|
+
advisor_profile: d.advisor_profile || null,
|
|
271
|
+
gift_picks: d.gift_picks.map((p) => ({
|
|
169
272
|
title: p.title,
|
|
170
273
|
price: p.price,
|
|
274
|
+
brand: p.brand || '',
|
|
171
275
|
merchant: p.merchant || '',
|
|
172
276
|
reasoning: p.reasoning || '',
|
|
173
277
|
url: p.affiliate_url || '',
|
|
174
278
|
})),
|
|
279
|
+
gift_intent: {
|
|
280
|
+
recipient: intent.recipient || '',
|
|
281
|
+
summary: intent.summary || '',
|
|
282
|
+
},
|
|
283
|
+
affiliate_notice: disclosure,
|
|
284
|
+
...(d.note ? { note: d.note } : {}),
|
|
285
|
+
}, null, 2) }] };
|
|
286
|
+
}
|
|
287
|
+
// ── CLARIFICATION MODE ──
|
|
288
|
+
if (d.status === 'clarification_needed') {
|
|
289
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
290
|
+
type: 'clarification_needed',
|
|
291
|
+
advisor: advisorLabel,
|
|
292
|
+
advisor_profile: d.advisor_profile || null,
|
|
293
|
+
message: d.message || '',
|
|
294
|
+
suggested_next: d.suggested_next || [],
|
|
295
|
+
context_summary: d.context_summary || null,
|
|
296
|
+
affiliate_notice: disclosure,
|
|
297
|
+
...(d.note ? { note: d.note } : {}),
|
|
175
298
|
}, null, 2) }] };
|
|
176
299
|
}
|
|
177
300
|
// ── NORMAL / FOLLOW-UP MODE ──
|
|
@@ -188,7 +311,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
188
311
|
content.push({ type: 'image', data: allImages[0].data, mimeType: allImages[0].mimeType });
|
|
189
312
|
}
|
|
190
313
|
// Add structured data — handle no_match vs product_recommendation
|
|
191
|
-
if (d.
|
|
314
|
+
if (d.status === 'no_match') {
|
|
192
315
|
content.push({ type: 'text', text: JSON.stringify({
|
|
193
316
|
type: 'no_match',
|
|
194
317
|
advisor: advisorLabel,
|
|
@@ -232,6 +355,16 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
232
355
|
if (img)
|
|
233
356
|
content.push({ type: 'image', data: img.data, mimeType: img.mimeType });
|
|
234
357
|
});
|
|
358
|
+
// Track session for follow-ups (only on product_found, not no_match)
|
|
359
|
+
if (d.status !== 'no_match' && d.picked) {
|
|
360
|
+
lastShopContext = {
|
|
361
|
+
originalQuery: wasFollowUp ? (lastShopContext?.originalQuery || String(args?.query || '')) : String(args?.query || ''),
|
|
362
|
+
query: effectiveQuery,
|
|
363
|
+
picked: d.picked.title || '',
|
|
364
|
+
price: d.picked.price || 0,
|
|
365
|
+
alternatives: (d.alternatives || []).map((a) => a.title),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
235
368
|
return { content };
|
|
236
369
|
}
|
|
237
370
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
@@ -293,6 +426,66 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
293
426
|
const result = await apiCall('/api/stats');
|
|
294
427
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
295
428
|
}
|
|
429
|
+
case 'test_web_search': {
|
|
430
|
+
const result = await apiCall('/api/agents/shop', {
|
|
431
|
+
method: 'POST',
|
|
432
|
+
body: JSON.stringify({
|
|
433
|
+
query: args?.query,
|
|
434
|
+
max_price: args?.max_price,
|
|
435
|
+
brand: args?.brand,
|
|
436
|
+
language: args?.language,
|
|
437
|
+
source: 'test',
|
|
438
|
+
}),
|
|
439
|
+
});
|
|
440
|
+
if (!result?.ok) {
|
|
441
|
+
return { content: [{ type: 'text', text: JSON.stringify({ type: 'web_search_error', error: result?.error || 'Unknown error', raw: result }, null, 2) }] };
|
|
442
|
+
}
|
|
443
|
+
const d = result.data;
|
|
444
|
+
// Clarification responses have different fields than product responses
|
|
445
|
+
if (d.status === 'clarification_needed') {
|
|
446
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
447
|
+
type: 'clarification_needed',
|
|
448
|
+
endpoint: '/api/agents/shop',
|
|
449
|
+
advisor: d.advisor_profile?.name || d.agent?.personality || null,
|
|
450
|
+
advisor_profile: d.advisor_profile || null,
|
|
451
|
+
message: d.message || null,
|
|
452
|
+
message_de: d.message_de || null,
|
|
453
|
+
message_en: d.message_en || null,
|
|
454
|
+
suggested_next: d.suggested_next || [],
|
|
455
|
+
context_summary: d.context_summary || null,
|
|
456
|
+
disclosure: d.disclosure || null,
|
|
457
|
+
note: d.note || null,
|
|
458
|
+
}, null, 2) }] };
|
|
459
|
+
}
|
|
460
|
+
// Gift responses
|
|
461
|
+
if (d.status === 'gift_found' && d.gift_picks) {
|
|
462
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
463
|
+
type: 'gift_found',
|
|
464
|
+
endpoint: '/api/agents/shop',
|
|
465
|
+
advisor: d.advisor_profile?.name || d.agent?.personality || null,
|
|
466
|
+
advisor_profile: d.advisor_profile || null,
|
|
467
|
+
message: d.message || null,
|
|
468
|
+
gift_picks: d.gift_picks.map((g) => ({ title: g.title, price: g.price, brand: g.brand, reasoning: g.reasoning || null })),
|
|
469
|
+
gift_intent: d.gift_intent || null,
|
|
470
|
+
disclosure: d.disclosure || null,
|
|
471
|
+
note: d.note || null,
|
|
472
|
+
}, null, 2) }] };
|
|
473
|
+
}
|
|
474
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
475
|
+
type: d.status || 'web_search_result',
|
|
476
|
+
endpoint: '/api/agents/shop',
|
|
477
|
+
advisor: d.advisor_profile?.name || d.agent?.personality || null,
|
|
478
|
+
advisor_profile: d.advisor_profile || null,
|
|
479
|
+
message: d.message || null,
|
|
480
|
+
picked: d.picked ? { title: d.picked.title, price: d.picked.price, brand: d.picked.brand, affiliate_url: d.picked.affiliate_url } : null,
|
|
481
|
+
alternatives: (d.alternatives || []).map((a) => ({ title: a.title, price: a.price, brand: a.brand })),
|
|
482
|
+
compared: d.compared || 0,
|
|
483
|
+
review: d.review || d.message || null,
|
|
484
|
+
reasoning: d.reasoning || d.message || null,
|
|
485
|
+
disclosure: d.disclosure || null,
|
|
486
|
+
note: d.note || null,
|
|
487
|
+
}, null, 2) }] };
|
|
488
|
+
}
|
|
296
489
|
default:
|
|
297
490
|
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
298
491
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "byclaw-mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
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
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,13 @@
|
|
|
11
11
|
"dev": "tsx src/index.ts",
|
|
12
12
|
"prepublishOnly": "npm run build"
|
|
13
13
|
},
|
|
14
|
-
"keywords": [
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"model-context-protocol",
|
|
17
|
+
"ai-shopping",
|
|
18
|
+
"claude",
|
|
19
|
+
"byclaw"
|
|
20
|
+
],
|
|
15
21
|
"author": "NJUDEV S.L.",
|
|
16
22
|
"license": "MIT",
|
|
17
23
|
"repository": {
|