@canonry/canonry 4.177.2 → 4.178.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 (29) hide show
  1. package/assets/assets/{AuditHistoryPanel-YZvGU1la.js → AuditHistoryPanel-BmKdyRrR.js} +1 -1
  2. package/assets/assets/{BacklinksPage-CB2uJWWy.js → BacklinksPage-DtqKPVLV.js} +1 -1
  3. package/assets/assets/{ChartPrimitives-BoYe2M-i.js → ChartPrimitives-B_LDVvTy.js} +1 -1
  4. package/assets/assets/{HistoryPage-D9IxxbQ9.js → HistoryPage-yyJsQiJQ.js} +1 -1
  5. package/assets/assets/{MeasurementPropertyPage-CxgnnPdp.js → MeasurementPropertyPage-hjW4iYtr.js} +1 -1
  6. package/assets/assets/ProjectPage-DZKpnPvq.js +9 -0
  7. package/assets/assets/{RunRow-2KxOta8F.js → RunRow-BZeBFb22.js} +1 -1
  8. package/assets/assets/{RunsPage-ya8vTkyd.js → RunsPage-tnXHsK_R.js} +1 -1
  9. package/assets/assets/{SettingsPage-lTy3o2bo.js → SettingsPage-Djj5k8vv.js} +1 -1
  10. package/assets/assets/{SiteHealthSection-Cyu7MIv2.js → SiteHealthSection-BkDncS11.js} +3 -3
  11. package/assets/assets/{TrafficPage-DjhKfw57.js → TrafficPage-nJggXVh-.js} +1 -1
  12. package/assets/assets/{TrafficSourceDetailPage-Pwi-Dl9Q.js → TrafficSourceDetailPage-zxONbSn-.js} +1 -1
  13. package/assets/assets/{extract-error-message-I1FVWHLH.js → extract-error-message-NpQu2vBm.js} +1 -1
  14. package/assets/assets/{index-DQVTRT9J.css → index-D0GbiVhZ.css} +1 -1
  15. package/assets/assets/index-Dwwnkd_V.js +83 -0
  16. package/assets/assets/{react-sigma_core.esm.min-BX6mOEq5.js → react-sigma_core.esm.min-CxMdd8U8.js} +1 -1
  17. package/assets/index.html +2 -2
  18. package/dist/{chunk-QRE3DAAU.js → chunk-A7FB2BVW.js} +2 -2
  19. package/dist/{chunk-XFAPF2YX.js → chunk-O7J5WFVZ.js} +361 -100
  20. package/dist/{chunk-6XUWDSLN.js → chunk-SXKA4OXZ.js} +1356 -907
  21. package/dist/{chunk-PQT3ECM3.js → chunk-T4JH77O6.js} +107 -0
  22. package/dist/{chunk-GJE334DA.js → chunk-YIQMTOEZ.js} +3029 -2080
  23. package/dist/cli.js +87 -5
  24. package/dist/index.js +4 -4
  25. package/dist/{intelligence-service-C57RIB37.js → intelligence-service-YISV327N.js} +2 -2
  26. package/dist/mcp.js +5 -380
  27. package/package.json +9 -9
  28. package/assets/assets/ProjectPage-CPmaXzhL.js +0 -9
  29. package/assets/assets/index-BeLVKN3H.js +0 -83
