@canonry/canonry 7.25.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 (41) hide show
  1. package/README.md +3 -2
  2. package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +24 -0
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +219 -6
  4. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +14 -2
  5. package/assets/assets/{AuditHistoryPanel-B-9DLSmD.js → AuditHistoryPanel-C5oZsvsR.js} +1 -1
  6. package/assets/assets/{BacklinksPage-DPssWU5-.js → BacklinksPage-CF5sGSTQ.js} +1 -1
  7. package/assets/assets/{HistoryPage-BAPUoHjI.js → HistoryPage-DumL6cl5.js} +1 -1
  8. package/assets/assets/{MeasurementPropertyPage-aTs5l1OV.js → MeasurementPropertyPage-DCegOp_w.js} +3 -3
  9. package/assets/assets/ProjectPage-CWDk48Y6.js +8 -0
  10. package/assets/assets/RunsPage-Bb1VMWYu.js +1 -0
  11. package/assets/assets/{SettingsPage-Oo861eRm.js → SettingsPage-CHGh169l.js} +1 -1
  12. package/assets/assets/{SiteHealthSection-BMdk3Och.js → SiteHealthSection-bGzH9m9-.js} +3 -3
  13. package/assets/assets/{TrafficPage-Ciot_0lN.js → TrafficPage-cfnEZ2Mu.js} +1 -1
  14. package/assets/assets/{TrafficSourceDetailPage-D7tWadfJ.js → TrafficSourceDetailPage-BQS0MAXE.js} +1 -1
  15. package/assets/assets/{extract-error-message-Bb4azvlK.js → extract-error-message-IWhACkO_.js} +1 -1
  16. package/assets/assets/index-BDY3-V1S.js +86 -0
  17. package/assets/assets/{react-sigma_core.esm.min-C-Y9Z74U.js → react-sigma_core.esm.min-BQt189AI.js} +1 -1
  18. package/assets/assets/{v2-overview-adapter-D2S25ivE.js → v2-overview-adapter-ZC3VuoTT.js} +1 -1
  19. package/assets/index.html +1 -1
  20. package/dist/{chunk-PKQF7BV2.js → chunk-65BHDVYO.js} +5650 -4002
  21. package/dist/{chunk-NOA7KJDB.js → chunk-BPFRGC7K.js} +3 -3
  22. package/dist/{chunk-I3546GTK.js → chunk-BWR5MNL5.js} +2 -2
  23. package/dist/chunk-C2I7M3B2.js +22948 -0
  24. package/dist/{chunk-PFEQWGJN.js → chunk-E43JM3QV.js} +11444 -10397
  25. package/dist/chunk-HGMYEDAQ.js +79 -0
  26. package/dist/{chunk-BMBHREYM.js → chunk-KOIOZ7QW.js} +195 -20
  27. package/dist/{chunk-6AOIFHC6.js → chunk-LYKDNJXR.js} +163 -107
  28. package/dist/chunk-WRUKKEGR.js +562 -0
  29. package/dist/chunk-WU2BRWXY.js +546 -0
  30. package/dist/{chunk-NX257AKC.js → chunk-WXX3NL4C.js} +108 -41
  31. package/dist/cli-catalog-J3O6W726.js +15 -0
  32. package/dist/cli-commands-QST2CPDG.js +15 -0
  33. package/dist/cli.js +136 -22502
  34. package/dist/{demo-server-RUZWYXSW.js → demo-server-KA7DWTY6.js} +5 -4
  35. package/dist/index.js +7 -6
  36. package/dist/{intelligence-service-TYSVXO3S.js → intelligence-service-LHZZTJPH.js} +2 -2
  37. package/dist/mcp.js +30 -15
  38. package/package.json +6 -6
  39. package/assets/assets/ProjectPage-sVY1ETmt.js +0 -8
  40. package/assets/assets/RunsPage-D0q7ifK3.js +0 -1
  41. package/assets/assets/index-ClA5Nf2V.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,
