@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/README.md +3 -0
- package/assets/assets/{index-PhLDQh1e.js → index-Ckr4V5dK.js} +108 -108
- package/assets/index.html +1 -1
- package/dist/{chunk-SZSWQG3J.js → chunk-FCYNFM4B.js} +1055 -267
- package/dist/{chunk-KWQCQMPY.js → chunk-PLI7EOPM.js} +177 -0
- package/dist/{chunk-PYHANJ3B.js → chunk-UM6RDSRJ.js} +327 -0
- package/dist/cli.js +250 -12
- package/dist/index.js +3 -3
- package/dist/{intelligence-service-2ZABHNR4.js → intelligence-service-54F3NGPM.js} +1 -1
- package/dist/mcp.js +62 -11
- package/package.json +9 -9
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
setGoogleAuthConfig,
|
|
18
18
|
showFirstRunNotice,
|
|
19
19
|
trackEvent
|
|
20
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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 = (
|
|
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 = (
|
|
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
|
|
4051
|
-
console.log(` Overall: ${
|
|
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)} ${
|
|
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} ${
|
|
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
|
|
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)} ${
|
|
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-
|
|
3
|
+
} from "./chunk-FCYNFM4B.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-PLI7EOPM.js";
|
|
7
|
+
import "./chunk-UM6RDSRJ.js";
|
|
8
8
|
import "./chunk-MLKGABMK.js";
|
|
9
9
|
export {
|
|
10
10
|
createServer,
|
package/dist/mcp.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
projectUpsertRequestSchema,
|
|
11
11
|
runTriggerRequestSchema,
|
|
12
12
|
scheduleUpsertRequestSchema
|
|
13
|
-
} from "./chunk-
|
|
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
|
-
|
|
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
|
-
|
|
903
|
-
|
|
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
|
-
|
|
920
|
-
entry
|
|
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:
|
|
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.
|
|
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-
|
|
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",
|