@ainyc/canonry 2.10.1 → 2.12.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.
package/dist/cli.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  setGoogleAuthConfig,
18
18
  showFirstRunNotice,
19
19
  trackEvent
20
- } from "./chunk-SZSWQG3J.js";
20
+ } from "./chunk-FCYNFM4B.js";
21
21
  import {
22
22
  CcReleaseSyncStatuses,
23
23
  CliError,
@@ -41,7 +41,7 @@ import {
41
41
  saveConfig,
42
42
  saveConfigPatch,
43
43
  usageError
44
- } from "./chunk-KWQCQMPY.js";
44
+ } from "./chunk-PLI7EOPM.js";
45
45
  import {
46
46
  apiKeys,
47
47
  competitors,
@@ -51,7 +51,7 @@ import {
51
51
  projects,
52
52
  querySnapshots,
53
53
  runs
54
- } from "./chunk-PYHANJ3B.js";
54
+ } from "./chunk-UM6RDSRJ.js";
55
55
  import "./chunk-MLKGABMK.js";
56
56
 
57
57
  // src/cli.ts
@@ -299,7 +299,7 @@ async function backfillAnswerVisibilityCommand(opts) {
299
299
  console.log(` Errors: ${providerErrors}`);
300
300
  }
301
301
  async function backfillInsightsCommand(project, opts) {
302
- const { IntelligenceService } = await import("./intelligence-service-2ZABHNR4.js");
302
+ const { IntelligenceService } = await import("./intelligence-service-54F3NGPM.js");
303
303
  const config = loadConfig();
304
304
  const db = createClient(config.database);
305
305
  migrate(db);
@@ -1847,7 +1847,7 @@ async function gaSocialReferralSummary(project, opts) {
1847
1847
  console.log(` Sessions: ${traffic.socialSessions} (${traffic.socialSharePct}% of ${traffic.totalSessions} total)`);
1848
1848
  console.log(` Users: ${traffic.socialUsers}`);
1849
1849
  console.log();
1850
- const fmtTrend = (pct) => pct === null ? "n/a" : `${pct >= 0 ? "+" : ""}${pct}%`;
1850
+ const fmtTrend = (pct2) => pct2 === null ? "n/a" : `${pct2 >= 0 ? "+" : ""}${pct2}%`;
1851
1851
  console.log(` 7d trend: ${fmtTrend(trend.trend7dPct)} (${trend.socialSessions7d} vs ${trend.socialSessionsPrev7d})`);
1852
1852
  console.log(` 30d trend: ${fmtTrend(trend.trend30dPct)} (${trend.socialSessions30d} vs ${trend.socialSessionsPrev30d})`);
1853
1853
  if (trend.biggestMover) {
@@ -1890,7 +1890,7 @@ async function gaSocialReferralSummary(project, opts) {
1890
1890
  async function gaAttribution(project, opts) {
1891
1891
  const client = getClient4();
1892
1892
  const traffic = await client.gaTraffic(project);
1893
- const fmtTrend = (pct) => pct === null ? "n/a" : `${pct >= 0 ? "+" : ""}${pct}%`;
1893
+ const fmtTrend = (pct2) => pct2 === null ? "n/a" : `${pct2 >= 0 ? "+" : ""}${pct2}%`;
1894
1894
  if (opts?.trend) {
1895
1895
  const trend = await client.gaAttributionTrend(project);
1896
1896
  if (opts.format === "json") {
@@ -4047,14 +4047,14 @@ function printMetrics(data) {
4047
4047
  console.log(`
4048
4048
  Citation Rate Trends (${data.window})`);
4049
4049
  console.log("\u2500".repeat(50));
4050
- const pct = (n) => `${(n * 100).toFixed(1)}%`;
4051
- console.log(` Overall: ${pct(data.overall.citationRate)} (${data.overall.cited}/${data.overall.total})`);
4050
+ const pct2 = (n) => `${(n * 100).toFixed(1)}%`;
4051
+ console.log(` Overall: ${pct2(data.overall.citationRate)} (${data.overall.cited}/${data.overall.total})`);
4052
4052
  console.log(` Trend: ${data.trend}`);
4053
4053
  if (Object.keys(data.byProvider).length > 0) {
4054
4054
  console.log(`
4055
4055
  By Provider:`);
4056
4056
  for (const [provider, metric] of Object.entries(data.byProvider)) {
4057
- console.log(` ${provider.padEnd(10)} ${pct(metric.citationRate).padStart(6)} (${metric.cited}/${metric.total})`);
4057
+ console.log(` ${provider.padEnd(10)} ${pct2(metric.citationRate).padStart(6)} (${metric.cited}/${metric.total})`);
4058
4058
  }
4059
4059
  }
4060
4060
  if (data.buckets.length > 0) {
@@ -4063,7 +4063,7 @@ Citation Rate Trends (${data.window})`);
4063
4063
  for (const bucket of data.buckets) {
4064
4064
  const start = bucket.startDate.slice(0, 10);
4065
4065
  const bar = bucket.total > 0 ? "\u2588".repeat(Math.round(bucket.citationRate * 20)) : "";
4066
- console.log(` ${start} ${pct(bucket.citationRate).padStart(6)} ${bar}`);
4066
+ console.log(` ${start} ${pct2(bucket.citationRate).padStart(6)} ${bar}`);
4067
4067
  }
4068
4068
  }
4069
4069
  }
@@ -4100,9 +4100,9 @@ Source Origin Breakdown`);
4100
4100
  return;
4101
4101
  }
4102
4102
  for (const cat of data.overall) {
4103
- const pct = `${(cat.percentage * 100).toFixed(1)}%`;
4103
+ const pct2 = `${(cat.percentage * 100).toFixed(1)}%`;
4104
4104
  const domains = cat.topDomains.slice(0, 3).map((d) => d.domain).join(", ");
4105
- console.log(` ${cat.label.padEnd(20)} ${pct.padStart(6)} (${cat.count}) ${domains}`);
4105
+ console.log(` ${cat.label.padEnd(20)} ${pct2.padStart(6)} (${cat.count}) ${domains}`);
4106
4106
  }
4107
4107
  }
4108
4108
 
@@ -6026,6 +6026,84 @@ async function showHealth(project, opts) {
6026
6026
  }
6027
6027
  }
6028
6028
 
6029
+ // src/commands/overview.ts
6030
+ async function showOverview(project, opts) {
6031
+ const client = createApiClient();
6032
+ const overview = await client.getProjectOverview(project);
6033
+ if (opts.format === "json") {
6034
+ console.log(JSON.stringify(overview, null, 2));
6035
+ return;
6036
+ }
6037
+ const { project: meta, latestRun, health, topInsights, keywordCounts, providers, transitions } = overview;
6038
+ console.log(`Overview: ${meta.displayName ?? meta.name} (${meta.name})
6039
+ `);
6040
+ console.log(` Domain: ${meta.canonicalDomain}`);
6041
+ console.log(` Country: ${meta.country}`);
6042
+ console.log(` Language: ${meta.language}`);
6043
+ if (latestRun.run) {
6044
+ const finished = latestRun.run.finishedAt ?? "\u2014";
6045
+ console.log(`
6046
+ Latest run: ${latestRun.run.id} (${latestRun.run.status}, ${finished})`);
6047
+ console.log(` Total runs: ${latestRun.totalRuns}`);
6048
+ } else {
6049
+ console.log("\n No runs yet.");
6050
+ }
6051
+ console.log(`
6052
+ Keywords cited: ${keywordCounts.citedKeywords}/${keywordCounts.totalKeywords} (${pct(keywordCounts.citedRate)})`);
6053
+ if (providers.length > 0) {
6054
+ console.log(" Providers:");
6055
+ for (const p of providers) {
6056
+ console.log(` ${p.provider.padEnd(10)} ${p.cited}/${p.total} (${pct(p.citedRate)})`);
6057
+ }
6058
+ }
6059
+ if (transitions.since) {
6060
+ console.log(`
6061
+ vs run at ${transitions.since}: +${transitions.gained} gained, -${transitions.lost} lost, ${transitions.emerging} emerging`);
6062
+ }
6063
+ if (health) {
6064
+ console.log(`
6065
+ Health: ${pct(health.overallCitedRate)} cited (${health.citedPairs}/${health.totalPairs} pairs)`);
6066
+ }
6067
+ if (topInsights.length > 0) {
6068
+ console.log("\n Top insights:");
6069
+ for (const insight of topInsights) {
6070
+ console.log(` [${insight.severity.toUpperCase()}] ${insight.type} \u2014 ${insight.title}`);
6071
+ }
6072
+ }
6073
+ }
6074
+ function pct(value) {
6075
+ return `${(value * 100).toFixed(1)}%`;
6076
+ }
6077
+
6078
+ // src/commands/search.ts
6079
+ async function searchProject(project, opts) {
6080
+ const client = createApiClient();
6081
+ const result = await client.searchProject(project, { q: opts.query, limit: opts.limit });
6082
+ if (opts.format === "json") {
6083
+ console.log(JSON.stringify(result, null, 2));
6084
+ return;
6085
+ }
6086
+ console.log(`Search: "${result.query}" \u2014 ${result.totalHits} hit${result.totalHits === 1 ? "" : "s"}${result.truncated ? " (truncated)" : ""}
6087
+ `);
6088
+ if (result.hits.length === 0) {
6089
+ console.log(" No matches.");
6090
+ return;
6091
+ }
6092
+ for (const hit of result.hits) {
6093
+ if (hit.kind === "snapshot") {
6094
+ console.log(` [snapshot] ${hit.keyword} (${hit.provider}, ${hit.citationState}) \u2014 ${hit.matchedField}`);
6095
+ console.log(` ${hit.snippet}`);
6096
+ console.log(` run=${hit.runId} at ${hit.createdAt}`);
6097
+ } else {
6098
+ const dismissed = hit.dismissed ? " [dismissed]" : "";
6099
+ console.log(` [insight ${hit.severity.toUpperCase()}] ${hit.type} \u2014 ${hit.title}${dismissed}`);
6100
+ console.log(` ${hit.snippet}`);
6101
+ console.log(` keyword=${hit.keyword} at ${hit.createdAt}`);
6102
+ }
6103
+ console.log("");
6104
+ }
6105
+ }
6106
+
6029
6107
  // src/cli-commands/intelligence.ts
6030
6108
  var INTELLIGENCE_CLI_COMMANDS = [
6031
6109
  {
@@ -6072,6 +6150,165 @@ var INTELLIGENCE_CLI_COMMANDS = [
6072
6150
  });
6073
6151
  await showHealth(project, { history, limit, format: input.format });
6074
6152
  }
6153
+ },
6154
+ {
6155
+ path: ["overview"],
6156
+ usage: "canonry overview <project> [--format json]",
6157
+ options: {},
6158
+ run: async (input) => {
6159
+ const usage = "canonry overview <project> [--format json]";
6160
+ const project = requireProject(input, "overview", usage);
6161
+ await showOverview(project, { format: input.format });
6162
+ }
6163
+ },
6164
+ {
6165
+ path: ["search"],
6166
+ usage: "canonry search <project> <query> [--limit <n>] [--format json]",
6167
+ options: {
6168
+ limit: { type: "string" }
6169
+ },
6170
+ run: async (input) => {
6171
+ const usage = "canonry search <project> <query> [--limit <n>] [--format json]";
6172
+ const project = requireProject(input, "search", usage);
6173
+ const query = requirePositional(input, 1, { command: "search", usage, message: "query is required" });
6174
+ const limit = parseIntegerOption(input, "limit", {
6175
+ command: "search",
6176
+ usage,
6177
+ message: "--limit must be an integer"
6178
+ });
6179
+ await searchProject(project, { query, limit, format: input.format });
6180
+ }
6181
+ }
6182
+ ];
6183
+
6184
+ // src/commands/content.ts
6185
+ async function listContentTargets(project, opts) {
6186
+ const client = createApiClient();
6187
+ const response = await client.getContentTargets(project, {
6188
+ limit: opts.limit,
6189
+ includeInProgress: opts.includeInProgress
6190
+ });
6191
+ if (opts.format === "json") {
6192
+ console.log(JSON.stringify(response, null, 2));
6193
+ return;
6194
+ }
6195
+ if (response.targets.length === 0) {
6196
+ console.log("No content targets surfaced. (Run `canonry run` to generate fresh signal.)");
6197
+ return;
6198
+ }
6199
+ console.log(
6200
+ `${response.targets.length} target${response.targets.length === 1 ? "" : "s"} (latestRunId=${response.contextMetrics.latestRunId})`
6201
+ );
6202
+ console.log("");
6203
+ for (const target of response.targets) {
6204
+ const action = target.action.toUpperCase().padEnd(11);
6205
+ const score = target.score.toFixed(1).padStart(6);
6206
+ const conf = target.actionConfidence.padEnd(6);
6207
+ console.log(`${action} ${score} conf=${conf} ${target.query}`);
6208
+ if (target.ourBestPage) {
6209
+ const posLabel = target.ourBestPage.gscAvgPosition !== null ? `pos #${target.ourBestPage.gscAvgPosition}` : "no GSC ranking";
6210
+ console.log(` our page: ${target.ourBestPage.url} (${posLabel})`);
6211
+ }
6212
+ if (target.winningCompetitor) {
6213
+ console.log(` winning: ${target.winningCompetitor.url} (${target.winningCompetitor.citationCount}\xD7 cited)`);
6214
+ }
6215
+ if (target.drivers.length > 0) {
6216
+ console.log(` why: ${target.drivers.join(" \xB7 ")}`);
6217
+ }
6218
+ if (target.existingAction) {
6219
+ console.log(` in-flight action: ${target.existingAction.actionId} (${target.existingAction.state})`);
6220
+ }
6221
+ console.log("");
6222
+ }
6223
+ }
6224
+ async function listContentSources(project, opts) {
6225
+ const client = createApiClient();
6226
+ const response = await client.getContentSources(project);
6227
+ if (opts.format === "json") {
6228
+ console.log(JSON.stringify(response, null, 2));
6229
+ return;
6230
+ }
6231
+ if (response.sources.length === 0) {
6232
+ console.log("No grounding sources captured yet.");
6233
+ return;
6234
+ }
6235
+ for (const row of response.sources) {
6236
+ console.log(`Q: ${row.query}`);
6237
+ if (row.groundingSources.length === 0) {
6238
+ console.log(" (no grounding sources)");
6239
+ } else {
6240
+ for (const g of row.groundingSources) {
6241
+ const tag = g.isOurDomain ? "OURS " : g.isCompetitor ? "COMP " : "OTHER ";
6242
+ console.log(` ${tag} ${g.uri} (${g.citationCount}\xD7)`);
6243
+ }
6244
+ }
6245
+ console.log("");
6246
+ }
6247
+ }
6248
+ async function listContentGaps(project, opts) {
6249
+ const client = createApiClient();
6250
+ const response = await client.getContentGaps(project);
6251
+ if (opts.format === "json") {
6252
+ console.log(JSON.stringify(response, null, 2));
6253
+ return;
6254
+ }
6255
+ if (response.gaps.length === 0) {
6256
+ console.log("No competitor-only-cited queries detected.");
6257
+ return;
6258
+ }
6259
+ console.log(`${response.gaps.length} gap${response.gaps.length === 1 ? "" : "s"} found`);
6260
+ console.log("");
6261
+ for (const gap of response.gaps) {
6262
+ const missPct = Math.round(gap.missRate * 100);
6263
+ console.log(`${missPct.toString().padStart(3)}% ${gap.competitorCount} competitor(s) ${gap.query}`);
6264
+ console.log(` competitors: ${gap.competitorDomains.join(", ")}`);
6265
+ console.log("");
6266
+ }
6267
+ }
6268
+
6269
+ // src/cli-commands/content.ts
6270
+ var CONTENT_CLI_COMMANDS = [
6271
+ {
6272
+ path: ["content", "targets"],
6273
+ usage: "canonry content targets <project> [--limit <n>] [--include-in-progress] [--format json]",
6274
+ options: {
6275
+ limit: { type: "string" },
6276
+ "include-in-progress": { type: "boolean" }
6277
+ },
6278
+ run: async (input) => {
6279
+ const usage = "canonry content targets <project> [--limit <n>] [--include-in-progress] [--format json]";
6280
+ const project = requireProject(input, "content targets", usage);
6281
+ const limit = parseIntegerOption(input, "limit", {
6282
+ command: "content targets",
6283
+ usage,
6284
+ message: "--limit must be a non-negative integer"
6285
+ });
6286
+ await listContentTargets(project, {
6287
+ limit,
6288
+ includeInProgress: input.values["include-in-progress"] === true,
6289
+ format: input.format
6290
+ });
6291
+ }
6292
+ },
6293
+ {
6294
+ path: ["content", "sources"],
6295
+ usage: "canonry content sources <project> [--format json]",
6296
+ options: {},
6297
+ run: async (input) => {
6298
+ const usage = "canonry content sources <project> [--format json]";
6299
+ const project = requireProject(input, "content sources", usage);
6300
+ await listContentSources(project, { format: input.format });
6301
+ }
6302
+ },
6303
+ {
6304
+ path: ["content", "gaps"],
6305
+ usage: "canonry content gaps <project> [--format json]",
6306
+ options: {},
6307
+ run: async (input) => {
6308
+ const usage = "canonry content gaps <project> [--format json]";
6309
+ const project = requireProject(input, "content gaps", usage);
6310
+ await listContentGaps(project, { format: input.format });
6311
+ }
6075
6312
  }
6076
6313
  ];
6077
6314
 
@@ -8567,6 +8804,7 @@ var REGISTERED_CLI_COMMANDS = [
8567
8804
  ...CDP_CLI_COMMANDS,
8568
8805
  ...GA_CLI_COMMANDS,
8569
8806
  ...INTELLIGENCE_CLI_COMMANDS,
8807
+ ...CONTENT_CLI_COMMANDS,
8570
8808
  ...AGENT_CLI_COMMANDS,
8571
8809
  ...MCP_CLI_COMMANDS
8572
8810
  ];
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-SZSWQG3J.js";
3
+ } from "./chunk-FCYNFM4B.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-KWQCQMPY.js";
7
- import "./chunk-PYHANJ3B.js";
6
+ } from "./chunk-PLI7EOPM.js";
7
+ import "./chunk-UM6RDSRJ.js";
8
8
  import "./chunk-MLKGABMK.js";
9
9
  export {
10
10
  createServer,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-PYHANJ3B.js";
3
+ } from "./chunk-UM6RDSRJ.js";
4
4
  import "./chunk-MLKGABMK.js";
5
5
  export {
6
6
  IntelligenceService
package/dist/mcp.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  projectUpsertRequestSchema,
11
11
  runTriggerRequestSchema,
12
12
  scheduleUpsertRequestSchema
13
- } from "./chunk-KWQCQMPY.js";
13
+ } from "./chunk-PLI7EOPM.js";
14
14
  import "./chunk-MLKGABMK.js";