@@ -109,6 +122,7 @@ import {
109
122
  queryTrackingPreviewRequestSchema,
110
123
  referralAssessmentQuerySchema,
111
124
  reportMonthSchema,
125
+ reportedQueryClasses,
112
126
  researchBatchCreateSchema,
113
127
  researchRunCreateSchema,
114
128
  resultsClearRequestSchema,
@@ -141,77 +155,11 @@ import {
141
155
  trafficConnectVercelRequestSchema,
142
156
  trafficConnectWordpressRequestSchema,
143
157
  trafficEventKindSchema,
158
+ trafficSamplesQuerySchema,
144
159
  trafficSeriesGranularitySchema,
145
160
  visibilityCompareSelectionSchema,
146
161
  visibilityReportRequestSchema
147
- } from "./chunk-PFEQWGJN.js";
148
-
149
- // src/support-links.ts
150
- var CANONRY_DISCORD_URL = "https://discord.gg/jnJ2H2z5Xh";
151
- var CANONRY_ISSUES_URL = "https://github.com/Canonry/canonry/issues";
152
-
153
- // src/cli-error.ts
154
- function isMachineFormat(format) {
155
- return format === "json" || format === "jsonl";
156
- }
157
- var EXIT_USER_ERROR = 1;
158
- var EXIT_SYSTEM_ERROR = 2;
159
- var CliError = class extends Error {
160
- code;
161
- displayMessage;
162
- details;
163
- exitCode;
164
- constructor(options) {
165
- super(options.message);
166
- this.name = "CliError";
167
- this.code = options.code;
168
- this.displayMessage = options.displayMessage;
169
- this.details = options.details;
170
- this.exitCode = options.exitCode ?? EXIT_USER_ERROR;
171
- }
172
- };
173
- function usageError(displayMessage, options) {
174
- const firstLine = displayMessage.split("\n", 1)[0] ?? "Error: invalid command usage";
175
- return new CliError({
176
- code: "CLI_USAGE_ERROR",
177
- message: options?.message ?? firstLine.replace(/^Error:\s*/, ""),
178
- displayMessage,
179
- details: options?.details
180
- });
181
- }
182
- function isEndpointMissing(err) {
183
- if (!(err instanceof CliError)) return false;
184
- const status = err.details?.httpStatus;
185
- return status === 404 || status === 405;
186
- }
187
- function systemError(message, options) {
188
- return new CliError({
189
- code: "CLI_SYSTEM_ERROR",
190
- message,
191
- displayMessage: options?.displayMessage,
192
- details: options?.details,
193
- exitCode: EXIT_SYSTEM_ERROR
194
- });
195
- }
196
- function printCliError(err, format) {
197
- if (isMachineFormat(format)) {
198
- 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" } };
199
- console.error(JSON.stringify(envelope, null, format === "jsonl" ? 0 : 2));
200
- return;
201
- }
202
- if (err instanceof CliError && err.displayMessage) {
203
- console.error(err.displayMessage);
204
- } else if (err instanceof Error) {
205
- console.error(`Error: ${err.message}`);
206
- } else {
207
- console.error("An unexpected error occurred");
208
- }
209
- const unexpected = !(err instanceof CliError) || err.exitCode === EXIT_SYSTEM_ERROR;
210
- if (unexpected && process.stderr.isTTY) {
211
- console.error(`
212
- Need help? Run 'canonry support' or ask in the Canonry Discord: ${CANONRY_DISCORD_URL}`);
213
- }
214
- }
162
+ } from "./chunk-E43JM3QV.js";
215
163
 
216
164
  // src/package-version.ts
217
165
  import { createRequire } from "module";
