byclaw-mcp 0.4.12 → 0.4.13
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 +26 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21,6 +21,24 @@ const SERVER_VERSION = (() => {
|
|
|
21
21
|
}
|
|
22
22
|
})();
|
|
23
23
|
const TOOL_PREFIX = `[byclaw-mcp v${SERVER_VERSION}] `;
|
|
24
|
+
// Different MCP clients render product images very differently:
|
|
25
|
+
// - Claude Code / Cursor: render `` markdown inline,
|
|
26
|
+
// and also render native MCP image content blocks → markdown wins by
|
|
27
|
+
// keeping ordering aligned with text.
|
|
28
|
+
// - claude.ai web + Claude Desktop App: render NEITHER external markdown
|
|
29
|
+
// images NOR MCP image content blocks from tool results in the user UI.
|
|
30
|
+
// Inline images only show when wrapped in an HTML artifact (the model
|
|
31
|
+
// calls a `visualize`/`show_widget`-style tool, e.g.
|
|
32
|
+
// `mcp__visualize__show_widget`, with `<img src="...">` inside).
|
|
33
|
+
// We surface this hint in every product_found tool result and in the tool
|
|
34
|
+
// descriptions so the model can pick the right rendering for its client
|
|
35
|
+
// without us having to detect the client server-side.
|
|
36
|
+
const RENDER_HINT = 'If your client renders external images inline (Claude Code, Cursor, etc.), ' +
|
|
37
|
+
'emit `` markdown for each product. ' +
|
|
38
|
+
'If your client does NOT render external images in chat (claude.ai web, ' +
|
|
39
|
+
'Claude Desktop App), wrap the picked product and every alternative in an ' +
|
|
40
|
+
'HTML artifact via the `visualize`/`show_widget` tool with `<img src="image_url">` ' +
|
|
41
|
+
'tags, so the user actually sees the products. The affiliate_url is the buy link.';
|
|
24
42
|
const API_BASE = process.env.BYCLAW_API_URL || 'https://byclaw.io';
|
|
25
43
|
const API_KEY = process.env.BYCLAW_API_KEY || '';
|
|
26
44
|
// Brand fallback when the catalog row has no image. Clients (Claude Desktop
|
|
@@ -78,7 +96,7 @@ server.setRequestHandler(types_js_1.ListPromptsRequestSchema, async () => ({
|
|
|
78
96
|
server.setRequestHandler(types_js_1.GetPromptRequestSchema, async () => ({
|
|
79
97
|
messages: [{
|
|
80
98
|
role: 'user',
|
|
81
|
-
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.\n\nThe tool returns structured JSON with: `picked` (title, price, brand, merchant, affiliate_url, image_url), `alternatives` (each with title, price, brand, merchant, affiliate_url, image_url), `compared` (int), `reasoning` (string), `review` (string), and `
|
|
99
|
+
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.\n\nThe tool returns structured JSON with: `picked` (title, price, brand, merchant, affiliate_url, image_url), `alternatives` (each with title, price, brand, merchant, affiliate_url, image_url), `compared` (int), `reasoning` (string), `review` (string), `disclosure` (string), and `_render_hint` (string).\n\n**Image rendering** — different clients need different approaches and the response\'s `_render_hint` field tells you which one to use:\n • If running inside **claude.ai web or Claude Desktop App**, the chat UI does NOT render external markdown images or MCP image content blocks. You MUST surface the products via an HTML artifact — call the `visualize` / `show_widget`-style tool and emit `<img src="image_url">` tags inside the widget so the user actually sees the products.\n • If running inside **Claude Code, Cursor, or another MCP client with inline image support**, emit `` markdown for each product directly in your reply.\n\nWhen presenting the result:\n1. Follow `_render_hint` for images (HTML widget on claude.ai/Desktop, markdown elsewhere).\n2. Show the recommended product with its price and the advisor\'s reasoning.\n3. Link the recommendation using its `affiliate_url` field as a clickable link (label: "<merchant> →").\n4. List every alternative with its title, price, merchant and link each using its `affiliate_url` field — never as plain text.\n5. ALWAYS display the `disclosure` text verbatim at the end, as a small italic note — this is legally required.\n\nbyclaw.io is operated by NJUDEV S.L., a European company.' },
|
|
82
100
|
}],
|
|
83
101
|
}));
|
|
84
102
|
// ═══════════════════════════════════════
|
|
@@ -89,7 +107,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
|
89
107
|
// ── Core: one-shot shopping ──
|
|
90
108
|
{
|
|
91
109
|
name: 'shop',
|
|
92
|
-
description: TOOL_PREFIX + 'Search and compare products across multiple shops. Returns a JSON object with `picked` (the recommendation) and `alternatives` (other candidates). Each product object includes `title`, `price`, `brand`, `merchant`, `affiliate_url`, and `image_url` (absolute HTTPS URL).
|
|
110
|
+
description: TOOL_PREFIX + 'Search and compare products across multiple shops. Returns a JSON object with `picked` (the recommendation) and `alternatives` (other candidates). Each product object includes `title`, `price`, `brand`, `merchant`, `affiliate_url`, and `image_url` (absolute HTTPS URL). The response also includes a `_render_hint` field that tells you exactly how to display images for your current client: claude.ai / Claude Desktop need an HTML artifact via the `visualize`/`show_widget` tool with `<img>` tags; Claude Code / Cursor / other MCP clients with image support can use markdown `` directly. ALWAYS follow that `_render_hint` so the user actually sees the products. The server additionally emits native MCP image content blocks for clients that render them natively.',
|
|
93
111
|
inputSchema: {
|
|
94
112
|
type: 'object',
|
|
95
113
|
properties: {
|
|
@@ -105,7 +123,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
|
105
123
|
// ── Optional: browse & explore ──
|
|
106
124
|
{
|
|
107
125
|
name: 'search_products',
|
|
108
|
-
description: TOOL_PREFIX + 'Browse products without triggering a recommendation. Use this only if the user explicitly wants to see a list of options. Each result item has `image_url` (absolute HTTPS URL) —
|
|
126
|
+
description: TOOL_PREFIX + 'Browse products without triggering a recommendation. Use this only if the user explicitly wants to see a list of options. Each result item has `image_url` (absolute HTTPS URL). The response includes a `_render_hint` field — follow it to pick markdown vs HTML-widget rendering for the current client (claude.ai/Desktop need an HTML widget; Claude Code/Cursor can use markdown ``).',
|
|
109
127
|
inputSchema: {
|
|
110
128
|
type: 'object',
|
|
111
129
|
properties: {
|
|
@@ -296,6 +314,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
296
314
|
alternatives: d.gift_picks.slice(1).map((p) => p.title),
|
|
297
315
|
};
|
|
298
316
|
return { content: [{ type: 'text', text: JSON.stringify({
|
|
317
|
+
_render_hint: RENDER_HINT,
|
|
299
318
|
type: 'gift_found',
|
|
300
319
|
advisor: advisorLabel,
|
|
301
320
|
advisor_profile: d.advisor_profile || null,
|
|
@@ -369,6 +388,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
369
388
|
else {
|
|
370
389
|
const hasPick = !!d.picked;
|
|
371
390
|
content.push({ type: 'text', text: JSON.stringify({
|
|
391
|
+
_render_hint: RENDER_HINT,
|
|
372
392
|
type: d.status || 'product_found',
|
|
373
393
|
advisor: advisorLabel,
|
|
374
394
|
advisor_profile: d.advisor_profile || null,
|
|
@@ -442,7 +462,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
442
462
|
result.data = result.data.map((p) => ({ ...p, image_url: imageUrlOf(p) }));
|
|
443
463
|
}
|
|
444
464
|
}
|
|
445
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
465
|
+
return { content: [{ type: 'text', text: JSON.stringify({ _render_hint: RENDER_HINT, ...result }, null, 2) }] };
|
|
446
466
|
}
|
|
447
467
|
case 'leave_review': {
|
|
448
468
|
const lang = args?.language;
|
|
@@ -515,6 +535,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
515
535
|
// Gift responses
|
|
516
536
|
if (d.status === 'gift_found' && d.gift_picks) {
|
|
517
537
|
return { content: [{ type: 'text', text: JSON.stringify({
|
|
538
|
+
_render_hint: RENDER_HINT,
|
|
518
539
|
type: 'gift_found',
|
|
519
540
|
advisor: d.advisor_profile?.name || d.agent?.personality || null,
|
|
520
541
|
advisor_profile: d.advisor_profile || null,
|
|
@@ -530,6 +551,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
530
551
|
// (no fallback to d.message — that produced bit-identical strings on no_match)
|
|
531
552
|
const hasPick = !!d.picked;
|
|
532
553
|
return { content: [{ type: 'text', text: JSON.stringify({
|
|
554
|
+
_render_hint: RENDER_HINT,
|
|
533
555
|
type: d.status || 'no_match',
|
|
534
556
|
advisor: d.advisor_profile?.name || d.agent?.personality || null,
|
|
535
557
|
advisor_profile: d.advisor_profile || null,
|
package/package.json
CHANGED