15
15
 
16
16
  // src/mcp/cli.ts
@@ -202,6 +202,32 @@ var canonryMcpTools = [
202
202
  openApiOperations: ["GET /api/v1/projects/{name}"],
203
203
  handler: (client, input) => client.getProject(input.project)
204
204
  }),
205
+ defineTool({
206
+ name: "canonry_project_overview",
207
+ title: "Get project overview (composite)",
208
+ description: 'One-call summary for "how is project X doing?" \u2014 bundles project info, latest run, top undismissed insights, latest health snapshot, keyword cited rate, per-provider breakdown, and gained/lost/emerging vs the previous run. Prefer this over fanning out to separate tools.',
209
+ access: "read",
210
+ tier: "core",
211
+ inputSchema: projectInputSchema,
212
+ annotations: readAnnotations(),
213
+ openApiOperations: ["GET /api/v1/projects/{name}/overview"],
214
+ handler: (client, input) => client.getProjectOverview(input.project)
215
+ }),
216
+ defineTool({
217
+ name: "canonry_search",
218
+ title: "Search project (composite)",
219
+ description: "Search query snapshots and intelligence insights for the given text. Looks at snapshot answer text, cited domains, raw provider responses, and insight title/keyword/recommendation/cause. Returns ranked hits with snippets \u2014 use it instead of paginating snapshots when you need to find a competitor mention or term.",
220
+ access: "read",
221
+ tier: "core",
222
+ inputSchema: z2.object({
223
+ project: projectNameSchema,
224
+ q: z2.string().min(2).describe("Search term, at least 2 characters."),
225
+ limit: z2.number().int().positive().max(50).optional().describe("Max combined hits (1-50, default 25).")
226
+ }),
227
+ annotations: readAnnotations(),
228
+ openApiOperations: ["GET /api/v1/projects/{name}/search"],
229
+ handler: (client, input) => client.searchProject(input.project, { q: input.q, limit: input.limit })
230
+ }),
205
231
  defineTool({
206
232
  name: "canonry_project_export",
207
233
  title: "Export project config",
@@ -885,7 +911,9 @@ var DynamicToolCatalog = class {
885
911
  loaded = /* @__PURE__ */ new Set();
886
912
  eager;
887
913
  scope;
888
- constructor(entries, scope, options = {}) {
914
+ server;
915
+ constructor(server, entries, scope, options = {}) {
916
+ this.server = server;
889
917
  this.entries = entries;
890
918
  this.scope = scope;
891
919
  this.eager = Boolean(options.eager);
@@ -899,9 +927,11 @@ var DynamicToolCatalog = class {
899
927
  }
900
928
  applyInitialEnablement() {
901
929
  if (this.eager) return;
902
- for (const entry of this.entries) {
903
- if (entry.tool.tier !== "core") entry.registered.disable();
904
- }
930
+ this.batchListChanged(() => {
931
+ for (const entry of this.entries) {
932
+ if (entry.tool.tier !== "core") entry.registered.disable();
933
+ }
934
+ });
905
935
  }
906
936
  loadToolkit(rawName) {
907
937
  if (!isCanonryMcpToolkitName(rawName)) {
@@ -916,9 +946,11 @@ var DynamicToolCatalog = class {
916
946
  if (this.loaded.has(name)) {
917
947
  return { status: "already-loaded", name, tools: matches.map((entry) => entry.tool.name) };
918
948
  }
919
- for (const entry of matches) {
920
- entry.registered.enable();
921
- }
949
+ this.batchListChanged(() => {
950
+ for (const entry of matches) {
951
+ entry.registered.enable();
952
+ }
953
+ });
922
954
  this.loaded.add(name);
923
955
  return { status: "loaded", name, tools: matches.map((entry) => entry.tool.name) };
924
956
  }
@@ -929,7 +961,7 @@ var DynamicToolCatalog = class {
929
961
  loadedToolkits: [...this.loaded].sort(),
930
962
  coreTools: this.entries.filter((entry) => entry.tool.tier === "core").map((entry) => entry.tool.name),
931
963
  toolkits: CANONRY_MCP_TOOLKITS.map((toolkit) => this.toolkitEntry(toolkit)).filter((entry) => entry.toolCount > 0),
932
- 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."
964
+ 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."
933
965
  };
934
966
  }
935
967
  toolkitEntry(toolkit) {
@@ -947,6 +979,25 @@ var DynamicToolCatalog = class {
947
979
  toolsForToolkit(name) {
948
980
  return this.entries.filter((entry) => entry.tool.tier === name).map((entry) => entry.tool.name);
949
981
  }
982
+ // RegisteredTool.enable/disable each call sendToolListChanged on the McpServer
983
+ // we registered with. Loading an 11-tool toolkit emits 11 notifications under
984
+ // that contract, which a spec-compliant client will treat as 11 catalog
985
+ // refetches. Coalesce them into one notification per batch by intercepting
986
+ // the SDK's sender for the duration of the batch.
987
+ batchListChanged(fn) {
988
+ const host = this.server;
989
+ const original = host.sendToolListChanged;
990
+ let suppressed = false;
991
+ host.sendToolListChanged = () => {
992
+ suppressed = true;
993
+ };
994
+ try {
995
+ fn();
996
+ } finally {
997
+ host.sendToolListChanged = original;
998
+ }
999
+ if (suppressed) original.call(host);
1000
+ }
950
1001
  };
951
1002
 
952
1003
  // src/mcp/server.ts
@@ -981,7 +1032,7 @@ function createCanonryMcpServerWithCatalog(options = {}) {
981
1032
  );
982
1033
  entries.push({ tool, registered });
983
1034
  }
984
- const catalog = new DynamicToolCatalog(entries, scope, { eager: options.eager });
1035
+ const catalog = new DynamicToolCatalog(server, entries, scope, { eager: options.eager });
985
1036
  catalog.applyInitialEnablement();
986
1037
  registerMetaTools(server, catalog);
987
1038
  return { server, catalog };
@@ -1004,7 +1055,7 @@ function registerMetaTools(server, catalog) {
1004
1055
  "canonry_load_toolkit",
1005
1056
  {
1006
1057
  title: "Load a Canonry MCP toolkit",
1007
- description: "Register a toolkit's tools for this session and emit notifications/tools/list_changed. Idempotent. Loaded toolkits remain loaded for the rest of the session.",
1058
+ 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 "Tool ... disabled".`,
1008
1059
  inputSchema: loadToolkitInputSchema.shape,
1009
1060
  annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: false }
1010
1061
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "2.10.1",
3
+ "version": "2.12.0",
4
4
  "type": "module",
5
5
  "description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -59,21 +59,21 @@
59
59
  "@types/node-cron": "^3.0.11",
60
60
  "tsup": "^8.5.1",
61
61
  "tsx": "^4.19.0",
62
+ "@ainyc/canonry-api-routes": "0.0.0",
63
+ "@ainyc/canonry-contracts": "0.0.0",
64
+ "@ainyc/canonry-db": "0.0.0",
62
65
  "@ainyc/canonry-config": "0.0.0",
63
66
  "@ainyc/canonry-intelligence": "0.0.0",
64
- "@ainyc/canonry-db": "0.0.0",
65
- "@ainyc/canonry-api-routes": "0.0.0",
66
67
  "@ainyc/canonry-integration-bing": "0.0.0",
67
68
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
68
- "@ainyc/canonry-integration-google": "0.0.0",
69
- "@ainyc/canonry-contracts": "0.0.0",
70
69
  "@ainyc/canonry-integration-wordpress": "0.0.0",
70
+ "@ainyc/canonry-integration-google": "0.0.0",
71
71
  "@ainyc/canonry-provider-cdp": "0.0.0",
72
- "@ainyc/canonry-provider-claude": "0.0.0",
73
- "@ainyc/canonry-provider-local": "0.0.0",
74
- "@ainyc/canonry-provider-perplexity": "0.0.0",
75
72
  "@ainyc/canonry-provider-gemini": "0.0.0",
76
- "@ainyc/canonry-provider-openai": "0.0.0"
73
+ "@ainyc/canonry-provider-local": "0.0.0",
74
+ "@ainyc/canonry-provider-claude": "0.0.0",
75
+ "@ainyc/canonry-provider-openai": "0.0.0",
76
+ "@ainyc/canonry-provider-perplexity": "0.0.0"
77
77
  },
78
78
  "scripts": {
79
79
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",