package/dist/cli.js CHANGED
@@ -26,11 +26,11 @@ import {
26
26
  showFirstRunNotice,
27
27
  trackCliCommandFinished,
28
28
  trackEvent
29
- } from "./chunk-XFAPF2YX.js";
29
+ } from "./chunk-O7J5WFVZ.js";
30
30
  import {
31
31
  autoSyncSkills,
32
32
  formatAutoSyncNotice
33
- } from "./chunk-QRE3DAAU.js";
33
+ } from "./chunk-A7FB2BVW.js";
34
34
  import {
35
35
  CliError,
36
36
  EXIT_SYSTEM_ERROR,
@@ -55,7 +55,7 @@ import {
55
55
  saveConfigPatch,
56
56
  systemError,
57
57
  usageError
58
- } from "./chunk-6XUWDSLN.js";
58
+ } from "./chunk-SXKA4OXZ.js";
59
59
  import {
60
60
  CLOUDFLARE_WORKER_BINDINGS,
61
61
  CLOUDFLARE_WORKER_GENERATED_MARKER,
@@ -69,7 +69,7 @@ import {
69
69
  projects,
70
70
  queries,
71
71
  renderReportHtml
72
- } from "./chunk-GJE334DA.js";
72
+ } from "./chunk-YIQMTOEZ.js";
73
73
  import {
74
74
  AdsDeliverySnapshotStatuses,
75
75
  AdsHistoricalCampaignRollupStatuses,
@@ -117,6 +117,7 @@ import {
117
117
  formatMicros,
118
118
  formatRatio,
119
119
  formatRunErrorOneLine,
120
+ googleAdsMetricsWindowSchema,
120
121
  measurementChangesQuerySchema,
121
122
  measurementDataQualityQuerySchema,
122
123
  measurementDiscoveryRequestSchema,
@@ -137,7 +138,7 @@ import {
137
138
  providerQuotaPolicySchema,
138
139
  resolveProviderInput,
139
140
  winnabilityClassSchema
140
- } from "./chunk-PQT3ECM3.js";
141
+ } from "./chunk-T4JH77O6.js";
141
142
 
142
143
  // src/cli.ts
143
144
  import { pathToFileURL } from "url";
@@ -5385,6 +5386,60 @@ async function googleAdsSync(client, project, opts) {
5385
5386
  if (printMachine(run, opts.format)) return;
5386
5387
  console.log(`Google Ads read-only sync queued (run ${run.id}). Use \`canonry run show ${run.id}\` to check it.`);
5387
5388
  }
5389
+ function performanceMicros(micros, currencyCode) {
5390
+ if (micros === null) return "\u2014";
5391
+ if (currencyCode === null) return (micros / 1e6).toFixed(2);
5392
+ return formatMicros(micros, currencyCode);
5393
+ }
5394
+ function performanceRatio(ratio) {
5395
+ return ratio === null ? "\u2014" : `${(ratio * 100).toFixed(2)}%`;
5396
+ }
5397
+ function performanceChange(ratio) {
5398
+ if (ratio === null) return "";
5399
+ const sign = ratio > 0 ? "+" : "";
5400
+ return ` (${sign}${(ratio * 100).toFixed(1)}% vs prior period)`;
5401
+ }
5402
+ async function googleAdsPerformance(client, project, opts) {
5403
+ const result = await client.getGoogleAdsPerformance(project, opts.window ? { window: opts.window } : {});
5404
+ if (printMachine(result, opts.format)) return;
5405
+ if (!result.source) {
5406
+ console.log(`No stored Google Ads metrics for project "${project}".`);
5407
+ console.log(`Run \`canonry google-ads sync ${project}\` after selecting a customer.`);
5408
+ return;
5409
+ }
5410
+ const currency = result.source.currencyCode;
5411
+ const { totals, comparison } = result;
5412
+ console.log(`Window: ${result.startDate} \u2192 ${result.endDate} (${result.days} closed day(s), ${result.window})`);
5413
+ console.log(`As of: ${result.source.asOfDate}${result.source.openDate ? ` (${result.source.openDate} still open, excluded)` : ""}`);
5414
+ console.log(`Account: ${result.source.customerId} (${currency ?? "?"}, ${result.source.timeZone ?? "unknown zone"})`);
5415
+ console.log(`Impressions: ${totals.impressions}${performanceChange(comparison?.change.impressions ?? null)}`);
5416
+ console.log(`Clicks: ${totals.clicks} (CTR ${performanceRatio(totals.ctr)})${performanceChange(comparison?.change.clicks ?? null)}`);
5417
+ console.log(`Cost: ${performanceMicros(totals.costMicros, currency)} (CPC ${performanceMicros(totals.cpcMicros, currency)})${performanceChange(comparison?.change.costMicros ?? null)}`);
5418
+ console.log(`Conversions: ${totals.conversions} (rate ${performanceRatio(totals.conversionRate)}, cost/conv ${performanceMicros(totals.costPerConversionMicros, currency)})`);
5419
+ if (!comparison) {
5420
+ console.log(`Comparison: unavailable (${result.comparisonUnavailableReason ?? "unknown"})`);
5421
+ }
5422
+ if (result.source.campaignsInInventory > 0 && result.source.campaignsQueried < result.source.campaignsInInventory) {
5423
+ console.log(`Coverage: ${result.source.campaignsQueried} of ${result.source.campaignsInInventory} campaigns; totals are a subset of the account.`);
5424
+ }
5425
+ if (result.source.truncated) {
5426
+ console.log(`Note: the provider returned a bounded result, so these totals are a subset sum.`);
5427
+ }
5428
+ if (result.campaigns.length > 0) {
5429
+ console.log(`
5430
+ ${result.campaigns.length} campaign(s):
5431
+ `);
5432
+ for (const campaign of result.campaigns) {
5433
+ const label = campaign.name ?? `(not in stored inventory: ${campaign.campaignId})`;
5434
+ console.log(` ${label.padEnd(32).slice(0, 32)} ${campaign.status.padEnd(8)} ${String(campaign.totals.impressions).padStart(8)} impr ${String(campaign.totals.clicks).padStart(6)} clicks ${performanceMicros(campaign.totals.costMicros, currency).padStart(12)}`);
5435
+ }
5436
+ }
5437
+ const filled = result.daily.filter((point) => point.origin === "filled").length;
5438
+ if (filled > 0) {
5439
+ console.log(`
5440
+ ${filled} of ${result.daily.length} day(s) had no delivery and are reported as zero.`);
5441
+ }
5442
+ }
5388
5443
  async function googleAdsSnapshots(client, project, opts) {
5389
5444
  const result = await client.listGoogleAdsSnapshots(project, { limit: opts.limit, cursor: opts.cursor });
5390
5445
  if (opts.format === "jsonl") {
@@ -5585,6 +5640,19 @@ Usage: ${usage}`, {
5585
5640
  }
5586
5641
  return limit;
5587
5642
  }
5643
+ function performanceWindow(input, usage) {
5644
+ const raw = getString(input.values, "window");
5645
+ if (raw === void 0) return void 0;
5646
+ const parsed = googleAdsMetricsWindowSchema.safeParse(raw);
5647
+ if (!parsed.success) {
5648
+ throw usageError(`Error: --window must be one of 7d, 14d, 30d
5649
+ Usage: ${usage}`, {
5650
+ message: "--window must be one of 7d, 14d, 30d",
5651
+ details: { command: "google-ads.performance", usage, option: "window", value: raw }
5652
+ });
5653
+ }
5654
+ return parsed.data;
5655
+ }
5588
5656
  function createGoogleMarketingCliCommands(createClient2) {
5589
5657
  return [
5590
5658
  {
@@ -5637,6 +5705,20 @@ function createGoogleMarketingCliCommands(createClient2) {
5637
5705
  await googleAdsSync(createClient2(), requireProject(input, "google-ads.sync", usage), { format: input.format });
5638
5706
  }
5639
5707
  },
5708
+ {
5709
+ path: ["google-ads", "performance"],
5710
+ usage: "canonry google-ads performance <project> [--window 7d|14d|30d] [--format json]",
5711
+ options: { window: stringOption() },
5712
+ run: async (input) => {
5713
+ const usage = "canonry google-ads performance <project> [--window 7d|14d|30d] [--format json]";
5714
+ const project = requireProject(input, "google-ads.performance", usage);
5715
+ const window = performanceWindow(input, usage);
5716
+ await googleAdsPerformance(createClient2(), project, {
5717
+ ...window ? { window } : {},
5718
+ format: input.format
5719
+ });
5720
+ }
5721
+ },
5640
5722
  {
5641
5723
  path: ["google-ads", "snapshots"],
5642
5724
  usage: "canonry google-ads snapshots <project> [--limit <n>] [--cursor <opaque>] [--format json|jsonl]",
package/dist/index.js CHANGED
@@ -3,12 +3,12 @@ import {
3
3
  createGoogleMarketingCredentialStore,
4
4
  createGoogleMarketingRuntime,
5
5
  createServer
6
- } from "./chunk-XFAPF2YX.js";
6
+ } from "./chunk-O7J5WFVZ.js";
7
7
  import {
8
8
  loadConfig
9
- } from "./chunk-6XUWDSLN.js";
10
- import "./chunk-GJE334DA.js";
11
- import "./chunk-PQT3ECM3.js";
9
+ } from "./chunk-SXKA4OXZ.js";
10
+ import "./chunk-YIQMTOEZ.js";
11
+ import "./chunk-T4JH77O6.js";
12
12
  export {
13
13
  GoogleMarketingRuntimeError,
14
14
  createGoogleMarketingCredentialStore,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-GJE334DA.js";
4
- import "./chunk-PQT3ECM3.js";
3
+ } from "./chunk-YIQMTOEZ.js";
4
+ import "./chunk-T4JH77O6.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };
package/dist/mcp.js CHANGED
@@ -1,391 +1,16 @@
1
1
  import {
2
2
  autoSyncSkills
3
- } from "./chunk-QRE3DAAU.js";
3
+ } from "./chunk-A7FB2BVW.js";
4
4
  import {
5
- CliError,
6
- PACKAGE_VERSION,
7
- canonryMcpTools,
8
- createApiClient
9
- } from "./chunk-6XUWDSLN.js";
5
+ createApiClient,
6
+ createCanonryMcpServer
7
+ } from "./chunk-SXKA4OXZ.js";
10
8
  import {
11
9
  isReadOnlyKey
12
- } from "./chunk-PQT3ECM3.js";
10
+ } from "./chunk-T4JH77O6.js";
13
11
 
14
12
  // src/mcp/cli.ts
15
13
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
16
-
17
- // src/mcp/server.ts
18
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
19
- import { z } from "zod";
20
-
21
- // src/mcp/results.ts
22
- import { ZodError } from "zod";
23
- function jsonToolResult(value) {
24
- const result = value === void 0 ? { ok: true } : value;
25
- return {
26
- content: [
27
- {
28
- type: "text",
29
- text: JSON.stringify(result, null, 2)
30
- }
31
- ]
32
- };
33
- }
34
- function errorToolResult(error) {
35
- return {
36
- isError: true,
37
- content: [
38
- {
39
- type: "text",
40
- text: JSON.stringify(toCanonryErrorEnvelope(error), null, 2)
41
- }
42
- ]
43
- };
44
- }
45
- async function withToolErrors(handler) {
46
- try {
47
- return jsonToolResult(await handler());
48
- } catch (error) {
49
- return errorToolResult(error);
50
- }
51
- }
52
- function toCanonryErrorEnvelope(error) {
53
- if (error instanceof ZodError) {
54
- return {
55
- error: {
56
- code: "VALIDATION_ERROR",
57
- message: zodErrorMessage(error),
58
- details: { issues: error.issues.map(formatZodIssue) }
59
- }
60
- };
61
- }
62
- if (error instanceof CliError) {
63
- return {
64
- error: {
65
- code: error.code,
66
- message: error.message,
67
- ...error.details ? { details: error.details } : {}
68
- }
69
- };
70
- }
71
- if (hasErrorEnvelope(error)) {
72
- const { code, message, details } = error.error;
73
- return {
74
- error: {
75
- code: typeof code === "string" ? code : "API_ERROR",
76
- message: typeof message === "string" ? message : "Canonry API error",
77
- ...details !== void 0 ? { details } : {}
78
- }
79
- };
80
- }
81
- if (error instanceof Error) {
82
- return {
83
- error: {
84
- code: "MCP_TOOL_ERROR",
85
- message: error.message
86
- }
87
- };
88
- }
89
- return {
90
- error: {
91
- code: "MCP_TOOL_ERROR",
92
- message: "Unknown MCP tool error"
93
- }
94
- };
95
- }
96
- function hasErrorEnvelope(value) {
97
- if (!value || typeof value !== "object" || !("error" in value)) return false;
98
- const error = value.error;
99
- return Boolean(error && typeof error === "object");
100
- }
101
- function formatZodIssue(issue) {
102
- return { path: issue.path.map(String).join("."), message: issue.message };
103
- }
104
- function zodErrorMessage(error) {
105
- const first = error.issues[0];
106
- if (!first) return "Input validation failed";
107
- const path = first.path.map(String).join(".");
108
- return path ? `${path}: ${first.message}` : first.message;
109
- }
110
-
111
- // src/mcp/toolkits.ts
112
- var CANONRY_MCP_TOOLKIT_NAMES = [
113
- "monitoring",
114
- "setup",
115
- "gsc",
116
- "ga",
117
- "gbp",
118
- "ads",
119
- "google-ads",
120
- "gtm",
121
- "conversion-tracking",
122
- "traffic",
123
- "agent",
124
- "discovery"
125
- ];
126
- var CANONRY_MCP_TOOLKITS = [
127
- {
128
- name: "monitoring",
129
- title: "Measurement, runs, insights, health",
130
- description: "Inspect portfolio and Property measurement, question results, run history, intelligence insights, health timelines, and bounded Site Health reads (overview, page audit evidence, focused subgraphs, paths, and scan changes).",
131
- whenToLoad: "Load when finding weak Properties, reviewing answers and competitors, checking data quality, tying a Site Health page score to concrete findings, investigating site architecture, comparing scans, or diagnosing regressions."
132
- },
133
- {
134
- name: "setup",
135
- title: "Project setup and measurement",
136
- description: "Manage project configuration, Advanced Measurement drafts, query assets, competitors, and schedules.",
137
- whenToLoad: "Load when onboarding a project, editing its measurement plan, or managing tracked queries, competitors, or schedules."
138
- },
139
- {
140
- name: "gsc",
141
- title: "Google Search Console",
142
- description: "Read GSC performance, inspections, coverage, sitemaps, and deindexed URLs.",
143
- whenToLoad: "Load when you need indexing, coverage, or sitemap data from Google Search Console."
144
- },
145
- {
146
- name: "ga",
147
- title: "Google Analytics 4",
148
- description: "Read GA traffic, AI/social referral history, attribution trend, and session history.",
149
- whenToLoad: "Load when you need traffic, referral, or attribution data from Google Analytics 4."
150
- },
151
- {
152
- name: "gbp",
153
- title: "Google Business Profile",
154
- description: "Local AEO signals: discover GBP locations under a connected account and toggle which ones sync. Future phases will add reviews, keyword impressions, daily performance metrics, and hotel attributes.",
155
- whenToLoad: "Load when the project tracks local search visibility or has connected Google Business Profile."
156
- },
157
- {
158
- name: "ads",
159
- title: "OpenAI ads (ChatGPT ads)",
160
- description: "Live OpenAI ad-account review state, targetable geo IDs, conversion pixels/event settings, synced campaign structure, paid-performance rollups, and guarded lifecycle operations.",
161
- whenToLoad: "Load when planning, creating, reviewing, or measuring ChatGPT ads, including geo and conversion-readiness checks before launch."
162
- },
163
- {
164
- name: "google-ads",
165
- title: "Google Ads conversion evidence",
166
- description: "Read a connected Google Ads account, its conversion actions, effective campaign goals, and bounded stored snapshots. v1 only reads provider state; it never edits campaigns, conversion actions, or bidding goals.",
167
- whenToLoad: "Load when selecting a Google Ads customer, reviewing conversion-action and effective-goal evidence, or starting a bounded read-only sync."
168
- },
169
- {
170
- name: "gtm",
171
- title: "Google Tag Manager conversion evidence",
172
- description: "Read GTM accounts, containers, workspaces, and sanitized live/draft tag graphs used to verify a conversion contract. v1 never edits a workspace or publishes a container version.",
173
- whenToLoad: "Load when selecting a GTM container, reviewing a live or draft conversion-tag graph, or starting a bounded read-only sync."
174
- },
175
- {
176
- name: "conversion-tracking",
177
- title: "Cross-provider conversion integrity",
178
- description: "Read declared conversion contracts and assess them against stored Google Ads and GTM evidence without calling or mutating either provider.",
179
- whenToLoad: "Load when listing or inspecting declared conversion contracts, or when checking whether stored Google Ads and GTM evidence is consistent with one contract."
180
- },
181
- {
182
- name: "traffic",
183
- title: "Server-side traffic ingestion",
184
- description: "Connect Cloud Run traffic sources, trigger syncs, and read crawler / AI-referral hourly rollups straight from server logs (no GA dependency).",
185
- whenToLoad: "Load when you need server-log evidence of crawler hits or AI-referral sessions (e.g. confirming GPTBot or ChatGPT-User on a page), or when wiring up / syncing a Cloud Run traffic source."
186
- },
187
- {
188
- name: "agent",
189
- title: "Aero agent lifecycle and memory",
190
- description: "Manage the built-in Aero agent: durable project-scoped memory (list/set/forget), clear the rolling transcript, and detach the external-agent webhook.",
191
- whenToLoad: "Load when reading or writing project-scoped Aero notes, clearing a stuck conversation, or removing an external agent webhook."
192
- },
193
- {
194
- name: "discovery",
195
- title: "Query discovery and research",
196
- description: "Two linked workflows: find queries expands an ICP into a promotable tracked-query basket; research queries runs saved free-form experiments against one API model. Research never adds queries to tracking.",
197
- whenToLoad: "Load when the operator wants to find ICP-led candidates for a tracked basket, or research specific free-form queries, model answers, sources, and location effects without changing tracking."
198
- }
199
- ];
200
- function isCanonryMcpToolkitName(value) {
201
- return CANONRY_MCP_TOOLKIT_NAMES.includes(value);
202
- }
203
-
204
- // src/mcp/dynamic-catalog.ts
205
- var DynamicToolCatalog = class {
206
- entries;
207
- loaded = /* @__PURE__ */ new Set();
208
- eager;
209
- scope;
210
- server;
211
- constructor(server, entries, scope, options = {}) {
212
- this.server = server;
213
- this.entries = entries;
214
- this.scope = scope;
215
- this.eager = Boolean(options.eager);
216
- if (this.eager) {
217
- for (const toolkit of CANONRY_MCP_TOOLKITS) {
218
- if (this.toolsForToolkit(toolkit.name).length > 0) {
219
- this.loaded.add(toolkit.name);
220
- }
221
- }
222
- }
223
- }
224
- applyInitialEnablement() {
225
- if (this.eager) return;
226
- this.batchListChanged(() => {
227
- for (const entry of this.entries) {
228
- if (entry.tool.tier !== "core") entry.registered.disable();
229
- }
230
- });
231
- }
232
- loadToolkit(rawName) {
233
- if (!isCanonryMcpToolkitName(rawName)) {
234
- const valid = CANONRY_MCP_TOOLKITS.map((t) => t.name).join(", ");
235
- throw new Error(`Unknown toolkit "${rawName}". Available: ${valid}.`);
236
- }
237
- const name = rawName;
238
- const matches = this.entries.filter((entry) => entry.tool.tier === name);
239
- if (matches.length === 0) {
240
- return { status: "empty", name, tools: [] };
241
- }
242
- if (this.loaded.has(name)) {
243
- return { status: "already-loaded", name, tools: matches.map((entry) => entry.tool.name) };
244
- }
245
- this.batchListChanged(() => {
246
- for (const entry of matches) {
247
- entry.registered.enable();
248
- }
249
- });
250
- this.loaded.add(name);
251
- return { status: "loaded", name, tools: matches.map((entry) => entry.tool.name) };
252
- }
253
- helpResult() {
254
- return {
255
- scope: this.scope,
256
- eager: this.eager,
257
- loadedToolkits: [...this.loaded].sort(),
258
- coreTools: this.entries.filter((entry) => entry.tool.tier === "core").map((entry) => entry.tool.name),
259
- toolkits: CANONRY_MCP_TOOLKITS.map((toolkit) => this.toolkitEntry(toolkit)).filter((entry) => entry.toolCount > 0),
260
- usage: "Call canonry_load_toolkit with one of the toolkit names listed in `toolkits[].name` to register its tools for the rest of this session. Wait for its response before calling any newly enabled tool."
261
- };
262
- }
263
- toolkitEntry(toolkit) {
264
- const tools = this.toolsForToolkit(toolkit.name);
265
- return {
266
- name: toolkit.name,
267
- title: toolkit.title,
268
- description: toolkit.description,
269
- whenToLoad: toolkit.whenToLoad,
270
- toolCount: tools.length,
271
- tools,
272
- loaded: this.loaded.has(toolkit.name)
273
- };
274
- }
275
- toolsForToolkit(name) {
276
- return this.entries.filter((entry) => entry.tool.tier === name).map((entry) => entry.tool.name);
277
- }
278
- // RegisteredTool.enable/disable each call sendToolListChanged on the McpServer
279
- // we registered with. Loading an 11-tool toolkit emits 11 notifications under
280
- // that contract, which a spec-compliant client will treat as 11 catalog
281
- // refetches. Coalesce them into one notification per batch by intercepting
282
- // the SDK's sender for the duration of the batch.
283
- batchListChanged(fn) {
284
- const host = this.server;
285
- const original = host.sendToolListChanged.bind(host);
286
- let suppressed = false;
287
- host.sendToolListChanged = () => {
288
- suppressed = true;
289
- };
290
- try {
291
- fn();
292
- } finally {
293
- host.sendToolListChanged = original;
294
- }
295
- if (suppressed) original();
296
- }
297
- };
298
-
299
- // src/mcp/server.ts
300
- function createCanonryMcpServer(options = {}) {
301
- return createCanonryMcpServerWithCatalog(options).server;
302
- }
303
- var SERVER_INSTRUCTIONS = `Canonry tracks how AI answer engines mention a brand and cite a domain.
304
-
305
- Load the "canonry" skill before operator work (project setup, integrations, traffic sources, sweeps, diagnosis). It carries the procedures and the failure modes; this text is only a pointer. Use "aero" for analyst work: regression diagnosis, reporting.
306
-
307
- Two signals, never interchangeable:
308
- - mentioned = the brand appears in the answer TEXT the model wrote.
309
- - cited = the domain appears in the SOURCE links behind the answer.
310
- A model can do either, both or neither. Never compute one from the other, and never report a number for one under the other's name.
311
-
312
- Sweeps and probes spend provider quota and write rows. Get explicit approval before any run, apply, or other mutation.
313
-
314
- Most reads are free. Five ads reads are NOT: canonry_ads_account, canonry_ads_geo_search, canonry_ads_live_delivery, canonry_ads_conversion_pixels and canonry_ads_conversion_event_settings call the provider live and spend against the advertiser account. They are marked read, so nothing in the tool list warns you. Get approval for those exactly as for a mutation.
315
-
316
- Google Marketing also calls providers live. Get approval before canonry_google_ads_customers, canonry_gtm_accounts, canonry_gtm_containers, canonry_gtm_workspaces, canonry_google_ads_sync or canonry_gtm_sync.
317
-
318
- A null answerMentioned means NOT CHECKED, not "not mentioned". Never coerce it to false.
319
-
320
- If no sweep has run, say so. Never state a mention or citation figure that no run produced.`;
321
- function createCanonryMcpServerWithCatalog(options = {}) {
322
- const clientFactory = options.clientFactory ?? createApiClient;
323
- const client = clientFactory();
324
- const scope = options.scope ?? "all";
325
- const server = new McpServer({
326
- name: "canonry",
327
- version: PACKAGE_VERSION
328
- }, {
329
- instructions: SERVER_INSTRUCTIONS
330
- });
331
- server.validateToolInput = async (_tool, args) => args;
332
- const entries = [];
333
- for (const registryTool of getCanonryMcpTools(scope)) {
334
- const tool = registryTool;
335
- const handler = tool.handler;
336
- const registered = server.registerTool(
337
- tool.name,
338
- {
339
- title: tool.title,
340
- description: tool.description,
341
- inputSchema: tool.inputSchema,
342
- annotations: tool.annotations
343
- },
344
- async (input) => withToolErrors(async () => {
345
- const parsed = tool.inputSchema.parse(input ?? {});
346
- return handler(client, parsed);
347
- })
348
- );
349
- entries.push({ tool, registered });
350
- }
351
- const catalog = new DynamicToolCatalog(server, entries, scope, { eager: options.eager });
352
- catalog.applyInitialEnablement();
353
- registerMetaTools(server, catalog);
354
- return { server, catalog };
355
- }
356
- var loadToolkitInputSchema = z.object({
357
- name: z.enum(CANONRY_MCP_TOOLKIT_NAMES).describe("Toolkit name. List options with canonry_help.")
358
- });
359
- function registerMetaTools(server, catalog) {
360
- server.registerTool(
361
- "canonry_help",
362
- {
363
- title: "List Canonry MCP toolkits",
364
- description: "List available toolkits and which are loaded. Call before canonry_load_toolkit if unsure which to load.",
365
- inputSchema: {},
366
- annotations: { readOnlyHint: true }
367
- },
368
- async () => withToolErrors(async () => catalog.helpResult())
369
- );
370
- server.registerTool(
371
- "canonry_load_toolkit",
372
- {
373
- title: "Load a Canonry MCP toolkit",
374
- description: `Register a toolkit's tools for this session and emit one notifications/tools/list_changed. Idempotent. Loaded toolkits remain loaded for the rest of the session. Wait for this call to return before calling any newly enabled tool \u2014 pipelining the call with a tools/call on the same connection can race the registration and fail with "MCP error -32602: Tool ... disabled".`,
375
- inputSchema: loadToolkitInputSchema.shape,
376
- annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: false }
377
- },
378
- async (input) => withToolErrors(async () => {
379
- const parsed = loadToolkitInputSchema.parse(input ?? {});
380
- return catalog.loadToolkit(parsed.name);
381
- })
382
- );
383
- }
384
- function getCanonryMcpTools(scope = "all") {
385
- return scope === "read-only" ? canonryMcpTools.filter((tool) => tool.access === "read") : [...canonryMcpTools];
386
- }
387
-
388
- // src/mcp/cli.ts
389
14
  var HELP_TEXT = `Usage: canonry-mcp [--read-only | --scope=<all|read-only>] [--eager]
390
15
 
391
16
  Stdio MCP adapter over the Canonry public API. Inherits config from
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonry/canonry",
3
- "version": "4.177.2",
3
+ "version": "4.178.0",
4
4
  "type": "module",
5
5
  "description": "Self-hosted AI visibility (AEO) platform: track how ChatGPT, Claude, Gemini, and Perplexity cite your domain, join it with Search Console, GA4, server-side traffic, and paid media, and fix what you find through agent tools (CLI, REST, MCP). Local SQLite.",
6
6
  "license": "FSL-1.1-ALv2",
@@ -71,30 +71,30 @@
71
71
  "tsup": "^8.5.1",
72
72
  "tsx": "^4.19.0",
73
73
  "@ainyc/canonry-api-client": "0.0.0",
74
- "@ainyc/canonry-contracts": "0.0.0",
75
74
  "@ainyc/canonry-api-routes": "0.0.0",
76
75
  "@ainyc/canonry-config": "0.0.0",
76
+ "@ainyc/canonry-contracts": "0.0.0",
77
77
  "@ainyc/canonry-db": "0.0.0",
78
78
  "@ainyc/canonry-integration-bing": "0.0.0",
79
- "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
80
- "@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
81
79
  "@ainyc/canonry-integration-cloud-run": "0.0.0",
82
- "@ainyc/canonry-integration-google": "0.0.0",
80
+ "@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
83
81
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
82
+ "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
84
83
  "@ainyc/canonry-integration-google-ads": "0.0.0",
84
+ "@ainyc/canonry-integration-google": "0.0.0",
85
85
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
86
86
  "@ainyc/canonry-integration-google-places": "0.0.0",
87
87
  "@ainyc/canonry-integration-google-tag-manager": "0.0.0",
88
- "@ainyc/canonry-integration-openai-ads": "0.0.0",
88
+ "@ainyc/canonry-integration-traffic": "0.0.0",
89
89
  "@ainyc/canonry-integration-wordpress": "0.0.0",
90
+ "@ainyc/canonry-integration-openai-ads": "0.0.0",
90
91
  "@ainyc/canonry-intelligence": "0.0.0",
91
- "@ainyc/canonry-provider-claude": "0.0.0",
92
92
  "@ainyc/canonry-provider-cdp": "0.0.0",
93
+ "@ainyc/canonry-provider-claude": "0.0.0",
93
94
  "@ainyc/canonry-provider-local": "0.0.0",
94
95
  "@ainyc/canonry-provider-openai": "0.0.0",
95
96
  "@ainyc/canonry-provider-gemini": "0.0.0",
96
- "@ainyc/canonry-provider-perplexity": "0.0.0",
97
- "@ainyc/canonry-integration-traffic": "0.0.0"
97
+ "@ainyc/canonry-provider-perplexity": "0.0.0"
98
98
  },
99
99
  "scripts": {
100
100
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",