@canonry/canonry 7.32.0 → 7.43.2

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 (40) hide show
  1. package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +24 -0
  2. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +204 -4
  3. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +14 -2
  4. package/assets/assets/{AuditHistoryPanel-cQyVA85P.js → AuditHistoryPanel-C5oZsvsR.js} +1 -1
  5. package/assets/assets/{BacklinksPage-UGZ_9N7F.js → BacklinksPage-CF5sGSTQ.js} +1 -1
  6. package/assets/assets/{HistoryPage-UJVFZ5LK.js → HistoryPage-DumL6cl5.js} +1 -1
  7. package/assets/assets/{MeasurementPropertyPage-ChpOxQfx.js → MeasurementPropertyPage-DCegOp_w.js} +3 -3
  8. package/assets/assets/ProjectPage-CWDk48Y6.js +8 -0
  9. package/assets/assets/RunsPage-Bb1VMWYu.js +1 -0
  10. package/assets/assets/{SettingsPage-DeKxjuTr.js → SettingsPage-CHGh169l.js} +1 -1
  11. package/assets/assets/{SiteHealthSection-COeVsgop.js → SiteHealthSection-bGzH9m9-.js} +3 -3
  12. package/assets/assets/{TrafficPage-DmMmeJKK.js → TrafficPage-cfnEZ2Mu.js} +1 -1
  13. package/assets/assets/{TrafficSourceDetailPage-CUehseZe.js → TrafficSourceDetailPage-BQS0MAXE.js} +1 -1
  14. package/assets/assets/{extract-error-message-BsxdS7Po.js → extract-error-message-IWhACkO_.js} +1 -1
  15. package/assets/assets/index-BDY3-V1S.js +86 -0
  16. package/assets/assets/{react-sigma_core.esm.min--MbbBWzJ.js → react-sigma_core.esm.min-BQt189AI.js} +1 -1
  17. package/assets/assets/{v2-overview-adapter-D-IScpdp.js → v2-overview-adapter-ZC3VuoTT.js} +1 -1
  18. package/assets/index.html +1 -1
  19. package/dist/{chunk-WRHA54BN.js → chunk-65BHDVYO.js} +5604 -3984
  20. package/dist/{chunk-FW4GX2LO.js → chunk-BPFRGC7K.js} +3 -3
  21. package/dist/{chunk-2C6NWFN4.js → chunk-BWR5MNL5.js} +2 -2
  22. package/dist/chunk-C2I7M3B2.js +22948 -0
  23. package/dist/{chunk-42XUY243.js → chunk-E43JM3QV.js} +11430 -10405
  24. package/dist/chunk-HGMYEDAQ.js +79 -0
  25. package/dist/{chunk-QFXD7GR5.js → chunk-KOIOZ7QW.js} +193 -19
  26. package/dist/{chunk-ZIETXJYU.js → chunk-LYKDNJXR.js} +158 -104
  27. package/dist/chunk-WRUKKEGR.js +562 -0
  28. package/dist/chunk-WU2BRWXY.js +546 -0
  29. package/dist/{chunk-K5EGSSXC.js → chunk-WXX3NL4C.js} +108 -41
  30. package/dist/cli-catalog-J3O6W726.js +15 -0
  31. package/dist/cli-commands-QST2CPDG.js +15 -0
  32. package/dist/cli.js +136 -22525
  33. package/dist/{demo-server-G5OK7SWE.js → demo-server-KA7DWTY6.js} +5 -4
  34. package/dist/index.js +7 -6
  35. package/dist/{intelligence-service-3ZUVTIO7.js → intelligence-service-LHZZTJPH.js} +2 -2
  36. package/dist/mcp.js +30 -15
  37. package/package.json +10 -10
  38. package/assets/assets/ProjectPage-CvxuPSiC.js +0 -8
  39. package/assets/assets/RunsPage-C6U_YcDY.js +0 -1
  40. 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,
