@ainyc/canonry 2.6.0 → 2.9.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/mcp.js CHANGED
@@ -3,11 +3,14 @@ import {
3
3
  competitorBatchRequestSchema,
4
4
  createApiClient,
5
5
  keywordBatchRequestSchema,
6
+ keywordGenerateRequestSchema,
6
7
  notificationCreateRequestSchema,
7
8
  notificationEventSchema,
9
+ projectConfigSchema,
10
+ projectUpsertRequestSchema,
8
11
  runTriggerRequestSchema,
9
12
  scheduleUpsertRequestSchema
10
- } from "./chunk-3DUTT6H2.js";
13
+ } from "./chunk-FPZUQADO.js";
11
14
  import "./chunk-MLKGABMK.js";
12
15
 
13
16
  // src/mcp/cli.ts
@@ -15,6 +18,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
15
18
 
16
19
  // src/mcp/server.ts
17
20
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
21
+ import { z as z3 } from "zod";
18
22
 
19
23
  // src/package-version.ts
20
24
  import { createRequire } from "module";
@@ -146,10 +150,21 @@ var keywordsInputSchema = z2.object({
146
150
  project: projectNameSchema,
147
151
  request: keywordBatchRequestSchema
148
152
  });
149
- var competitorsAddInputSchema = z2.object({
153
+ var keywordGenerateInputSchema = z2.object({
154
+ project: projectNameSchema,
155
+ request: keywordGenerateRequestSchema
156
+ });
157
+ var competitorsInputSchema = z2.object({
150
158
  project: projectNameSchema,
151
159
  request: competitorBatchRequestSchema
152
160
  });
161
+ var projectUpsertInputSchema = z2.object({
162
+ project: projectNameSchema,
163
+ request: projectUpsertRequestSchema
164
+ });
165
+ var applyConfigInputSchema = z2.object({
166
+ config: projectConfigSchema
167
+ });
153
168
  var scheduleSetInputSchema = z2.object({
154
169
  project: projectNameSchema,
155
170
  schedule: scheduleUpsertRequestSchema
@@ -170,6 +185,7 @@ var canonryMcpTools = [
170
185
  title: "List Canonry projects",
171
186
  description: "List all Canonry projects available through the configured API.",
172
187
  access: "read",
188
+ tier: "core",
173
189
  inputSchema: emptyInputSchema,
174
190
  annotations: readAnnotations(),
175
191
  openApiOperations: ["GET /api/v1/projects"],
@@ -180,6 +196,7 @@ var canonryMcpTools = [
180
196
  title: "Get project",
181
197
  description: "Get a Canonry project by name.",
182
198
  access: "read",
199
+ tier: "core",
183
200
  inputSchema: projectInputSchema,
184
201
  annotations: readAnnotations(),
185
202
  openApiOperations: ["GET /api/v1/projects/{name}"],
@@ -190,6 +207,7 @@ var canonryMcpTools = [
190
207
  title: "Export project config",
191
208
  description: "Export a Canonry project in config-as-code format.",
192
209
  access: "read",
210
+ tier: "setup",
193
211
  inputSchema: projectInputSchema,
194
212
  annotations: readAnnotations(),
195
213
  openApiOperations: ["GET /api/v1/projects/{name}/export"],
@@ -200,6 +218,7 @@ var canonryMcpTools = [
200
218
  title: "Get project history",
201
219
  description: "Get audit history for a Canonry project.",
202
220
  access: "read",
221
+ tier: "monitoring",
203
222
  inputSchema: projectInputSchema,
204
223
  annotations: readAnnotations(),
205
224
  openApiOperations: ["GET /api/v1/projects/{name}/history"],
@@ -210,6 +229,7 @@ var canonryMcpTools = [
210
229
  title: "List project runs",
211
230
  description: "List runs for a Canonry project.",
212
231
  access: "read",
232
+ tier: "monitoring",
213
233
  inputSchema: runsListInputSchema,
214
234
  annotations: readAnnotations(),
215
235
  openApiOperations: ["GET /api/v1/projects/{name}/runs"],
@@ -220,6 +240,7 @@ var canonryMcpTools = [
220
240
  title: "Get latest project run",
221
241
  description: "Get the latest run and total run count for a Canonry project.",
222
242
  access: "read",
243
+ tier: "monitoring",
223
244
  inputSchema: projectInputSchema,
224
245
  annotations: readAnnotations(),
225
246
  openApiOperations: ["GET /api/v1/projects/{name}/runs/latest"],
@@ -230,6 +251,7 @@ var canonryMcpTools = [
230
251
  title: "Get run",
231
252
  description: "Get a Canonry run with its snapshots.",
232
253
  access: "read",
254
+ tier: "monitoring",
233
255
  inputSchema: runGetInputSchema,
234
256
  annotations: readAnnotations(),
235
257
  openApiOperations: ["GET /api/v1/runs/{id}"],
@@ -240,6 +262,7 @@ var canonryMcpTools = [
240
262
  title: "Get project timeline",
241
263
  description: "Get per-keyword citation history for a Canonry project.",
242
264
  access: "read",
265
+ tier: "monitoring",
243
266
  inputSchema: timelineInputSchema,
244
267
  annotations: readAnnotations(),
245
268
  openApiOperations: ["GET /api/v1/projects/{name}/timeline"],
@@ -250,6 +273,7 @@ var canonryMcpTools = [
250
273
  title: "List query snapshots",
251
274
  description: "List paginated query snapshots for a Canonry project.",
252
275
  access: "read",
276
+ tier: "monitoring",
253
277
  inputSchema: snapshotsListInputSchema,
254
278
  annotations: readAnnotations(),
255
279
  openApiOperations: ["GET /api/v1/projects/{name}/snapshots"],
@@ -264,6 +288,7 @@ var canonryMcpTools = [
264
288
  title: "Diff snapshots",
265
289
  description: "Compare query snapshot states between two Canonry runs.",
266
290
  access: "read",
291
+ tier: "monitoring",
267
292
  inputSchema: snapshotsDiffInputSchema,
268
293
  annotations: readAnnotations(),
269
294
  openApiOperations: ["GET /api/v1/projects/{name}/snapshots/diff"],
@@ -274,6 +299,7 @@ var canonryMcpTools = [
274
299
  title: "List insights",
275
300
  description: "List intelligence insights for a Canonry project.",
276
301
  access: "read",
302
+ tier: "monitoring",
277
303
  inputSchema: insightsListInputSchema,
278
304
  annotations: readAnnotations(),
279
305
  openApiOperations: ["GET /api/v1/projects/{name}/insights"],
@@ -284,6 +310,7 @@ var canonryMcpTools = [
284
310
  title: "Get insight",
285
311
  description: "Get one intelligence insight for a Canonry project.",
286
312
  access: "read",
313
+ tier: "monitoring",
287
314
  inputSchema: insightInputSchema,
288
315
  annotations: readAnnotations(),
289
316
  openApiOperations: ["GET /api/v1/projects/{name}/insights/{id}"],
@@ -294,6 +321,7 @@ var canonryMcpTools = [
294
321
  title: "Get latest health",
295
322
  description: "Get the latest health snapshot for a Canonry project.",
296
323
  access: "read",
324
+ tier: "monitoring",
297
325
  inputSchema: projectInputSchema,
298
326
  annotations: readAnnotations(),
299
327
  openApiOperations: ["GET /api/v1/projects/{name}/health/latest"],
@@ -304,6 +332,7 @@ var canonryMcpTools = [
304
332
  title: "Get health history",
305
333
  description: "Get health snapshot history for a Canonry project.",
306
334
  access: "read",
335
+ tier: "monitoring",
307
336
  inputSchema: healthHistoryInputSchema,
308
337
  annotations: readAnnotations(),
309
338
  openApiOperations: ["GET /api/v1/projects/{name}/health/history"],
@@ -314,6 +343,7 @@ var canonryMcpTools = [
314
343
  title: "List keywords",
315
344
  description: "List tracked keywords for a Canonry project.",
316
345
  access: "read",
346
+ tier: "setup",
317
347
  inputSchema: projectInputSchema,
318
348
  annotations: readAnnotations(),
319
349
  openApiOperations: ["GET /api/v1/projects/{name}/keywords"],
@@ -324,6 +354,7 @@ var canonryMcpTools = [
324
354
  title: "List competitors",
325
355
  description: "List tracked competitors for a Canonry project.",
326
356
  access: "read",
357
+ tier: "setup",
327
358
  inputSchema: projectInputSchema,
328
359
  annotations: readAnnotations(),
329
360
  openApiOperations: ["GET /api/v1/projects/{name}/competitors"],
@@ -334,6 +365,7 @@ var canonryMcpTools = [
334
365
  title: "Get schedule",
335
366
  description: "Get the scheduled run configuration for a Canonry project.",
336
367
  access: "read",
368
+ tier: "setup",
337
369
  inputSchema: projectInputSchema,
338
370
  annotations: readAnnotations(),
339
371
  openApiOperations: ["GET /api/v1/projects/{name}/schedule"],
@@ -344,6 +376,7 @@ var canonryMcpTools = [
344
376
  title: "Get settings",
345
377
  description: "Get Canonry API settings and configured provider status.",
346
378
  access: "read",
379
+ tier: "core",
347
380
  inputSchema: emptyInputSchema,
348
381
  annotations: readAnnotations(),
349
382
  openApiOperations: ["GET /api/v1/settings"],
@@ -354,6 +387,7 @@ var canonryMcpTools = [
354
387
  title: "List Google connections",
355
388
  description: "List configured Google connections for a Canonry project.",
356
389
  access: "read",
390
+ tier: "gsc",
357
391
  inputSchema: projectInputSchema,
358
392
  annotations: readAnnotations(),
359
393
  openApiOperations: ["GET /api/v1/projects/{name}/google/connections"],
@@ -364,6 +398,7 @@ var canonryMcpTools = [
364
398
  title: "Get GSC performance",
365
399
  description: "Get stored Google Search Console performance rows for a Canonry project.",
366
400
  access: "read",
401
+ tier: "gsc",
367
402
  inputSchema: gscPerformanceInputSchema,
368
403
  annotations: readAnnotations(),
369
404
  openApiOperations: ["GET /api/v1/projects/{name}/google/gsc/performance"],
@@ -374,6 +409,7 @@ var canonryMcpTools = [
374
409
  title: "List GSC inspections",
375
410
  description: "List stored URL inspection rows for a Canonry project.",
376
411
  access: "read",
412
+ tier: "gsc",
377
413
  inputSchema: gscInspectionsInputSchema,
378
414
  annotations: readAnnotations(),
379
415
  openApiOperations: ["GET /api/v1/projects/{name}/google/gsc/inspections"],
@@ -384,6 +420,7 @@ var canonryMcpTools = [
384
420
  title: "List deindexed GSC URLs",
385
421
  description: "List URLs that appear to have become deindexed in Google Search Console data.",
386
422
  access: "read",
423
+ tier: "gsc",
387
424
  inputSchema: projectInputSchema,
388
425
  annotations: readAnnotations(),
389
426
  openApiOperations: ["GET /api/v1/projects/{name}/google/gsc/deindexed"],
@@ -394,6 +431,7 @@ var canonryMcpTools = [
394
431
  title: "Get GSC coverage",
395
432
  description: "Get Google Search Console coverage summary for a Canonry project.",
396
433
  access: "read",
434
+ tier: "gsc",
397
435
  inputSchema: projectInputSchema,
398
436
  annotations: readAnnotations(),
399
437
  openApiOperations: ["GET /api/v1/projects/{name}/google/gsc/coverage"],
@@ -404,6 +442,7 @@ var canonryMcpTools = [
404
442
  title: "Get GSC coverage history",
405
443
  description: "Get Google Search Console coverage history snapshots for a Canonry project.",
406
444
  access: "read",
445
+ tier: "gsc",
407
446
  inputSchema: gscCoverageHistoryInputSchema,
408
447
  annotations: readAnnotations(),
409
448
  openApiOperations: ["GET /api/v1/projects/{name}/google/gsc/coverage/history"],
@@ -414,6 +453,7 @@ var canonryMcpTools = [
414
453
  title: "Get GSC sitemaps",
415
454
  description: "Get sitemap data from Google Search Console for a Canonry project.",
416
455
  access: "read",
456
+ tier: "gsc",
417
457
  inputSchema: projectInputSchema,
418
458
  annotations: readAnnotations(true),
419
459
  openApiOperations: ["GET /api/v1/projects/{name}/google/gsc/sitemaps"],
@@ -424,6 +464,7 @@ var canonryMcpTools = [
424
464
  title: "Get GA status",
425
465
  description: "Get Google Analytics connection status for a Canonry project.",
426
466
  access: "read",
467
+ tier: "ga",
427
468
  inputSchema: projectInputSchema,
428
469
  annotations: readAnnotations(),
429
470
  openApiOperations: ["GET /api/v1/projects/{name}/ga/status"],
@@ -434,6 +475,7 @@ var canonryMcpTools = [
434
475
  title: "Get GA traffic",
435
476
  description: "Get Google Analytics traffic summary for a Canonry project.",
436
477
  access: "read",
478
+ tier: "ga",
437
479
  inputSchema: gaTrafficInputSchema,
438
480
  annotations: readAnnotations(),
439
481
  openApiOperations: ["GET /api/v1/projects/{name}/ga/traffic"],
@@ -444,6 +486,7 @@ var canonryMcpTools = [
444
486
  title: "Get GA coverage",
445
487
  description: "Get Google Analytics page coverage for a Canonry project.",
446
488
  access: "read",
489
+ tier: "ga",
447
490
  inputSchema: projectInputSchema,
448
491
  annotations: readAnnotations(),
449
492
  openApiOperations: ["GET /api/v1/projects/{name}/ga/coverage"],
@@ -454,6 +497,7 @@ var canonryMcpTools = [
454
497
  title: "Get GA AI referral history",
455
498
  description: "Get AI referral sessions per day grouped by source.",
456
499
  access: "read",
500
+ tier: "ga",
457
501
  inputSchema: gaWindowInputSchema,
458
502
  annotations: readAnnotations(),
459
503
  openApiOperations: ["GET /api/v1/projects/{name}/ga/ai-referral-history"],
@@ -464,6 +508,7 @@ var canonryMcpTools = [
464
508
  title: "Get GA social referral history",
465
509
  description: "Get social referral sessions per day grouped by source.",
466
510
  access: "read",
511
+ tier: "ga",
467
512
  inputSchema: gaWindowInputSchema,
468
513
  annotations: readAnnotations(),
469
514
  openApiOperations: ["GET /api/v1/projects/{name}/ga/social-referral-history"],
@@ -474,6 +519,7 @@ var canonryMcpTools = [
474
519
  title: "Get GA social referral trend",
475
520
  description: "Get social referral trend with biggest mover for a Canonry project.",
476
521
  access: "read",
522
+ tier: "ga",
477
523
  inputSchema: projectInputSchema,
478
524
  annotations: readAnnotations(),
479
525
  openApiOperations: ["GET /api/v1/projects/{name}/ga/social-referral-trend"],
@@ -484,6 +530,7 @@ var canonryMcpTools = [
484
530
  title: "Get GA attribution trend",
485
531
  description: "Get per-channel attribution trends for organic, AI, social, and total sessions.",
486
532
  access: "read",
533
+ tier: "ga",
487
534
  inputSchema: projectInputSchema,
488
535
  annotations: readAnnotations(),
489
536
  openApiOperations: ["GET /api/v1/projects/{name}/ga/attribution-trend"],
@@ -494,16 +541,65 @@ var canonryMcpTools = [
494
541
  title: "Get GA session history",
495
542
  description: "Get total sessions per day for a Canonry project.",
496
543
  access: "read",
544
+ tier: "ga",
497
545
  inputSchema: gaWindowInputSchema,
498
546
  annotations: readAnnotations(),
499
547
  openApiOperations: ["GET /api/v1/projects/{name}/ga/session-history"],
500
548
  handler: (client, input) => client.gaSessionHistory(input.project, compactStringParams(input, ["window"]))
501
549
  }),
550
+ defineTool({
551
+ name: "canonry_project_upsert",
552
+ title: "Create or replace project",
553
+ description: "Create or replace a Canonry project. PUT semantics \u2014 fields not in the request are reset to their defaults. Provide the full intended project shape.",
554
+ access: "write",
555
+ tier: "setup",
556
+ inputSchema: projectUpsertInputSchema,
557
+ annotations: writeAnnotations({ idempotentHint: true, destructiveHint: true }),
558
+ openApiOperations: ["PUT /api/v1/projects/{name}"],
559
+ handler: (client, input) => client.putProject(input.project, input.request)
560
+ }),
561
+ defineTool({
562
+ name: "canonry_apply_config",
563
+ title: "Apply project config",
564
+ description: "Apply one Canonry config-as-code project document. Replaces the project to match the config \u2014 fields omitted from the spec are reset to defaults. For multi-document YAML, call this tool once per project document.",
565
+ access: "write",
566
+ tier: "core",
567
+ inputSchema: applyConfigInputSchema,
568
+ // Declarative apply is safe to repeat, but it replaces configured child state.
569
+ annotations: writeAnnotations({ idempotentHint: true, destructiveHint: true }),
570
+ openApiOperations: ["POST /api/v1/apply"],
571
+ handler: (client, input) => client.apply(input.config)
572
+ }),
573
+ defineTool({
574
+ name: "canonry_keywords_generate",
575
+ title: "Generate keyword suggestions",
576
+ description: "Generate candidate key phrases using a configured provider. Returns suggestions only; use canonry_keywords_add to persist them.",
577
+ access: "write",
578
+ tier: "setup",
579
+ inputSchema: keywordGenerateInputSchema,
580
+ annotations: writeAnnotations({ idempotentHint: false, openWorldHint: true }),
581
+ openApiOperations: ["POST /api/v1/projects/{name}/keywords/generate"],
582
+ handler: (client, input) => client.generateKeywords(input.project, input.request.provider, input.request.count)
583
+ }),
584
+ defineTool({
585
+ name: "canonry_keywords_replace",
586
+ title: "Replace keywords",
587
+ description: "Replace the tracked keyword set for a Canonry project.",
588
+ access: "write",
589
+ tier: "setup",
590
+ inputSchema: keywordsInputSchema,
591
+ annotations: writeAnnotations({ idempotentHint: true, destructiveHint: true }),
592
+ openApiOperations: ["PUT /api/v1/projects/{name}/keywords"],
593
+ handler: async (client, input) => {
594
+ await client.putKeywords(input.project, uniqueStrings(input.request.keywords));
595
+ }
596
+ }),
502
597
  defineTool({
503
598
  name: "canonry_run_trigger",
504
599
  title: "Trigger run",
505
600
  description: "Trigger an answer-visibility run for a Canonry project.",
506
601
  access: "write",
602
+ tier: "core",
507
603
  inputSchema: runTriggerInputSchema,
508
604
  annotations: writeAnnotations({ idempotentHint: false, openWorldHint: true }),
509
605
  openApiOperations: ["POST /api/v1/projects/{name}/runs"],
@@ -514,6 +610,7 @@ var canonryMcpTools = [
514
610
  title: "Cancel run",
515
611
  description: "Cancel a queued or running Canonry run.",
516
612
  access: "write",
613
+ tier: "core",
517
614
  inputSchema: runGetInputSchema,
518
615
  annotations: writeAnnotations({ idempotentHint: false, destructiveHint: true }),
519
616
  openApiOperations: ["POST /api/v1/runs/{id}/cancel"],
@@ -524,6 +621,7 @@ var canonryMcpTools = [
524
621
  title: "Add keywords",
525
622
  description: "Append tracked keywords to a Canonry project; existing keywords are skipped by the API.",
526
623
  access: "write",
624
+ tier: "setup",
527
625
  inputSchema: keywordsInputSchema,
528
626
  annotations: writeAnnotations({ idempotentHint: true }),
529
627
  openApiOperations: ["POST /api/v1/projects/{name}/keywords"],
@@ -536,6 +634,7 @@ var canonryMcpTools = [
536
634
  title: "Remove keywords",
537
635
  description: "Remove tracked keywords from a Canonry project.",
538
636
  access: "write",
637
+ tier: "setup",
539
638
  inputSchema: keywordsInputSchema,
540
639
  annotations: writeAnnotations({ idempotentHint: true, destructiveHint: true }),
541
640
  openApiOperations: ["DELETE /api/v1/projects/{name}/keywords"],
@@ -548,13 +647,25 @@ var canonryMcpTools = [
548
647
  title: "Add competitors",
549
648
  description: "Add tracked competitor domains to a Canonry project.",
550
649
  access: "write",
551
- inputSchema: competitorsAddInputSchema,
650
+ tier: "setup",
651
+ inputSchema: competitorsInputSchema,
552
652
  annotations: writeAnnotations({ idempotentHint: true }),
553
- openApiOperations: ["GET /api/v1/projects/{name}/competitors", "PUT /api/v1/projects/{name}/competitors"],
653
+ openApiOperations: ["POST /api/v1/projects/{name}/competitors"],
654
+ handler: async (client, input) => {
655
+ await client.appendCompetitors(input.project, uniqueStrings(input.request.competitors));
656
+ }
657
+ }),
658
+ defineTool({
659
+ name: "canonry_competitors_remove",
660
+ title: "Remove competitors",
661
+ description: "Remove tracked competitor domains from a Canonry project.",
662
+ access: "write",
663
+ tier: "setup",
664
+ inputSchema: competitorsInputSchema,
665
+ annotations: writeAnnotations({ idempotentHint: true, destructiveHint: true }),
666
+ openApiOperations: ["DELETE /api/v1/projects/{name}/competitors"],
554
667
  handler: async (client, input) => {
555
- const existing = await client.listCompetitors(input.project);
556
- const merged = uniqueStrings([...existing.map((c) => c.domain), ...input.request.competitors]);
557
- await client.putCompetitors(input.project, merged);
668
+ await client.deleteCompetitors(input.project, uniqueStrings(input.request.competitors));
558
669
  }
559
670
  }),
560
671
  defineTool({
@@ -562,6 +673,7 @@ var canonryMcpTools = [
562
673
  title: "Set schedule",
563
674
  description: "Create or replace the scheduled run configuration for a Canonry project.",
564
675
  access: "write",
676
+ tier: "setup",
565
677
  inputSchema: scheduleSetInputSchema,
566
678
  annotations: writeAnnotations({ idempotentHint: true }),
567
679
  openApiOperations: ["PUT /api/v1/projects/{name}/schedule"],
@@ -572,6 +684,7 @@ var canonryMcpTools = [
572
684
  title: "Delete schedule",
573
685
  description: "Delete the scheduled run configuration for a Canonry project.",
574
686
  access: "write",
687
+ tier: "setup",
575
688
  inputSchema: projectInputSchema,
576
689
  annotations: writeAnnotations({ idempotentHint: false, destructiveHint: true }),
577
690
  openApiOperations: ["DELETE /api/v1/projects/{name}/schedule"],
@@ -584,6 +697,7 @@ var canonryMcpTools = [
584
697
  title: "Dismiss insight",
585
698
  description: "Dismiss an intelligence insight for a Canonry project.",
586
699
  access: "write",
700
+ tier: "setup",
587
701
  inputSchema: insightInputSchema,
588
702
  annotations: writeAnnotations({ idempotentHint: true }),
589
703
  openApiOperations: ["POST /api/v1/projects/{name}/insights/{id}/dismiss"],
@@ -594,6 +708,7 @@ var canonryMcpTools = [
594
708
  title: "Attach agent webhook",
595
709
  description: "Attach an external agent webhook to project run and insight events.",
596
710
  access: "write",
711
+ tier: "core",
597
712
  inputSchema: agentWebhookAttachInputSchema,
598
713
  annotations: writeAnnotations({ idempotentHint: true }),
599
714
  openApiOperations: ["GET /api/v1/projects/{name}/notifications", "POST /api/v1/projects/{name}/notifications"],
@@ -618,6 +733,7 @@ var canonryMcpTools = [
618
733
  title: "Detach agent webhook",
619
734
  description: "Detach the external agent webhook for a Canonry project.",
620
735
  access: "write",
736
+ tier: "agent",
621
737
  inputSchema: projectInputSchema,
622
738
  annotations: writeAnnotations({ idempotentHint: true, destructiveHint: true }),
623
739
  openApiOperations: ["GET /api/v1/projects/{name}/notifications", "DELETE /api/v1/projects/{name}/notifications/{id}"],
@@ -634,6 +750,7 @@ var canonryMcpTools = [
634
750
  ];
635
751
  var CANONRY_MCP_TOOL_COUNT = canonryMcpTools.length;
636
752
  var CANONRY_MCP_READ_TOOL_COUNT = canonryMcpTools.filter((tool) => tool.access === "read").length;
753
+ var CANONRY_MCP_CORE_TOOL_COUNT = canonryMcpTools.filter((tool) => tool.tier === "core").length;
637
754
 
638
755
  // src/mcp/results.ts
639
756
  function jsonToolResult(value) {
@@ -705,8 +822,119 @@ function hasErrorEnvelope(value) {
705
822
  return Boolean(error && typeof error === "object");
706
823
  }
707
824
 
825
+ // src/mcp/toolkits.ts
826
+ var CANONRY_MCP_TOOLKIT_NAMES = ["monitoring", "setup", "gsc", "ga", "agent"];
827
+ var CANONRY_MCP_TOOLKITS = [
828
+ {
829
+ name: "monitoring",
830
+ title: "Runs, snapshots, insights, health",
831
+ description: "Inspect run history, query snapshots, intelligence insights, and health timelines.",
832
+ whenToLoad: "Load when investigating regressions, comparing runs, or reviewing insights and health history."
833
+ },
834
+ {
835
+ name: "setup",
836
+ title: "Project configuration",
837
+ description: "Manage keywords, competitors, schedules, project upsert, and config-as-code roundtrips.",
838
+ whenToLoad: "Load when onboarding a new project or editing tracked keywords, competitors, or schedules."
839
+ },
840
+ {
841
+ name: "gsc",
842
+ title: "Google Search Console",
843
+ description: "Read GSC performance, inspections, coverage, sitemaps, and deindexed URLs.",
844
+ whenToLoad: "Load when you need indexing, coverage, or sitemap data from Google Search Console."
845
+ },
846
+ {
847
+ name: "ga",
848
+ title: "Google Analytics 4",
849
+ description: "Read GA traffic, AI/social referral history, attribution trend, and session history.",
850
+ whenToLoad: "Load when you need traffic, referral, or attribution data from Google Analytics 4."
851
+ },
852
+ {
853
+ name: "agent",
854
+ title: "Agent webhook lifecycle",
855
+ description: "Detach the configured external-agent webhook from a project.",
856
+ whenToLoad: "Load when removing an agent webhook subscription. (Attach lives in the core tier.)"
857
+ }
858
+ ];
859
+ function isCanonryMcpToolkitName(value) {
860
+ return CANONRY_MCP_TOOLKIT_NAMES.includes(value);
861
+ }
862
+
863
+ // src/mcp/dynamic-catalog.ts
864
+ var DynamicToolCatalog = class {
865
+ entries;
866
+ loaded = /* @__PURE__ */ new Set();
867
+ eager;
868
+ scope;
869
+ constructor(entries, scope, options = {}) {
870
+ this.entries = entries;
871
+ this.scope = scope;
872
+ this.eager = Boolean(options.eager);
873
+ if (this.eager) {
874
+ for (const toolkit of CANONRY_MCP_TOOLKITS) {
875
+ if (this.toolsForToolkit(toolkit.name).length > 0) {
876
+ this.loaded.add(toolkit.name);
877
+ }
878
+ }
879
+ }
880
+ }
881
+ applyInitialEnablement() {
882
+ if (this.eager) return;
883
+ for (const entry of this.entries) {
884
+ if (entry.tool.tier !== "core") entry.registered.disable();
885
+ }
886
+ }
887
+ loadToolkit(rawName) {
888
+ if (!isCanonryMcpToolkitName(rawName)) {
889
+ const valid = CANONRY_MCP_TOOLKITS.map((t) => t.name).join(", ");
890
+ throw new Error(`Unknown toolkit "${rawName}". Available: ${valid}.`);
891
+ }
892
+ const name = rawName;
893
+ const matches = this.entries.filter((entry) => entry.tool.tier === name);
894
+ if (matches.length === 0) {
895
+ return { status: "empty", name, tools: [] };
896
+ }
897
+ if (this.loaded.has(name)) {
898
+ return { status: "already-loaded", name, tools: matches.map((entry) => entry.tool.name) };
899
+ }
900
+ for (const entry of matches) {
901
+ entry.registered.enable();
902
+ }
903
+ this.loaded.add(name);
904
+ return { status: "loaded", name, tools: matches.map((entry) => entry.tool.name) };
905
+ }
906
+ helpResult() {
907
+ return {
908
+ scope: this.scope,
909
+ eager: this.eager,
910
+ loadedToolkits: [...this.loaded].sort(),
911
+ coreTools: this.entries.filter((entry) => entry.tool.tier === "core").map((entry) => entry.tool.name),
912
+ toolkits: CANONRY_MCP_TOOLKITS.map((toolkit) => this.toolkitEntry(toolkit)).filter((entry) => entry.toolCount > 0),
913
+ 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."
914
+ };
915
+ }
916
+ toolkitEntry(toolkit) {
917
+ const tools = this.toolsForToolkit(toolkit.name);
918
+ return {
919
+ name: toolkit.name,
920
+ title: toolkit.title,
921
+ description: toolkit.description,
922
+ whenToLoad: toolkit.whenToLoad,
923
+ toolCount: tools.length,
924
+ tools,
925
+ loaded: this.loaded.has(toolkit.name)
926
+ };
927
+ }
928
+ toolsForToolkit(name) {
929
+ return this.entries.filter((entry) => entry.tool.tier === name).map((entry) => entry.tool.name);
930
+ }
931
+ };
932
+
708
933
  // src/mcp/server.ts
709
934
  function createCanonryMcpServer(options = {}) {
935
+ return createCanonryMcpServerWithCatalog(options).server;
936
+ }
937
+ function createCanonryMcpServerWithCatalog(options = {}) {
710
938
  const clientFactory = options.clientFactory ?? createApiClient;
711
939
  const client = clientFactory();
712
940
  const scope = options.scope ?? "all";
@@ -714,10 +942,11 @@ function createCanonryMcpServer(options = {}) {
714
942
  name: "canonry",
715
943
  version: PACKAGE_VERSION
716
944
  });
945
+ const entries = [];
717
946
  for (const registryTool of getCanonryMcpTools(scope)) {
718
947
  const tool = registryTool;
719
948
  const handler = tool.handler;
720
- server.registerTool(
949
+ const registered = server.registerTool(
721
950
  tool.name,
722
951
  {
723
952
  title: tool.title,
@@ -727,8 +956,36 @@ function createCanonryMcpServer(options = {}) {
727
956
  },
728
957
  async (input) => withToolErrors(() => handler(client, input))
729
958
  );
959
+ entries.push({ tool, registered });
730
960
  }
731
- return server;
961
+ const catalog = new DynamicToolCatalog(entries, scope, { eager: options.eager });
962
+ catalog.applyInitialEnablement();
963
+ registerMetaTools(server, catalog);
964
+ return { server, catalog };
965
+ }
966
+ function registerMetaTools(server, catalog) {
967
+ server.registerTool(
968
+ "canonry_help",
969
+ {
970
+ title: "List Canonry MCP toolkits",
971
+ description: "List available toolkits and which are loaded. Call before canonry_load_toolkit if unsure which to load.",
972
+ inputSchema: {},
973
+ annotations: { readOnlyHint: true }
974
+ },
975
+ async () => withToolErrors(async () => catalog.helpResult())
976
+ );
977
+ server.registerTool(
978
+ "canonry_load_toolkit",
979
+ {
980
+ title: "Load a Canonry MCP toolkit",
981
+ 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.",
982
+ inputSchema: {
983
+ name: z3.enum(CANONRY_MCP_TOOLKIT_NAMES).describe("Toolkit name. List options with canonry_help.")
984
+ },
985
+ annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: false }
986
+ },
987
+ async ({ name }) => withToolErrors(async () => catalog.loadToolkit(name))
988
+ );
732
989
  }
733
990
  function getCanonryMcpTools(scope = "all") {
734
991
  return scope === "read-only" ? canonryMcpTools.filter((tool) => tool.access === "read") : [...canonryMcpTools];
@@ -736,17 +993,23 @@ function getCanonryMcpTools(scope = "all") {
736
993
 
737
994
  // src/mcp/cli.ts
738
995
  async function main(argv = process.argv.slice(2)) {
739
- const server = createCanonryMcpServer({ scope: parseScope(argv) });
996
+ const options = parseCliOptions(argv);
997
+ const server = createCanonryMcpServer({ scope: options.scope, eager: options.eager });
740
998
  await server.connect(new StdioServerTransport());
741
999
  }
742
- function parseScope(argv, envScope = process.env.CANONRY_MCP_SCOPE) {
743
- let scope = normalizeScope(envScope);
1000
+ function parseCliOptions(argv, env = process.env) {
1001
+ let scope = normalizeScope(env.CANONRY_MCP_SCOPE);
1002
+ let eager = parseEagerEnv(env.CANONRY_MCP_EAGER);
744
1003
  for (let i = 0; i < argv.length; i += 1) {
745
1004
  const arg = argv[i];
746
1005
  if (arg === "--read-only") {
747
1006
  scope = "read-only";
748
1007
  continue;
749
1008
  }
1009
+ if (arg === "--eager") {
1010
+ eager = true;
1011
+ continue;
1012
+ }
750
1013
  if (arg === "--scope") {
751
1014
  const next = argv[i + 1];
752
1015
  if (!next) throw new Error("Missing value for --scope");
@@ -760,13 +1023,18 @@ function parseScope(argv, envScope = process.env.CANONRY_MCP_SCOPE) {
760
1023
  }
761
1024
  throw new Error(`Unknown canonry-mcp argument: ${arg}`);
762
1025
  }
763
- return scope;
1026
+ return { scope, eager };
764
1027
  }
765
1028
  function normalizeScope(value) {
766
1029
  if (!value || value === "all") return "all";
767
1030
  if (value === "read-only") return "read-only";
768
1031
  throw new Error(`Invalid MCP scope "${value}". Expected "all" or "read-only".`);
769
1032
  }
1033
+ function parseEagerEnv(value) {
1034
+ if (!value) return false;
1035
+ const normalized = value.trim().toLowerCase();
1036
+ return normalized === "1" || normalized === "true" || normalized === "yes";
1037
+ }
770
1038
  if (import.meta.url === `file://${process.argv[1]}`) {
771
1039
  main().catch((error) => {
772
1040
  const message = error instanceof Error ? error.message : "canonry-mcp failed";
@@ -777,5 +1045,5 @@ if (import.meta.url === `file://${process.argv[1]}`) {
777
1045
  }
778
1046
  export {
779
1047
  main,
780
- parseScope
1048
+ parseCliOptions
781
1049
  };