@canonry/canonry 7.32.0 → 7.44.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.
Files changed (43) hide show
  1. package/assets/agent-workspace/skills/aero/references/agent-operations.md +9 -0
  2. package/assets/agent-workspace/skills/aero/references/site-health.md +12 -0
  3. package/assets/agent-workspace/skills/canonry/SKILL.md +9 -0
  4. package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +24 -0
  5. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +207 -4
  6. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +14 -2
  7. package/assets/assets/{AuditHistoryPanel-cQyVA85P.js → AuditHistoryPanel-CvANw3ds.js} +1 -1
  8. package/assets/assets/{BacklinksPage-UGZ_9N7F.js → BacklinksPage-C6x9ELjk.js} +1 -1
  9. package/assets/assets/{HistoryPage-UJVFZ5LK.js → HistoryPage-r36HX1cC.js} +1 -1
  10. package/assets/assets/{MeasurementPropertyPage-ChpOxQfx.js → MeasurementPropertyPage-ComoCzR4.js} +3 -3
  11. package/assets/assets/ProjectPage-CIeU-buk.js +8 -0
  12. package/assets/assets/RunsPage-DI6eJATZ.js +1 -0
  13. package/assets/assets/{SettingsPage-DeKxjuTr.js → SettingsPage-Bw7GdXxs.js} +1 -1
  14. package/assets/assets/{SiteHealthSection-COeVsgop.js → SiteHealthSection-C-JaNKpS.js} +3 -3
  15. package/assets/assets/{TrafficPage-DmMmeJKK.js → TrafficPage-Cnsp276v.js} +1 -1
  16. package/assets/assets/{TrafficSourceDetailPage-CUehseZe.js → TrafficSourceDetailPage-CBIaJk7e.js} +1 -1
  17. package/assets/assets/{extract-error-message-BsxdS7Po.js → extract-error-message-C_Owa0n9.js} +1 -1
  18. package/assets/assets/index-ZS9sSTMS.js +86 -0
  19. package/assets/assets/{react-sigma_core.esm.min--MbbBWzJ.js → react-sigma_core.esm.min-ugyh-ooq.js} +1 -1
  20. package/assets/assets/{v2-overview-adapter-D-IScpdp.js → v2-overview-adapter-BLSrVQ7K.js} +1 -1
  21. package/assets/index.html +1 -1
  22. package/dist/chunk-3JVF3ULD.js +550 -0
  23. package/dist/chunk-45Z5RJZW.js +562 -0
  24. package/dist/{chunk-QFXD7GR5.js → chunk-5LY7EG4U.js} +238 -20
  25. package/dist/chunk-EJRV7RBZ.js +23066 -0
  26. package/dist/{chunk-WRHA54BN.js → chunk-G47XJVAS.js} +5885 -3900
  27. package/dist/{chunk-42XUY243.js → chunk-GZ2KIONF.js} +11507 -10391
  28. package/dist/chunk-HGMYEDAQ.js +79 -0
  29. package/dist/{chunk-2C6NWFN4.js → chunk-JVBI6VBT.js} +2 -2
  30. package/dist/{chunk-ZIETXJYU.js → chunk-MPWBFKGK.js} +201 -104
  31. package/dist/{chunk-K5EGSSXC.js → chunk-QQYQDTFZ.js} +142 -47
  32. package/dist/{chunk-FW4GX2LO.js → chunk-SW54HXSZ.js} +3 -3
  33. package/dist/cli-catalog-PNJEQV5H.js +15 -0
  34. package/dist/cli-commands-4UT2CRXX.js +15 -0
  35. package/dist/cli.js +136 -22525
  36. package/dist/{demo-server-G5OK7SWE.js → demo-server-WRHR25HF.js} +5 -4
  37. package/dist/index.js +7 -6
  38. package/dist/{intelligence-service-3ZUVTIO7.js → intelligence-service-QXXRUB2G.js} +2 -2
  39. package/dist/mcp.js +30 -15
  40. package/package.json +10 -10
  41. package/assets/assets/ProjectPage-CvxuPSiC.js +0 -8
  42. package/assets/assets/RunsPage-C6U_YcDY.js +0 -1
  43. package/assets/assets/index-ahEr6Oc-.js +0 -86