@@ -142,77 +155,11 @@ import {
142
155
  trafficConnectVercelRequestSchema,
143
156
  trafficConnectWordpressRequestSchema,
144
157
  trafficEventKindSchema,
158
+ trafficSamplesQuerySchema,
145
159
  trafficSeriesGranularitySchema,
146
160
  visibilityCompareSelectionSchema,
147
161
  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
- }
162
+ } from "./chunk-E43JM3QV.js";
216
163
 
217
164
  // src/package-version.ts
218
165
  import { createRequire } from "module";
@@ -389,6 +336,13 @@ var writeAnnotations = (opts) => ({
389
336
  destructiveHint: Boolean(opts.destructiveHint),
390
337
  ...opts.openWorldHint ? { openWorldHint: opts.openWorldHint } : {}
391
338
  });
339
+ function toolOperationAccess(tool) {
340
+ const derived = maxOperationAccess(tool.openApiOperations.map((operation) => {
341
+ const separator = operation.indexOf(" ");
342
+ return separator < 0 ? OperationAccesses.stored : operationAccess(operation.slice(0, separator), operation.slice(separator + 1));
343
+ }));
344
+ return tool.access === "write" ? OperationAccesses.write : derived;
345
+ }
392
346
  function defineTool(tool) {
393
347
  return {
394
348
  ...tool,
@@ -542,6 +496,7 @@ var measurementDraftActionOpenApiOperations = [
542
496
  "POST /api/v1/projects/{name}/measurement-plan/draft/actions/discard"
543
497
  ];
544
498
  var runsListInputSchema = z3.object({
499
+ since: z3.string().optional().describe("Inclusive ISO 8601 lower bound on creation time. Omit to read all dates."),
545
500
  project: projectNameSchema,
546
501
  limit: z3.number().int().positive().max(500).optional(),
547
502
  kind: runListFilterQuerySchema.shape.kind.describe('Restrict to a single run kind (e.g. "answer-visibility"). Unknown values are rejected.'),
@@ -563,12 +518,9 @@ var timelineInputSchema = z3.object({
563
518
  limit: z3.number().int().positive().max(100).optional().describe("Restrict history to the most recent N project runs.")
564
519
  });
565
520
  var historyFilterShape = {
521
+ ...auditHistoryQuerySchema.shape,
566
522
  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.")
523
+ offset: z3.number().int().nonnegative().optional()
572
524
  };
573
525
  var projectHistoryInputSchema = z3.object({ project: projectNameSchema, ...historyFilterShape });
574
526
  var globalHistoryInputSchema = z3.object(historyFilterShape);
@@ -939,6 +891,12 @@ var competitorsAddInputSchema = z3.object({
939
891
  project: projectNameSchema,
940
892
  request: competitorAppendRequestSchema
941
893
  });
894
+ var competitorChangePreviewInputSchema = z3.discriminatedUnion("change", [
895
+ competitorChangePreviewRequestSchema.options[0].extend({ project: projectNameSchema }),
896
+ competitorChangePreviewRequestSchema.options[1].extend({ project: projectNameSchema }),
897
+ competitorChangePreviewRequestSchema.options[2].extend({ project: projectNameSchema }),
898
+ competitorChangePreviewRequestSchema.options[3].extend({ project: projectNameSchema })
899
+ ]);
942
900
  var competitorDomainInputSchema = z3.string().trim().min(1).describe("Tracked competitor domain (any spelling; it resolves to the stored registrable domain).");
943
901
  var competitorAliasesInputSchema = competitorAliasesRequestSchema.safeExtend({
944
902
  project: projectNameSchema,
@@ -953,6 +911,9 @@ var competitorLandscapeInputSchema = competitorLandscapeQuerySchema.safeExtend({
953
911
  project: projectNameSchema,
954
912
  runId: z3.string().trim().min(1).optional().describe(latestSweepRunIdDescription)
955
913
  }).strict();
914
+ var competitorSuggestionsInputSchema = competitorSuggestionsQuerySchema.safeExtend({
915
+ project: projectNameSchema
916
+ }).strict();
956
917
  function latestSweepUnlessPooled(query) {
957
918
  if (query.runId !== void 0 || query.window !== void 0) return query;
958
919
  if (query.queryClass !== "branded" && query.queryClass !== "non-brand") return query;
@@ -962,6 +923,10 @@ var projectUpsertInputSchema = z3.object({
962
923
  project: projectNameSchema,
963
924
  request: projectUpsertRequestSchema
964
925
  });
926
+ var projectPatchInputSchema = z3.object({
927
+ project: projectNameSchema,
928
+ request: projectPatchRequestSchema
929
+ });
965
930
  var applyConfigInputSchema = z3.object({
966
931
  config: projectConfigSchema
967
932
  });
@@ -984,6 +949,10 @@ var doctorInputSchema = z3.object({
984
949
  project: projectNameSchema.optional().describe("Project name to scope project-level checks. Omit to run global checks (provider keys, config, etc.)."),
985
950
  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
951
  });
952
+ var statusInputSchema = z3.object({
953
+ project: projectNameSchema.optional().describe("Optional project name. Omit to read every visible project."),
954
+ problems: z3.boolean().optional().describe("Return only failing, stale, or never-synced projects and integrations.")
955
+ });
987
956
  var contentTargetsInputSchema = z3.object({
988
957
  project: projectNameSchema,
989
958
  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."),
@@ -1286,7 +1255,7 @@ var sentimentPreviewInputSchema = sentimentBackfillSelectionSchema.safeExtend({
1286
1255
  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
1256
  });
1288
1257
  var sentimentBackfillInputSchema = sentimentBackfillRequestSchema.extend({ project: projectNameSchema });
1289
- var canonryMcpTools = [
1258
+ var rawCanonryMcpTools = [
1290
1259
  defineTool({
1291
1260
  name: "canonry_sentiment_settings",
1292
1261
  title: "Read sentiment readiness and settings",
@@ -1592,6 +1561,34 @@ var canonryMcpTools = [
1592
1561
  return client.getCompetitorLandscape(project, latestSweepUnlessPooled(query));
1593
1562
  }
1594
1563
  }),
1564
+ defineTool({
1565
+ name: "canonry_competitors_suggest",
1566
+ title: "Suggest competitors from stored evidence",
1567
+ 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.",
1568
+ access: "read",
1569
+ tier: "setup",
1570
+ inputSchema: competitorSuggestionsInputSchema,
1571
+ annotations: readAnnotations(),
1572
+ openApiOperations: ["GET /api/v1/projects/{name}/competitor-suggestions"],
1573
+ handler: (client, input) => {
1574
+ const { project, ...query } = input;
1575
+ return client.suggestCompetitors(project, query);
1576
+ }
1577
+ }),
1578
+ defineTool({
1579
+ name: "canonry_competitors_change_preview",
1580
+ title: "Preview competitor change impact",
1581
+ 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.",
1582
+ access: "read",
1583
+ tier: "setup",
1584
+ inputSchema: competitorChangePreviewInputSchema,
1585
+ annotations: readAnnotations(),
1586
+ openApiOperations: ["POST /api/v1/projects/{name}/competitor-changes/preview"],
1587
+ handler: (client, input) => {
1588
+ const { project, ...request } = input;
1589
+ return client.previewCompetitorChange(project, request);
1590
+ }
1591
+ }),
1595
1592
  defineTool({
1596
1593
  name: "canonry_search",
1597
1594
  title: "Search project (composite)",
@@ -1618,10 +1615,22 @@ var canonryMcpTools = [
1618
1615
  openApiOperations: ["GET /api/v1/doctor", "GET /api/v1/projects/{name}/doctor"],
1619
1616
  handler: (client, input) => client.runDoctor({ project: input.project, checkIds: input.checks, ...input.reportMonth ? { reportMonth: input.reportMonth } : {} })
1620
1617
  }),
1618
+ defineTool({
1619
+ name: "canonry_status",
1620
+ title: "Read integration status",
1621
+ 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.",
1622
+ access: "read",
1623
+ tier: "core",
1624
+ inputSchema: statusInputSchema,
1625
+ outputSchema: instanceStatusDtoSchema,
1626
+ annotations: readAnnotations(),
1627
+ openApiOperations: ["GET /api/v1/status"],
1628
+ handler: (client, input) => client.getStatus({ project: input.project, problems: input.problems })
1629
+ }),
1621
1630
  defineTool({
1622
1631
  name: "canonry_project_export",
1623
1632
  title: "Export project config",
1624
- description: "Export a Canonry project in config-as-code format.",
1633
+ description: "Export a Canonry project in config-as-code format. Notification webhook URLs are always redacted for MCP.",
1625
1634
  access: "read",
1626
1635
  tier: "setup",
1627
1636
  inputSchema: projectInputSchema,
@@ -1653,6 +1662,10 @@ var canonryMcpTools = [
1653
1662
  limit: input.limit,
1654
1663
  offset: input.offset,
1655
1664
  since: input.since,
1665
+ until: input.until,
1666
+ actionPrefix: input.actionPrefix,
1667
+ excludeEntityTypes: input.excludeEntityTypes,
1668
+ entityId: input.entityId,
1656
1669
  action: input.action,
1657
1670
  actor: input.actor,
1658
1671
  entityType: input.entityType
@@ -1672,13 +1685,13 @@ var canonryMcpTools = [
1672
1685
  defineTool({
1673
1686
  name: "canonry_runs_list",
1674
1687
  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.",
1688
+ 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
1689
  access: "read",
1677
1690
  tier: "monitoring",
1678
1691
  inputSchema: runsListInputSchema,
1679
1692
  annotations: readAnnotations(),
1680
1693
  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)
1694
+ handler: (client, input) => client.listRuns(input.project, input.limit, input.kind, input.status, input.excludeKind, input.excludeKinds, input.since)
1682
1695
  }),
1683
1696
  defineTool({
1684
1697
  name: "canonry_runs_latest",
@@ -2265,11 +2278,11 @@ var canonryMcpTools = [
2265
2278
  defineTool({
2266
2279
  name: "canonry_bing_coverage",
2267
2280
  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",
2281
+ 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.",
2282
+ access: "write",
2270
2283
  tier: "bing",
2271
2284
  inputSchema: projectInputSchema,
2272
- annotations: readAnnotations(),
2285
+ annotations: writeAnnotations({ idempotentHint: true }),
2273
2286
  openApiOperations: ["GET /api/v1/projects/{name}/bing/coverage"],
2274
2287
  handler: (client, input) => client.bingCoverage(input.project)
2275
2288
  }),
@@ -2317,6 +2330,20 @@ var canonryMcpTools = [
2317
2330
  openApiOperations: ["GET /api/v1/projects/{name}/ga/properties"],
2318
2331
  handler: (client, input) => client.gaProperties(input.project)
2319
2332
  }),
2333
+ defineTool({
2334
+ name: "canonry_ga_acquisition",
2335
+ title: "Read stored GA4 acquisition",
2336
+ 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.",
2337
+ access: "read",
2338
+ tier: "ga",
2339
+ inputSchema: gaAcquisitionQuerySchema.extend({ project: projectNameSchema, limit: z3.number().int().min(1).max(500).optional() }),
2340
+ annotations: readAnnotations(),
2341
+ openApiOperations: ["GET /api/v1/projects/{name}/ga/acquisition"],
2342
+ handler: (client, input) => {
2343
+ const { project, ...query } = input;
2344
+ return client.gaAcquisition(project, query);
2345
+ }
2346
+ }),
2320
2347
  defineTool({
2321
2348
  name: "canonry_ga_measurement_analysis",
2322
2349
  title: "Analyze GA acquisition and search demand",
@@ -2463,7 +2490,7 @@ var canonryMcpTools = [
2463
2490
  access: "write",
2464
2491
  tier: "gbp",
2465
2492
  inputSchema: gbpDiscoverInputSchema,
2466
- annotations: writeAnnotations({ idempotentHint: true }),
2493
+ annotations: writeAnnotations({ idempotentHint: true, openWorldHint: true }),
2467
2494
  openApiOperations: ["POST /api/v1/projects/{name}/gbp/locations/discover"],
2468
2495
  handler: (client, input) => client.discoverGbpLocations(input.project, {
2469
2496
  selectAllNew: input.selectAllNew,
@@ -2501,7 +2528,7 @@ var canonryMcpTools = [
2501
2528
  access: "write",
2502
2529
  tier: "gbp",
2503
2530
  inputSchema: gbpSyncInputSchema,
2504
- annotations: writeAnnotations({ idempotentHint: true }),
2531
+ annotations: writeAnnotations({ idempotentHint: true, openWorldHint: true }),
2505
2532
  openApiOperations: ["POST /api/v1/projects/{name}/gbp/sync"],
2506
2533
  handler: (client, input) => client.triggerGbpSync(input.project, {
2507
2534
  locationNames: input.locationNames,
@@ -2655,6 +2682,20 @@ var canonryMcpTools = [
2655
2682
  return client.trafficReferralAssessment(project, query);
2656
2683
  }
2657
2684
  }),
2685
+ defineTool({
2686
+ name: "canonry_traffic_samples",
2687
+ title: "Read stored traffic samples",
2688
+ 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.",
2689
+ access: "read",
2690
+ tier: "traffic",
2691
+ inputSchema: trafficSamplesQuerySchema.extend({ project: projectNameSchema }),
2692
+ annotations: readAnnotations(),
2693
+ openApiOperations: ["GET /api/v1/projects/{name}/traffic/samples"],
2694
+ handler: (client, input) => {
2695
+ const { project, ...query } = input;
2696
+ return client.trafficListSamples(project, query);
2697
+ }
2698
+ }),
2658
2699
  defineTool({
2659
2700
  name: "canonry_traffic_events",
2660
2701
  title: "List traffic events",
@@ -2763,10 +2804,21 @@ var canonryMcpTools = [
2763
2804
  openApiOperations: ["PUT /api/v1/projects/{name}"],
2764
2805
  handler: (client, input) => client.putProject(input.project, input.request)
2765
2806
  }),
2807
+ defineTool({
2808
+ name: "canonry_project_update",
2809
+ title: "Update project fields",
2810
+ 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.",
2811
+ access: "write",
2812
+ tier: "setup",
2813
+ inputSchema: projectPatchInputSchema,
2814
+ annotations: writeAnnotations({ idempotentHint: true }),
2815
+ openApiOperations: ["PATCH /api/v1/projects/{name}"],
2816
+ handler: (client, input) => client.patchProject(input.project, input.request)
2817
+ }),
2766
2818
  defineTool({
2767
2819
  name: "canonry_apply_config",
2768
2820
  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.",
2821
+ 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
2822
  access: "write",
2771
2823
  tier: "core",
2772
2824
  inputSchema: applyConfigInputSchema,
@@ -2775,6 +2827,17 @@ var canonryMcpTools = [
2775
2827
  openApiOperations: ["POST /api/v1/apply"],
2776
2828
  handler: (client, input) => client.apply(input.config)
2777
2829
  }),
2830
+ defineTool({
2831
+ name: "canonry_apply_config_preview",
2832
+ title: "Preview project config apply",
2833
+ 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.",
2834
+ access: "read",
2835
+ tier: "core",
2836
+ inputSchema: applyConfigInputSchema,
2837
+ annotations: readAnnotations(),
2838
+ openApiOperations: ["POST /api/v1/apply/preview"],
2839
+ handler: (client, input) => client.previewApply(input.config)
2840
+ }),
2778
2841
  defineTool({
2779
2842
  name: "canonry_queries_generate",
2780
2843
  title: "Generate query suggestions",
@@ -3329,20 +3392,18 @@ var canonryMcpTools = [
3329
3392
  defineTool({
3330
3393
  name: "canonry_competitors_add",
3331
3394
  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.",
3395
+ 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
3396
  access: "write",
3334
3397
  tier: "setup",
3335
3398
  inputSchema: competitorsAddInputSchema,
3336
3399
  annotations: writeAnnotations({ idempotentHint: true }),
3337
3400
  openApiOperations: ["POST /api/v1/projects/{name}/competitors"],
3338
- handler: async (client, input) => {
3339
- await client.appendCompetitors(input.project, input.request.competitors);
3340
- }
3401
+ handler: (client, input) => client.appendCompetitors(input.project, input.request.competitors, { acknowledgeBulkAdd: input.request.acknowledgeBulkAdd })
3341
3402
  }),
3342
3403
  defineTool({
3343
3404
  name: "canonry_competitors_aliases_set",
3344
3405
  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.',
3406
+ 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
3407
  access: "write",
3347
3408
  tier: "setup",
3348
3409
  inputSchema: competitorAliasesInputSchema,
@@ -3364,7 +3425,7 @@ var canonryMcpTools = [
3364
3425
  defineTool({
3365
3426
  name: "canonry_competitors_auto_aliases_apply",
3366
3427
  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.",
3428
+ 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
3429
  access: "write",
3369
3430
  tier: "setup",
3370
3431
  inputSchema: projectInputSchema,
@@ -3397,15 +3458,13 @@ var canonryMcpTools = [
3397
3458
  defineTool({
3398
3459
  name: "canonry_competitors_remove",
3399
3460
  title: "Remove competitors",
3400
- description: "Remove tracked competitor domains from a Canonry project.",
3461
+ 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
3462
  access: "write",
3402
3463
  tier: "setup",
3403
3464
  inputSchema: competitorsInputSchema,
3404
3465
  annotations: writeAnnotations({ idempotentHint: true, destructiveHint: true }),
3405
3466
  openApiOperations: ["DELETE /api/v1/projects/{name}/competitors"],
3406
- handler: async (client, input) => {
3407
- await client.deleteCompetitors(input.project, uniqueStrings(input.request.competitors));
3408
- }
3467
+ handler: (client, input) => client.deleteCompetitors(input.project, uniqueStrings(input.request.competitors))
3409
3468
  }),
3410
3469
  defineTool({
3411
3470
  name: "canonry_schedule_set",
@@ -4435,29 +4494,24 @@ var canonryMcpTools = [
4435
4494
  access: "write",
4436
4495
  tier: "ads",
4437
4496
  inputSchema: projectInputSchema,
4438
- annotations: writeAnnotations({ idempotentHint: true }),
4497
+ annotations: writeAnnotations({ idempotentHint: true, openWorldHint: true }),
4439
4498
  openApiOperations: ["POST /api/v1/projects/{name}/ads/sync"],
4440
4499
  handler: (client, input) => client.triggerAdsSync(input.project)
4441
4500
  })
4442
4501
  ];
4502
+ var canonryMcpTools = rawCanonryMcpTools.map((tool) => {
4503
+ if (tool.access !== "read" || toolOperationAccess(tool) !== OperationAccesses.live) return tool;
4504
+ return { ...tool, annotations: { ...tool.annotations, openWorldHint: true } };
4505
+ });
4443
4506
  var CanonryMcpToolNames = Object.freeze(
4444
4507
  Object.fromEntries(canonryMcpTools.map((tool) => [tool.name, tool.name]))
4445
4508
  );
4446
4509
 
4447
4510
  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
4511
  PACKAGE_VERSION,
4459
4512
  measurementDraftOperationSchema,
4460
4513
  runMeasurementDraftAction,
4514
+ toolOperationAccess,
4461
4515
  canonryMcpTools,
4462
4516
  CanonryMcpToolNames
4463
4517
  };