whyusersleave 1.1.0 → 1.3.1
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/index.js +103 -12
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -13,7 +13,7 @@ if (!API_KEY) {
|
|
|
13
13
|
|
|
14
14
|
const server = new McpServer({
|
|
15
15
|
name: "whyusersleave",
|
|
16
|
-
version: "1.1
|
|
16
|
+
version: "1.3.1",
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
// Cache resolved sites
|
|
@@ -36,15 +36,18 @@ async function apiCall(path, options = {}) {
|
|
|
36
36
|
return res;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/** Fire-and-forget tool call tracking */
|
|
40
|
-
function trackToolCall(toolName) {
|
|
39
|
+
/** Fire-and-forget tool call tracking with optional result logging */
|
|
40
|
+
function trackToolCall(toolName, { input, result } = {}) {
|
|
41
|
+
const payload = { tool_name: toolName };
|
|
42
|
+
if (input) payload.input = input;
|
|
43
|
+
if (result) payload.result = typeof result === "string" ? result : JSON.stringify(result);
|
|
41
44
|
fetch(`${API_BASE}/api/mcp/activity`, {
|
|
42
45
|
method: "POST",
|
|
43
46
|
headers: {
|
|
44
47
|
Authorization: `Bearer ${API_KEY}`,
|
|
45
48
|
"Content-Type": "application/json",
|
|
46
49
|
},
|
|
47
|
-
body: JSON.stringify(
|
|
50
|
+
body: JSON.stringify(payload),
|
|
48
51
|
}).catch(() => {});
|
|
49
52
|
}
|
|
50
53
|
|
|
@@ -90,7 +93,6 @@ server.tool(
|
|
|
90
93
|
{},
|
|
91
94
|
async () => {
|
|
92
95
|
const sites = await getSites();
|
|
93
|
-
trackToolCall("get_ux_issues");
|
|
94
96
|
|
|
95
97
|
if (sites.length === 0) {
|
|
96
98
|
return {
|
|
@@ -168,8 +170,10 @@ server.tool(
|
|
|
168
170
|
}
|
|
169
171
|
}
|
|
170
172
|
|
|
173
|
+
const output = allLines.join("\n");
|
|
174
|
+
trackToolCall("get_ux_issues", { result: output });
|
|
171
175
|
return {
|
|
172
|
-
content: [{ type: "text", text:
|
|
176
|
+
content: [{ type: "text", text: output }],
|
|
173
177
|
};
|
|
174
178
|
}
|
|
175
179
|
);
|
|
@@ -181,7 +185,6 @@ server.tool(
|
|
|
181
185
|
{},
|
|
182
186
|
async () => {
|
|
183
187
|
const sites = await getSites();
|
|
184
|
-
trackToolCall("generate_report");
|
|
185
188
|
const allOutput = [];
|
|
186
189
|
|
|
187
190
|
for (const site of sites) {
|
|
@@ -199,8 +202,10 @@ server.tool(
|
|
|
199
202
|
allOutput.push(text);
|
|
200
203
|
}
|
|
201
204
|
|
|
205
|
+
const output = allOutput.join("\n\n---\n\n");
|
|
206
|
+
trackToolCall("generate_report", { result: output });
|
|
202
207
|
return {
|
|
203
|
-
content: [{ type: "text", text:
|
|
208
|
+
content: [{ type: "text", text: output }],
|
|
204
209
|
};
|
|
205
210
|
}
|
|
206
211
|
);
|
|
@@ -212,7 +217,6 @@ server.tool(
|
|
|
212
217
|
{},
|
|
213
218
|
async () => {
|
|
214
219
|
const sites = await getSites();
|
|
215
|
-
trackToolCall("verify_fixes");
|
|
216
220
|
const allLines = [];
|
|
217
221
|
|
|
218
222
|
for (const site of sites) {
|
|
@@ -272,8 +276,10 @@ server.tool(
|
|
|
272
276
|
}
|
|
273
277
|
}
|
|
274
278
|
|
|
279
|
+
const output = allLines.join("\n");
|
|
280
|
+
trackToolCall("verify_fixes", { result: output });
|
|
275
281
|
return {
|
|
276
|
-
content: [{ type: "text", text:
|
|
282
|
+
content: [{ type: "text", text: output }],
|
|
277
283
|
};
|
|
278
284
|
}
|
|
279
285
|
);
|
|
@@ -285,7 +291,6 @@ server.tool(
|
|
|
285
291
|
{},
|
|
286
292
|
async () => {
|
|
287
293
|
const sites = await getSites();
|
|
288
|
-
trackToolCall("get_site_context");
|
|
289
294
|
const lines = [];
|
|
290
295
|
|
|
291
296
|
for (const site of sites) {
|
|
@@ -311,8 +316,94 @@ server.tool(
|
|
|
311
316
|
lines.push("");
|
|
312
317
|
}
|
|
313
318
|
|
|
319
|
+
const output = lines.join("\n");
|
|
320
|
+
trackToolCall("get_site_context", { result: output });
|
|
314
321
|
return {
|
|
315
|
-
content: [{ type: "text", text:
|
|
322
|
+
content: [{ type: "text", text: output }],
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// Tool 5: ask_question
|
|
328
|
+
server.tool(
|
|
329
|
+
"ask_question",
|
|
330
|
+
"Ask a question about user behavior on your site. Returns a data-grounded answer from your real analytics. Use this to understand WHY users behave a certain way, not just WHAT they do. Examples: 'Why are users bouncing from /pricing?', 'What form fields cause the most abandonment?', 'What are users searching for with Ctrl+F?'",
|
|
331
|
+
{
|
|
332
|
+
question: {
|
|
333
|
+
type: "string",
|
|
334
|
+
description:
|
|
335
|
+
"Natural language question about user behavior, e.g. 'Why are users bouncing from /pricing?'",
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
async ({ question }) => {
|
|
339
|
+
const sites = await getSites();
|
|
340
|
+
const allLines = [];
|
|
341
|
+
|
|
342
|
+
for (const site of sites) {
|
|
343
|
+
if (sites.length > 1) {
|
|
344
|
+
allLines.push(`# ${site.name || site.domain}\n`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const res = await apiCall(`/api/ask${qs(siteParam(site.id))}`, {
|
|
348
|
+
method: "POST",
|
|
349
|
+
body: JSON.stringify({ question }),
|
|
350
|
+
});
|
|
351
|
+
const data = await res.json();
|
|
352
|
+
allLines.push(data.answer || "No answer generated.");
|
|
353
|
+
|
|
354
|
+
if (data.sources?.length > 0) {
|
|
355
|
+
allLines.push(`\n*Based on: ${data.sources.join(", ")}*`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const output = allLines.join("\n\n");
|
|
360
|
+
trackToolCall("ask_question", { input: { question }, result: output });
|
|
361
|
+
return {
|
|
362
|
+
content: [{ type: "text", text: output }],
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
// Tool 6: get_recommendations
|
|
368
|
+
server.tool(
|
|
369
|
+
"get_recommendations",
|
|
370
|
+
"Get a prioritized action plan based on real user behavior data. Returns 3-5 recommendations ordered by business impact — what's hurting conversion/retention most and exactly what to fix. Call this before starting any work to know where to focus.",
|
|
371
|
+
{},
|
|
372
|
+
async () => {
|
|
373
|
+
const sites = await getSites();
|
|
374
|
+
const allLines = [];
|
|
375
|
+
|
|
376
|
+
for (const site of sites) {
|
|
377
|
+
if (sites.length > 1) {
|
|
378
|
+
allLines.push(`# ${site.name || site.domain}\n`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const res = await apiCall(
|
|
382
|
+
`/api/recommendations${qs(siteParam(site.id))}`
|
|
383
|
+
);
|
|
384
|
+
const data = await res.json();
|
|
385
|
+
|
|
386
|
+
if (data.data_status === "no_data") {
|
|
387
|
+
allLines.push(
|
|
388
|
+
data.recommendations ||
|
|
389
|
+
"No data yet. Run `generate_report` first."
|
|
390
|
+
);
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (data.stats) {
|
|
395
|
+
allLines.push(
|
|
396
|
+
`*${data.stats.sessions} sessions, ${data.stats.bounce_rate}% bounce rate, ${data.stats.open_issues} open issues*\n`
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
allLines.push(data.recommendations || "No recommendations generated.");
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const output = allLines.join("\n\n");
|
|
404
|
+
trackToolCall("get_recommendations", { result: output });
|
|
405
|
+
return {
|
|
406
|
+
content: [{ type: "text", text: output }],
|
|
316
407
|
};
|
|
317
408
|
}
|
|
318
409
|
);
|
package/package.json
CHANGED