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.
- package/dist/index.js +52 -13
- 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 `` 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.';
|
|
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':
|
|
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:
|
|
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 `
|
|
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.' },
|
|
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).
|
|
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.',
|
|
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) —
|
|
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 ``).',
|
|
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(
|
|
580
|
+
console.error(`byclaw MCP server v${SERVER_VERSION} running on stdio`);
|
|
542
581
|
}
|
|
543
582
|
main().catch(console.error);
|
package/package.json
CHANGED