byclaw-mcp 0.4.11 → 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 +52 -13
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,6 +4,41 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
5
5
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
6
6
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
+ const fs_1 = require("fs");
8
+ const path_1 = require("path");
9
+ // Single source of truth for the version string. Used in serverInfo,
10
+ // startup banner, User-Agent, and as a [byclaw-mcp vX.Y.Z] prefix on
11
+ // every tool description so MCP clients (and the model running inside
12
+ // them) can see at a glance which version they're talking to without
13
+ // peeking at the MCP-internal serverInfo handshake metadata.
14
+ const SERVER_VERSION = (() => {
15
+ try {
16
+ const pkg = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '..', 'package.json'), 'utf8'));
17
+ return typeof pkg.version === 'string' ? pkg.version : '0.0.0';
18
+ }
19
+ catch {
20
+ return '0.0.0';
21
+ }
22
+ })();
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.';
7
42
  const API_BASE = process.env.BYCLAW_API_URL || 'https://byclaw.io';
8
43
  const API_KEY = process.env.BYCLAW_API_KEY || '';
9
44
  // Brand fallback when the catalog row has no image. Clients (Claude Desktop
@@ -22,7 +57,7 @@ async function fetchImageBase64(url) {
22
57
  try {
23
58
  const res = await fetch(url, {
24
59
  signal: AbortSignal.timeout(8000),
25
- headers: { 'User-Agent': 'Mozilla/5.0 (compatible; byclaw-mcp/0.4.11)' },
60
+ headers: { 'User-Agent': `Mozilla/5.0 (compatible; byclaw-mcp/${SERVER_VERSION})` },
26
61
  redirect: 'follow',
27
62
  });
28
63
  if (!res.ok)
@@ -50,7 +85,7 @@ async function apiCall(path, options = {}) {
50
85
  });
51
86
  return res.json();
52
87
  }
53
- const server = new index_js_1.Server({ name: 'byclaw', version: '0.4.11' }, { capabilities: { tools: {}, prompts: {} } });
88
+ const server = new index_js_1.Server({ name: 'byclaw', version: SERVER_VERSION }, { capabilities: { tools: {}, prompts: {} } });
54
89
  // Prompts