@@ -1,3 +1,7 @@
1
+ import {
2
+ CliError,
3
+ EXIT_SYSTEM_ERROR
4
+ } from "./chunk-HGMYEDAQ.js";
1
5
  import {
2
6
  AGENT_MEMORY_KEY_MAX_LENGTH,
3
7
  AGENT_MEMORY_VALUE_MAX_BYTES,
@@ -7,6 +11,7 @@ import {
7
11
  GOOGLE_MARKETING_STORED_SNAPSHOT_PAGE_MAX,
8
12
  LATEST_RUN_ID,
9
13
  MEASUREMENT_DEFAULT_QUERY_CLASS,
14
+ OperationAccesses,
10
15
  PROVIDER_ACCOUNT_FAILURE_STREAK,
11
16
  PROVIDER_ACCOUNT_RETRY_HOURS,
12
17
  RESEARCH_RUN_SCOPE,
@@ -27,6 +32,7 @@ import {
27
32
  adsUnresolvedOperationListQuerySchema,
28
33
  agentConversationCreateSchema,
29
34
  apiKeyDtoSchema,
35
+ auditHistoryQuerySchema,
30
36
  backlinkSourceSchema,
31
37
  calendarDateSchema,
32
38
  canonicalizeGtmAccountId,
@@ -35,7 +41,9 @@ import {
35
41
  competitorAliasesRequestSchema,
36
42
  competitorAppendRequestSchema,
37
43
  competitorBatchRequestSchema,
44
+ competitorChangePreviewRequestSchema,
38
45
  competitorLandscapeQuerySchema,
46
+ competitorSuggestionsQuerySchema,
39
47
  contentTargetDismissRequestSchema,
40
48
  describeError,
41
49
  discoveryBucketSchema,
@@ -44,14 +52,17 @@ import {
44
52
  discoveryRunRequestSchema,
45
53
  feedbackAcceptedDtoSchema,
46
54
  feedbackSubmissionSchema,
55
+ gaAcquisitionQuerySchema,
47
56
  gaMeasurementAnalysisWindowSchema,
48
57
  gaMeasurementHostScopeSchema,
49
58
  gaSearchLandingWindowSchema,
50
59
  googleAdsMetricsWindowSchema,
51
60
  gscPerformanceOrderBySchema,
61
+ instanceStatusDtoSchema,
52
62
  keywordBatchRequestSchema,
53
63
  keywordGenerateRequestSchema,
54
64
  logQuerySchema,
65
+ maxOperationAccess,
55
66
  measurementChangesQuerySchema,
56
67
  measurementDataQualityQuerySchema,
57
68
  measurementDiscoveryRequestSchema,
@@ -94,9 +105,11 @@ import {
94
105
  measurementQuestionResultQuerySchema,
95
106
  notificationCreateRequestSchema,
96
107
  notificationEventSchema,
108
+ operationAccess,
97
109
  operationalLogListReadSchema,
98
110
  organicEvidencePeriodSchema,
99
111
  projectConfigSchema,
112
+ projectPatchRequestSchema,
100
113
  projectUpsertRequestSchema,
101
114
  providerQuotaPolicySchema,
102
115
  providerReloadRequestSchema,
@@ -134,6 +147,9 @@ import {
134
147
  sentimentSummaryIncludeSchema,
135
148
  sentimentSummaryReadSchema,
136
149
  settingsDtoSchema,
150
+ siteAuditIssueListQuerySchema,
151
+ siteAuditIssuePagesResponseSchema,
152
+ siteAuditIssuesResponseSchema,
137
153
  siteAuditPageBudgetSchema,
138
154
  snapshotRequestInputSchema,
139
155
  telemetryStatusDtoSchema,
@@ -142,77 +158,11 @@ import {
142
158
  trafficConnectVercelRequestSchema,
143
159
  trafficConnectWordpressRequestSchema,
144
160
  trafficEventKindSchema,
161
+ trafficSamplesQuerySchema,
145
162
  trafficSeriesGranularitySchema,
146
163
  visibilityCompareSelectionSchema,
147
164
  visibilityReportRequestSchema
148
- } from "./chunk-42XUY243.js";
149
-
150
- // src/support-links.ts
151
- var CANONRY_DISCORD_URL = "https://discord.gg/jnJ2H2z5Xh";
152
- var CANONRY_ISSUES_URL = "https://github.com/Canonry/canonry/issues";
153
-
154
- // src/cli-error.ts
155
- function isMachineFormat(format) {
156
- return format === "json" || format === "jsonl";
157
- }
158
- var EXIT_USER_ERROR = 1;
159
- var EXIT_SYSTEM_ERROR = 2;
160
- var CliError = class extends Error {
161
- code;
162
- displayMessage;
163
- details;
164
- exitCode;
165
- constructor(options) {
166
- super(options.message);
167
- this.name = "CliError";
168
- this.code = options.code;
169
- this.displayMessage = options.displayMessage;
170
- this.details = options.details;
171
- this.exitCode = options.exitCode ?? EXIT_USER_ERROR;
172
- }
173
- };
174
- function usageError(displayMessage, options) {
175
- const firstLine = displayMessage.split("\n", 1)[0] ?? "Error: invalid command usage";
176
- return new CliError({
177
- code: "CLI_USAGE_ERROR",
178
- message: options?.message ?? firstLine.replace(/^Error:\s*/, ""),
179
- displayMessage,
180
- details: options?.details
181
- });
182
- }
183
- function isEndpointMissing(err) {
184
- if (!(err instanceof CliError)) return false;
185
- const status = err.details?.httpStatus;
186
- return status === 404 || status === 405;
187
- }
188
- function systemError(message, options) {
189
- return new CliError({
190
- code: "CLI_SYSTEM_ERROR",
191
- message,
192
- displayMessage: options?.displayMessage,
193
- details: options?.details,
194
- exitCode: EXIT_SYSTEM_ERROR
195
- });
196
- }
197
- function printCliError(err, format) {
198
- if (isMachineFormat(format)) {
199
- const envelope = err instanceof CliError ? { error: { code: err.code, message: err.message, ...err.details ? { details: err.details } : {} } } : { error: { code: "CLI_ERROR", message: err instanceof Error ? err.message : "An unexpected error occurred" } };
200
- console.error(JSON.stringify(envelope, null, format === "jsonl" ? 0 : 2));
201
- return;
202
- }
203
- if (err instanceof CliError && err.displayMessage) {
204
- console.error(err.displayMessage);
205
- } else if (err instanceof Error) {
206
- console.error(`Error: ${err.message}`);
207
- } else {
208
- console.error("An unexpected error occurred");
209
- }
210
- const unexpected = !(err instanceof CliError) || err.exitCode === EXIT_SYSTEM_ERROR;
211
- if (unexpected && process.stderr.isTTY) {
212
- console.error(`
213
- Need help? Run 'canonry support' or ask in the Canonry Discord: ${CANONRY_DISCORD_URL}`);
214
- }
215
- }
165
+ } from "./chunk-GZ2KIONF.js";
216
166
 
217
167
  // src/package-version.ts
218
168
  import { createRequire } from "module";
@@ -389,6 +339,13 @@ var writeAnnotations = (opts) => ({
389
339
  destructiveHint: Boolean(opts.destructiveHint),
390
340
  ...opts.openWorldHint ? { openWorldHint: opts.openWorldHint } : {}
391
341
  });
342
+ function toolOperationAccess(tool) {
343
+ const derived = maxOperationAccess(tool.openApiOperations.map((operation) => {
344
+ const separator = operation.indexOf(" ");
345
+ return separator < 0 ? OperationAccesses.stored : operationAccess(operation.slice(0, separator), operation.slice(separator + 1));
346
+ }));
347
+ return tool.access === "write" ? OperationAccesses.write : derived;
348
+ }
392
349
  function defineTool(tool) {
393
350
  return {
394
351
  ...tool,
@@ -542,6 +499,7 @@ var measurementDraftActionOpenApiOperations = [
542
499
  "POST /api/v1/projects/{name}/measurement-plan/draft/actions/discard"
543
500
  ];
544
501
  var runsListInputSchema = z3.object({
502
+ since: z3.string().optional().describe("Inclusive ISO 8601 lower bound on creation time. Omit to read all dates."),
545
503
  project: projectNameSchema,
546
504
  limit: z3.number().int().positive().max(500).optional(),
547
505
  kind: runListFilterQuerySchema.shape.kind.describe('Restrict to a single run kind (e.g. "answer-visibility"). Unknown values are rejected.'),
@@ -563,12 +521,9 @@ var timelineInputSchema = z3.object({
563
521
  limit: z3.number().int().positive().max(100).optional().describe("Restrict history to the most recent N project runs.")
564
522
  });
565
523
  var historyFilterShape = {
524
+ ...auditHistoryQuerySchema.shape,
566
525
  limit: z3.number().int().positive().max(500).optional(),
567
- offset: z3.number().int().nonnegative().optional(),
568
- since: z3.string().optional().describe("ISO 8601 lower bound."),
569
- action: z3.string().optional().describe("Exact audit action filter."),
570
- actor: z3.string().optional().describe("Exact actor filter."),
571
- entityType: z3.string().optional().describe("Exact entity type filter.")
526
+ offset: z3.number().int().nonnegative().optional()
572
527
  };
573
528
  var projectHistoryInputSchema = z3.object({ project: projectNameSchema, ...historyFilterShape });
574
529
  var globalHistoryInputSchema = z3.object(historyFilterShape);
@@ -939,6 +894,12 @@ var competitorsAddInputSchema = z3.object({
939
894
  project: projectNameSchema,
940
895
  request: competitorAppendRequestSchema
941
896
  });
897
+ var competitorChangePreviewInputSchema = z3.discriminatedUnion("change", [
898
+ competitorChangePreviewRequestSchema.options[0].extend({ project: projectNameSchema }),
899
+ competitorChangePreviewRequestSchema.options[1].extend({ project: projectNameSchema }),
900
+ competitorChangePreviewRequestSchema.options[2].extend({ project: projectNameSchema }),
901
+ competitorChangePreviewRequestSchema.options[3].extend({ project: projectNameSchema })
902
+ ]);
942
903
  var competitorDomainInputSchema = z3.string().trim().min(1).describe("Tracked competitor domain (any spelling; it resolves to the stored registrable domain).");
943
904
  var competitorAliasesInputSchema = competitorAliasesRequestSchema.safeExtend({
944
905
  project: projectNameSchema,
@@ -953,6 +914,9 @@ var competitorLandscapeInputSchema = competitorLandscapeQuerySchema.safeExtend({
953
914
  project: projectNameSchema,
954
915
  runId: z3.string().trim().min(1).optional().describe(latestSweepRunIdDescription)
955
916
  }).strict();
917
+ var competitorSuggestionsInputSchema = competitorSuggestionsQuerySchema.safeExtend({
918
+ project: projectNameSchema
919
+ }).strict();
956
920
  function latestSweepUnlessPooled(query) {
957
921
  if (query.runId !== void 0 || query.window !== void 0) return query;
958
922
  if (query.queryClass !== "branded" && query.queryClass !== "non-brand") return query;
@@ -962,6 +926,10 @@ var projectUpsertInputSchema = z3.object({
962
926
  project: projectNameSchema,
963
927
  request: projectUpsertRequestSchema
964
928
  });
929
+ var projectPatchInputSchema = z3.object({
930
+ project: projectNameSchema,
931
+ request: projectPatchRequestSchema
932
+ });
965
933
  var applyConfigInputSchema = z3.object({
966
934
  config: projectConfigSchema
967
935
  });
@@ -984,6 +952,10 @@ var doctorInputSchema = z3.object({
984
952
  project: projectNameSchema.optional().describe("Project name to scope project-level checks. Omit to run global checks (provider keys, config, etc.)."),
985
953
  checks: z3.array(z3.string().min(1)).optional().describe('Optional check IDs or wildcard prefixes (e.g. "google.auth.*", "config.providers"). Empty/omitted runs all matching checks for the chosen scope.')
986
954
  });
955
+ var statusInputSchema = z3.object({
956
+ project: projectNameSchema.optional().describe("Optional project name. Omit to read every visible project."),
957
+ problems: z3.boolean().optional().describe("Return only failing, stale, or never-synced projects and integrations.")
958
+ });
987
959
  var contentTargetsInputSchema = z3.object({
988
960
  project: projectNameSchema,
989
961
  limit: z3.number().int().positive().max(500).optional().describe("Max rows. Defaults to all. Use a small number (3-10) when summarizing for the user."),
@@ -1137,6 +1109,14 @@ var technicalAeoCrawlInputSchema = z3.object({
1137
1109
  runId: crawlRunIdSchema,
1138
1110
  date: calendarDateSchema.optional().describe("Exact UTC scan date YYYY-MM-DD, mutually exclusive with runId. A dated no-data result may include availableScanDates. When the user omitted the year, resolve it from matchingMonthDayDates and request that exact returned date before concluding the scan is missing. Never silently select another date.")
1139
1111
  });
1112
+ var siteHealthIssuesInputSchema = siteAuditIssueListQuerySchema.extend({
1113
+ project: projectNameSchema,
1114
+ runId: siteAuditIssueListQuerySchema.shape.runId.describe("Select an exact saved scan. Omit for the default scan; use its returned runId for subsequent pages and issue details."),
1115
+ cursor: siteAuditIssueListQuerySchema.shape.cursor.describe("Opaque nextCursor from this same scan and issue feed.")
1116
+ });
1117
+ var siteHealthIssuePagesInputSchema = siteHealthIssuesInputSchema.extend({
1118
+ issueId: z3.string().min(1).describe("Exact issue ID from canonry_site_health_issues. Keep the returned runId when inspecting affected pages.")
1119
+ });
1140
1120
  var siteHealthPageAuditInputSchema = z3.object({
1141
1121
  project: projectNameSchema,
1142
1122
  runId: crawlRunIdSchema,
@@ -1286,7 +1266,7 @@ var sentimentPreviewInputSchema = sentimentBackfillSelectionSchema.safeExtend({
1286
1266
  if (input.from && input.to && Date.parse(input.from) > Date.parse(input.to)) ctx.addIssue({ code: "custom", message: "from must be at or before to" });
1287
1267
  });
1288
1268
  var sentimentBackfillInputSchema = sentimentBackfillRequestSchema.extend({ project: projectNameSchema });
1289
- var canonryMcpTools = [
1269
+ var rawCanonryMcpTools = [
1290
1270
  defineTool({
1291
1271
  name: "canonry_sentiment_settings",
1292
1272
  title: "Read sentiment readiness and settings",
@@ -1592,6 +1572,34 @@ var canonryMcpTools = [
1592
1572
  return client.getCompetitorLandscape(project, latestSweepUnlessPooled(query));
1593
1573
  }
1594
1574
  }),
1575
+ defineTool({
1576
+ name: "canonry_competitors_suggest",
1577
+ title: "Suggest competitors from stored evidence",
1578
+ description: "Read stored completed and partial answer evidence and rank possible direct competitors. Candidate naming and citation counts are separate, cited-only domains are excluded, listing and marketplace surfaces are excluded, and the response discloses the answer and sweep denominator. No provider, discovery, classifier, or write operation runs. A Simple project without a usable brand alias pools all query classes and echoes queryClass=all. Use the returned evidence and sample URLs to review a candidate before tracking it.",
1579
+ access: "read",
1580
+ tier: "setup",
1581
+ inputSchema: competitorSuggestionsInputSchema,
1582
+ annotations: readAnnotations(),
1583
+ openApiOperations: ["GET /api/v1/projects/{name}/competitor-suggestions"],
1584
+ handler: (client, input) => {
1585
+ const { project, ...query } = input;
1586
+ return client.suggestCompetitors(project, query);
1587
+ }
1588
+ }),
1589
+ defineTool({
1590
+ name: "canonry_competitors_change_preview",
1591
+ title: "Preview competitor change impact",
1592
+ description: "Preview the stored-evidence share impact of one explicit competitor add, remove, replace, or curated alias change. It reads the latest completed or partial sweep and never writes competitors, aliases, history, or provider state. Use this before a separate write when an operator wants the before and after denominator and percentage-point delta. Ordinary competitor writes do not call this implicitly.",
1593
+ access: "read",
1594
+ tier: "setup",
1595
+ inputSchema: competitorChangePreviewInputSchema,
1596
+ annotations: readAnnotations(),
1597
+ openApiOperations: ["POST /api/v1/projects/{name}/competitor-changes/preview"],
1598
+ handler: (client, input) => {
1599
+ const { project, ...request } = input;
1600
+ return client.previewCompetitorChange(project, request);
1601
+ }
1602
+ }),
1595
1603
  defineTool({
1596
1604
  name: "canonry_search",
1597
1605
  title: "Search project (composite)",
@@ -1618,10 +1626,22 @@ var canonryMcpTools = [
1618
1626
  openApiOperations: ["GET /api/v1/doctor", "GET /api/v1/projects/{name}/doctor"],
1619
1627
  handler: (client, input) => client.runDoctor({ project: input.project, checkIds: input.checks, ...input.reportMonth ? { reportMonth: input.reportMonth } : {} })
1620
1628
  }),
1629
+ defineTool({
1630
+ name: "canonry_status",
1631
+ title: "Read integration status",
1632
+ description: "Read stored synchronization status for one project or the instance. This is a read-only local database read and never calls providers or runs doctor checks.",
1633
+ access: "read",
1634
+ tier: "core",
1635
+ inputSchema: statusInputSchema,
1636
+ outputSchema: instanceStatusDtoSchema,
1637
+ annotations: readAnnotations(),
1638
+ openApiOperations: ["GET /api/v1/status"],
1639
+ handler: (client, input) => client.getStatus({ project: input.project, problems: input.problems })
1640
+ }),
1621
1641
  defineTool({
1622
1642
  name: "canonry_project_export",
1623
1643
  title: "Export project config",
1624
- description: "Export a Canonry project in config-as-code format.",
1644
+ description: "Export a Canonry project in config-as-code format. Notification webhook URLs are always redacted for MCP.",
1625
1645
  access: "read",
1626
1646
  tier: "setup",
1627
1647
  inputSchema: projectInputSchema,
@@ -1653,6 +1673,10 @@ var canonryMcpTools = [
1653
1673
  limit: input.limit,
1654
1674
  offset: input.offset,
1655
1675
  since: input.since,
1676
+ until: input.until,
1677
+ actionPrefix: input.actionPrefix,
1678
+ excludeEntityTypes: input.excludeEntityTypes,
1679
+ entityId: input.entityId,
1656
1680
  action: input.action,
1657
1681
  actor: input.actor,
1658
1682
  entityType: input.entityType
@@ -1672,13 +1696,13 @@ var canonryMcpTools = [
1672
1696
  defineTool({
1673
1697
  name: "canonry_runs_list",
1674
1698
  title: "List project runs",
1675
- description: "List runs for a Canonry project. Includes both real runs (trigger='manual'/'scheduled'/'config-apply'/'backfill') AND probe runs (trigger='probe'). Probe runs are operator/agent test runs that don't influence dashboard, analytics, intelligence, or notifications \u2014 filter by `trigger !== 'probe'` if you only want runs that feed project metrics. Narrow with `kind` and/or `status` (e.g. status='running' lists only in-flight work). Use `excludeKind` or `excludeKinds` to remove kinds before applying the limit.",
1699
+ description: "List runs for a Canonry project. Includes both real runs (trigger='manual'/'scheduled'/'config-apply'/'backfill') AND probe runs (trigger='probe'). Probe runs are operator/agent test runs that don't influence dashboard, analytics, intelligence, or notifications; filter by `trigger !== 'probe'` if you only want runs that feed project metrics. Narrow with `kind` and/or `status` (e.g. status='running' lists only in-flight work). Use `excludeKind` or `excludeKinds` to remove kinds before applying the limit. `since` bounds creation time; omitting limit returns every matching run.",
1676
1700
  access: "read",
1677
1701
  tier: "monitoring",
1678
1702
  inputSchema: runsListInputSchema,
1679
1703
  annotations: readAnnotations(),
1680
1704
  openApiOperations: ["GET /api/v1/projects/{name}/runs"],
1681
- handler: (client, input) => client.listRuns(input.project, input.limit, input.kind, input.status, input.excludeKind, input.excludeKinds)
1705
+ handler: (client, input) => client.listRuns(input.project, input.limit, input.kind, input.status, input.excludeKind, input.excludeKinds, input.since)
1682
1706
  }),
1683
1707
  defineTool({
1684
1708
  name: "canonry_runs_latest",
@@ -2265,11 +2289,11 @@ var canonryMcpTools = [
2265
2289
  defineTool({
2266
2290
  name: "canonry_bing_coverage",
2267
2291
  title: "Get Bing coverage",
2268
- description: "Read stored Bing index coverage counts and URL inspections for a Canonry project. Preserves indexed, not indexed and unknown evidence, the server percentage and last inspection time. Project-wide for Simple and Advanced portfolios; Property, Target and market attribution are unavailable. Requires the existing Bing connection. Never calls Bing or starts a sync.",
2269
- access: "read",
2292
+ description: "Read Bing index coverage counts and URL inspections for a Canonry project. The route records an idempotent daily coverage snapshot while returning the result, but does not call Bing or start a sync. Preserves indexed, not indexed and unknown evidence, the server percentage and last inspection time. Project-wide for Simple and Advanced portfolios; Property, Target and market attribution are unavailable. Requires the existing Bing connection.",
2293
+ access: "write",
2270
2294
  tier: "bing",
2271
2295
  inputSchema: projectInputSchema,
2272
- annotations: readAnnotations(),
2296
+ annotations: writeAnnotations({ idempotentHint: true }),
2273
2297
  openApiOperations: ["GET /api/v1/projects/{name}/bing/coverage"],
2274
2298
  handler: (client, input) => client.bingCoverage(input.project)
2275
2299
  }),
@@ -2317,6 +2341,20 @@ var canonryMcpTools = [
2317
2341
  openApiOperations: ["GET /api/v1/projects/{name}/ga/properties"],
2318
2342
  handler: (client, input) => client.gaProperties(input.project)
2319
2343
  }),
2344
+ defineTool({
2345
+ name: "canonry_ga_acquisition",
2346
+ title: "Read stored GA4 acquisition",
2347
+ description: "Read stored GA4 sessions by day, channel, source/medium, host and normalized landing page. Sessions only; totalSessions is the full filtered total, independent of grouping and pagination. No Google calls. Project-only attribution; stored rows cannot distinguish an older connected GA4 property. Meaningful page query parameters remain distinct.",
2348
+ access: "read",
2349
+ tier: "ga",
2350
+ inputSchema: gaAcquisitionQuerySchema.extend({ project: projectNameSchema, limit: z3.number().int().min(1).max(500).optional() }),
2351
+ annotations: readAnnotations(),
2352
+ openApiOperations: ["GET /api/v1/projects/{name}/ga/acquisition"],
2353
+ handler: (client, input) => {
2354
+ const { project, ...query } = input;
2355
+ return client.gaAcquisition(project, query);
2356
+ }
2357
+ }),
2320
2358
  defineTool({
2321
2359
  name: "canonry_ga_measurement_analysis",
2322
2360
  title: "Analyze GA acquisition and search demand",
@@ -2463,7 +2501,7 @@ var canonryMcpTools = [
2463
2501
  access: "write",
2464
2502
  tier: "gbp",
2465
2503
  inputSchema: gbpDiscoverInputSchema,
2466
- annotations: writeAnnotations({ idempotentHint: true }),
2504
+ annotations: writeAnnotations({ idempotentHint: true, openWorldHint: true }),
2467
2505
  openApiOperations: ["POST /api/v1/projects/{name}/gbp/locations/discover"],
2468
2506
  handler: (client, input) => client.discoverGbpLocations(input.project, {
2469
2507
  selectAllNew: input.selectAllNew,
@@ -2501,7 +2539,7 @@ var canonryMcpTools = [
2501
2539
  access: "write",
2502
2540
  tier: "gbp",
2503
2541
  inputSchema: gbpSyncInputSchema,
2504
- annotations: writeAnnotations({ idempotentHint: true }),
2542
+ annotations: writeAnnotations({ idempotentHint: true, openWorldHint: true }),
2505
2543
  openApiOperations: ["POST /api/v1/projects/{name}/gbp/sync"],
2506
2544
  handler: (client, input) => client.triggerGbpSync(input.project, {
2507
2545
  locationNames: input.locationNames,
@@ -2655,6 +2693,20 @@ var canonryMcpTools = [
2655
2693
  return client.trafficReferralAssessment(project, query);
2656
2694
  }
2657
2695
  }),
2696
+ defineTool({
2697
+ name: "canonry_traffic_samples",
2698
+ title: "Read stored traffic samples",
2699
+ description: "Read the stored sampled tail of server-side traffic with 30-day retention. Sample rows are not a count of visits. Default kind excludes unknown requests. Project/source only; Property, Target and market dimensions are unavailable. No provider call or write.",
2700
+ access: "read",
2701
+ tier: "traffic",
2702
+ inputSchema: trafficSamplesQuerySchema.extend({ project: projectNameSchema }),
2703
+ annotations: readAnnotations(),
2704
+ openApiOperations: ["GET /api/v1/projects/{name}/traffic/samples"],
2705
+ handler: (client, input) => {
2706
+ const { project, ...query } = input;
2707
+ return client.trafficListSamples(project, query);
2708
+ }
2709
+ }),
2658
2710
  defineTool({
2659
2711
  name: "canonry_traffic_events",
2660
2712
  title: "List traffic events",
@@ -2763,10 +2815,21 @@ var canonryMcpTools = [
2763
2815
  openApiOperations: ["PUT /api/v1/projects/{name}"],
2764
2816
  handler: (client, input) => client.putProject(input.project, input.request)
2765
2817
  }),
2818
+ defineTool({
2819
+ name: "canonry_project_update",
2820
+ title: "Update project fields",
2821
+ description: "Update named fields on an existing Canonry project with PATCH semantics. Omitted fields keep their stored values, measurement merges one level, and expectedConfigRevision prevents overwriting a newer edit. Prefer this for targeted changes over canonry_project_upsert.",
2822
+ access: "write",
2823
+ tier: "setup",
2824
+ inputSchema: projectPatchInputSchema,
2825
+ annotations: writeAnnotations({ idempotentHint: true }),
2826
+ openApiOperations: ["PATCH /api/v1/projects/{name}"],
2827
+ handler: (client, input) => client.patchProject(input.project, input.request)
2828
+ }),
2766
2829
  defineTool({
2767
2830
  name: "canonry_apply_config",
2768
2831
  title: "Apply project config",
2769
- description: "Apply one Canonry config-as-code project document. Replaces the project to match the config; fields omitted from the spec are reset to defaults, with these exceptions: a spec with neither queries nor keywords leaves the tracked-query basket unchanged (to clear the basket, pass an explicit empty queries list); an omitted providerDispatchModes keeps the stored preference; an omitted qualifiedAliases keeps the stored list, minus names that no longer qualify (a removed alias, a spelling of the display name, or a competitor's name); send [] to clear it; and a competitor given as a plain domain keeps its stored curated aliases, while `{ domain, aliases }` sets them exactly (`[]` clears). A spec identity that claims a competitor's alias drops that alias (audited); a new competitor whose name overlaps another competitor's alias (one brand key contains the other) or that is the project's own site fails the apply. An omitted siteAuditMaxPages keeps the stored Site Health page budget (null means the full site). An omitted competitorAutoAliases keeps the stored answer-derived competitor alias mode (off, preview or apply). For multi-document YAML, call this tool once per project document.",
2832
+ description: "Apply one Canonry config-as-code project document. Replaces the project to match the config; fields omitted from the spec are reset to defaults, with these exceptions: a spec with neither queries nor keywords leaves the tracked-query basket unchanged (to clear the basket, pass an explicit empty queries list); an omitted providerDispatchModes keeps the stored preference; an omitted qualifiedAliases keeps the stored list, minus names that no longer qualify (a removed alias, a spelling of the display name, or a competitor's name); send [] to clear it; and a competitor given as a plain domain keeps its stored curated aliases, while `{ domain, aliases }` sets them exactly (`[]` clears). A spec identity that claims a competitor's alias drops that alias (audited); a new competitor whose name overlaps another competitor's alias (one brand key contains the other) or that is the project's own site fails the apply. An omitted siteAuditMaxPages keeps the stored Site Health page budget (null means the full site). An omitted competitorAutoAliases keeps the stored answer-derived competitor alias mode (off, preview or apply). For multi-document YAML, call this tool once per project document. Call canonry_apply_config_preview first.",
2770
2833
  access: "write",
2771
2834
  tier: "core",
2772
2835
  inputSchema: applyConfigInputSchema,
@@ -2775,6 +2838,17 @@ var canonryMcpTools = [
2775
2838
  openApiOperations: ["POST /api/v1/apply"],
2776
2839
  handler: (client, input) => client.apply(input.config)
2777
2840
  }),
2841
+ defineTool({
2842
+ name: "canonry_apply_config_preview",
2843
+ title: "Preview project config apply",
2844
+ description: "Preview one Canonry config-as-code project document. Returns per-field and child-resource changes with notification hosts only; the server resolves notification DNS to preserve the real apply verdict, then rolls back the transaction before responding, so no database writes, audit rows, or callbacks persist.",
2845
+ access: "read",
2846
+ tier: "core",
2847
+ inputSchema: applyConfigInputSchema,
2848
+ annotations: readAnnotations(),
2849
+ openApiOperations: ["POST /api/v1/apply/preview"],
2850
+ handler: (client, input) => client.previewApply(input.config)
2851
+ }),
2778
2852
  defineTool({
2779
2853
  name: "canonry_queries_generate",
2780
2854
  title: "Generate query suggestions",
@@ -3329,20 +3403,18 @@ var canonryMcpTools = [
3329
3403
  defineTool({
3330
3404
  name: "canonry_competitors_add",
3331
3405
  title: "Add competitors",
3332
- description: "Add tracked competitor domains to a Canonry project. Each entry is a domain, or `{ domain, aliases }` to also add curated answer-text names to that competitor (tracked already or not; existing aliases are kept, the same rules as canonry_competitors_aliases_set apply). A new domain whose name overlaps another competitor's curated alias, or the label or a curated alias of a competitor an Advanced market pins (one brand key contains the other, so the alias \"Tune\" claims tunespoke.example), is rejected until that name is removed, and so is the project's own site. Already tracked domains (any spelling) are skipped.",
3406
+ description: "Use canonry_competitors_change_preview to inspect the stored share impact before this separate write. Measured projects require `acknowledgeBulkAdd: true` when new domains plus recent additions exceed the shared five-domain, 24-hour guard. Review with canonry_competitors_suggest or canonry_competitors_change_preview first; the built-in agent cannot acknowledge bulk growth. Add tracked competitor domains to a Canonry project. Each entry is a domain, or `{ domain, aliases }` to also add curated answer-text names to that competitor (tracked already or not; existing aliases are kept, the same rules as canonry_competitors_aliases_set apply). A new domain whose name overlaps another competitor's curated alias, or the label or a curated alias of a competitor an Advanced market pins (one brand key contains the other, so the alias \"Tune\" claims tunespoke.example), is rejected until that name is removed, and so is the project's own site. Already tracked domains (any spelling) are skipped.",
3333
3407
  access: "write",
3334
3408
  tier: "setup",
3335
3409
  inputSchema: competitorsAddInputSchema,
3336
3410
  annotations: writeAnnotations({ idempotentHint: true }),
3337
3411
  openApiOperations: ["POST /api/v1/projects/{name}/competitors"],
3338
- handler: async (client, input) => {
3339
- await client.appendCompetitors(input.project, input.request.competitors);
3340
- }
3412
+ handler: (client, input) => client.appendCompetitors(input.project, input.request.competitors, { acknowledgeBulkAdd: input.request.acknowledgeBulkAdd })
3341
3413
  }),
3342
3414
  defineTool({
3343
3415
  name: "canonry_competitors_aliases_set",
3344
3416
  title: "Set competitor aliases",
3345
- description: 'Set one tracked competitor\'s operator-curated aliases exactly (`[]` clears): the brand names it goes by in answer text when they differ from its domain, e.g. "TuneSpoke" for spoketuneworks.example or a 3-letter brand the domain label floor drops. Every competitor mention matcher (mention share, competitor landscape, mention gaps, run and history signals) uses them on stored answers at read time. Rejected: aliases under 3 letters or digits, over 80 characters, more than 10, or whose brand key contains, or sits inside, one of the project\'s own names or hosts or a name another tracked competitor answers to (its aliases, domain label or written host), because answers may split or join the words: "Tune" cannot belong to one competitor while another answers to "Tune Spoke", "TuneSpoke" or tunespoke.example, and joined-word lookalikes ("Tune" and "Tuner") are refused too. A competitor an Advanced market pins (active revision, pending draft, or a superseded revision the project\'s runs were measured under) counts as another competitor. Names detected automatically from stored answers (`autoAliases`) are matched the same way; a curated alias always wins, so an auto name the new list overlaps is dropped. Idempotent; returns the competitor.',
3417
+ description: 'Use canonry_competitors_change_preview to inspect the stored share impact before this separate write. Set one tracked competitor\'s operator-curated aliases exactly (`[]` clears): the brand names it goes by in answer text when they differ from its domain, e.g. "TuneSpoke" for spoketuneworks.example or a 3-letter brand the domain label floor drops. Every competitor mention matcher (mention share, competitor landscape, mention gaps, run and history signals) uses them on stored answers at read time. Rejected: aliases under 3 letters or digits, over 80 characters, more than 10, or whose brand key contains, or sits inside, one of the project\'s own names or hosts or a name another tracked competitor answers to (its aliases, domain label or written host), because answers may split or join the words: "Tune" cannot belong to one competitor while another answers to "Tune Spoke", "TuneSpoke" or tunespoke.example, and joined-word lookalikes ("Tune" and "Tuner") are refused too. A competitor an Advanced market pins (active revision, pending draft, or a superseded revision the project\'s runs were measured under) counts as another competitor. Names detected automatically from stored answers (`autoAliases`) are matched the same way; a curated alias always wins, so an auto name the new list overlaps is dropped. Idempotent; returns the competitor.',
3346
3418
  access: "write",
3347
3419
  tier: "setup",
3348
3420
  inputSchema: competitorAliasesInputSchema,
@@ -3364,7 +3436,7 @@ var canonryMcpTools = [
3364
3436
  defineTool({
3365
3437
  name: "canonry_competitors_auto_aliases_apply",
3366
3438
  title: "Apply competitor auto-aliases",
3367
- description: "Run automatic competitor alias detection now from the project's stored answers and store the result, whatever the project's competitorAutoAliases mode (a one-off apply; a project in `apply` mode also gets this pass after every completed or partial sweep, a competitor add, an unblock and a market pin write that changes the pins). Stored names restate history at read time: every period's competitor counts change, and so does share of voice for a tracked competitor; competitorIdentityChangedAt on the landscape, project-frame visibility-compare and analytics metrics marks when. Idempotent: unchanged answers write nothing. Curated aliases and blocked names always win. A change is audited as the system pass plus competitors.auto-aliases-apply-requested naming who asked. Not available to the built-in Aero agent. Returns what was stored, per competitor, with evidence.",
3439
+ description: "Use canonry_competitors_auto_aliases_detect first to preview names and their stored share impact. Run automatic competitor alias detection now from the project's stored answers and store the result, whatever the project's competitorAutoAliases mode (a one-off apply; a project in `apply` mode also gets this pass after every completed or partial sweep, a competitor add, an unblock and a market pin write that changes the pins). Stored names restate history at read time: every period's competitor counts change, and so does share of voice for a tracked competitor; competitorIdentityChangedAt on the landscape, project-frame visibility-compare and analytics metrics marks when. Idempotent: unchanged answers write nothing. Curated aliases and blocked names always win. A change is audited as the system pass plus competitors.auto-aliases-apply-requested naming who asked. Not available to the built-in Aero agent. Returns what was stored, per competitor, with evidence.",
3368
3440
  access: "write",
3369
3441
  tier: "setup",
3370
3442
  inputSchema: projectInputSchema,
@@ -3397,15 +3469,13 @@ var canonryMcpTools = [
3397
3469
  defineTool({
3398
3470
  name: "canonry_competitors_remove",
3399
3471
  title: "Remove competitors",
3400
- description: "Remove tracked competitor domains from a Canonry project.",
3472
+ description: "Use canonry_competitors_change_preview to inspect the stored share impact before this separate write. Remove tracked competitor domains from a Canonry project.",
3401
3473
  access: "write",
3402
3474
  tier: "setup",
3403
3475
  inputSchema: competitorsInputSchema,
3404
3476
  annotations: writeAnnotations({ idempotentHint: true, destructiveHint: true }),
3405
3477
  openApiOperations: ["DELETE /api/v1/projects/{name}/competitors"],
3406
- handler: async (client, input) => {
3407
- await client.deleteCompetitors(input.project, uniqueStrings(input.request.competitors));
3408
- }
3478
+ handler: (client, input) => client.deleteCompetitors(input.project, uniqueStrings(input.request.competitors))
3409
3479
  }),
3410
3480
  defineTool({
3411
3481
  name: "canonry_schedule_set",
@@ -3714,6 +3784,38 @@ var canonryMcpTools = [
3714
3784
  openApiOperations: ["GET /api/v1/projects/{name}/technical-aeo/crawl"],
3715
3785
  handler: (client, input) => client.getTechnicalAeoCrawl(input.project, { runId: input.runId, date: input.date })
3716
3786
  }),
3787
+ defineTool({
3788
+ name: "canonry_site_health_issues",
3789
+ title: "List stored Site Health issues",
3790
+ description: "Read paginated finding groups from a saved audit, including duplicate metadata and critical defects regardless of aggregate score. Never starts a crawl. Preserve scan date, run/attempt identity, coverage and partial/unavailable states: an empty result does not prove the site is clean. Disposition separates actionable findings, advisory guidance, unverified checks and information; severity retains the recorded critical-defect level. recommendationsScope distinguishes issue-specific defect guidance from whole-factor advice. stableIdentity=false means the ID cannot establish continuity across scans. Follow nextCursor with the returned runId, or inspect one issue with canonry_site_health_issue_pages.",
3791
+ access: "read",
3792
+ tier: "monitoring",
3793
+ inputSchema: siteHealthIssuesInputSchema,
3794
+ outputSchema: siteAuditIssuesResponseSchema,
3795
+ annotations: readAnnotations(),
3796
+ openApiOperations: ["GET /api/v1/projects/{name}/technical-aeo/issues"],
3797
+ handler: (client, input) => client.getSiteAuditIssues(input.project, {
3798
+ runId: input.runId,
3799
+ cursor: input.cursor,
3800
+ limit: input.limit
3801
+ })
3802
+ }),
3803
+ defineTool({
3804
+ name: "canonry_site_health_issue_pages",
3805
+ title: "Read pages affected by a saved audit issue",
3806
+ description: "Read paginated affected URLs and saved messages for one issue in the selected audit. Use the issue ID and runId from canonry_site_health_issues; continue with nextCursor without changing the selection. Null node keys and unavailable/partial coverage remain explicit. recommendationsScope identifies issue-specific defect guidance or broader factor advice; do not present factor advice as an exact fix for this finding. This stored read never crawls, changes content or authorizes edits.",
3807
+ access: "read",
3808
+ tier: "monitoring",
3809
+ inputSchema: siteHealthIssuePagesInputSchema,
3810
+ outputSchema: siteAuditIssuePagesResponseSchema,
3811
+ annotations: readAnnotations(),
3812
+ openApiOperations: ["GET /api/v1/projects/{name}/technical-aeo/issues/{issueId}/pages"],
3813
+ handler: (client, input) => client.getSiteAuditIssuePages(input.project, input.issueId, {
3814
+ runId: input.runId,
3815
+ cursor: input.cursor,
3816
+ limit: input.limit
3817
+ })
3818
+ }),
3717
3819
  defineTool({
3718
3820
  name: "canonry_site_health_page_audit",
3719
3821
  title: "Get Site Health page audit",
@@ -4435,29 +4537,24 @@ var canonryMcpTools = [
4435
4537
  access: "write",
4436
4538
  tier: "ads",
4437
4539
  inputSchema: projectInputSchema,
4438
- annotations: writeAnnotations({ idempotentHint: true }),
4540
+ annotations: writeAnnotations({ idempotentHint: true, openWorldHint: true }),
4439
4541
  openApiOperations: ["POST /api/v1/projects/{name}/ads/sync"],
4440
4542
  handler: (client, input) => client.triggerAdsSync(input.project)
4441
4543
  })
4442
4544
  ];
4545
+ var canonryMcpTools = rawCanonryMcpTools.map((tool) => {
4546
+ if (tool.access !== "read" || toolOperationAccess(tool) !== OperationAccesses.live) return tool;
4547
+ return { ...tool, annotations: { ...tool.annotations, openWorldHint: true } };
4548
+ });
4443
4549
  var CanonryMcpToolNames = Object.freeze(
4444
4550
  Object.fromEntries(canonryMcpTools.map((tool) => [tool.name, tool.name]))
4445
4551
  );
4446
4552
 
4447
4553
  export {
4448
- CANONRY_DISCORD_URL,
4449
- CANONRY_ISSUES_URL,
4450
- isMachineFormat,
4451
- EXIT_USER_ERROR,
4452
- EXIT_SYSTEM_ERROR,
4453
- CliError,
4454
- usageError,
4455
- isEndpointMissing,
4456
- systemError,
4457
- printCliError,
4458
4554
  PACKAGE_VERSION,
4459
4555
  measurementDraftOperationSchema,
4460
4556
  runMeasurementDraftAction,
4557
+ toolOperationAccess,
4461
4558
  canonryMcpTools,
4462
4559
  CanonryMcpToolNames
4463
4560
  };