@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.
@@ -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
- function createProjectGuidance(err) {
96
- switch (err.message) {
97
- case "insufficient_scope":
98
- return "Creating a project 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) cannot create projects.";
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
- try {
138
- const created = await client.post("/projects", {
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
- throw err;
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: z9.string(),
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 import_meta = {};
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
- function createProjectGuidance(err) {
104
- switch (err.message) {
105
- case "insufficient_scope":
106
- return "Creating a project 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) cannot create projects.";
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
- try {
146
- const created = await client.post("/projects", {
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
- throw err;
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: import_zod9.z.string(),
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 { version: PKG_VERSION } = (0, import_node_module.createRequire)(importMetaUrl)("../package.json");
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
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ApiClient,
4
4
  createMcpServer
5
- } from "./chunk-TLBLOBSB.js";
5
+ } from "./chunk-QESH43WV.js";
6
6
 
7
7
  // src/index.ts
8
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
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
- function createProjectGuidance(err) {
127
- switch (err.message) {
128
- case "insufficient_scope":
129
- return "Creating a project 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) cannot create projects.";
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
- try {
169
- const created = await client.post("/projects", {
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
- throw err;
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: import_zod9.z.string(),
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 { version: PKG_VERSION } = (0, import_node_module.createRequire)(importMetaUrl)("../package.json");
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
@@ -3,7 +3,7 @@ import {
3
3
  ApiClient,
4
4
  ApiError,
5
5
  createMcpServer
6
- } from "./chunk-TLBLOBSB.js";
6
+ } from "./chunk-QESH43WV.js";
7
7
  export {
8
8
  ApiClient,
9
9
  ApiError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentientui/mcp",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "MCP server for SentientUI — exposes project data and actions to AI agents",
5
5
  "license": "MIT",
6
6
  "homepage": "https://sentient-ui.com",