@vantageos/vantage-registry-mcp 1.2.0 → 1.4.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 (3) hide show
  1. package/package.json +5 -3
  2. package/server.js +1695 -0
  3. package/server.ts +1028 -181
package/server.ts CHANGED
@@ -13,16 +13,17 @@
13
13
  * upsert_template, list_templates, get_template
14
14
  * upsert_runbook, get_runbook, list_runbooks, list_runbooks_by_category, list_runbooks_by_team, delete_runbook
15
15
  * link_runbook_template, unlink_runbook_template, list_templates_for_runbook, list_runbooks_for_template
16
+ * register_component, list_components, get_component, search_components, update_component, delete_component
16
17
  * get_stats
17
18
  *
18
- * Orchestrator: Omega — VantageOS Team | 2026-05-19
19
+ * Orchestrator: Omega — VantageOS Team | 2026-06-01
19
20
  */
20
21
 
22
+ import { readFileSync } from "node:fs";
23
+ import { resolve } from "node:path";
21
24
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
25
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
23
26
  import { ConvexHttpClient } from "convex/browser";
24
- import { readFileSync } from "fs";
25
- import { resolve } from "path";
26
27
  import { z } from "zod";
27
28
  import { api } from "../convex/_generated/api.js";
28
29
 
@@ -80,7 +81,9 @@ const hookStatusSchema = z
80
81
 
81
82
  const sourceSchema = z
82
83
  .enum(["internal", "external", "plugin"])
83
- .describe("Component source — internal (our code), external (third-party), plugin (from plugin)");
84
+ .describe(
85
+ "Component source — internal (our code), external (third-party), plugin (from plugin)",
86
+ );
84
87
 
85
88
  const pluginSourceSchema = z
86
89
  .enum(["internal", "external"])
@@ -127,7 +130,7 @@ const convex = new ConvexHttpClient(convexUrl);
127
130
 
128
131
  const server = new McpServer({
129
132
  name: "vantage-registry",
130
- version: "1.2.0",
133
+ version: "1.4.0",
131
134
  });
132
135
 
133
136
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -143,12 +146,17 @@ server.tool(
143
146
  agentCount: z.number().int().describe("Number of agents in this team"),
144
147
  skillCount: z.number().int().describe("Number of skills in this team"),
145
148
  status: teamStatusSchema,
146
- project: z.string().optional().describe("Associated project — e.g. 'vantage-starter'"),
149
+ project: z
150
+ .string()
151
+ .optional()
152
+ .describe("Associated project — e.g. 'vantage-starter'"),
147
153
  },
148
154
  async (args) => {
149
155
  const id = await convex.mutation(api.teams.upsert, args);
150
156
  return {
151
- content: [{ type: "text", text: JSON.stringify({ id, ...args }, null, 2) }],
157
+ content: [
158
+ { type: "text", text: JSON.stringify({ id, ...args }, null, 2) },
159
+ ],
152
160
  };
153
161
  },
154
162
  );
