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.
Files changed (2) hide show
  1. package/dist/index.js +26 -4
  2. 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 `![title](image_url)` 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 `![title](image_url)` 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 `disclosure` (string).\n\nWhen presenting the result:\n1. Render the picked product image inline using markdown image syntax: `![title](image_url)`. Do the same for every alternative users need to SEE what they\'re being recommended.\n2. Show the recommended product with its price and the advisor\'s reasoning\n3. Link the recommendation using its `affiliate_url` field (format: "<merchant> →" as a clickable link)\n4. List every alternative with its title, price, merchant and link each using its `affiliate_url` field — do not show alternatives as plain text, they must be clickable links and visible images\n5. ALWAYS display the `disclosure` text verbatim at the end, as a small italic note — this is legally required\n\nThe server also emits native MCP image content blocks alongside the JSON for clients that render them natively (Claude Code). Markdown-only clients (Claude Desktop today) should render via `image_url`. If your client supports both, prefer markdown so the order matches the textual context.\n\nbyclaw.io is operated by NJUDEV S.L., a European company.' },
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 `![title](image_url)` 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). Render every product image inline using markdown image syntax: `![title](image_url)`. The server also emits native MCP image content blocks for clients that render them natively — use whichever your client supports, prefer markdown when in doubt.',
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 `![title](image_url)` 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) — render it inline with markdown `![title](image_url)` so users see what each product looks like.',
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 `![title](image_url)`).',
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "byclaw-mcp",
3
- "version": "0.4.12",
3
+ "version": "0.4.13",
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": {