@@ -388,6 +336,13 @@ var writeAnnotations = (opts) => ({
388
336
  destructiveHint: Boolean(opts.destructiveHint),
389
337
  ...opts.openWorldHint ? { openWorldHint: opts.openWorldHint } : {}
390
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
+ }
391
346
  function defineTool(tool) {
392
347
  return {
393
348
  ...tool,
@@ -541,6 +496,7 @@ var measurementDraftActionOpenApiOperations = [
541
496
  "POST /api/v1/projects/{name}/measurement-plan/draft/actions/discard"
542
497
  ];
543
498
  var runsListInputSchema = z3.object({
499
+ since: z3.string().optional().describe("Inclusive ISO 8601 lower bound on creation time. Omit to read all dates."),
544
500
  project: projectNameSchema,
545
501
  limit: z3.number().int().positive().max(500).optional(),
546
502
  kind: runListFilterQuerySchema.shape.kind.describe('Restrict to a single run kind (e.g. "answer-visibility"). Unknown values are rejected.'),
@@ -562,12 +518,9 @@ var timelineInputSchema = z3.object({
562
518
  limit: z3.number().int().positive().max(100).optional().describe("Restrict history to the most recent N project runs.")
563
519
  });
564
520
  var historyFilterShape = {
521
+ ...auditHistoryQuerySchema.shape,
565
522
  limit: z3.number().int().positive().max(500).optional(),
566
- offset: z3.number().int().nonnegative().optional(),
567
- since: z3.string().optional().describe("ISO 8601 lower bound."),
568
- action: z3.string().optional().describe("Exact audit action filter."),
569
- actor: z3.string().optional().describe("Exact actor filter."),
570
- entityType: z3.string().optional().describe("Exact entity type filter.")
523
+ offset: z3.number().int().nonnegative().optional()
571
524
  };
572
525
  var projectHistoryInputSchema = z3.object({ project: projectNameSchema, ...historyFilterShape });
573
526
  var globalHistoryInputSchema = z3.object(historyFilterShape);
@@ -938,6 +891,12 @@ var competitorsAddInputSchema = z3.object({
938
891
  project: projectNameSchema,
939
892
  request: competitorAppendRequestSchema
940
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
+ ]);
941
900
  var competitorDomainInputSchema = z3.string().trim().min(1).describe("Tracked competitor domain (any spelling; it resolves to the stored registrable domain).");
942
901
  var competitorAliasesInputSchema = competitorAliasesRequestSchema.safeExtend({
943
902
  project: projectNameSchema,
@@ -952,6 +911,9 @@ var competitorLandscapeInputSchema = competitorLandscapeQuerySchema.safeExtend({
952
911
  project: projectNameSchema,
953
912
  runId: z3.string().trim().min(1).optional().describe(latestSweepRunIdDescription)
954
913
  }).strict();
914
+ var competitorSuggestionsInputSchema = competitorSuggestionsQuerySchema.safeExtend({
915
+ project: projectNameSchema
916
+ }).strict();
955
917
  function latestSweepUnlessPooled(query) {
956
918
  if (query.runId !== void 0 || query.window !== void 0) return query;
957
919
  if (query.queryClass !== "branded" && query.queryClass !== "non-brand") return query;
@@ -961,6 +923,10 @@ var projectUpsertInputSchema = z3.object({
961
923
  project: projectNameSchema,
962
924
  request: projectUpsertRequestSchema
963
925
  });
926
+ var projectPatchInputSchema = z3.object({
927
+ project: projectNameSchema,
928
+ request: projectPatchRequestSchema
929
+ });
964
930
  var applyConfigInputSchema = z3.object({
965
931
  config: projectConfigSchema
966
932
  });
@@ -983,6 +949,10 @@ var doctorInputSchema = z3.object({
983
949
  project: projectNameSchema.optional().describe("Project name to scope project-level checks. Omit to run global checks (provider keys, config, etc.)."),
984
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.')
985
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
+ });
986
956
  var contentTargetsInputSchema = z3.object({
987
957
  project: projectNameSchema,
988
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."),
@@ -1285,7 +1255,7 @@ var sentimentPreviewInputSchema = sentimentBackfillSelectionSchema.safeExtend({
1285
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" });
1286
1256
  });
1287
1257
  var sentimentBackfillInputSchema = sentimentBackfillRequestSchema.extend({ project: projectNameSchema });
1288
- var canonryMcpTools = [
1258
+ var rawCanonryMcpTools = [
1289
1259
  defineTool({
1290
1260
  name: "canonry_sentiment_settings",
1291
1261
  title: "Read sentiment readiness and settings",
@@ -1591,6 +1561,34 @@ var canonryMcpTools = [
1591
1561
  return client.getCompetitorLandscape(project, latestSweepUnlessPooled(query));
1592
1562
  }
1593
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
+ }),
1594
1592
  defineTool({
1595
1593
  name: "canonry_search",
1596
1594
  title: "Search project (composite)",
@@ -1617,10 +1615,22 @@ var canonryMcpTools = [
1617
1615
  openApiOperations: ["GET /api/v1/doctor", "GET /api/v1/projects/{name}/doctor"],
1618
1616
  handler: (client, input) => client.runDoctor({ project: input.project, checkIds: input.checks, ...input.reportMonth ? { reportMonth: input.reportMonth } : {} })
1619
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
+ }),
1620
1630
  defineTool({
1621
1631
  name: "canonry_project_export",
1622
1632
  title: "Export project config",
1623
- 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.",
1624
1634
  access: "read",
1625
1635
  tier: "setup",
1626
1636
  inputSchema: projectInputSchema,
@@ -1652,6 +1662,10 @@ var canonryMcpTools = [
1652
1662
  limit: input.limit,
1653
1663
  offset: input.offset,
1654
1664
  since: input.since,
1665
+ until: input.until,
1666
+ actionPrefix: input.actionPrefix,
1667
+ excludeEntityTypes: input.excludeEntityTypes,
1668
+ entityId: input.entityId,
1655
1669
  action: input.action,
1656
1670
  actor: input.actor,
1657
1671
  entityType: input.entityType
@@ -1671,13 +1685,13 @@ var canonryMcpTools = [
1671
1685
  defineTool({
1672
1686
  name: "canonry_runs_list",
1673
1687
  title: "List project runs",
1674
- 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.",
1675
1689
  access: "read",
1676
1690
  tier: "monitoring",
1677
1691
  inputSchema: runsListInputSchema,
1678
1692
  annotations: readAnnotations(),
1679
1693
  openApiOperations: ["GET /api/v1/projects/{name}/runs"],
1680
- 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)
1681
1695
  }),
1682
1696
  defineTool({
1683
1697
  name: "canonry_runs_latest",
@@ -1969,7 +1983,7 @@ var canonryMcpTools = [
1969
1983
  defineTool({
1970
1984
  name: "canonry_queries_list",
1971
1985
  title: "List queries",
1972
- description: "List tracked queries for a Canonry project.",
1986
+ description: "List tracked queries for a Canonry project. Each row carries queryClass as the server classes it: branded, non-brand, or null when no single class exists (no usable brand name, or the active measurement plan holds no single class for the query). A row with no queryClass comes from a server older than the field: the class is unknown, not null. Never infer a class from the query text.",
1973
1987
  access: "read",
1974
1988
  tier: "setup",
1975
1989
  inputSchema: projectInputSchema,
@@ -2264,11 +2278,11 @@ var canonryMcpTools = [
2264
2278
  defineTool({
2265
2279
  name: "canonry_bing_coverage",
2266
2280
  title: "Get Bing coverage",
2267
- 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.",
2268
- 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",
2269
2283
  tier: "bing",
2270
2284
  inputSchema: projectInputSchema,
2271
- annotations: readAnnotations(),
2285
+ annotations: writeAnnotations({ idempotentHint: true }),
2272
2286
  openApiOperations: ["GET /api/v1/projects/{name}/bing/coverage"],
2273
2287
  handler: (client, input) => client.bingCoverage(input.project)
2274
2288
  }),
@@ -2316,6 +2330,20 @@ var canonryMcpTools = [
2316
2330
  openApiOperations: ["GET /api/v1/projects/{name}/ga/properties"],
2317
2331
  handler: (client, input) => client.gaProperties(input.project)
2318
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
+ }),
2319
2347
  defineTool({
2320
2348
  name: "canonry_ga_measurement_analysis",
2321
2349
  title: "Analyze GA acquisition and search demand",
@@ -2462,7 +2490,7 @@ var canonryMcpTools = [
2462
2490
  access: "write",
2463
2491
  tier: "gbp",
2464
2492
  inputSchema: gbpDiscoverInputSchema,
2465
- annotations: writeAnnotations({ idempotentHint: true }),
2493
+ annotations: writeAnnotations({ idempotentHint: true, openWorldHint: true }),
2466
2494
  openApiOperations: ["POST /api/v1/projects/{name}/gbp/locations/discover"],
2467
2495
  handler: (client, input) => client.discoverGbpLocations(input.project, {
2468
2496
  selectAllNew: input.selectAllNew,
@@ -2500,7 +2528,7 @@ var canonryMcpTools = [
2500
2528
  access: "write",
2501
2529
  tier: "gbp",
2502
2530
  inputSchema: gbpSyncInputSchema,
2503
- annotations: writeAnnotations({ idempotentHint: true }),
2531
+ annotations: writeAnnotations({ idempotentHint: true, openWorldHint: true }),
2504
2532
  openApiOperations: ["POST /api/v1/projects/{name}/gbp/sync"],
2505
2533
  handler: (client, input) => client.triggerGbpSync(input.project, {
2506
2534
  locationNames: input.locationNames,
@@ -2654,6 +2682,20 @@ var canonryMcpTools = [
2654
2682
  return client.trafficReferralAssessment(project, query);
2655
2683
  }
2656
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
+ }),
2657
2699
  defineTool({
2658
2700
  name: "canonry_traffic_events",
2659
2701
  title: "List traffic events",
@@ -2762,10 +2804,21 @@ var canonryMcpTools = [
2762
2804
  openApiOperations: ["PUT /api/v1/projects/{name}"],
2763
2805
  handler: (client, input) => client.putProject(input.project, input.request)
2764
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
+ }),
2765
2818
  defineTool({
2766
2819
  name: "canonry_apply_config",
2767
2820
  title: "Apply project config",
2768
- 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.",
2769
2822
  access: "write",
2770
2823
  tier: "core",
2771
2824
  inputSchema: applyConfigInputSchema,
@@ -2774,6 +2827,17 @@ var canonryMcpTools = [
2774
2827
  openApiOperations: ["POST /api/v1/apply"],
2775
2828
  handler: (client, input) => client.apply(input.config)
2776
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
+ }),
2777
2841
  defineTool({
2778
2842
  name: "canonry_queries_generate",
2779
2843
  title: "Generate query suggestions",
@@ -3275,14 +3339,15 @@ var canonryMcpTools = [
3275
3339
  defineTool({
3276
3340
  name: "canonry_queries_add",
3277
3341
  title: "Add queries",
3278
- description: "Append tracked queries to a Canonry project; existing queries are skipped by the API.",
3342
+ description: "Append tracked queries to a Canonry project; existing queries are skipped by the API. Returns queryClasses: each requested query with the class the server gave it (branded, non-brand, or null when no single class exists). An entry with no queryClass means the server did not report one.",
3279
3343
  access: "write",
3280
3344
  tier: "setup",
3281
3345
  inputSchema: queriesInputSchema,
3282
3346
  annotations: writeAnnotations({ idempotentHint: true }),
3283
3347
  openApiOperations: ["POST /api/v1/projects/{name}/queries"],
3284
3348
  handler: async (client, input) => {
3285
- await client.appendQueries(input.project, uniqueStrings(input.request.queries));
3349
+ const queries = uniqueStrings(input.request.queries);
3350
+ return { ok: true, queryClasses: reportedQueryClasses(queries, await client.appendQueries(input.project, queries)) };
3286
3351
  }
3287
3352
  }),
3288
3353
  defineTool({
@@ -3327,20 +3392,18 @@ var canonryMcpTools = [
3327
3392
  defineTool({
3328
3393
  name: "canonry_competitors_add",
3329
3394
  title: "Add competitors",
3330
- 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.",
3331
3396
  access: "write",
3332
3397
  tier: "setup",
3333
3398
  inputSchema: competitorsAddInputSchema,
3334
3399
  annotations: writeAnnotations({ idempotentHint: true }),
3335
3400
  openApiOperations: ["POST /api/v1/projects/{name}/competitors"],
3336
- handler: async (client, input) => {
3337
- await client.appendCompetitors(input.project, input.request.competitors);
3338
- }
3401
+ handler: (client, input) => client.appendCompetitors(input.project, input.request.competitors, { acknowledgeBulkAdd: input.request.acknowledgeBulkAdd })
3339
3402
  }),
3340
3403
  defineTool({
3341
3404
  name: "canonry_competitors_aliases_set",
3342
3405
  title: "Set competitor aliases",
3343
- 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.',
3344
3407
  access: "write",
3345
3408
  tier: "setup",
3346
3409
  inputSchema: competitorAliasesInputSchema,
@@ -3362,7 +3425,7 @@ var canonryMcpTools = [
3362
3425
  defineTool({
3363
3426
  name: "canonry_competitors_auto_aliases_apply",
3364
3427
  title: "Apply competitor auto-aliases",
3365
- 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.",
3366
3429
  access: "write",
3367
3430
  tier: "setup",
3368
3431
  inputSchema: projectInputSchema,
@@ -3395,15 +3458,13 @@ var canonryMcpTools = [
3395
3458
  defineTool({
3396
3459
  name: "canonry_competitors_remove",
3397
3460
  title: "Remove competitors",
3398
- 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.",
3399
3462
  access: "write",
3400
3463
  tier: "setup",
3401
3464
  inputSchema: competitorsInputSchema,
3402
3465
  annotations: writeAnnotations({ idempotentHint: true, destructiveHint: true }),
3403
3466
  openApiOperations: ["DELETE /api/v1/projects/{name}/competitors"],
3404
- handler: async (client, input) => {
3405
- await client.deleteCompetitors(input.project, uniqueStrings(input.request.competitors));
3406
- }
3467
+ handler: (client, input) => client.deleteCompetitors(input.project, uniqueStrings(input.request.competitors))
3407
3468
  }),
3408
3469
  defineTool({
3409
3470
  name: "canonry_schedule_set",
@@ -4433,29 +4494,24 @@ var canonryMcpTools = [
4433
4494
  access: "write",
4434
4495
  tier: "ads",
4435
4496
  inputSchema: projectInputSchema,
4436
- annotations: writeAnnotations({ idempotentHint: true }),
4497
+ annotations: writeAnnotations({ idempotentHint: true, openWorldHint: true }),
4437
4498
  openApiOperations: ["POST /api/v1/projects/{name}/ads/sync"],
4438
4499
  handler: (client, input) => client.triggerAdsSync(input.project)
4439
4500
  })
4440
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
+ });
4441
4506
  var CanonryMcpToolNames = Object.freeze(
4442
4507
  Object.fromEntries(canonryMcpTools.map((tool) => [tool.name, tool.name]))
4443
4508
  );
4444
4509
 
4445
4510
  export {
4446
- CANONRY_DISCORD_URL,
4447
- CANONRY_ISSUES_URL,
4448
- isMachineFormat,
4449
- EXIT_USER_ERROR,
4450
- EXIT_SYSTEM_ERROR,
4451
- CliError,
4452
- usageError,
4453
- isEndpointMissing,
4454
- systemError,
4455
- printCliError,
4456
4511
  PACKAGE_VERSION,
4457
4512
  measurementDraftOperationSchema,
4458
4513
  runMeasurementDraftAction,
4514
+ toolOperationAccess,
4459
4515
  canonryMcpTools,
4460
4516
  CanonryMcpToolNames
4461
4517
  };