@sentientui/mcp 0.8.1 → 0.9.0
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/{chunk-TLBLOBSB.js → chunk-QESH43WV.js} +147 -59
- package/dist/index.cjs +147 -62
- package/dist/index.js +1 -1
- package/dist/lib.cjs +150 -63
- package/dist/lib.js +1 -1
- package/package.json +1 -1
|
@@ -47,7 +47,6 @@ var ApiClient = class {
|
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
// src/server.ts
|
|
50
|
-
import { createRequire } from "module";
|
|
51
50
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
52
51
|
|
|
53
52
|
// src/tools/projects.ts
|
|
@@ -56,7 +55,10 @@ import { z as z2 } from "zod";
|
|
|
56
55
|
// src/tools/common.ts
|
|
57
56
|
import { z } from "zod";
|
|
58
57
|
var projectIdSchema = z.string().uuid().describe("The project UUID");
|
|
59
|
-
function apiErrorGuidance(err) {
|
|
58
|
+
function apiErrorGuidance(err, extra) {
|
|
59
|
+
if (extra && Object.prototype.hasOwnProperty.call(extra, err.message)) {
|
|
60
|
+
return extra[err.message];
|
|
61
|
+
}
|
|
60
62
|
switch (err.message) {
|
|
61
63
|
case "insufficient_scope":
|
|
62
64
|
return "This action needs an account login. Connect via the hosted MCP URL (https://api.sentient-ui.com/mcp) and sign in \u2014 a project-scoped server key (sk_\u2026) or anonymous demo token cannot do this.";
|
|
@@ -75,13 +77,13 @@ function apiErrorGuidance(err) {
|
|
|
75
77
|
}
|
|
76
78
|
return null;
|
|
77
79
|
}
|
|
78
|
-
function withApiErrorGuidance(fn) {
|
|
80
|
+
function withApiErrorGuidance(fn, extra) {
|
|
79
81
|
return async (args) => {
|
|
80
82
|
try {
|
|
81
83
|
return await fn(args);
|
|
82
84
|
} catch (err) {
|
|
83
85
|
if (err instanceof ApiError) {
|
|
84
|
-
const guidance = apiErrorGuidance(err);
|
|
86
|
+
const guidance = apiErrorGuidance(err, extra);
|
|
85
87
|
if (guidance) {
|
|
86
88
|
return { content: [{ type: "text", text: guidance }], isError: true };
|
|
87
89
|
}
|
|
@@ -92,22 +94,10 @@ function withApiErrorGuidance(fn) {
|
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
// src/tools/projects.ts
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
case "demo_read_only":
|
|
100
|
-
return "Demo mode is read-only. Create a SentientUI account and sign in to make projects.";
|
|
101
|
-
case "insufficient_role":
|
|
102
|
-
return "Your account role cannot create projects \u2014 this needs the account owner or an admin.";
|
|
103
|
-
case "project_limit_reached":
|
|
104
|
-
return "You've reached your plan's project limit. Upgrade your plan or remove an existing project, then try again.";
|
|
105
|
-
case "name_required":
|
|
106
|
-
return "A project name is required to create a project.";
|
|
107
|
-
default:
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
97
|
+
var CREATE_PROJECT_GUIDANCE = {
|
|
98
|
+
project_limit_reached: "You've reached your plan's project limit. Upgrade your plan or remove an existing project, then try again.",
|
|
99
|
+
name_required: "A project name is required to create a project."
|
|
100
|
+
};
|
|
111
101
|
function registerProjectTools(server, client) {
|
|
112
102
|
server.registerTool(
|
|
113
103
|
"create_project",
|
|
@@ -133,41 +123,31 @@ function registerProjectTools(server, client) {
|
|
|
133
123
|
openWorldHint: false
|
|
134
124
|
}
|
|
135
125
|
},
|
|
136
|
-
async ({ name, contextType, framework, websiteUrl }) => {
|
|
137
|
-
|
|
138
|
-
|
|
126
|
+
withApiErrorGuidance(async ({ name, contextType, framework, websiteUrl }) => {
|
|
127
|
+
const created = await client.post("/projects", {
|
|
128
|
+
name,
|
|
129
|
+
contextType,
|
|
130
|
+
framework,
|
|
131
|
+
origin: websiteUrl
|
|
132
|
+
});
|
|
133
|
+
const resolvedContextType = contextType != null ? contextType : "saas";
|
|
134
|
+
return {
|
|
135
|
+
content: [{
|
|
136
|
+
type: "text",
|
|
137
|
+
text: [
|
|
138
|
+
`Created project "${name}" (id: ${created.id}, type: ${resolvedContextType}).`,
|
|
139
|
+
`Public key: ${created.apiKey}`,
|
|
140
|
+
`Next: install @sentientui/react with this key. Ask me to pull the setup guide (get_integration_guide) and I'll wrap your first component.`
|
|
141
|
+
].join("\n")
|
|
142
|
+
}],
|
|
143
|
+
structuredContent: {
|
|
144
|
+
projectId: created.id,
|
|
145
|
+
publicKey: created.apiKey,
|
|
139
146
|
name,
|
|
140
|
-
contextType
|
|
141
|
-
framework,
|
|
142
|
-
origin: websiteUrl
|
|
143
|
-
});
|
|
144
|
-
const resolvedContextType = contextType != null ? contextType : "saas";
|
|
145
|
-
return {
|
|
146
|
-
content: [{
|
|
147
|
-
type: "text",
|
|
148
|
-
text: [
|
|
149
|
-
`Created project "${name}" (id: ${created.id}, type: ${resolvedContextType}).`,
|
|
150
|
-
`Public key: ${created.apiKey}`,
|
|
151
|
-
`Next: install @sentientui/react with this key. Ask me to pull the setup guide (get_integration_guide) and I'll wrap your first component.`
|
|
152
|
-
].join("\n")
|
|
153
|
-
}],
|
|
154
|
-
structuredContent: {
|
|
155
|
-
projectId: created.id,
|
|
156
|
-
publicKey: created.apiKey,
|
|
157
|
-
name,
|
|
158
|
-
contextType: resolvedContextType
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
} catch (err) {
|
|
162
|
-
if (err instanceof ApiError) {
|
|
163
|
-
const guidance = createProjectGuidance(err);
|
|
164
|
-
if (guidance) {
|
|
165
|
-
return { content: [{ type: "text", text: guidance }], isError: true };
|
|
166
|
-
}
|
|
147
|
+
contextType: resolvedContextType
|
|
167
148
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
149
|
+
};
|
|
150
|
+
}, CREATE_PROJECT_GUIDANCE)
|
|
171
151
|
);
|
|
172
152
|
server.registerTool(
|
|
173
153
|
"list_projects",
|
|
@@ -919,13 +899,16 @@ function registerVariantWriteTools(server, client) {
|
|
|
919
899
|
description: "Create a NO-CODE managed text variant for a component (content stored in SentientUI, rendered by <AdaptiveText>). Use this ONLY for text-only variants the user wants without a code change. For variants that will live in the codebase (full components \u2014 copy, markup, styling), use get_variant_brief and write the variant in code instead; those auto-register on deploy and do not need create_variant. Requires a paid plan (server keys are Starter+; anonymous demo tokens are read-only).",
|
|
920
900
|
inputSchema: {
|
|
921
901
|
projectId: projectIdSchema,
|
|
922
|
-
componentId: z9.string().describe("The component ID to add a variant to"),
|
|
923
|
-
displayName: z9.string().describe("Human-readable name for the new variant"),
|
|
924
|
-
content: z9.string().optional().describe("The text content for this managed variant (rendered by <AdaptiveText>). Generate it from get_variant_brief context; omit only to create an empty placeholder to fill in from the dashboard.")
|
|
902
|
+
componentId: z9.string().min(1).max(200).describe("The component ID to add a variant to"),
|
|
903
|
+
displayName: z9.string().min(1).max(200).describe("Human-readable name for the new variant"),
|
|
904
|
+
content: z9.string().max(1e4).optional().describe("The text content for this managed variant (rendered by <AdaptiveText>). Generate it from get_variant_brief context; omit only to create an empty placeholder to fill in from the dashboard.")
|
|
925
905
|
},
|
|
926
906
|
outputSchema: {
|
|
927
907
|
variantId: z9.string().describe("The new variant ID"),
|
|
928
|
-
displayName
|
|
908
|
+
// API returns `body.displayName ?? null`, so a successful create can
|
|
909
|
+
// carry a null name — match that contract or outputSchema validation
|
|
910
|
+
// would reject an otherwise-successful response.
|
|
911
|
+
displayName: z9.string().nullable(),
|
|
929
912
|
componentId: z9.string(),
|
|
930
913
|
state: z9.literal("draft").describe("New managed variants start in draft state"),
|
|
931
914
|
hasContent: z9.boolean().describe("Whether text content was provided at creation")
|
|
@@ -1418,9 +1401,113 @@ function registerIntegrationGuideTools(server) {
|
|
|
1418
1401
|
);
|
|
1419
1402
|
}
|
|
1420
1403
|
|
|
1404
|
+
// src/tools/agent-traffic.ts
|
|
1405
|
+
import { z as z13 } from "zod";
|
|
1406
|
+
var PLAN_GATE_GUIDANCE = {
|
|
1407
|
+
agent_analytics_requires_paid_plan: "Agent analytics requires a paid SentientUI plan (Starter or above). Upgrade at https://sentient-ui.com, then try again."
|
|
1408
|
+
};
|
|
1409
|
+
function registerAgentTrafficTools(server, client) {
|
|
1410
|
+
server.registerTool(
|
|
1411
|
+
"get_agent_traffic",
|
|
1412
|
+
{
|
|
1413
|
+
title: "Agent traffic",
|
|
1414
|
+
description: "Which AI agents and crawlers are reading this site: totals by type (passive crawlers, agentic browsers, agent API calls), engine breakdown, and the paths they fetch most. Agent traffic is tracked separately and never counted in conversion rate.",
|
|
1415
|
+
inputSchema: { projectId: projectIdSchema },
|
|
1416
|
+
outputSchema: {
|
|
1417
|
+
totals: z13.object({ crawler: z13.number(), api: z13.number(), browser: z13.number() }),
|
|
1418
|
+
engines: z13.array(
|
|
1419
|
+
z13.object({
|
|
1420
|
+
engine: z13.string(),
|
|
1421
|
+
count: z13.number(),
|
|
1422
|
+
sharePct: z13.number(),
|
|
1423
|
+
lastSeen: z13.string(),
|
|
1424
|
+
firstSeenInRange: z13.boolean().describe("First observed within the queried period")
|
|
1425
|
+
})
|
|
1426
|
+
),
|
|
1427
|
+
topPaths: z13.array(z13.object({ path: z13.string(), count: z13.number(), engines: z13.number() }))
|
|
1428
|
+
},
|
|
1429
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
1430
|
+
},
|
|
1431
|
+
withApiErrorGuidance(async ({ projectId }) => {
|
|
1432
|
+
const id = encodeURIComponent(projectId);
|
|
1433
|
+
const s = await client.get(`/projects/${id}/agent-activity/summary`);
|
|
1434
|
+
const structuredContent = { totals: s.totals, engines: s.engines, topPaths: s.topPaths };
|
|
1435
|
+
const total = s.totals.crawler + s.totals.api + s.totals.browser;
|
|
1436
|
+
if (total === 0) {
|
|
1437
|
+
return {
|
|
1438
|
+
content: [{
|
|
1439
|
+
type: "text",
|
|
1440
|
+
text: "No agent traffic observed yet. Passive crawlers (GPTBot, ClaudeBot, \u2026) run no JavaScript \u2014 install sentientAgentMiddleware from @sentientui/react/next to capture them server-side."
|
|
1441
|
+
}],
|
|
1442
|
+
structuredContent
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
const lines = [
|
|
1446
|
+
`Agent traffic: ${s.totals.crawler} crawler fetches, ${s.totals.api} agent API calls, ${s.totals.browser} agentic browser sessions.`,
|
|
1447
|
+
"",
|
|
1448
|
+
"Engines:",
|
|
1449
|
+
...s.engines.map((e) => `- ${e.engine}: ${e.count} fetches (${e.sharePct}%)${e.firstSeenInRange ? " \u2014 NEW this period" : ""}`),
|
|
1450
|
+
"",
|
|
1451
|
+
"Most-fetched paths:",
|
|
1452
|
+
...s.topPaths.map((p) => `- ${p.path}: ${p.count} fetches by ${p.engines} engine(s)`)
|
|
1453
|
+
];
|
|
1454
|
+
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
1455
|
+
}, PLAN_GATE_GUIDANCE)
|
|
1456
|
+
);
|
|
1457
|
+
server.registerTool(
|
|
1458
|
+
"get_agent_legibility",
|
|
1459
|
+
{
|
|
1460
|
+
title: "Agent legibility",
|
|
1461
|
+
description: "Whether the pages AI agents actually read are machine-legible: per-path checks for price, product name, positioning, and CTA in the server HTML, plus agent API blocks served without agent data. Each failure comes with a concrete fix.",
|
|
1462
|
+
inputSchema: { projectId: projectIdSchema },
|
|
1463
|
+
outputSchema: {
|
|
1464
|
+
paths: z13.array(
|
|
1465
|
+
z13.object({
|
|
1466
|
+
path: z13.string(),
|
|
1467
|
+
score: z13.number().describe("0\u2013100, 25 per passing check"),
|
|
1468
|
+
checks: z13.object({
|
|
1469
|
+
price: z13.boolean(),
|
|
1470
|
+
name: z13.boolean(),
|
|
1471
|
+
positioning: z13.boolean(),
|
|
1472
|
+
cta: z13.boolean(),
|
|
1473
|
+
notes: z13.array(z13.string())
|
|
1474
|
+
}),
|
|
1475
|
+
lastChecked: z13.string()
|
|
1476
|
+
})
|
|
1477
|
+
),
|
|
1478
|
+
emptyBlocks: z13.array(z13.object({ block: z13.string(), variant: z13.string(), occurrences: z13.number() }))
|
|
1479
|
+
},
|
|
1480
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
1481
|
+
},
|
|
1482
|
+
withApiErrorGuidance(async ({ projectId }) => {
|
|
1483
|
+
const id = encodeURIComponent(projectId);
|
|
1484
|
+
const l = await client.get(`/projects/${id}/agent-activity/legibility`);
|
|
1485
|
+
const structuredContent = { paths: l.paths, emptyBlocks: l.emptyBlocks };
|
|
1486
|
+
if (l.paths.length === 0 && l.emptyBlocks.length === 0) {
|
|
1487
|
+
return {
|
|
1488
|
+
content: [{ type: "text", text: "No legibility results yet \u2014 they appear once AI crawlers start fetching pages (checked daily)." }],
|
|
1489
|
+
structuredContent
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
const lines = [
|
|
1493
|
+
"Legibility of agent-read paths:",
|
|
1494
|
+
...l.paths.map((p) => {
|
|
1495
|
+
const failed = ["price", "name", "positioning", "cta"].filter((k) => !p.checks[k]);
|
|
1496
|
+
return `- ${p.path}: ${p.score}/100${failed.length ? ` \u2014 missing: ${failed.join(", ")}` : " \u2014 fully legible"}`;
|
|
1497
|
+
}),
|
|
1498
|
+
...l.paths.flatMap((p) => p.checks.notes.map((n) => ` fix (${p.path}): ${n}`))
|
|
1499
|
+
];
|
|
1500
|
+
if (l.emptyBlocks.length > 0) {
|
|
1501
|
+
lines.push("", "Agent API blocks served without agent data (add agentDataByVariant):");
|
|
1502
|
+
lines.push(...l.emptyBlocks.map((b) => `- ${b.block} (variant ${b.variant}): ${b.occurrences} calls`));
|
|
1503
|
+
}
|
|
1504
|
+
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
1505
|
+
}, PLAN_GATE_GUIDANCE)
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1421
1509
|
// src/server.ts
|
|
1422
|
-
var
|
|
1423
|
-
var { version: PKG_VERSION } = createRequire(import_meta.url)("../package.json");
|
|
1510
|
+
var PKG_VERSION = true ? "0.9.0" : "0.0.0-dev";
|
|
1424
1511
|
function createMcpServer(client) {
|
|
1425
1512
|
const server = new McpServer(
|
|
1426
1513
|
{
|
|
@@ -1453,6 +1540,7 @@ function createMcpServer(client) {
|
|
|
1453
1540
|
registerTestBriefTools(server, client);
|
|
1454
1541
|
registerVariantWriteTools(server, client);
|
|
1455
1542
|
registerIntegrationGuideTools(server);
|
|
1543
|
+
registerAgentTrafficTools(server, client);
|
|
1456
1544
|
return server;
|
|
1457
1545
|
}
|
|
1458
1546
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
-
// ../../node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.14_tsx@4.22.1_typescript@5.9.3_yaml@2.9.0/node_modules/tsup/assets/cjs_shims.js
|
|
5
|
-
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
6
|
-
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
7
|
-
|
|
8
4
|
// src/index.ts
|
|
9
5
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
10
6
|
|
|
@@ -55,7 +51,6 @@ var ApiClient = class {
|
|
|
55
51
|
};
|
|
56
52
|
|
|
57
53
|
// src/server.ts
|
|
58
|
-
var import_node_module = require("module");
|
|
59
54
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
60
55
|
|
|
61
56
|
// src/tools/projects.ts
|
|
@@ -64,7 +59,10 @@ var import_zod2 = require("zod");
|
|
|
64
59
|
// src/tools/common.ts
|
|
65
60
|
var import_zod = require("zod");
|
|
66
61
|
var projectIdSchema = import_zod.z.string().uuid().describe("The project UUID");
|
|
67
|
-
function apiErrorGuidance(err) {
|
|
62
|
+
function apiErrorGuidance(err, extra) {
|
|
63
|
+
if (extra && Object.prototype.hasOwnProperty.call(extra, err.message)) {
|
|
64
|
+
return extra[err.message];
|
|
65
|
+
}
|
|
68
66
|
switch (err.message) {
|
|
69
67
|
case "insufficient_scope":
|
|
70
68
|
return "This action needs an account login. Connect via the hosted MCP URL (https://api.sentient-ui.com/mcp) and sign in \u2014 a project-scoped server key (sk_\u2026) or anonymous demo token cannot do this.";
|
|
@@ -83,13 +81,13 @@ function apiErrorGuidance(err) {
|
|
|
83
81
|
}
|
|
84
82
|
return null;
|
|
85
83
|
}
|
|
86
|
-
function withApiErrorGuidance(fn) {
|
|
84
|
+
function withApiErrorGuidance(fn, extra) {
|
|
87
85
|
return async (args) => {
|
|
88
86
|
try {
|
|
89
87
|
return await fn(args);
|
|
90
88
|
} catch (err) {
|
|
91
89
|
if (err instanceof ApiError) {
|
|
92
|
-
const guidance = apiErrorGuidance(err);
|
|
90
|
+
const guidance = apiErrorGuidance(err, extra);
|
|
93
91
|
if (guidance) {
|
|
94
92
|
return { content: [{ type: "text", text: guidance }], isError: true };
|
|
95
93
|
}
|
|
@@ -100,22 +98,10 @@ function withApiErrorGuidance(fn) {
|
|
|
100
98
|
}
|
|
101
99
|
|
|
102
100
|
// src/tools/projects.ts
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
case "demo_read_only":
|
|
108
|
-
return "Demo mode is read-only. Create a SentientUI account and sign in to make projects.";
|
|
109
|
-
case "insufficient_role":
|
|
110
|
-
return "Your account role cannot create projects \u2014 this needs the account owner or an admin.";
|
|
111
|
-
case "project_limit_reached":
|
|
112
|
-
return "You've reached your plan's project limit. Upgrade your plan or remove an existing project, then try again.";
|
|
113
|
-
case "name_required":
|
|
114
|
-
return "A project name is required to create a project.";
|
|
115
|
-
default:
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
101
|
+
var CREATE_PROJECT_GUIDANCE = {
|
|
102
|
+
project_limit_reached: "You've reached your plan's project limit. Upgrade your plan or remove an existing project, then try again.",
|
|
103
|
+
name_required: "A project name is required to create a project."
|
|
104
|
+
};
|
|
119
105
|
function registerProjectTools(server, client) {
|
|
120
106
|
server.registerTool(
|
|
121
107
|
"create_project",
|
|
@@ -141,41 +127,31 @@ function registerProjectTools(server, client) {
|
|
|
141
127
|
openWorldHint: false
|
|
142
128
|
}
|
|
143
129
|
},
|
|
144
|
-
async ({ name, contextType, framework, websiteUrl }) => {
|
|
145
|
-
|
|
146
|
-
|
|
130
|
+
withApiErrorGuidance(async ({ name, contextType, framework, websiteUrl }) => {
|
|
131
|
+
const created = await client.post("/projects", {
|
|
132
|
+
name,
|
|
133
|
+
contextType,
|
|
134
|
+
framework,
|
|
135
|
+
origin: websiteUrl
|
|
136
|
+
});
|
|
137
|
+
const resolvedContextType = contextType != null ? contextType : "saas";
|
|
138
|
+
return {
|
|
139
|
+
content: [{
|
|
140
|
+
type: "text",
|
|
141
|
+
text: [
|
|
142
|
+
`Created project "${name}" (id: ${created.id}, type: ${resolvedContextType}).`,
|
|
143
|
+
`Public key: ${created.apiKey}`,
|
|
144
|
+
`Next: install @sentientui/react with this key. Ask me to pull the setup guide (get_integration_guide) and I'll wrap your first component.`
|
|
145
|
+
].join("\n")
|
|
146
|
+
}],
|
|
147
|
+
structuredContent: {
|
|
148
|
+
projectId: created.id,
|
|
149
|
+
publicKey: created.apiKey,
|
|
147
150
|
name,
|
|
148
|
-
contextType
|
|
149
|
-
framework,
|
|
150
|
-
origin: websiteUrl
|
|
151
|
-
});
|
|
152
|
-
const resolvedContextType = contextType != null ? contextType : "saas";
|
|
153
|
-
return {
|
|
154
|
-
content: [{
|
|
155
|
-
type: "text",
|
|
156
|
-
text: [
|
|
157
|
-
`Created project "${name}" (id: ${created.id}, type: ${resolvedContextType}).`,
|
|
158
|
-
`Public key: ${created.apiKey}`,
|
|
159
|
-
`Next: install @sentientui/react with this key. Ask me to pull the setup guide (get_integration_guide) and I'll wrap your first component.`
|
|
160
|
-
].join("\n")
|
|
161
|
-
}],
|
|
162
|
-
structuredContent: {
|
|
163
|
-
projectId: created.id,
|
|
164
|
-
publicKey: created.apiKey,
|
|
165
|
-
name,
|
|
166
|
-
contextType: resolvedContextType
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
} catch (err) {
|
|
170
|
-
if (err instanceof ApiError) {
|
|
171
|
-
const guidance = createProjectGuidance(err);
|
|
172
|
-
if (guidance) {
|
|
173
|
-
return { content: [{ type: "text", text: guidance }], isError: true };
|
|
174
|
-
}
|
|
151
|
+
contextType: resolvedContextType
|
|
175
152
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
153
|
+
};
|
|
154
|
+
}, CREATE_PROJECT_GUIDANCE)
|
|
179
155
|
);
|
|
180
156
|
server.registerTool(
|
|
181
157
|
"list_projects",
|
|
@@ -927,13 +903,16 @@ function registerVariantWriteTools(server, client) {
|
|
|
927
903
|
description: "Create a NO-CODE managed text variant for a component (content stored in SentientUI, rendered by <AdaptiveText>). Use this ONLY for text-only variants the user wants without a code change. For variants that will live in the codebase (full components \u2014 copy, markup, styling), use get_variant_brief and write the variant in code instead; those auto-register on deploy and do not need create_variant. Requires a paid plan (server keys are Starter+; anonymous demo tokens are read-only).",
|
|
928
904
|
inputSchema: {
|
|
929
905
|
projectId: projectIdSchema,
|
|
930
|
-
componentId: import_zod9.z.string().describe("The component ID to add a variant to"),
|
|
931
|
-
displayName: import_zod9.z.string().describe("Human-readable name for the new variant"),
|
|
932
|
-
content: import_zod9.z.string().optional().describe("The text content for this managed variant (rendered by <AdaptiveText>). Generate it from get_variant_brief context; omit only to create an empty placeholder to fill in from the dashboard.")
|
|
906
|
+
componentId: import_zod9.z.string().min(1).max(200).describe("The component ID to add a variant to"),
|
|
907
|
+
displayName: import_zod9.z.string().min(1).max(200).describe("Human-readable name for the new variant"),
|
|
908
|
+
content: import_zod9.z.string().max(1e4).optional().describe("The text content for this managed variant (rendered by <AdaptiveText>). Generate it from get_variant_brief context; omit only to create an empty placeholder to fill in from the dashboard.")
|
|
933
909
|
},
|
|
934
910
|
outputSchema: {
|
|
935
911
|
variantId: import_zod9.z.string().describe("The new variant ID"),
|
|
936
|
-
displayName
|
|
912
|
+
// API returns `body.displayName ?? null`, so a successful create can
|
|
913
|
+
// carry a null name — match that contract or outputSchema validation
|
|
914
|
+
// would reject an otherwise-successful response.
|
|
915
|
+
displayName: import_zod9.z.string().nullable(),
|
|
937
916
|
componentId: import_zod9.z.string(),
|
|
938
917
|
state: import_zod9.z.literal("draft").describe("New managed variants start in draft state"),
|
|
939
918
|
hasContent: import_zod9.z.boolean().describe("Whether text content was provided at creation")
|
|
@@ -1426,8 +1405,113 @@ function registerIntegrationGuideTools(server) {
|
|
|
1426
1405
|
);
|
|
1427
1406
|
}
|
|
1428
1407
|
|
|
1408
|
+
// src/tools/agent-traffic.ts
|
|
1409
|
+
var import_zod13 = require("zod");
|
|
1410
|
+
var PLAN_GATE_GUIDANCE = {
|
|
1411
|
+
agent_analytics_requires_paid_plan: "Agent analytics requires a paid SentientUI plan (Starter or above). Upgrade at https://sentient-ui.com, then try again."
|
|
1412
|
+
};
|
|
1413
|
+
function registerAgentTrafficTools(server, client) {
|
|
1414
|
+
server.registerTool(
|
|
1415
|
+
"get_agent_traffic",
|
|
1416
|
+
{
|
|
1417
|
+
title: "Agent traffic",
|
|
1418
|
+
description: "Which AI agents and crawlers are reading this site: totals by type (passive crawlers, agentic browsers, agent API calls), engine breakdown, and the paths they fetch most. Agent traffic is tracked separately and never counted in conversion rate.",
|
|
1419
|
+
inputSchema: { projectId: projectIdSchema },
|
|
1420
|
+
outputSchema: {
|
|
1421
|
+
totals: import_zod13.z.object({ crawler: import_zod13.z.number(), api: import_zod13.z.number(), browser: import_zod13.z.number() }),
|
|
1422
|
+
engines: import_zod13.z.array(
|
|
1423
|
+
import_zod13.z.object({
|
|
1424
|
+
engine: import_zod13.z.string(),
|
|
1425
|
+
count: import_zod13.z.number(),
|
|
1426
|
+
sharePct: import_zod13.z.number(),
|
|
1427
|
+
lastSeen: import_zod13.z.string(),
|
|
1428
|
+
firstSeenInRange: import_zod13.z.boolean().describe("First observed within the queried period")
|
|
1429
|
+
})
|
|
1430
|
+
),
|
|
1431
|
+
topPaths: import_zod13.z.array(import_zod13.z.object({ path: import_zod13.z.string(), count: import_zod13.z.number(), engines: import_zod13.z.number() }))
|
|
1432
|
+
},
|
|
1433
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
1434
|
+
},
|
|
1435
|
+
withApiErrorGuidance(async ({ projectId }) => {
|
|
1436
|
+
const id = encodeURIComponent(projectId);
|
|
1437
|
+
const s = await client.get(`/projects/${id}/agent-activity/summary`);
|
|
1438
|
+
const structuredContent = { totals: s.totals, engines: s.engines, topPaths: s.topPaths };
|
|
1439
|
+
const total = s.totals.crawler + s.totals.api + s.totals.browser;
|
|
1440
|
+
if (total === 0) {
|
|
1441
|
+
return {
|
|
1442
|
+
content: [{
|
|
1443
|
+
type: "text",
|
|
1444
|
+
text: "No agent traffic observed yet. Passive crawlers (GPTBot, ClaudeBot, \u2026) run no JavaScript \u2014 install sentientAgentMiddleware from @sentientui/react/next to capture them server-side."
|
|
1445
|
+
}],
|
|
1446
|
+
structuredContent
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
const lines = [
|
|
1450
|
+
`Agent traffic: ${s.totals.crawler} crawler fetches, ${s.totals.api} agent API calls, ${s.totals.browser} agentic browser sessions.`,
|
|
1451
|
+
"",
|
|
1452
|
+
"Engines:",
|
|
1453
|
+
...s.engines.map((e) => `- ${e.engine}: ${e.count} fetches (${e.sharePct}%)${e.firstSeenInRange ? " \u2014 NEW this period" : ""}`),
|
|
1454
|
+
"",
|
|
1455
|
+
"Most-fetched paths:",
|
|
1456
|
+
...s.topPaths.map((p) => `- ${p.path}: ${p.count} fetches by ${p.engines} engine(s)`)
|
|
1457
|
+
];
|
|
1458
|
+
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
1459
|
+
}, PLAN_GATE_GUIDANCE)
|
|
1460
|
+
);
|
|
1461
|
+
server.registerTool(
|
|
1462
|
+
"get_agent_legibility",
|
|
1463
|
+
{
|
|
1464
|
+
title: "Agent legibility",
|
|
1465
|
+
description: "Whether the pages AI agents actually read are machine-legible: per-path checks for price, product name, positioning, and CTA in the server HTML, plus agent API blocks served without agent data. Each failure comes with a concrete fix.",
|
|
1466
|
+
inputSchema: { projectId: projectIdSchema },
|
|
1467
|
+
outputSchema: {
|
|
1468
|
+
paths: import_zod13.z.array(
|
|
1469
|
+
import_zod13.z.object({
|
|
1470
|
+
path: import_zod13.z.string(),
|
|
1471
|
+
score: import_zod13.z.number().describe("0\u2013100, 25 per passing check"),
|
|
1472
|
+
checks: import_zod13.z.object({
|
|
1473
|
+
price: import_zod13.z.boolean(),
|
|
1474
|
+
name: import_zod13.z.boolean(),
|
|
1475
|
+
positioning: import_zod13.z.boolean(),
|
|
1476
|
+
cta: import_zod13.z.boolean(),
|
|
1477
|
+
notes: import_zod13.z.array(import_zod13.z.string())
|
|
1478
|
+
}),
|
|
1479
|
+
lastChecked: import_zod13.z.string()
|
|
1480
|
+
})
|
|
1481
|
+
),
|
|
1482
|
+
emptyBlocks: import_zod13.z.array(import_zod13.z.object({ block: import_zod13.z.string(), variant: import_zod13.z.string(), occurrences: import_zod13.z.number() }))
|
|
1483
|
+
},
|
|
1484
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
1485
|
+
},
|
|
1486
|
+
withApiErrorGuidance(async ({ projectId }) => {
|
|
1487
|
+
const id = encodeURIComponent(projectId);
|
|
1488
|
+
const l = await client.get(`/projects/${id}/agent-activity/legibility`);
|
|
1489
|
+
const structuredContent = { paths: l.paths, emptyBlocks: l.emptyBlocks };
|
|
1490
|
+
if (l.paths.length === 0 && l.emptyBlocks.length === 0) {
|
|
1491
|
+
return {
|
|
1492
|
+
content: [{ type: "text", text: "No legibility results yet \u2014 they appear once AI crawlers start fetching pages (checked daily)." }],
|
|
1493
|
+
structuredContent
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
const lines = [
|
|
1497
|
+
"Legibility of agent-read paths:",
|
|
1498
|
+
...l.paths.map((p) => {
|
|
1499
|
+
const failed = ["price", "name", "positioning", "cta"].filter((k) => !p.checks[k]);
|
|
1500
|
+
return `- ${p.path}: ${p.score}/100${failed.length ? ` \u2014 missing: ${failed.join(", ")}` : " \u2014 fully legible"}`;
|
|
1501
|
+
}),
|
|
1502
|
+
...l.paths.flatMap((p) => p.checks.notes.map((n) => ` fix (${p.path}): ${n}`))
|
|
1503
|
+
];
|
|
1504
|
+
if (l.emptyBlocks.length > 0) {
|
|
1505
|
+
lines.push("", "Agent API blocks served without agent data (add agentDataByVariant):");
|
|
1506
|
+
lines.push(...l.emptyBlocks.map((b) => `- ${b.block} (variant ${b.variant}): ${b.occurrences} calls`));
|
|
1507
|
+
}
|
|
1508
|
+
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
1509
|
+
}, PLAN_GATE_GUIDANCE)
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1429
1513
|
// src/server.ts
|
|
1430
|
-
var
|
|
1514
|
+
var PKG_VERSION = true ? "0.9.0" : "0.0.0-dev";
|
|
1431
1515
|
function createMcpServer(client) {
|
|
1432
1516
|
const server = new import_mcp.McpServer(
|
|
1433
1517
|
{
|
|
@@ -1460,6 +1544,7 @@ function createMcpServer(client) {
|
|
|
1460
1544
|
registerTestBriefTools(server, client);
|
|
1461
1545
|
registerVariantWriteTools(server, client);
|
|
1462
1546
|
registerIntegrationGuideTools(server);
|
|
1547
|
+
registerAgentTrafficTools(server, client);
|
|
1463
1548
|
return server;
|
|
1464
1549
|
}
|
|
1465
1550
|
|
package/dist/index.js
CHANGED
package/dist/lib.cjs
CHANGED
|
@@ -27,17 +27,15 @@ __export(lib_exports, {
|
|
|
27
27
|
});
|
|
28
28
|
module.exports = __toCommonJS(lib_exports);
|
|
29
29
|
|
|
30
|
-
// ../../node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.14_tsx@4.22.1_typescript@5.9.3_yaml@2.9.0/node_modules/tsup/assets/cjs_shims.js
|
|
31
|
-
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
32
|
-
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
33
|
-
|
|
34
30
|
// src/server.ts
|
|
35
|
-
var import_node_module = require("module");
|
|
36
31
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
37
32
|
|
|
38
33
|
// src/tools/projects.ts
|
|
39
34
|
var import_zod2 = require("zod");
|
|
40
35
|
|
|
36
|
+
// src/tools/common.ts
|
|
37
|
+
var import_zod = require("zod");
|
|
38
|
+
|
|
41
39
|
// src/api-client.ts
|
|
42
40
|
var ApiError = class extends Error {
|
|
43
41
|
constructor(status, message) {
|
|
@@ -85,9 +83,11 @@ var ApiClient = class {
|
|
|
85
83
|
};
|
|
86
84
|
|
|
87
85
|
// src/tools/common.ts
|
|
88
|
-
var import_zod = require("zod");
|
|
89
86
|
var projectIdSchema = import_zod.z.string().uuid().describe("The project UUID");
|
|
90
|
-
function apiErrorGuidance(err) {
|
|
87
|
+
function apiErrorGuidance(err, extra) {
|
|
88
|
+
if (extra && Object.prototype.hasOwnProperty.call(extra, err.message)) {
|
|
89
|
+
return extra[err.message];
|
|
90
|
+
}
|
|
91
91
|
switch (err.message) {
|
|
92
92
|
case "insufficient_scope":
|
|
93
93
|
return "This action needs an account login. Connect via the hosted MCP URL (https://api.sentient-ui.com/mcp) and sign in \u2014 a project-scoped server key (sk_\u2026) or anonymous demo token cannot do this.";
|
|
@@ -106,13 +106,13 @@ function apiErrorGuidance(err) {
|
|
|
106
106
|
}
|
|
107
107
|
return null;
|
|
108
108
|
}
|
|
109
|
-
function withApiErrorGuidance(fn) {
|
|
109
|
+
function withApiErrorGuidance(fn, extra) {
|
|
110
110
|
return async (args) => {
|
|
111
111
|
try {
|
|
112
112
|
return await fn(args);
|
|
113
113
|
} catch (err) {
|
|
114
114
|
if (err instanceof ApiError) {
|
|
115
|
-
const guidance = apiErrorGuidance(err);
|
|
115
|
+
const guidance = apiErrorGuidance(err, extra);
|
|
116
116
|
if (guidance) {
|
|
117
117
|
return { content: [{ type: "text", text: guidance }], isError: true };
|
|
118
118
|
}
|
|
@@ -123,22 +123,10 @@ function withApiErrorGuidance(fn) {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
// src/tools/projects.ts
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
case "demo_read_only":
|
|
131
|
-
return "Demo mode is read-only. Create a SentientUI account and sign in to make projects.";
|
|
132
|
-
case "insufficient_role":
|
|
133
|
-
return "Your account role cannot create projects \u2014 this needs the account owner or an admin.";
|
|
134
|
-
case "project_limit_reached":
|
|
135
|
-
return "You've reached your plan's project limit. Upgrade your plan or remove an existing project, then try again.";
|
|
136
|
-
case "name_required":
|
|
137
|
-
return "A project name is required to create a project.";
|
|
138
|
-
default:
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
126
|
+
var CREATE_PROJECT_GUIDANCE = {
|
|
127
|
+
project_limit_reached: "You've reached your plan's project limit. Upgrade your plan or remove an existing project, then try again.",
|
|
128
|
+
name_required: "A project name is required to create a project."
|
|
129
|
+
};
|
|
142
130
|
function registerProjectTools(server, client) {
|
|
143
131
|
server.registerTool(
|
|
144
132
|
"create_project",
|
|
@@ -164,41 +152,31 @@ function registerProjectTools(server, client) {
|
|
|
164
152
|
openWorldHint: false
|
|
165
153
|
}
|
|
166
154
|
},
|
|
167
|
-
async ({ name, contextType, framework, websiteUrl }) => {
|
|
168
|
-
|
|
169
|
-
|
|
155
|
+
withApiErrorGuidance(async ({ name, contextType, framework, websiteUrl }) => {
|
|
156
|
+
const created = await client.post("/projects", {
|
|
157
|
+
name,
|
|
158
|
+
contextType,
|
|
159
|
+
framework,
|
|
160
|
+
origin: websiteUrl
|
|
161
|
+
});
|
|
162
|
+
const resolvedContextType = contextType != null ? contextType : "saas";
|
|
163
|
+
return {
|
|
164
|
+
content: [{
|
|
165
|
+
type: "text",
|
|
166
|
+
text: [
|
|
167
|
+
`Created project "${name}" (id: ${created.id}, type: ${resolvedContextType}).`,
|
|
168
|
+
`Public key: ${created.apiKey}`,
|
|
169
|
+
`Next: install @sentientui/react with this key. Ask me to pull the setup guide (get_integration_guide) and I'll wrap your first component.`
|
|
170
|
+
].join("\n")
|
|
171
|
+
}],
|
|
172
|
+
structuredContent: {
|
|
173
|
+
projectId: created.id,
|
|
174
|
+
publicKey: created.apiKey,
|
|
170
175
|
name,
|
|
171
|
-
contextType
|
|
172
|
-
framework,
|
|
173
|
-
origin: websiteUrl
|
|
174
|
-
});
|
|
175
|
-
const resolvedContextType = contextType != null ? contextType : "saas";
|
|
176
|
-
return {
|
|
177
|
-
content: [{
|
|
178
|
-
type: "text",
|
|
179
|
-
text: [
|
|
180
|
-
`Created project "${name}" (id: ${created.id}, type: ${resolvedContextType}).`,
|
|
181
|
-
`Public key: ${created.apiKey}`,
|
|
182
|
-
`Next: install @sentientui/react with this key. Ask me to pull the setup guide (get_integration_guide) and I'll wrap your first component.`
|
|
183
|
-
].join("\n")
|
|
184
|
-
}],
|
|
185
|
-
structuredContent: {
|
|
186
|
-
projectId: created.id,
|
|
187
|
-
publicKey: created.apiKey,
|
|
188
|
-
name,
|
|
189
|
-
contextType: resolvedContextType
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
} catch (err) {
|
|
193
|
-
if (err instanceof ApiError) {
|
|
194
|
-
const guidance = createProjectGuidance(err);
|
|
195
|
-
if (guidance) {
|
|
196
|
-
return { content: [{ type: "text", text: guidance }], isError: true };
|
|
197
|
-
}
|
|
176
|
+
contextType: resolvedContextType
|
|
198
177
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
178
|
+
};
|
|
179
|
+
}, CREATE_PROJECT_GUIDANCE)
|
|
202
180
|
);
|
|
203
181
|
server.registerTool(
|
|
204
182
|
"list_projects",
|
|
@@ -950,13 +928,16 @@ function registerVariantWriteTools(server, client) {
|
|
|
950
928
|
description: "Create a NO-CODE managed text variant for a component (content stored in SentientUI, rendered by <AdaptiveText>). Use this ONLY for text-only variants the user wants without a code change. For variants that will live in the codebase (full components \u2014 copy, markup, styling), use get_variant_brief and write the variant in code instead; those auto-register on deploy and do not need create_variant. Requires a paid plan (server keys are Starter+; anonymous demo tokens are read-only).",
|
|
951
929
|
inputSchema: {
|
|
952
930
|
projectId: projectIdSchema,
|
|
953
|
-
componentId: import_zod9.z.string().describe("The component ID to add a variant to"),
|
|
954
|
-
displayName: import_zod9.z.string().describe("Human-readable name for the new variant"),
|
|
955
|
-
content: import_zod9.z.string().optional().describe("The text content for this managed variant (rendered by <AdaptiveText>). Generate it from get_variant_brief context; omit only to create an empty placeholder to fill in from the dashboard.")
|
|
931
|
+
componentId: import_zod9.z.string().min(1).max(200).describe("The component ID to add a variant to"),
|
|
932
|
+
displayName: import_zod9.z.string().min(1).max(200).describe("Human-readable name for the new variant"),
|
|
933
|
+
content: import_zod9.z.string().max(1e4).optional().describe("The text content for this managed variant (rendered by <AdaptiveText>). Generate it from get_variant_brief context; omit only to create an empty placeholder to fill in from the dashboard.")
|
|
956
934
|
},
|
|
957
935
|
outputSchema: {
|
|
958
936
|
variantId: import_zod9.z.string().describe("The new variant ID"),
|
|
959
|
-
displayName
|
|
937
|
+
// API returns `body.displayName ?? null`, so a successful create can
|
|
938
|
+
// carry a null name — match that contract or outputSchema validation
|
|
939
|
+
// would reject an otherwise-successful response.
|
|
940
|
+
displayName: import_zod9.z.string().nullable(),
|
|
960
941
|
componentId: import_zod9.z.string(),
|
|
961
942
|
state: import_zod9.z.literal("draft").describe("New managed variants start in draft state"),
|
|
962
943
|
hasContent: import_zod9.z.boolean().describe("Whether text content was provided at creation")
|
|
@@ -1449,8 +1430,113 @@ function registerIntegrationGuideTools(server) {
|
|
|
1449
1430
|
);
|
|
1450
1431
|
}
|
|
1451
1432
|
|
|
1433
|
+
// src/tools/agent-traffic.ts
|
|
1434
|
+
var import_zod13 = require("zod");
|
|
1435
|
+
var PLAN_GATE_GUIDANCE = {
|
|
1436
|
+
agent_analytics_requires_paid_plan: "Agent analytics requires a paid SentientUI plan (Starter or above). Upgrade at https://sentient-ui.com, then try again."
|
|
1437
|
+
};
|
|
1438
|
+
function registerAgentTrafficTools(server, client) {
|
|
1439
|
+
server.registerTool(
|
|
1440
|
+
"get_agent_traffic",
|
|
1441
|
+
{
|
|
1442
|
+
title: "Agent traffic",
|
|
1443
|
+
description: "Which AI agents and crawlers are reading this site: totals by type (passive crawlers, agentic browsers, agent API calls), engine breakdown, and the paths they fetch most. Agent traffic is tracked separately and never counted in conversion rate.",
|
|
1444
|
+
inputSchema: { projectId: projectIdSchema },
|
|
1445
|
+
outputSchema: {
|
|
1446
|
+
totals: import_zod13.z.object({ crawler: import_zod13.z.number(), api: import_zod13.z.number(), browser: import_zod13.z.number() }),
|
|
1447
|
+
engines: import_zod13.z.array(
|
|
1448
|
+
import_zod13.z.object({
|
|
1449
|
+
engine: import_zod13.z.string(),
|
|
1450
|
+
count: import_zod13.z.number(),
|
|
1451
|
+
sharePct: import_zod13.z.number(),
|
|
1452
|
+
lastSeen: import_zod13.z.string(),
|
|
1453
|
+
firstSeenInRange: import_zod13.z.boolean().describe("First observed within the queried period")
|
|
1454
|
+
})
|
|
1455
|
+
),
|
|
1456
|
+
topPaths: import_zod13.z.array(import_zod13.z.object({ path: import_zod13.z.string(), count: import_zod13.z.number(), engines: import_zod13.z.number() }))
|
|
1457
|
+
},
|
|
1458
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
1459
|
+
},
|
|
1460
|
+
withApiErrorGuidance(async ({ projectId }) => {
|
|
1461
|
+
const id = encodeURIComponent(projectId);
|
|
1462
|
+
const s = await client.get(`/projects/${id}/agent-activity/summary`);
|
|
1463
|
+
const structuredContent = { totals: s.totals, engines: s.engines, topPaths: s.topPaths };
|
|
1464
|
+
const total = s.totals.crawler + s.totals.api + s.totals.browser;
|
|
1465
|
+
if (total === 0) {
|
|
1466
|
+
return {
|
|
1467
|
+
content: [{
|
|
1468
|
+
type: "text",
|
|
1469
|
+
text: "No agent traffic observed yet. Passive crawlers (GPTBot, ClaudeBot, \u2026) run no JavaScript \u2014 install sentientAgentMiddleware from @sentientui/react/next to capture them server-side."
|
|
1470
|
+
}],
|
|
1471
|
+
structuredContent
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
const lines = [
|
|
1475
|
+
`Agent traffic: ${s.totals.crawler} crawler fetches, ${s.totals.api} agent API calls, ${s.totals.browser} agentic browser sessions.`,
|
|
1476
|
+
"",
|
|
1477
|
+
"Engines:",
|
|
1478
|
+
...s.engines.map((e) => `- ${e.engine}: ${e.count} fetches (${e.sharePct}%)${e.firstSeenInRange ? " \u2014 NEW this period" : ""}`),
|
|
1479
|
+
"",
|
|
1480
|
+
"Most-fetched paths:",
|
|
1481
|
+
...s.topPaths.map((p) => `- ${p.path}: ${p.count} fetches by ${p.engines} engine(s)`)
|
|
1482
|
+
];
|
|
1483
|
+
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
1484
|
+
}, PLAN_GATE_GUIDANCE)
|
|
1485
|
+
);
|
|
1486
|
+
server.registerTool(
|
|
1487
|
+
"get_agent_legibility",
|
|
1488
|
+
{
|
|
1489
|
+
title: "Agent legibility",
|
|
1490
|
+
description: "Whether the pages AI agents actually read are machine-legible: per-path checks for price, product name, positioning, and CTA in the server HTML, plus agent API blocks served without agent data. Each failure comes with a concrete fix.",
|
|
1491
|
+
inputSchema: { projectId: projectIdSchema },
|
|
1492
|
+
outputSchema: {
|
|
1493
|
+
paths: import_zod13.z.array(
|
|
1494
|
+
import_zod13.z.object({
|
|
1495
|
+
path: import_zod13.z.string(),
|
|
1496
|
+
score: import_zod13.z.number().describe("0\u2013100, 25 per passing check"),
|
|
1497
|
+
checks: import_zod13.z.object({
|
|
1498
|
+
price: import_zod13.z.boolean(),
|
|
1499
|
+
name: import_zod13.z.boolean(),
|
|
1500
|
+
positioning: import_zod13.z.boolean(),
|
|
1501
|
+
cta: import_zod13.z.boolean(),
|
|
1502
|
+
notes: import_zod13.z.array(import_zod13.z.string())
|
|
1503
|
+
}),
|
|
1504
|
+
lastChecked: import_zod13.z.string()
|
|
1505
|
+
})
|
|
1506
|
+
),
|
|
1507
|
+
emptyBlocks: import_zod13.z.array(import_zod13.z.object({ block: import_zod13.z.string(), variant: import_zod13.z.string(), occurrences: import_zod13.z.number() }))
|
|
1508
|
+
},
|
|
1509
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
1510
|
+
},
|
|
1511
|
+
withApiErrorGuidance(async ({ projectId }) => {
|
|
1512
|
+
const id = encodeURIComponent(projectId);
|
|
1513
|
+
const l = await client.get(`/projects/${id}/agent-activity/legibility`);
|
|
1514
|
+
const structuredContent = { paths: l.paths, emptyBlocks: l.emptyBlocks };
|
|
1515
|
+
if (l.paths.length === 0 && l.emptyBlocks.length === 0) {
|
|
1516
|
+
return {
|
|
1517
|
+
content: [{ type: "text", text: "No legibility results yet \u2014 they appear once AI crawlers start fetching pages (checked daily)." }],
|
|
1518
|
+
structuredContent
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
const lines = [
|
|
1522
|
+
"Legibility of agent-read paths:",
|
|
1523
|
+
...l.paths.map((p) => {
|
|
1524
|
+
const failed = ["price", "name", "positioning", "cta"].filter((k) => !p.checks[k]);
|
|
1525
|
+
return `- ${p.path}: ${p.score}/100${failed.length ? ` \u2014 missing: ${failed.join(", ")}` : " \u2014 fully legible"}`;
|
|
1526
|
+
}),
|
|
1527
|
+
...l.paths.flatMap((p) => p.checks.notes.map((n) => ` fix (${p.path}): ${n}`))
|
|
1528
|
+
];
|
|
1529
|
+
if (l.emptyBlocks.length > 0) {
|
|
1530
|
+
lines.push("", "Agent API blocks served without agent data (add agentDataByVariant):");
|
|
1531
|
+
lines.push(...l.emptyBlocks.map((b) => `- ${b.block} (variant ${b.variant}): ${b.occurrences} calls`));
|
|
1532
|
+
}
|
|
1533
|
+
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
1534
|
+
}, PLAN_GATE_GUIDANCE)
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1452
1538
|
// src/server.ts
|
|
1453
|
-
var
|
|
1539
|
+
var PKG_VERSION = true ? "0.9.0" : "0.0.0-dev";
|
|
1454
1540
|
function createMcpServer(client) {
|
|
1455
1541
|
const server = new import_mcp.McpServer(
|
|
1456
1542
|
{
|
|
@@ -1483,6 +1569,7 @@ function createMcpServer(client) {
|
|
|
1483
1569
|
registerTestBriefTools(server, client);
|
|
1484
1570
|
registerVariantWriteTools(server, client);
|
|
1485
1571
|
registerIntegrationGuideTools(server);
|
|
1572
|
+
registerAgentTrafficTools(server, client);
|
|
1486
1573
|
return server;
|
|
1487
1574
|
}
|
|
1488
1575
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/lib.js
CHANGED