@@ -157,7 +165,9 @@ server.tool(
157
165
  "list_teams",
158
166
  "List all teams in VantageRegistry. Optionally filter by status (active, planned, deprecated).",
159
167
  {
160
- status: teamStatusSchema.optional().describe("Filter by status — omit to list all"),
168
+ status: teamStatusSchema
169
+ .optional()
170
+ .describe("Filter by status — omit to list all"),
161
171
  },
162
172
  async ({ status }) => {
163
173
  const results = await convex.query(api.teams.list, { status });
@@ -189,7 +199,9 @@ server.tool(
189
199
  "upsert_agent",
190
200
  "Create or update an agent in VantageRegistry. Upserts by name+team — if an agent with the same name and team exists, it is updated.",
191
201
  {
192
- name: z.string().describe("Agent name — e.g. 'copywriter', 'strategy-researcher'"),
202
+ name: z
203
+ .string()
204
+ .describe("Agent name — e.g. 'copywriter', 'strategy-researcher'"),
193
205
  team: z.string().describe("Team this agent belongs to"),
194
206
  description: z.string().describe("What this agent does"),
195
207
  content: z.string().describe("Full agent definition content (markdown)"),
@@ -200,9 +212,15 @@ server.tool(
200
212
  source: sourceSchema,
201
213
  pricing: z.enum(["free", "paid"]).optional().describe("Pricing tier"),
202
214
  price: z.number().optional().describe("Price in EUR (if paid)"),
203
- license: z.string().optional().describe("License — e.g. 'MIT', 'proprietary'"),
215
+ license: z
216
+ .string()
217
+ .optional()
218
+ .describe("License — e.g. 'MIT', 'proprietary'"),
204
219
  publisherId: z.string().optional().describe("Publisher identifier"),
205
- categories: z.array(z.string()).optional().describe("Categories beyond team name"),
220
+ categories: z
221
+ .array(z.string())
222
+ .optional()
223
+ .describe("Categories beyond team name"),
206
224
  },
207
225
  async (args) => {
208
226
  const id = await convex.mutation(api.agents.upsert, args);
@@ -210,7 +228,11 @@ server.tool(
210
228
  content: [
211
229
  {
212
230
  type: "text",
213
- text: JSON.stringify({ id, name: args.name, team: args.team }, null, 2),
231
+ text: JSON.stringify(
232
+ { id, name: args.name, team: args.team },
233
+ null,
234
+ 2,
235
+ ),
214
236
  },
215
237
  ],
216
238
  };
@@ -219,13 +241,35 @@ server.tool(
219
241
 
220
242
  server.tool(
221
243
  "list_agents",
222
- "List all agents in VantageRegistry. Optionally filter by status.",
244
+ "List all agents in VantageRegistry. Optionally filter by status, team, or category. " +
245
+ "fields='lite' returns compact {_id, name, team, status}. fields='full' returns the complete document including content.",
223
246
  {
224
- status: agentStatusSchema.optional().describe("Filter by status — omit to list all"),
225
- summary: z.boolean().optional().describe("Return summary only (no content). Default: true"),
247
+ status: agentStatusSchema
248
+ .optional()
249
+ .describe("Filter by status — omit to list all"),
250
+ team: z.string().optional().describe("Filter by team — e.g. 'dev', 'core'"),
251
+ category: z.string().optional().describe("Filter by category"),
252
+ fields: z
253
+ .enum(["lite", "full"])
254
+ .optional()
255
+ .describe(
256
+ "Projection: 'lite' (compact) or 'full' (complete document). Omit to use legacy summary behaviour.",
257
+ ),
258
+ summary: z
259
+ .boolean()
260
+ .optional()
261
+ .describe(
262
+ "Return summary only (no content). Default: true — legacy, prefer fields='lite'",
263
+ ),
226
264
  },
227
- async ({ status, summary }) => {
228
- const results = await convex.query(api.agents.list, { status, summary });
265
+ async ({ status, team, category, fields, summary }) => {
266
+ const results = await convex.query(api.agents.list, {
267
+ status,
268
+ team,
269
+ category,
270
+ fields,
271
+ summary,
272
+ });
229
273
  return {
230
274
  content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
231
275
  };
@@ -237,10 +281,16 @@ server.tool(
237
281
  "List all agents belonging to a specific team.",
238
282
  {
239
283
  team: z.string().describe("Team name to filter by"),
240
- summary: z.boolean().optional().describe("Return summary only (no content). Default: true"),
284
+ summary: z
285
+ .boolean()
286
+ .optional()
287
+ .describe("Return summary only (no content). Default: true"),
241
288
  },
242
289
  async ({ team, summary }) => {
243
- const results = await convex.query(api.agents.listByTeam, { team, summary });
290
+ const results = await convex.query(api.agents.listByTeam, {
291
+ team,
292
+ summary,
293
+ });
244
294
  return {
245
295
  content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
246
296
  };
@@ -269,10 +319,14 @@ server.tool(
269
319
  "upsert_skill",
270
320
  "Create or update a skill in VantageRegistry. Upserts by name+team — if a skill with the same name and team exists, it is updated.",
271
321
  {
272
- name: z.string().describe("Skill name — e.g. 'social-post', 'competitor-watch'"),
322
+ name: z
323
+ .string()
324
+ .describe("Skill name — e.g. 'social-post', 'competitor-watch'"),
273
325
  team: z.string().describe("Team this skill belongs to"),
274
326
  category: skillCategorySchema,
275
- description: z.string().describe("What this skill does — use pushy trigger descriptions"),
327
+ description: z
328
+ .string()
329
+ .describe("What this skill does — use pushy trigger descriptions"),
276
330
  content: z.string().describe("Full SKILL.md content"),
277
331
  filePath: z.string().describe("File path relative to project root"),
278
332
  lineCount: z.number().int().describe("Number of lines in the skill file"),
@@ -281,9 +335,15 @@ server.tool(
281
335
  source: sourceSchema,
282
336
  pricing: z.enum(["free", "paid"]).optional().describe("Pricing tier"),
283
337
  price: z.number().optional().describe("Price in EUR (if paid)"),
284
- license: z.string().optional().describe("License — e.g. 'MIT', 'proprietary'"),
338
+ license: z
339
+ .string()
340
+ .optional()
341
+ .describe("License — e.g. 'MIT', 'proprietary'"),
285
342
  publisherId: z.string().optional().describe("Publisher identifier"),
286
- categories: z.array(z.string()).optional().describe("Categories beyond team name"),
343
+ categories: z
344
+ .array(z.string())
345
+ .optional()
346
+ .describe("Categories beyond team name"),
287
347
  },
288
348
  async (args) => {
289
349
  const id = await convex.mutation(api.skills.upsert, args);
@@ -291,7 +351,11 @@ server.tool(
291
351
  content: [
292
352
  {
293
353
  type: "text",
294
- text: JSON.stringify({ id, name: args.name, team: args.team, category: args.category }, null, 2),
354
+ text: JSON.stringify(
355
+ { id, name: args.name, team: args.team, category: args.category },
356
+ null,
357
+ 2,
358
+ ),
295
359
  },
296
360
  ],
297
361
  };
@@ -300,13 +364,39 @@ server.tool(
300
364
 
301
365
  server.tool(
302
366
  "list_skills",
303
- "List all skills in VantageRegistry. Optionally filter by status.",
367
+ "List all skills in VantageRegistry. Optionally filter by status, team, or category. " +
368
+ "fields='lite' returns compact {_id, name, team, category, status}. fields='full' returns the complete document including content.",
304
369
  {
305
- status: skillStatusSchema.optional().describe("Filter by status — omit to list all"),
306
- summary: z.boolean().optional().describe("Return summary only (no content). Default: true"),
370
+ status: skillStatusSchema
371
+ .optional()
372
+ .describe("Filter by status — omit to list all"),
373
+ team: z.string().optional().describe("Filter by team — e.g. 'dev', 'core'"),
374
+ category: skillCategorySchema
375
+ .optional()
376
+ .describe(
377
+ "Filter by category: capability, composite, playbook, root, or external",
378
+ ),
379
+ fields: z
380
+ .enum(["lite", "full"])
381
+ .optional()
382
+ .describe(
383
+ "Projection: 'lite' (compact) or 'full' (complete document). Omit to use legacy summary behaviour.",
384
+ ),
385
+ summary: z
386
+ .boolean()
387
+ .optional()
388
+ .describe(
389
+ "Return summary only (no content). Default: true — legacy, prefer fields='lite'",
390
+ ),
307
391
  },
308
- async ({ status, summary }) => {
309
- const results = await convex.query(api.skills.list, { status, summary });
392
+ async ({ status, team, category, fields, summary }) => {
393
+ const results = await convex.query(api.skills.list, {
394
+ status,
395
+ team,
396
+ category,
397
+ fields,
398
+ summary,
399
+ });
310
400
  return {
311
401
  content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
312
402
  };
@@ -318,10 +408,16 @@ server.tool(
318
408
  "List all skills belonging to a specific team.",
319
409
  {
320
410
  team: z.string().describe("Team name to filter by"),
321
- summary: z.boolean().optional().describe("Return summary only (no content). Default: true"),
411
+ summary: z
412
+ .boolean()
413
+ .optional()
414
+ .describe("Return summary only (no content). Default: true"),
322
415
  },
323
416
  async ({ team, summary }) => {
324
- const results = await convex.query(api.skills.listByTeam, { team, summary });
417
+ const results = await convex.query(api.skills.listByTeam, {
418
+ team,
419
+ summary,
420
+ });
325
421
  return {
326
422
  content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
327
423
  };
@@ -333,10 +429,16 @@ server.tool(
333
429
  "List all skills of a specific category (capability, composite, playbook, root, external).",
334
430
  {
335
431
  category: skillCategorySchema,
336
- summary: z.boolean().optional().describe("Return summary only (no content). Default: true"),
432
+ summary: z
433
+ .boolean()
434
+ .optional()
435
+ .describe("Return summary only (no content). Default: true"),
337
436
  },
338
437
  async ({ category, summary }) => {
339
- const results = await convex.query(api.skills.listByCategory, { category, summary });
438
+ const results = await convex.query(api.skills.listByCategory, {
439
+ category,
440
+ summary,
441
+ });
340
442
  return {
341
443
  content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
342
444
  };
@@ -367,10 +469,14 @@ server.tool(
367
469
  "Returns content, sha256 hash, semver version, and last sync timestamp. " +
368
470
  "Returns null fields if the skill has not been hosted in VR yet.",
369
471
  {
370
- name: z.string().describe("Skill slug — e.g. 'check-messages', 'blog-writer'"),
472
+ name: z
473
+ .string()
474
+ .describe("Skill slug — e.g. 'check-messages', 'blog-writer'"),
371
475
  },
372
476
  async ({ name }) => {
373
- const result = await convex.query(api.skillContentDb.getSkillContent, { name });
477
+ const result = await convex.query(api.skillContentDb.getSkillContent, {
478
+ name,
479
+ });
374
480
  return {
375
481
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
376
482
  };
@@ -389,14 +495,23 @@ server.tool(
389
495
  {
390
496
  name: z.string().describe("Skill slug — e.g. 'check-messages'"),
391
497
  content: z.string().describe("Full SKILL.md body (frontmatter + markdown)"),
392
- createIfMissing: z.boolean().optional().default(false).describe("Auto-create a placeholder skill row if it does not exist (default: false)"),
498
+ createIfMissing: z
499
+ .boolean()
500
+ .optional()
501
+ .default(false)
502
+ .describe(
503
+ "Auto-create a placeholder skill row if it does not exist (default: false)",
504
+ ),
393
505
  },
394
506
  async ({ name, content, createIfMissing }) => {
395
- const result = await convex.mutation(api.skillContentDb.upsertSkillContent, {
396
- name,
397
- content,
398
- createIfMissing,
399
- });
507
+ const result = await convex.mutation(
508
+ api.skillContentDb.upsertSkillContent,
509
+ {
510
+ name,
511
+ content,
512
+ createIfMissing,
513
+ },
514
+ );
400
515
  return {
401
516
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
402
517
  };
@@ -410,11 +525,20 @@ server.tool(
410
525
  "canonical hash + stored filePath so the caller can compute disk SHA256 and compare. " +
411
526
  "scope='all' (default) returns every skill. name=<slug> filters to a single skill.",
412
527
  {
413
- name: z.string().optional().describe("Skill slug — filter to a single skill"),
414
- scope: z.string().optional().describe("'all' to return every skill (default when name is omitted)"),
528
+ name: z
529
+ .string()
530
+ .optional()
531
+ .describe("Skill slug — filter to a single skill"),
532
+ scope: z
533
+ .string()
534
+ .optional()
535
+ .describe("'all' to return every skill (default when name is omitted)"),
415
536
  },
416
537
  async ({ name, scope }) => {
417
- const result = await convex.query(api.skillContentDb.detectSkillDrift, { name, scope });
538
+ const result = await convex.query(api.skillContentDb.detectSkillDrift, {
539
+ name,
540
+ scope,
541
+ });
418
542
  return {
419
543
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
420
544
  };
@@ -431,10 +555,14 @@ server.tool(
431
555
  "Returns content, sha256 hash, semver version, and last sync timestamp. " +
432
556
  "Returns null fields if the agent has not been hosted in VR yet.",
433
557
  {
434
- name: z.string().describe("Agent slug — e.g. 'dev-convex-expert', 'dev-senior-dev'"),
558
+ name: z
559
+ .string()
560
+ .describe("Agent slug — e.g. 'dev-convex-expert', 'dev-senior-dev'"),
435
561
  },
436
562
  async ({ name }) => {
437
- const result = await convex.query(api.agentContentDb.getAgentContent, { name });
563
+ const result = await convex.query(api.agentContentDb.getAgentContent, {
564
+ name,
565
+ });
438
566
  return {
439
567
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
440
568
  };
@@ -451,15 +579,26 @@ server.tool(
451
579
  "Throws if the agent does not exist and createIfMissing is not set.",
452
580
  {
453
581
  name: z.string().describe("Agent slug — e.g. 'dev-convex-expert'"),
454
- content: z.string().describe("Full agent content body (AGENT.md or similar)"),
455
- createIfMissing: z.boolean().optional().default(false).describe("Auto-create a placeholder agent row if it does not exist (default: false)"),
582
+ content: z
583
+ .string()
584
+ .describe("Full agent content body (AGENT.md or similar)"),
585
+ createIfMissing: z
586
+ .boolean()
587
+ .optional()
588
+ .default(false)
589
+ .describe(
590
+ "Auto-create a placeholder agent row if it does not exist (default: false)",
591
+ ),
456
592
  },
457
593
  async ({ name, content, createIfMissing }) => {
458
- const result = await convex.mutation(api.agentContentDb.upsertAgentContent, {
459
- name,
460
- content,
461
- createIfMissing,
462
- });
594
+ const result = await convex.mutation(
595
+ api.agentContentDb.upsertAgentContent,
596
+ {
597
+ name,
598
+ content,
599
+ createIfMissing,
600
+ },
601
+ );
463
602
  return {
464
603
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
465
604
  };
@@ -473,11 +612,20 @@ server.tool(
473
612
  "canonical hash + stored filePath so the caller can compute disk SHA256 and compare. " +
474
613
  "scope='all' (default) returns every agent. name=<slug> filters to a single agent.",
475
614
  {
476
- name: z.string().optional().describe("Agent slug — filter to a single agent"),
477
- scope: z.string().optional().describe("'all' to return every agent (default when name is omitted)"),
615
+ name: z
616
+ .string()
617
+ .optional()
618
+ .describe("Agent slug — filter to a single agent"),
619
+ scope: z
620
+ .string()
621
+ .optional()
622
+ .describe("'all' to return every agent (default when name is omitted)"),
478
623
  },
479
624
  async ({ name, scope }) => {
480
- const result = await convex.query(api.agentContentDb.detectAgentDrift, { name, scope });
625
+ const result = await convex.query(api.agentContentDb.detectAgentDrift, {
626
+ name,
627
+ scope,
628
+ });
481
629
  return {
482
630
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
483
631
  };
@@ -494,10 +642,14 @@ server.tool(
494
642
  "Returns content, sha256 hash, semver version, and last sync timestamp. " +
495
643
  "Returns null fields if the plugin has not been hosted in VR yet.",
496
644
  {
497
- name: z.string().describe("Plugin slug — e.g. 'perello-bootstrap', 'perello-identity'"),
645
+ name: z
646
+ .string()
647
+ .describe("Plugin slug — e.g. 'perello-bootstrap', 'perello-identity'"),
498
648
  },
499
649
  async ({ name }) => {
500
- const result = await convex.query(api.pluginContentDb.getPluginContent, { name });
650
+ const result = await convex.query(api.pluginContentDb.getPluginContent, {
651
+ name,
652
+ });
501
653
  return {
502
654
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
503
655
  };
@@ -515,14 +667,23 @@ server.tool(
515
667
  {
516
668
  name: z.string().describe("Plugin slug — e.g. 'perello-bootstrap'"),
517
669
  content: z.string().describe("Full plugin content body"),
518
- createIfMissing: z.boolean().optional().default(false).describe("Auto-create a placeholder plugin row if it does not exist (default: false)"),
670
+ createIfMissing: z
671
+ .boolean()
672
+ .optional()
673
+ .default(false)
674
+ .describe(
675
+ "Auto-create a placeholder plugin row if it does not exist (default: false)",
676
+ ),
519
677
  },
520
678
  async ({ name, content, createIfMissing }) => {
521
- const result = await convex.mutation(api.pluginContentDb.upsertPluginContent, {
522
- name,
523
- content,
524
- createIfMissing,
525
- });
679
+ const result = await convex.mutation(
680
+ api.pluginContentDb.upsertPluginContent,
681
+ {
682
+ name,
683
+ content,
684
+ createIfMissing,
685
+ },
686
+ );
526
687
  return {
527
688
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
528
689
  };
@@ -536,11 +697,20 @@ server.tool(
536
697
  "canonical hash + stored filePath so the caller can compute disk SHA256 and compare. " +
537
698
  "scope='all' (default) returns every plugin. name=<slug> filters to a single plugin.",
538
699
  {
539
- name: z.string().optional().describe("Plugin slug — filter to a single plugin"),
540
- scope: z.string().optional().describe("'all' to return every plugin (default when name is omitted)"),
700
+ name: z
701
+ .string()
702
+ .optional()
703
+ .describe("Plugin slug — filter to a single plugin"),
704
+ scope: z
705
+ .string()
706
+ .optional()
707
+ .describe("'all' to return every plugin (default when name is omitted)"),
541
708
  },
542
709
  async ({ name, scope }) => {
543
- const result = await convex.query(api.pluginContentDb.detectPluginDrift, { name, scope });
710
+ const result = await convex.query(api.pluginContentDb.detectPluginDrift, {
711
+ name,
712
+ scope,
713
+ });
544
714
  return {
545
715
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
546
716
  };
@@ -557,10 +727,14 @@ server.tool(
557
727
  "Returns content, sha256 hash, semver version, and last sync timestamp. " +
558
728
  "Returns null fields if the hook has not been hosted in VR yet.",
559
729
  {
560
- name: z.string().describe("Hook slug — e.g. 'enforce-no-task-in-message', 'check-pii'"),
730
+ name: z
731
+ .string()
732
+ .describe("Hook slug — e.g. 'enforce-no-task-in-message', 'check-pii'"),
561
733
  },
562
734
  async ({ name }) => {
563
- const result = await convex.query(api.hookContentDb.getHookContent, { name });
735
+ const result = await convex.query(api.hookContentDb.getHookContent, {
736
+ name,
737
+ });
564
738
  return {
565
739
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
566
740
  };
@@ -579,7 +753,13 @@ server.tool(
579
753
  {
580
754
  name: z.string().describe("Hook slug — e.g. 'enforce-no-task-in-message'"),
581
755
  content: z.string().describe("Full hook script content"),
582
- createIfMissing: z.boolean().optional().default(false).describe("Auto-create a placeholder hook row if it does not exist (default: false)"),
756
+ createIfMissing: z
757
+ .boolean()
758
+ .optional()
759
+ .default(false)
760
+ .describe(
761
+ "Auto-create a placeholder hook row if it does not exist (default: false)",
762
+ ),
583
763
  },
584
764
  async ({ name, content, createIfMissing }) => {
585
765
  const result = await convex.mutation(api.hookContentDb.upsertHookContent, {
@@ -601,10 +781,16 @@ server.tool(
601
781
  "scope='all' (default) returns every hook. name=<slug> filters to a single hook.",
602
782
  {
603
783
  name: z.string().optional().describe("Hook slug — filter to a single hook"),
604
- scope: z.string().optional().describe("'all' to return every hook (default when name is omitted)"),
784
+ scope: z
785
+ .string()
786
+ .optional()
787
+ .describe("'all' to return every hook (default when name is omitted)"),
605
788
  },
606
789
  async ({ name, scope }) => {
607
- const result = await convex.query(api.hookContentDb.detectHookDrift, { name, scope });
790
+ const result = await convex.query(api.hookContentDb.detectHookDrift, {
791
+ name,
792
+ scope,
793
+ });
608
794
  return {
609
795
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
610
796
  };
@@ -621,10 +807,14 @@ server.tool(
621
807
  "Returns content, sha256 hash, semver version, and last sync timestamp. " +
622
808
  "Returns null fields if the command has not been hosted in VR yet.",
623
809
  {
624
- name: z.string().describe("Command slug — e.g. 'sync-workspace-from-vr', 'deploy-prod'"),
810
+ name: z
811
+ .string()
812
+ .describe("Command slug — e.g. 'sync-workspace-from-vr', 'deploy-prod'"),
625
813
  },
626
814
  async ({ name }) => {
627
- const result = await convex.query(api.commandContentDb.getCommandContent, { name });
815
+ const result = await convex.query(api.commandContentDb.getCommandContent, {
816
+ name,
817
+ });
628
818
  return {
629
819
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
630
820
  };
@@ -643,10 +833,13 @@ server.tool(
643
833
  content: z.string().describe("Full command script content"),
644
834
  },
645
835
  async ({ name, content }) => {
646
- const result = await convex.mutation(api.commandContentDb.upsertCommandContent, {
647
- name,
648
- content,
649
- });
836
+ const result = await convex.mutation(
837
+ api.commandContentDb.upsertCommandContent,
838
+ {
839
+ name,
840
+ content,
841
+ },
842
+ );
650
843
  return {
651
844
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
652
845
  };
@@ -660,11 +853,20 @@ server.tool(
660
853
  "canonical hash + stored filePath so the caller can compute disk SHA256 and compare. " +
661
854
  "scope='all' (default) returns every command. name=<slug> filters to a single command.",
662
855
  {
663
- name: z.string().optional().describe("Command slug — filter to a single command"),
664
- scope: z.string().optional().describe("'all' to return every command (default when name is omitted)"),
856
+ name: z
857
+ .string()
858
+ .optional()
859
+ .describe("Command slug — filter to a single command"),
860
+ scope: z
861
+ .string()
862
+ .optional()
863
+ .describe("'all' to return every command (default when name is omitted)"),
665
864
  },
666
865
  async ({ name, scope }) => {
667
- const result = await convex.query(api.commandContentDb.detectCommandDrift, { name, scope });
866
+ const result = await convex.query(api.commandContentDb.detectCommandDrift, {
867
+ name,
868
+ scope,
869
+ });
668
870
  return {
669
871
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
670
872
  };
@@ -679,7 +881,9 @@ server.tool(
679
881
  "upsert_plugin",
680
882
  "Create or update a plugin in VantageRegistry. Upserts by name.",
681
883
  {
682
- name: z.string().describe("Plugin name — e.g. 'perello-bootstrap', 'perello-identity'"),
884
+ name: z
885
+ .string()
886
+ .describe("Plugin name — e.g. 'perello-bootstrap', 'perello-identity'"),
683
887
  description: z.string().describe("What this plugin provides"),
684
888
  agentCount: z.number().int().describe("Number of agents in this plugin"),
685
889
  skillCount: z.number().int().describe("Number of skills in this plugin"),
@@ -690,9 +894,15 @@ server.tool(
690
894
  filePath: z.string().optional().describe("Plugin directory path"),
691
895
  pricing: z.enum(["free", "paid"]).optional().describe("Pricing tier"),
692
896
  price: z.number().optional().describe("Price in EUR (if paid)"),
693
- license: z.string().optional().describe("License — e.g. 'MIT', 'proprietary'"),
897
+ license: z
898
+ .string()
899
+ .optional()
900
+ .describe("License — e.g. 'MIT', 'proprietary'"),
694
901
  publisherId: z.string().optional().describe("Publisher identifier"),
695
- categories: z.array(z.string()).optional().describe("Categories beyond team name"),
902
+ categories: z
903
+ .array(z.string())
904
+ .optional()
905
+ .describe("Categories beyond team name"),
696
906
  },
697
907
  async (args) => {
698
908
  const id = await convex.mutation(api.plugins.upsert, args);
@@ -709,12 +919,28 @@ server.tool(
709
919
 
710
920
  server.tool(
711
921
  "list_plugins",
712
- "List all plugins in VantageRegistry. Optionally filter by status.",
922
+ "List all plugins in VantageRegistry. Optionally filter by status, source, or team. " +
923
+ "fields='lite' returns compact {_id, name, status, source}. fields='full' returns the complete document.",
713
924
  {
714
- status: pluginStatusSchema.optional().describe("Filter by status — omit to list all"),
715
- },
716
- async ({ status }) => {
717
- const results = await convex.query(api.plugins.list, { status });
925
+ status: pluginStatusSchema
926
+ .optional()
927
+ .describe("Filter by status omit to list all"),
928
+ source: pluginSourceSchema
929
+ .optional()
930
+ .describe("Filter by source: 'internal' or 'external'"),
931
+ team: z.string().optional().describe("Filter by owning team"),
932
+ fields: z
933
+ .enum(["lite", "full"])
934
+ .optional()
935
+ .describe("Projection: 'lite' (compact) or 'full' (complete document)"),
936
+ },
937
+ async ({ status, source, team, fields }) => {
938
+ const results = await convex.query(api.plugins.list, {
939
+ status,
940
+ source,
941
+ team,
942
+ fields,
943
+ });
718
944
  return {
719
945
  content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
720
946
  };
@@ -743,13 +969,20 @@ server.tool(
743
969
  "upsert_hook",
744
970
  "Create or update a hook in VantageRegistry. Upserts by name.",
745
971
  {
746
- name: z.string().describe("Hook name — e.g. 'check-pii', 'enforce-skill-references'"),
972
+ name: z
973
+ .string()
974
+ .describe("Hook name — e.g. 'check-pii', 'enforce-skill-references'"),
747
975
  event: hookEventSchema,
748
- matcher: z.string().optional().describe("Tool matcher pattern — e.g. 'Bash', 'Agent'"),
976
+ matcher: z
977
+ .string()
978
+ .optional()
979
+ .describe("Tool matcher pattern — e.g. 'Bash', 'Agent'"),
749
980
  description: z.string().describe("What this hook does"),
750
981
  content: z.string().describe("Full hook script content"),
751
982
  filePath: z.string().describe("File path relative to project root"),
752
- registered: z.boolean().describe("Whether this hook is registered in settings.json"),
983
+ registered: z
984
+ .boolean()
985
+ .describe("Whether this hook is registered in settings.json"),
753
986
  status: hookStatusSchema,
754
987
  version: z.string().optional().describe("Semantic version — e.g. '1.0.0'"),
755
988
  },
@@ -759,7 +992,11 @@ server.tool(
759
992
  content: [
760
993
  {
761
994
  type: "text",
762
- text: JSON.stringify({ id, name: args.name, event: args.event }, null, 2),
995
+ text: JSON.stringify(
996
+ { id, name: args.name, event: args.event },
997
+ null,
998
+ 2,
999
+ ),
763
1000
  },
764
1001
  ],
765
1002
  };
@@ -768,12 +1005,30 @@ server.tool(
768
1005
 
769
1006
  server.tool(
770
1007
  "list_hooks",
771
- "List all hooks in VantageRegistry. Optionally filter by status.",
1008
+ "List all hooks in VantageRegistry. Optionally filter by status or lifecycle event. " +
1009
+ "fields='lite' returns compact {_id, name, status, event, registered}. fields='full' returns the complete document including content.",
772
1010
  {
773
- status: hookStatusSchema.optional().describe("Filter by status — omit to list all"),
1011
+ status: hookStatusSchema
1012
+ .optional()
1013
+ .describe("Filter by status — omit to list all"),
1014
+ event: hookEventSchema
1015
+ .optional()
1016
+ .describe(
1017
+ "Filter by lifecycle event — e.g. 'PreToolUse', 'SessionStart'",
1018
+ ),
1019
+ fields: z
1020
+ .enum(["lite", "full"])
1021
+ .optional()
1022
+ .describe(
1023
+ "Projection: 'lite' (compact) or 'full' (complete document with content)",
1024
+ ),
774
1025
  },
775
- async ({ status }) => {
776
- const results = await convex.query(api.hooks.list, { status });
1026
+ async ({ status, event, fields }) => {
1027
+ const results = await convex.query(api.hooks.list, {
1028
+ status,
1029
+ event,
1030
+ fields,
1031
+ });
777
1032
  return {
778
1033
  content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
779
1034
  };
@@ -802,8 +1057,13 @@ server.tool(
802
1057
  "upsert_prompt",
803
1058
  "Create or update a prompt in VantageRegistry. Upserts by name.",
804
1059
  {
805
- name: z.string().describe("Prompt name — e.g. 'system-prompt', 'review-checklist'"),
806
- team: z.string().optional().describe("Team this prompt belongs to — defaults to 'global'"),
1060
+ name: z
1061
+ .string()
1062
+ .describe("Prompt name — e.g. 'system-prompt', 'review-checklist'"),
1063
+ team: z
1064
+ .string()
1065
+ .optional()
1066
+ .describe("Team this prompt belongs to — defaults to 'global'"),
807
1067
  purpose: z.string().describe("What this prompt is used for"),
808
1068
  content: z.string().describe("Full prompt content"),
809
1069
  version: z.string().optional().describe("Semantic version — e.g. '1.0.0'"),
@@ -858,7 +1118,10 @@ server.tool(
858
1118
  "Create or update a template in VantageRegistry. Upserts by name.",
859
1119
  {
860
1120
  name: z.string().describe("Template name — e.g. 'agent-brief', 'skill-md'"),
861
- team: z.string().optional().describe("Team this template belongs to — defaults to 'global'"),
1121
+ team: z
1122
+ .string()
1123
+ .optional()
1124
+ .describe("Team this template belongs to — defaults to 'global'"),
862
1125
  purpose: z.string().describe("What this template is used for"),
863
1126
  content: z.string().describe("Full template content"),
864
1127
  version: z.string().optional().describe("Semantic version — e.g. '1.0.0'"),
@@ -881,10 +1144,15 @@ server.tool(
881
1144
  "List all templates in VantageRegistry. Optionally filter by team and/or template_type.",
882
1145
  {
883
1146
  team: z.string().optional().describe("Filter by team — omit to list all"),
884
- template_type: templateTypeSchema.optional().describe("Filter by template type: mission, document, or checklist"),
1147
+ template_type: templateTypeSchema
1148
+ .optional()
1149
+ .describe("Filter by template type: mission, document, or checklist"),
885
1150
  },
886
1151
  async ({ team, template_type }) => {
887
- const results = await convex.query(api.templates.list, { team, template_type });
1152
+ const results = await convex.query(api.templates.list, {
1153
+ team,
1154
+ template_type,
1155
+ });
888
1156
  return {
889
1157
  content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
890
1158
  };
@@ -915,25 +1183,54 @@ server.tool(
915
1183
  "(lastTestedAt, lastReviewerScore, lastEvalDelta, testStatus). " +
916
1184
  "reviewerScore format is 'X/Y' e.g. '37/45'. evalDelta is pp delta vs without-skill baseline.",
917
1185
  {
918
- skillId: z.string().describe("Convex document ID of the skill being tested"),
919
- runDate: z.number().describe("Unix timestamp (ms) when the run was executed"),
920
- runByOrchestrator: z.string().describe("Orchestrator that ran the test e.g. 'alpha', 'pi'"),
921
- runByMission: z.string().optional().describe("Mission reference — e.g. 'D70'"),
1186
+ skillId: z
1187
+ .string()
1188
+ .describe("Convex document ID of the skill being tested"),
1189
+ runDate: z
1190
+ .number()
1191
+ .describe("Unix timestamp (ms) when the run was executed"),
1192
+ runByOrchestrator: z
1193
+ .string()
1194
+ .describe("Orchestrator that ran the test — e.g. 'alpha', 'pi'"),
1195
+ runByMission: z
1196
+ .string()
1197
+ .optional()
1198
+ .describe("Mission reference — e.g. 'D70'"),
922
1199
  reviewerScore: z.string().describe("Score as 'X/Y' — e.g. '37/45'"),
923
- evalDelta: z.number().describe("Percentage-point delta vs without-skill baseline"),
1200
+ evalDelta: z
1201
+ .number()
1202
+ .describe("Percentage-point delta vs without-skill baseline"),
924
1203
  evalAbsoluteWith: z.number().describe("Absolute score with skill (0-100)"),
925
- evalAbsoluteWithout: z.number().describe("Absolute score without skill (0-100)"),
926
- runnerModel: z.string().describe("Model used to run the skill — e.g. 'claude-sonnet-4-6'"),
927
- judgeModel: z.string().describe("Model used as judge — e.g. 'claude-haiku-4-5'"),
928
- corpusVersion: z.string().describe("Eval corpus version used — e.g. '2.0.0'"),
1204
+ evalAbsoluteWithout: z
1205
+ .number()
1206
+ .describe("Absolute score without skill (0-100)"),
1207
+ runnerModel: z
1208
+ .string()
1209
+ .describe("Model used to run the skill — e.g. 'claude-sonnet-4-6'"),
1210
+ judgeModel: z
1211
+ .string()
1212
+ .describe("Model used as judge — e.g. 'claude-haiku-4-5'"),
1213
+ corpusVersion: z
1214
+ .string()
1215
+ .describe("Eval corpus version used — e.g. '2.0.0'"),
929
1216
  harnessVersion: z.string().describe("Test harness version — e.g. '0.1.0'"),
930
1217
  skillSha: z.string().describe("Git SHA of the skill file at test time"),
931
- gradingJsonPath: z.string().optional().describe("Path to grading output JSON"),
932
- benchmarkJsonPath: z.string().optional().describe("Path to benchmark output JSON"),
1218
+ gradingJsonPath: z
1219
+ .string()
1220
+ .optional()
1221
+ .describe("Path to grading output JSON"),
1222
+ benchmarkJsonPath: z
1223
+ .string()
1224
+ .optional()
1225
+ .describe("Path to benchmark output JSON"),
933
1226
  reportPath: z.string().optional().describe("Path to human-readable report"),
934
- timeSpentMinutes: z.number().optional().describe("Time spent running the test in minutes"),
1227
+ timeSpentMinutes: z
1228
+ .number()
1229
+ .optional()
1230
+ .describe("Time spent running the test in minutes"),
935
1231
  notes: z.string().optional().describe("Free-form notes about this run"),
936
- testStatus: z.enum(["untested", "tested", "needs_retest", "improving", "untestable"])
1232
+ testStatus: z
1233
+ .enum(["untested", "tested", "needs_retest", "improving", "untestable"])
937
1234
  .optional()
938
1235
  .describe("Override test status — defaults to 'tested'"),
939
1236
  },
@@ -943,7 +1240,16 @@ server.tool(
943
1240
  skillId: args.skillId as any,
944
1241
  });
945
1242
  return {
946
- content: [{ type: "text", text: JSON.stringify({ id, skillId: args.skillId, runDate: args.runDate }, null, 2) }],
1243
+ content: [
1244
+ {
1245
+ type: "text",
1246
+ text: JSON.stringify(
1247
+ { id, skillId: args.skillId, runDate: args.runDate },
1248
+ null,
1249
+ 2,
1250
+ ),
1251
+ },
1252
+ ],
947
1253
  };
948
1254
  },
949
1255
  );
@@ -953,7 +1259,10 @@ server.tool(
953
1259
  "Return test run history for a skill, ordered newest-first.",
954
1260
  {
955
1261
  skillId: z.string().describe("Convex document ID of the skill"),
956
- limit: z.number().optional().describe("Max number of runs to return — defaults to 50"),
1262
+ limit: z
1263
+ .number()
1264
+ .optional()
1265
+ .describe("Max number of runs to return — defaults to 50"),
957
1266
  },
958
1267
  async ({ skillId, limit }) => {
959
1268
  const results = await convex.query(api.skillTestRuns.getSkillTestHistory, {
@@ -969,12 +1278,31 @@ server.tool(
969
1278
  server.tool(
970
1279
  "list_skills_by_freshness",
971
1280
  "Return skills that have never been tested or were last tested more than staleDays ago. " +
972
- "Use this to find skills that need a quality test run.",
1281
+ "Use this to find skills that need a quality test run. " +
1282
+ "Defaults to fields='lite' (bounded output) to stay under MCP token cap. " +
1283
+ "Use fields='full' to retrieve complete skill documents.",
973
1284
  {
974
- staleDays: z.number().describe("Number of days since last test run to consider stale"),
1285
+ staleDays: z
1286
+ .number()
1287
+ .describe("Number of days since last test run to consider stale"),
1288
+ fields: z
1289
+ .enum(["lite", "full"])
1290
+ .optional()
1291
+ .default("lite")
1292
+ .describe(
1293
+ "Projection: 'lite' (default, compact — bounded output) or 'full' (complete document)",
1294
+ ),
1295
+ limit: z
1296
+ .number()
1297
+ .int()
1298
+ .optional()
1299
+ .describe("Max results — defaults to 100, max 500"),
975
1300
  },
976
- async ({ staleDays }) => {
977
- const results = await convex.query(api.skillTestRuns.listSkillsByFreshness, { staleDays });
1301
+ async ({ staleDays, fields, limit }) => {
1302
+ const results = await convex.query(
1303
+ api.skillTestRuns.listSkillsByFreshness,
1304
+ { staleDays, fields, limit },
1305
+ );
978
1306
  return {
979
1307
  content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
980
1308
  };
@@ -984,13 +1312,33 @@ server.tool(
984
1312
  server.tool(
985
1313
  "list_skills_below_threshold",
986
1314
  "Return skills whose last test run was below a score ratio (scoreMin as 0-1 fraction of 'X/Y') " +
987
- "or below a delta threshold (deltaMin in pp). Omit a param to skip that filter.",
1315
+ "or below a delta threshold (deltaMin in pp). Omit a param to skip that filter. " +
1316
+ "fields='lite' returns compact output. fields='full' returns complete skill documents.",
988
1317
  {
989
- scoreMin: z.number().optional().describe("Minimum acceptable score ratio (0-1). Skills below this are returned."),
990
- deltaMin: z.number().optional().describe("Minimum acceptable eval delta in pp. Skills below this are returned."),
1318
+ scoreMin: z
1319
+ .number()
1320
+ .optional()
1321
+ .describe(
1322
+ "Minimum acceptable score ratio (0-1). Skills below this are returned.",
1323
+ ),
1324
+ deltaMin: z
1325
+ .number()
1326
+ .optional()
1327
+ .describe(
1328
+ "Minimum acceptable eval delta in pp. Skills below this are returned.",
1329
+ ),
1330
+ fields: z
1331
+ .enum(["lite", "full"])
1332
+ .optional()
1333
+ .describe(
1334
+ "Projection: 'lite' (compact) or 'full' (complete document). Omit for full (backward compat).",
1335
+ ),
991
1336
  },
992
- async ({ scoreMin, deltaMin }) => {
993
- const results = await convex.query(api.skillTestRuns.listSkillsBelowThreshold, { scoreMin, deltaMin });
1337
+ async ({ scoreMin, deltaMin, fields }) => {
1338
+ const results = await convex.query(
1339
+ api.skillTestRuns.listSkillsBelowThreshold,
1340
+ { scoreMin, deltaMin, fields },
1341
+ );
994
1342
  return {
995
1343
  content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
996
1344
  };
@@ -1004,20 +1352,46 @@ server.tool(
1004
1352
  skillId: z.string().describe("Convex document ID of the skill"),
1005
1353
  version: z.string().describe("Corpus version — e.g. '2.0.0'"),
1006
1354
  casesCount: z.number().int().describe("Number of eval cases"),
1007
- assertionsCount: z.number().int().describe("Total number of assertions across all cases"),
1008
- baselinePrompt: z.string().describe("The baseline prompt used without the skill"),
1009
- evalsJsonContent: z.string().describe("Full JSON content of the evals file, or '{}' as placeholder"),
1010
- authoredBy: z.string().describe("Orchestrator or person who authored this corpus"),
1011
- authoredAt: z.number().describe("Unix timestamp (ms) when the corpus was authored"),
1012
- supersedesVersion: z.string().optional().describe("Version this corpus supersedes — e.g. '1.0.0'"),
1355
+ assertionsCount: z
1356
+ .number()
1357
+ .int()
1358
+ .describe("Total number of assertions across all cases"),
1359
+ baselinePrompt: z
1360
+ .string()
1361
+ .describe("The baseline prompt used without the skill"),
1362
+ evalsJsonContent: z
1363
+ .string()
1364
+ .describe("Full JSON content of the evals file, or '{}' as placeholder"),
1365
+ authoredBy: z
1366
+ .string()
1367
+ .describe("Orchestrator or person who authored this corpus"),
1368
+ authoredAt: z
1369
+ .number()
1370
+ .describe("Unix timestamp (ms) when the corpus was authored"),
1371
+ supersedesVersion: z
1372
+ .string()
1373
+ .optional()
1374
+ .describe("Version this corpus supersedes — e.g. '1.0.0'"),
1013
1375
  },
1014
1376
  async (args) => {
1015
- const id = await convex.mutation(api.skillEvalCorpus.upsertSkillEvalCorpus, {
1016
- ...args,
1017
- skillId: args.skillId as any,
1018
- });
1377
+ const id = await convex.mutation(
1378
+ api.skillEvalCorpus.upsertSkillEvalCorpus,
1379
+ {
1380
+ ...args,
1381
+ skillId: args.skillId as any,
1382
+ },
1383
+ );
1019
1384
  return {
1020
- content: [{ type: "text", text: JSON.stringify({ id, skillId: args.skillId, version: args.version }, null, 2) }],
1385
+ content: [
1386
+ {
1387
+ type: "text",
1388
+ text: JSON.stringify(
1389
+ { id, skillId: args.skillId, version: args.version },
1390
+ null,
1391
+ 2,
1392
+ ),
1393
+ },
1394
+ ],
1021
1395
  };
1022
1396
  },
1023
1397
  );
@@ -1042,21 +1416,34 @@ const runbookInputSchema = z.object({
1042
1416
  const runbookOutputSchema = z.object({
1043
1417
  name: z.string().describe("Output artifact name"),
1044
1418
  description: z.string().describe("What this output contains"),
1045
- path_pattern: z.string().optional().describe("File path pattern — e.g. 'reports/{name}.md'"),
1419
+ path_pattern: z
1420
+ .string()
1421
+ .optional()
1422
+ .describe("File path pattern — e.g. 'reports/{name}.md'"),
1046
1423
  });
1047
1424
 
1048
1425
  const applicabilitySchema = z.object({
1049
- orchestrators: z.array(z.string()).describe("Orchestrator slugs this runbook applies to"),
1050
- business_units: z.array(z.string()).describe("Business unit slugs this runbook applies to"),
1051
- use_cases: z.array(z.string()).describe("Use case slugs this runbook applies to"),
1426
+ orchestrators: z
1427
+ .array(z.string())
1428
+ .describe("Orchestrator slugs this runbook applies to"),
1429
+ business_units: z
1430
+ .array(z.string())
1431
+ .describe("Business unit slugs this runbook applies to"),
1432
+ use_cases: z
1433
+ .array(z.string())
1434
+ .describe("Use case slugs this runbook applies to"),
1052
1435
  });
1053
1436
 
1054
1437
  const linkedTemplateSchema = z.object({
1055
1438
  name: z.string().describe("Template name"),
1056
- template_type: z.string().describe("Template type — mission, document, or checklist"),
1439
+ template_type: z
1440
+ .string()
1441
+ .describe("Template type — mission, document, or checklist"),
1057
1442
  version: z.string().describe("Template version — e.g. '1.0.0'"),
1058
1443
  usage_phase: z.string().describe("Phase name where this template is used"),
1059
- required: z.boolean().describe("Whether this template is required for the runbook"),
1444
+ required: z
1445
+ .boolean()
1446
+ .describe("Whether this template is required for the runbook"),
1060
1447
  description: z.string().describe("How this template is used in this runbook"),
1061
1448
  });
1062
1449
 
@@ -1064,32 +1451,60 @@ server.tool(
1064
1451
  "upsert_runbook",
1065
1452
  "Create or update a runbook in VantageRegistry. Upserts by name — if a runbook with the same name exists, it is updated.",
1066
1453
  {
1067
- name: z.string().describe("Runbook slug — e.g. 'deploy-production', 'onboard-agent'"),
1454
+ name: z
1455
+ .string()
1456
+ .describe("Runbook slug — e.g. 'deploy-production', 'onboard-agent'"),
1068
1457
  description: z.string().describe("What this runbook accomplishes"),
1069
1458
  version: z.string().describe("Semantic version — e.g. '1.0.0'"),
1070
1459
  status: runbookStatusSchema,
1071
- category: z.string().describe("Category — e.g. 'deployment', 'onboarding', 'quality'"),
1460
+ category: z
1461
+ .string()
1462
+ .describe("Category — e.g. 'deployment', 'onboarding', 'quality'"),
1072
1463
  tags: z.array(z.string()).describe("Searchable tags"),
1073
1464
  content: z.string().describe("Full runbook content (markdown)"),
1074
1465
  phases: z.array(phaseSchema).describe("Ordered phases of the runbook"),
1075
- inputs: z.array(runbookInputSchema).describe("Input parameters for the runbook"),
1076
- outputs: z.array(runbookOutputSchema).describe("Output artifacts produced by the runbook"),
1466
+ inputs: z
1467
+ .array(runbookInputSchema)
1468
+ .describe("Input parameters for the runbook"),
1469
+ outputs: z
1470
+ .array(runbookOutputSchema)
1471
+ .describe("Output artifacts produced by the runbook"),
1077
1472
  applicability: applicabilitySchema,
1078
1473
  author: z.string().describe("Author slug — e.g. 'omega'"),
1079
1474
  team: z.string().describe("Owning team slug — e.g. 'core', 'dev'"),
1080
- related_skills: z.array(z.string()).describe("Skill slugs referenced by this runbook"),
1081
- related_agents: z.array(z.string()).describe("Agent slugs referenced by this runbook"),
1082
- linked_templates: z.array(linkedTemplateSchema).describe("Templates used during this runbook"),
1475
+ related_skills: z
1476
+ .array(z.string())
1477
+ .describe("Skill slugs referenced by this runbook"),
1478
+ related_agents: z
1479
+ .array(z.string())
1480
+ .describe("Agent slugs referenced by this runbook"),
1481
+ linked_templates: z
1482
+ .array(linkedTemplateSchema)
1483
+ .describe("Templates used during this runbook"),
1083
1484
  },
1084
1485
  async (args) => {
1085
1486
  try {
1086
1487
  const result = await convex.mutation(api.runbooks.upsertRunbook, args);
1087
1488
  return {
1088
- content: [{ type: "text", text: JSON.stringify({ success: true, data: result }, null, 2) }],
1489
+ content: [
1490
+ {
1491
+ type: "text",
1492
+ text: JSON.stringify({ success: true, data: result }, null, 2),
1493
+ },
1494
+ ],
1089
1495
  };
1090
1496
  } catch (err) {
1091
1497
  return {
1092
- content: [{ type: "text", text: JSON.stringify({ success: false, error: String(err) }, null, 2) }],
1498
+ content: [
1499
+ {
1500
+ type: "text",
1501
+ text: JSON.stringify(
1502
+ { success: false, error: String(err) },
1503
+ null,
1504
+ 2,
1505
+ ),
1506
+ },
1507
+ ],
1093
1508
  };
1094
1509
  }
1095
1510
  },
@@ -1100,17 +1515,37 @@ server.tool(
1100
1515
  "Get a single runbook by name. Optionally guard by exact version.",
1101
1516
  {
1102
1517
  name: z.string().describe("Runbook slug — e.g. 'deploy-production'"),
1103
- version: z.string().optional().describe("If provided, returns null when stored version differs"),
1518
+ version: z
1519
+ .string()
1520
+ .optional()
1521
+ .describe("If provided, returns null when stored version differs"),
1104
1522
  },
1105
1523
  async ({ name, version }) => {
1106
1524
  try {
1107
- const result = await convex.query(api.runbooks.getRunbook, { name, version });
1525
+ const result = await convex.query(api.runbooks.getRunbook, {
1526
+ name,
1527
+ version,
1528
+ });
1108
1529
  return {
1109
- content: [{ type: "text", text: JSON.stringify({ success: true, data: result }, null, 2) }],
1530
+ content: [
1531
+ {
1532
+ type: "text",
1533
+ text: JSON.stringify({ success: true, data: result }, null, 2),
1534
+ },
1535
+ ],
1110
1536
  };
1111
1537
  } catch (err) {
1112
1538
  return {
1113
- content: [{ type: "text", text: JSON.stringify({ success: false, error: String(err) }, null, 2) }],
1539
+ content: [
1540
+ {
1541
+ type: "text",
1542
+ text: JSON.stringify(
1543
+ { success: false, error: String(err) },
1544
+ null,
1545
+ 2,
1546
+ ),
1547
+ },
1548
+ ],
1114
1549
  };
1115
1550
  }
1116
1551
  },
@@ -1120,23 +1555,55 @@ server.tool(
1120
1555
  "list_runbooks",
1121
1556
  "List runbooks in VantageRegistry. Defaults to published status. Supports applicability filters.",
1122
1557
  {
1123
- status: runbookStatusSchema.optional().describe("Filter by status — defaults to 'published'"),
1124
- category: z.string().optional().describe("Filter by category — e.g. 'deployment'"),
1558
+ status: runbookStatusSchema
1559
+ .optional()
1560
+ .describe("Filter by status — defaults to 'published'"),
1561
+ category: z
1562
+ .string()
1563
+ .optional()
1564
+ .describe("Filter by category — e.g. 'deployment'"),
1125
1565
  team: z.string().optional().describe("Filter by owning team"),
1126
- applicability_orchestrator: z.string().optional().describe("Filter by orchestrator slug in applicability"),
1127
- applicability_bu: z.string().optional().describe("Filter by business unit slug in applicability"),
1128
- applicability_use_case: z.string().optional().describe("Filter by use case slug in applicability"),
1129
- limit: z.number().int().optional().describe("Max results to return defaults to 50, max 200"),
1566
+ applicability_orchestrator: z
1567
+ .string()
1568
+ .optional()
1569
+ .describe("Filter by orchestrator slug in applicability"),
1570
+ applicability_bu: z
1571
+ .string()
1572
+ .optional()
1573
+ .describe("Filter by business unit slug in applicability"),
1574
+ applicability_use_case: z
1575
+ .string()
1576
+ .optional()
1577
+ .describe("Filter by use case slug in applicability"),
1578
+ limit: z
1579
+ .number()
1580
+ .int()
1581
+ .optional()
1582
+ .describe("Max results to return — defaults to 50, max 200"),
1130
1583
  },
1131
1584
  async (args) => {
1132
1585
  try {
1133
1586
  const result = await convex.query(api.runbooks.listRunbooks, args);
1134
1587
  return {
1135
- content: [{ type: "text", text: JSON.stringify({ success: true, data: result }, null, 2) }],
1588
+ content: [
1589
+ {
1590
+ type: "text",
1591
+ text: JSON.stringify({ success: true, data: result }, null, 2),
1592
+ },
1593
+ ],
1136
1594
  };
1137
1595
  } catch (err) {
1138
1596
  return {
1139
- content: [{ type: "text", text: JSON.stringify({ success: false, error: String(err) }, null, 2) }],
1597
+ content: [
1598
+ {
1599
+ type: "text",
1600
+ text: JSON.stringify(
1601
+ { success: false, error: String(err) },
1602
+ null,
1603
+ 2,
1604
+ ),
1605
+ },
1606
+ ],
1140
1607
  };
1141
1608
  }
1142
1609
  },
@@ -1146,18 +1613,41 @@ server.tool(
1146
1613
  "list_runbooks_by_category",
1147
1614
  "List runbooks for a specific category using the byCategory index.",
1148
1615
  {
1149
- category: z.string().describe("Category to filter by — e.g. 'deployment', 'quality'"),
1150
- status: runbookStatusSchema.optional().describe("Filter by status — omit to list all statuses in this category"),
1616
+ category: z
1617
+ .string()
1618
+ .describe("Category to filter by — e.g. 'deployment', 'quality'"),
1619
+ status: runbookStatusSchema
1620
+ .optional()
1621
+ .describe(
1622
+ "Filter by status — omit to list all statuses in this category",
1623
+ ),
1151
1624
  },
1152
1625
  async ({ category, status }) => {
1153
1626
  try {
1154
- const result = await convex.query(api.runbooks.listRunbooksByCategory, { category, status });
1627
+ const result = await convex.query(api.runbooks.listRunbooksByCategory, {
1628
+ category,
1629
+ status,
1630
+ });
1155
1631
  return {
1156
- content: [{ type: "text", text: JSON.stringify({ success: true, data: result }, null, 2) }],
1632
+ content: [
1633
+ {
1634
+ type: "text",
1635
+ text: JSON.stringify({ success: true, data: result }, null, 2),
1636
+ },
1637
+ ],
1157
1638
  };
1158
1639
  } catch (err) {
1159
1640
  return {
1160
- content: [{ type: "text", text: JSON.stringify({ success: false, error: String(err) }, null, 2) }],
1641
+ content: [
1642
+ {
1643
+ type: "text",
1644
+ text: JSON.stringify(
1645
+ { success: false, error: String(err) },
1646
+ null,
1647
+ 2,
1648
+ ),
1649
+ },
1650
+ ],
1161
1651
  };
1162
1652
  }
1163
1653
  },
@@ -1168,17 +1658,36 @@ server.tool(
1168
1658
  "List runbooks for a specific team using the byTeam index.",
1169
1659
  {
1170
1660
  team: z.string().describe("Team slug to filter by — e.g. 'core', 'dev'"),
1171
- status: runbookStatusSchema.optional().describe("Filter by status — omit to list all statuses for this team"),
1661
+ status: runbookStatusSchema
1662
+ .optional()
1663
+ .describe("Filter by status — omit to list all statuses for this team"),
1172
1664
  },
1173
1665
  async ({ team, status }) => {
1174
1666
  try {
1175
- const result = await convex.query(api.runbooks.listRunbooksByTeam, { team, status });
1667
+ const result = await convex.query(api.runbooks.listRunbooksByTeam, {
1668
+ team,
1669
+ status,
1670
+ });
1176
1671
  return {
1177
- content: [{ type: "text", text: JSON.stringify({ success: true, data: result }, null, 2) }],
1672
+ content: [
1673
+ {
1674
+ type: "text",
1675
+ text: JSON.stringify({ success: true, data: result }, null, 2),
1676
+ },
1677
+ ],
1178
1678
  };
1179
1679
  } catch (err) {
1180
1680
  return {
1181
- content: [{ type: "text", text: JSON.stringify({ success: false, error: String(err) }, null, 2) }],
1681
+ content: [
1682
+ {
1683
+ type: "text",
1684
+ text: JSON.stringify(
1685
+ { success: false, error: String(err) },
1686
+ null,
1687
+ 2,
1688
+ ),
1689
+ },
1690
+ ],
1182
1691
  };
1183
1692
  }
1184
1693
  },
@@ -1192,13 +1701,29 @@ server.tool(
1192
1701
  },
1193
1702
  async ({ name }) => {
1194
1703
  try {
1195
- const result = await convex.mutation(api.runbooks.deleteRunbook, { name });
1704
+ const result = await convex.mutation(api.runbooks.deleteRunbook, {
1705
+ name,
1706
+ });
1196
1707
  return {
1197
- content: [{ type: "text", text: JSON.stringify({ success: true, data: result }, null, 2) }],
1708
+ content: [
1709
+ {
1710
+ type: "text",
1711
+ text: JSON.stringify({ success: true, data: result }, null, 2),
1712
+ },
1713
+ ],
1198
1714
  };
1199
1715
  } catch (err) {
1200
1716
  return {
1201
- content: [{ type: "text", text: JSON.stringify({ success: false, error: String(err) }, null, 2) }],
1717
+ content: [
1718
+ {
1719
+ type: "text",
1720
+ text: JSON.stringify(
1721
+ { success: false, error: String(err) },
1722
+ null,
1723
+ 2,
1724
+ ),
1725
+ },
1726
+ ],
1202
1727
  };
1203
1728
  }
1204
1729
  },
@@ -1416,6 +1941,306 @@ server.tool(
1416
1941
  },
1417
1942
  );
1418
1943
 
1944
+ // ═══════════════════════════════════════════════════════════════════════════════
1945
+ // COMPONENTS CATALOG (D90 — closes orphaned list_components tool)
1946
+ // ═══════════════════════════════════════════════════════════════════════════════
1947
+
1948
+ const componentKindSchema = z
1949
+ .enum(["skill", "agent", "hook", "plugin", "prompt", "runbook", "template"])
1950
+ .describe("Component kind");
1951
+
1952
+ const componentStatusSchema = z
1953
+ .enum(["active", "deprecated", "experimental"])
1954
+ .describe("Component status");
1955
+
1956
+ server.tool(
1957
+ "register_component",
1958
+ "Create or update a component in the VantageRegistry components catalog. " +
1959
+ "Upserts by name+kind — if a component with the same name and kind exists it is updated. " +
1960
+ "Use this to register any VantageOS primitive: skill, agent, hook, plugin, prompt, runbook, or template.",
1961
+ {
1962
+ name: z
1963
+ .string()
1964
+ .describe(
1965
+ "Component name slug — e.g. 'check-messages', 'dev-convex-expert'",
1966
+ ),
1967
+ kind: componentKindSchema,
1968
+ status: componentStatusSchema,
1969
+ ownerTeam: z
1970
+ .string()
1971
+ .optional()
1972
+ .describe("Team that owns this component — e.g. 'core', 'dev'"),
1973
+ description: z
1974
+ .string()
1975
+ .optional()
1976
+ .describe("Short description of what this component does"),
1977
+ vrEntityId: z
1978
+ .string()
1979
+ .optional()
1980
+ .describe("ID of the backing VR entity (skill _id, agent _id, etc.)"),
1981
+ metadata: z
1982
+ .record(z.string(), z.unknown())
1983
+ .optional()
1984
+ .describe("Arbitrary metadata bag — store extra fields here"),
1985
+ tags: z
1986
+ .array(z.string())
1987
+ .optional()
1988
+ .describe("Searchable tags — e.g. ['ai', 'backend']"),
1989
+ },
1990
+ async (args) => {
1991
+ try {
1992
+ const id = await convex.mutation(api.components.registerComponent, args);
1993
+ return {
1994
+ content: [
1995
+ {
1996
+ type: "text",
1997
+ text: JSON.stringify(
1998
+ { id, name: args.name, kind: args.kind },
1999
+ null,
2000
+ 2,
2001
+ ),
2002
+ },
2003
+ ],
2004
+ };
2005
+ } catch (err) {
2006
+ return {
2007
+ content: [
2008
+ {
2009
+ type: "text",
2010
+ text: JSON.stringify(
2011
+ { success: false, error: String(err) },
2012
+ null,
2013
+ 2,
2014
+ ),
2015
+ },
2016
+ ],
2017
+ };
2018
+ }
2019
+ },
2020
+ );
2021
+
2022
+ server.tool(
2023
+ "list_components",
2024
+ "List components in the VantageRegistry catalog. " +
2025
+ "Supports filtering by kind, ownerTeam, status, and tags. " +
2026
+ "fields='lite' (default) returns compact {_id, name, kind, status, ownerTeam}. " +
2027
+ "fields='full' returns the complete document including description, tags, metadata, vrEntityId. " +
2028
+ "limit defaults to 100, max 500.",
2029
+ {
2030
+ kind: componentKindSchema
2031
+ .optional()
2032
+ .describe("Filter by kind — omit to return all kinds"),
2033
+ ownerTeam: z.string().optional().describe("Filter by owning team"),
2034
+ status: componentStatusSchema
2035
+ .optional()
2036
+ .describe("Filter by status — omit to return all statuses"),
2037
+ tags: z
2038
+ .array(z.string())
2039
+ .optional()
2040
+ .describe("Filter by tags — ALL specified tags must match"),
2041
+ fields: z
2042
+ .enum(["lite", "full"])
2043
+ .optional()
2044
+ .describe("Projection: 'lite' (default) or 'full'"),
2045
+ limit: z
2046
+ .number()
2047
+ .int()
2048
+ .optional()
2049
+ .describe("Max results — defaults to 100, max 500"),
2050
+ },
2051
+ async ({ kind, ownerTeam, status, tags, fields, limit }) => {
2052
+ try {
2053
+ const filter =
2054
+ kind !== undefined ||
2055
+ ownerTeam !== undefined ||
2056
+ status !== undefined ||
2057
+ tags !== undefined
2058
+ ? { kind, ownerTeam, status, tags }
2059
+ : undefined;
2060
+ const results = await convex.query(api.components.listComponents, {
2061
+ filter,
2062
+ fields,
2063
+ limit,
2064
+ });
2065
+ return {
2066
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
2067
+ };
2068
+ } catch (err) {
2069
+ return {
2070
+ content: [
2071
+ {
2072
+ type: "text",
2073
+ text: JSON.stringify(
2074
+ { success: false, error: String(err) },
2075
+ null,
2076
+ 2,
2077
+ ),
2078
+ },
2079
+ ],
2080
+ };
2081
+ }
2082
+ },
2083
+ );
2084
+
2085
+ server.tool(
2086
+ "get_component",
2087
+ "Get a single component by its Convex document ID. Returns the full document or null if not found.",
2088
+ {
2089
+ id: z.string().describe("Convex document ID of the component"),
2090
+ },
2091
+ async ({ id }) => {
2092
+ try {
2093
+ const result = await convex.query(api.components.getComponent, {
2094
+ id: id as any,
2095
+ });
2096
+ return {
2097
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
2098
+ };
2099
+ } catch (err) {
2100
+ return {
2101
+ content: [
2102
+ {
2103
+ type: "text",
2104
+ text: JSON.stringify(
2105
+ { success: false, error: String(err) },
2106
+ null,
2107
+ 2,
2108
+ ),
2109
+ },
2110
+ ],
2111
+ };
2112
+ }
2113
+ },
2114
+ );
2115
+
2116
+ server.tool(
2117
+ "search_components",
2118
+ "Search components by name prefix. Returns up to `limit` lite results whose name starts with the query string. " +
2119
+ "Case-sensitive index range scan. Use list_components with filters for kind/status/tags filtering.",
2120
+ {
2121
+ query: z
2122
+ .string()
2123
+ .describe("Name prefix to search for — e.g. 'check', 'dev-'"),
2124
+ limit: z
2125
+ .number()
2126
+ .int()
2127
+ .optional()
2128
+ .describe("Max results — defaults to 50, max 200"),
2129
+ },
2130
+ async ({ query, limit }) => {
2131
+ try {
2132
+ const results = await convex.query(api.components.searchComponents, {
2133
+ query,
2134
+ limit,
2135
+ });
2136
+ return {
2137
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
2138
+ };
2139
+ } catch (err) {
2140
+ return {
2141
+ content: [
2142
+ {
2143
+ type: "text",
2144
+ text: JSON.stringify(
2145
+ { success: false, error: String(err) },
2146
+ null,
2147
+ 2,
2148
+ ),
2149
+ },
2150
+ ],
2151
+ };
2152
+ }
2153
+ },
2154
+ );
2155
+
2156
+ server.tool(
2157
+ "update_component",
2158
+ "Patch individual fields on an existing component. Only provided fields are updated. Throws if not found.",
2159
+ {
2160
+ id: z.string().describe("Convex document ID of the component to update"),
2161
+ name: z.string().optional().describe("New name slug"),
2162
+ kind: componentKindSchema.optional().describe("New kind"),
2163
+ ownerTeam: z.string().optional().describe("New owning team"),
2164
+ description: z.string().optional().describe("New description"),
2165
+ status: componentStatusSchema.optional().describe("New status"),
2166
+ vrEntityId: z.string().optional().describe("Updated vrEntityId reference"),
2167
+ metadata: z
2168
+ .record(z.string(), z.unknown())
2169
+ .optional()
2170
+ .describe("Updated metadata bag"),
2171
+ tags: z.array(z.string()).optional().describe("Updated tags array"),
2172
+ },
2173
+ async (args) => {
2174
+ try {
2175
+ await convex.mutation(api.components.updateComponent, {
2176
+ ...args,
2177
+ id: args.id as any,
2178
+ });
2179
+ return {
2180
+ content: [
2181
+ {
2182
+ type: "text",
2183
+ text: JSON.stringify({ success: true, id: args.id }, null, 2),
2184
+ },
2185
+ ],
2186
+ };
2187
+ } catch (err) {
2188
+ return {
2189
+ content: [
2190
+ {
2191
+ type: "text",
2192
+ text: JSON.stringify(
2193
+ { success: false, error: String(err) },
2194
+ null,
2195
+ 2,
2196
+ ),
2197
+ },
2198
+ ],
2199
+ };
2200
+ }
2201
+ },
2202
+ );
2203
+
2204
+ server.tool(
2205
+ "delete_component",
2206
+ "Soft-delete a component by ID — sets status to 'deprecated'. " +
2207
+ "The document remains in the database and is still retrievable. " +
2208
+ "Returns {deleted: true} if found, {deleted: false} if not found.",
2209
+ {
2210
+ id: z
2211
+ .string()
2212
+ .describe("Convex document ID of the component to soft-delete"),
2213
+ },
2214
+ async ({ id }) => {
2215
+ try {
2216
+ const result = await convex.mutation(api.components.deleteComponent, {
2217
+ id: id as any,
2218
+ });
2219
+ return {
2220
+ content: [
2221
+ {
2222
+ type: "text",
2223
+ text: JSON.stringify({ success: true, ...result }, null, 2),
2224
+ },
2225
+ ],
2226
+ };
2227
+ } catch (err) {
2228
+ return {
2229
+ content: [
2230
+ {
2231
+ type: "text",
2232
+ text: JSON.stringify(
2233
+ { success: false, error: String(err) },
2234
+ null,
2235
+ 2,
2236
+ ),
2237
+ },
2238
+ ],
2239
+ };
2240
+ }
2241
+ },
2242
+ );
2243
+
1419
2244
  // ═══════════════════════════════════════════════════════════════════════════════
1420
2245
  // STATS
1421
2246
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -1427,15 +2252,30 @@ server.tool(
1427
2252
  "Use this to get a quick overview of the registry contents.",
1428
2253
  {},
1429
2254
  async () => {
1430
- const [teams, agents, skills, plugins, hooks, rbDraft, rbPublished, rbDeprecated] = await Promise.all([
2255
+ const [
2256
+ teams,
2257
+ agents,
2258
+ skills,
2259
+ plugins,
2260
+ hooks,
2261
+ rbDraft,
2262
+ rbPublished,
2263
+ rbDeprecated,
2264
+ ] = await Promise.all([
1431
2265
  convex.query(api.teams.list, {}),
1432
2266
  convex.query(api.agents.list, {}),
1433
2267
  convex.query(api.skills.list, {}),
1434
2268
  convex.query(api.plugins.list, {}),
1435
2269
  convex.query(api.hooks.list, {}),
1436
2270
  convex.query(api.runbooks.listRunbooks, { status: "draft", limit: 1000 }),
1437
- convex.query(api.runbooks.listRunbooks, { status: "published", limit: 1000 }),
1438
- convex.query(api.runbooks.listRunbooks, { status: "deprecated", limit: 1000 }),
2271
+ convex.query(api.runbooks.listRunbooks, {
2272
+ status: "published",
2273
+ limit: 1000,
2274
+ }),
2275
+ convex.query(api.runbooks.listRunbooks, {
2276
+ status: "deprecated",
2277
+ limit: 1000,
2278
+ }),
1439
2279
  ]);
1440
2280
 
1441
2281
  // Build by_category map across all statuses
@@ -1444,7 +2284,8 @@ server.tool(
1444
2284
  byCategoryMap[rb.category] = (byCategoryMap[rb.category] ?? 0) + 1;
1445
2285
  }
1446
2286
 
1447
- const runbooksTotal = rbDraft.length + rbPublished.length + rbDeprecated.length;
2287
+ const runbooksTotal =
2288
+ rbDraft.length + rbPublished.length + rbDeprecated.length;
1448
2289
 
1449
2290
  const stats = {
1450
2291
  teams: teams.length,
@@ -1461,7 +2302,13 @@ server.tool(
1461
2302
  },
1462
2303
  by_category: byCategoryMap,
1463
2304
  },
1464
- total: teams.length + agents.length + skills.length + plugins.length + hooks.length + runbooksTotal,
2305
+ total:
2306
+ teams.length +
2307
+ agents.length +
2308
+ skills.length +
2309
+ plugins.length +
2310
+ hooks.length +
2311
+ runbooksTotal,
1465
2312
  };
1466
2313
 
1467
2314
  return {