55
90
  server.setRequestHandler(types_js_1.ListPromptsRequestSchema, async () => ({
56
91
  prompts: [{
@@ -61,7 +96,7 @@ server.setRequestHandler(types_js_1.ListPromptsRequestSchema, async () => ({
61
96
  server.setRequestHandler(types_js_1.GetPromptRequestSchema, async () => ({
62
97
  messages: [{
63
98
  role: 'user',
64
- 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.' },
65
100
  }],
66
101
  }));
67
102
  // ═══════════════════════════════════════
@@ -72,7 +107,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
72
107
  // ── Core: one-shot shopping ──
73
108
  {
74
109
  name: 'shop',
75
- description: '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.',
76
111
  inputSchema: {
77
112
  type: 'object',
78
113
  properties: {
@@ -88,7 +123,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
88
123
  // ── Optional: browse & explore ──
89
124
  {
90
125
  name: 'search_products',
91
- description: '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)`).',
92
127
  inputSchema: {
93
128
  type: 'object',
94
129
  properties: {
@@ -103,7 +138,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
103
138
  },
104
139
  {
105
140
  name: 'leave_review',
106
- description: 'Write a product review. Only call if the user explicitly asks to leave a review. Only de and en allowed.',
141
+ description: TOOL_PREFIX + 'Write a product review. Only call if the user explicitly asks to leave a review. Only de and en allowed.',
107
142
  inputSchema: {
108
143
  type: 'object',
109
144
  properties: {
@@ -119,17 +154,17 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
119
154
  // ── Discovery ──
120
155
  {
121
156
  name: 'list_categories',
122
- description: 'Browse all available product categories.',
157
+ description: TOOL_PREFIX + 'Browse all available product categories.',
123
158
  inputSchema: { type: 'object', properties: {} },
124
159
  },
125
160
  {
126
161
  name: 'get_trending',
127
- description: 'Get trending products that agents are buying right now.',
162
+ description: TOOL_PREFIX + 'Get trending products that agents are buying right now.',
128
163
  inputSchema: { type: 'object', properties: {} },
129
164
  },
130
165
  {
131
166
  name: 'get_product_reviews',
132
- description: 'Get agent reviews for a product.',
167
+ description: TOOL_PREFIX + 'Get agent reviews for a product.',
133
168
  inputSchema: {
134
169
  type: 'object',
135
170
  properties: {
@@ -139,13 +174,13 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
139
174
  },
140
175
  {
141
176
  name: 'get_platform_stats',
142
- description: 'Get platform statistics: products compared, reviews written, recommendations sent.',
177
+ description: TOOL_PREFIX + 'Get platform statistics: products compared, reviews written, recommendations sent.',
143
178
  inputSchema: { type: 'object', properties: {} },
144
179
  },
145
180
  // ── QA / Testing ──
146
181
  {
147
182
  name: 'test_web_search',
148
- description: 'Test the web agent search endpoint (same as dashboard search). Use for QA to verify web search behavior vs MCP shop behavior.',
183
+ description: TOOL_PREFIX + 'Test the web agent search endpoint (same as dashboard search). Use for QA to verify web search behavior vs MCP shop behavior.',
149
184
  inputSchema: {
150
185
  type: 'object',
151
186
  properties: {
@@ -279,6 +314,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
279
314
  alternatives: d.gift_picks.slice(1).map((p) => p.title),
280
315
  };
281
316
  return { content: [{ type: 'text', text: JSON.stringify({
317
+ _render_hint: RENDER_HINT,
282
318
  type: 'gift_found',
283
319
  advisor: advisorLabel,
284
320
  advisor_profile: d.advisor_profile || null,
@@ -352,6 +388,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
352
388
  else {
353
389
  const hasPick = !!d.picked;
354
390
  content.push({ type: 'text', text: JSON.stringify({
391
+ _render_hint: RENDER_HINT,
355
392
  type: d.status || 'product_found',
356
393
  advisor: advisorLabel,
357
394
  advisor_profile: d.advisor_profile || null,
@@ -425,7 +462,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
425
462
  result.data = result.data.map((p) => ({ ...p, image_url: imageUrlOf(p) }));
426
463
  }
427
464
  }
428
- 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) }] };
429
466
  }
430
467
  case 'leave_review': {
431
468
  const lang = args?.language;
@@ -498,6 +535,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
498
535
  // Gift responses
499
536
  if (d.status === 'gift_found' && d.gift_picks) {
500
537
  return { content: [{ type: 'text', text: JSON.stringify({
538
+ _render_hint: RENDER_HINT,
501
539
  type: 'gift_found',
502
540
  advisor: d.advisor_profile?.name || d.agent?.personality || null,
503
541
  advisor_profile: d.advisor_profile || null,
@@ -513,6 +551,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
513
551
  // (no fallback to d.message — that produced bit-identical strings on no_match)
514
552
  const hasPick = !!d.picked;
515
553
  return { content: [{ type: 'text', text: JSON.stringify({
554
+ _render_hint: RENDER_HINT,
516
555
  type: d.status || 'no_match',
517
556
  advisor: d.advisor_profile?.name || d.agent?.personality || null,
518
557
  advisor_profile: d.advisor_profile || null,
@@ -538,6 +577,6 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
538
577
  async function main() {
539
578
  const transport = new stdio_js_1.StdioServerTransport();
540
579
  await server.connect(transport);
541
- console.error('byclaw MCP server v0.4.11 running on stdio');
580
+ console.error(`byclaw MCP server v${SERVER_VERSION} running on stdio`);
542
581
  }
543
582
  main().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "byclaw-mcp",
3
- "version": "0.4.11",
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": {