@vantageos/vantage-registry-mcp 1.2.0 → 1.5.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 +1732 -0
  3. package/server.ts +1094 -184
package/server.ts CHANGED
@@ -10,19 +10,20 @@
10
10
  * upsert_plugin, list_plugins, get_plugin
11
11
  * upsert_hook, list_hooks, get_hook
12
12
  * upsert_prompt, list_prompts, get_prompt
13
- * upsert_template, list_templates, get_template
13
+ * upsert_template, list_templates, get_template, detect_template_drift, list_templates_by_category
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"])
@@ -109,7 +112,7 @@ const runbookStatusSchema = z
109
112
  .describe("Runbook status");
110
113
 
111
114
  const templateTypeSchema = z
112
- .enum(["mission", "document", "checklist"])
115
+ .enum(["standard", "mission", "brief", "runbook", "document", "checklist"])
113
116
  .describe("Template type");
114
117
 
115
118
  const linkTypeSchema = z
@@ -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.5.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'"),
@@ -855,13 +1115,38 @@ server.tool(
855
1115
 
856
1116
  server.tool(
857
1117
  "upsert_template",
858
- "Create or update a template in VantageRegistry. Upserts by name.",
1118
+ "Create or update a template in VantageRegistry. Upserts by name. " +
1119
+ "contentHash is auto-computed server-side (sha256 of content) when omitted.",
859
1120
  {
860
1121
  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'"),
1122
+ team: z
1123
+ .string()
1124
+ .optional()
1125
+ .describe("Team this template belongs to — defaults to 'global'"),
862
1126
  purpose: z.string().describe("What this template is used for"),
863
1127
  content: z.string().describe("Full template content"),
864
1128
  version: z.string().optional().describe("Semantic version — e.g. '1.0.0'"),
1129
+ template_type: templateTypeSchema
1130
+ .optional()
1131
+ .describe(
1132
+ "Template type: standard, mission, brief, runbook, document, or checklist",
1133
+ ),
1134
+ category: z
1135
+ .string()
1136
+ .optional()
1137
+ .describe("Category — e.g. 'standards/fleet-shared'"),
1138
+ contentHash: z
1139
+ .string()
1140
+ .optional()
1141
+ .describe("sha256 of content — auto-computed server-side when omitted"),
1142
+ sourceCommit: z
1143
+ .string()
1144
+ .optional()
1145
+ .describe("Git commit SHA the template content was sourced from"),
1146
+ sourceRepo: z
1147
+ .string()
1148
+ .optional()
1149
+ .describe("Git repo the template content was sourced from"),
865
1150
  },
866
1151
  async (args) => {
867
1152
  const id = await convex.mutation(api.templates.upsert, args);
@@ -881,10 +1166,15 @@ server.tool(
881
1166
  "List all templates in VantageRegistry. Optionally filter by team and/or template_type.",
882
1167
  {
883
1168
  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"),
1169
+ template_type: templateTypeSchema
1170
+ .optional()
1171
+ .describe("Filter by template type: mission, document, or checklist"),
885
1172
  },
886
1173
  async ({ team, template_type }) => {
887
- const results = await convex.query(api.templates.list, { team, template_type });
1174
+ const results = await convex.query(api.templates.list, {
1175
+ team,
1176
+ template_type,
1177
+ });
888
1178
  return {
889
1179
  content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
890
1180
  };
@@ -905,6 +1195,47 @@ server.tool(
905
1195
  },
906
1196
  );
907
1197
 
1198
+ server.tool(
1199
+ "detect_template_drift",
1200
+ "Return the VR-side sha256 contentHash + provenance (sourceCommit, sourceRepo) " +
1201
+ "for each template in scope. IMPORTANT: Convex queries have no filesystem access. " +
1202
+ "This tool returns the VR canonical contentHash so the caller can read the source " +
1203
+ "file, compute its SHA256, and compare. name=<slug> filters to a single template; " +
1204
+ "omit name to return every template.",
1205
+ {
1206
+ name: z
1207
+ .string()
1208
+ .optional()
1209
+ .describe("Template slug — filter to a single template"),
1210
+ },
1211
+ async ({ name }) => {
1212
+ const result = await convex.query(api.templates.detectTemplateDrift, {
1213
+ name,
1214
+ });
1215
+ return {
1216
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1217
+ };
1218
+ },
1219
+ );
1220
+
1221
+ server.tool(
1222
+ "list_templates_by_category",
1223
+ "List templates for a specific category using the byCategory index.",
1224
+ {
1225
+ category: z
1226
+ .string()
1227
+ .describe("Category to filter by — e.g. 'standards/fleet-shared'"),
1228
+ },
1229
+ async ({ category }) => {
1230
+ const result = await convex.query(api.templates.listTemplatesByCategory, {
1231
+ category,
1232
+ });
1233
+ return {
1234
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1235
+ };
1236
+ },
1237
+ );
1238
+
908
1239
  // ═══════════════════════════════════════════════════════════════════════════════
909
1240
  // SKILL TEST RUNS
910
1241
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -915,25 +1246,54 @@ server.tool(
915
1246
  "(lastTestedAt, lastReviewerScore, lastEvalDelta, testStatus). " +
916
1247
  "reviewerScore format is 'X/Y' e.g. '37/45'. evalDelta is pp delta vs without-skill baseline.",
917
1248
  {
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'"),
1249
+ skillId: z
1250
+ .string()
1251
+ .describe("Convex document ID of the skill being tested"),
1252
+ runDate: z
1253
+ .number()
1254
+ .describe("Unix timestamp (ms) when the run was executed"),
1255
+ runByOrchestrator: z
1256
+ .string()
1257
+ .describe("Orchestrator that ran the test — e.g. 'alpha', 'pi'"),
1258
+ runByMission: z
1259
+ .string()
1260
+ .optional()
1261
+ .describe("Mission reference — e.g. 'D70'"),
922
1262
  reviewerScore: z.string().describe("Score as 'X/Y' — e.g. '37/45'"),
923
- evalDelta: z.number().describe("Percentage-point delta vs without-skill baseline"),
1263
+ evalDelta: z
1264
+ .number()
1265
+ .describe("Percentage-point delta vs without-skill baseline"),
924
1266
  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'"),
1267
+ evalAbsoluteWithout: z
1268
+ .number()
1269
+ .describe("Absolute score without skill (0-100)"),
1270
+ runnerModel: z
1271
+ .string()
1272
+ .describe("Model used to run the skill — e.g. 'claude-sonnet-4-6'"),
1273
+ judgeModel: z
1274
+ .string()
1275
+ .describe("Model used as judge — e.g. 'claude-haiku-4-5'"),
1276
+ corpusVersion: z
1277
+ .string()
1278
+ .describe("Eval corpus version used — e.g. '2.0.0'"),
929
1279
  harnessVersion: z.string().describe("Test harness version — e.g. '0.1.0'"),
930
1280
  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"),
1281
+ gradingJsonPath: z
1282
+ .string()
1283
+ .optional()
1284
+ .describe("Path to grading output JSON"),
1285
+ benchmarkJsonPath: z
1286
+ .string()
1287
+ .optional()
1288
+ .describe("Path to benchmark output JSON"),
933
1289
  reportPath: z.string().optional().describe("Path to human-readable report"),
934
- timeSpentMinutes: z.number().optional().describe("Time spent running the test in minutes"),
1290
+ timeSpentMinutes: z
1291
+ .number()
1292
+ .optional()
1293
+ .describe("Time spent running the test in minutes"),
935
1294
  notes: z.string().optional().describe("Free-form notes about this run"),
936
- testStatus: z.enum(["untested", "tested", "needs_retest", "improving", "untestable"])
1295
+ testStatus: z
1296
+ .enum(["untested", "tested", "needs_retest", "improving", "untestable"])
937
1297
  .optional()
938
1298
  .describe("Override test status — defaults to 'tested'"),
939
1299
  },
@@ -943,7 +1303,16 @@ server.tool(
943
1303
  skillId: args.skillId as any,
944
1304
  });
945
1305
  return {
946
- content: [{ type: "text", text: JSON.stringify({ id, skillId: args.skillId, runDate: args.runDate }, null, 2) }],
1306
+ content: [
1307
+ {
1308
+ type: "text",
1309
+ text: JSON.stringify(
1310
+ { id, skillId: args.skillId, runDate: args.runDate },
1311
+ null,
1312
+ 2,
1313
+ ),
1314
+ },
1315
+ ],
947
1316
  };
948
1317
  },
949
1318
  );
@@ -953,7 +1322,10 @@ server.tool(
953
1322
  "Return test run history for a skill, ordered newest-first.",
954
1323
  {
955
1324
  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"),
1325
+ limit: z
1326
+ .number()
1327
+ .optional()
1328
+ .describe("Max number of runs to return — defaults to 50"),
957
1329
  },
958
1330
  async ({ skillId, limit }) => {
959
1331
  const results = await convex.query(api.skillTestRuns.getSkillTestHistory, {
@@ -969,12 +1341,31 @@ server.tool(
969
1341
  server.tool(
970
1342
  "list_skills_by_freshness",
971
1343
  "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.",
1344
+ "Use this to find skills that need a quality test run. " +
1345
+ "Defaults to fields='lite' (bounded output) to stay under MCP token cap. " +
1346
+ "Use fields='full' to retrieve complete skill documents.",
973
1347
  {
974
- staleDays: z.number().describe("Number of days since last test run to consider stale"),
1348
+ staleDays: z
1349
+ .number()
1350
+ .describe("Number of days since last test run to consider stale"),
1351
+ fields: z
1352
+ .enum(["lite", "full"])
1353
+ .optional()
1354
+ .default("lite")
1355
+ .describe(
1356
+ "Projection: 'lite' (default, compact — bounded output) or 'full' (complete document)",
1357
+ ),
1358
+ limit: z
1359
+ .number()
1360
+ .int()
1361
+ .optional()
1362
+ .describe("Max results — defaults to 100, max 500"),
975
1363
  },
976
- async ({ staleDays }) => {
977
- const results = await convex.query(api.skillTestRuns.listSkillsByFreshness, { staleDays });
1364
+ async ({ staleDays, fields, limit }) => {
1365
+ const results = await convex.query(
1366
+ api.skillTestRuns.listSkillsByFreshness,
1367
+ { staleDays, fields, limit },
1368
+ );
978
1369
  return {
979
1370
  content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
980
1371
  };
@@ -984,13 +1375,33 @@ server.tool(
984
1375
  server.tool(
985
1376
  "list_skills_below_threshold",
986
1377
  "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.",
1378
+ "or below a delta threshold (deltaMin in pp). Omit a param to skip that filter. " +
1379
+ "fields='lite' returns compact output. fields='full' returns complete skill documents.",
988
1380
  {
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."),
1381
+ scoreMin: z
1382
+ .number()
1383
+ .optional()
1384
+ .describe(
1385
+ "Minimum acceptable score ratio (0-1). Skills below this are returned.",
1386
+ ),
1387
+ deltaMin: z
1388
+ .number()
1389
+ .optional()
1390
+ .describe(
1391
+ "Minimum acceptable eval delta in pp. Skills below this are returned.",
1392
+ ),
1393
+ fields: z
1394
+ .enum(["lite", "full"])
1395
+ .optional()
1396
+ .describe(
1397
+ "Projection: 'lite' (compact) or 'full' (complete document). Omit for full (backward compat).",
1398
+ ),
991
1399
  },
992
- async ({ scoreMin, deltaMin }) => {
993
- const results = await convex.query(api.skillTestRuns.listSkillsBelowThreshold, { scoreMin, deltaMin });
1400
+ async ({ scoreMin, deltaMin, fields }) => {
1401
+ const results = await convex.query(
1402
+ api.skillTestRuns.listSkillsBelowThreshold,
1403
+ { scoreMin, deltaMin, fields },
1404
+ );
994
1405
  return {
995
1406
  content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
996
1407
  };
@@ -1004,20 +1415,46 @@ server.tool(
1004
1415
  skillId: z.string().describe("Convex document ID of the skill"),
1005
1416
  version: z.string().describe("Corpus version — e.g. '2.0.0'"),
1006
1417
  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'"),
1418
+ assertionsCount: z
1419
+ .number()
1420
+ .int()
1421
+ .describe("Total number of assertions across all cases"),
1422
+ baselinePrompt: z
1423
+ .string()
1424
+ .describe("The baseline prompt used without the skill"),
1425
+ evalsJsonContent: z
1426
+ .string()
1427
+ .describe("Full JSON content of the evals file, or '{}' as placeholder"),
1428
+ authoredBy: z
1429
+ .string()
1430
+ .describe("Orchestrator or person who authored this corpus"),
1431
+ authoredAt: z
1432
+ .number()
1433
+ .describe("Unix timestamp (ms) when the corpus was authored"),
1434
+ supersedesVersion: z
1435
+ .string()
1436
+ .optional()
1437
+ .describe("Version this corpus supersedes — e.g. '1.0.0'"),
1013
1438
  },
1014
1439
  async (args) => {
1015
- const id = await convex.mutation(api.skillEvalCorpus.upsertSkillEvalCorpus, {
1016
- ...args,
1017
- skillId: args.skillId as any,
1018
- });
1440
+ const id = await convex.mutation(
1441
+ api.skillEvalCorpus.upsertSkillEvalCorpus,
1442
+ {
1443
+ ...args,
1444
+ skillId: args.skillId as any,
1445
+ },
1446
+ );
1019
1447
  return {
1020
- content: [{ type: "text", text: JSON.stringify({ id, skillId: args.skillId, version: args.version }, null, 2) }],
1448
+ content: [
1449
+ {
1450
+ type: "text",
1451
+ text: JSON.stringify(
1452
+ { id, skillId: args.skillId, version: args.version },
1453
+ null,
1454
+ 2,
1455
+ ),
1456
+ },
1457
+ ],
1021
1458
  };
1022
1459
  },
1023
1460
  );
@@ -1042,21 +1479,34 @@ const runbookInputSchema = z.object({
1042
1479
  const runbookOutputSchema = z.object({
1043
1480
  name: z.string().describe("Output artifact name"),
1044
1481
  description: z.string().describe("What this output contains"),
1045
- path_pattern: z.string().optional().describe("File path pattern — e.g. 'reports/{name}.md'"),
1482
+ path_pattern: z
1483
+ .string()
1484
+ .optional()
1485
+ .describe("File path pattern — e.g. 'reports/{name}.md'"),
1046
1486
  });
1047
1487
 
1048
1488
  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"),
1489
+ orchestrators: z
1490
+ .array(z.string())
1491
+ .describe("Orchestrator slugs this runbook applies to"),
1492
+ business_units: z
1493
+ .array(z.string())
1494
+ .describe("Business unit slugs this runbook applies to"),
1495
+ use_cases: z
1496
+ .array(z.string())
1497
+ .describe("Use case slugs this runbook applies to"),
1052
1498
  });
1053
1499
 
1054
1500
  const linkedTemplateSchema = z.object({
1055
1501
  name: z.string().describe("Template name"),
1056
- template_type: z.string().describe("Template type — mission, document, or checklist"),
1502
+ template_type: z
1503
+ .string()
1504
+ .describe("Template type — mission, document, or checklist"),
1057
1505
  version: z.string().describe("Template version — e.g. '1.0.0'"),
1058
1506
  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"),
1507
+ required: z
1508
+ .boolean()
1509
+ .describe("Whether this template is required for the runbook"),
1060
1510
  description: z.string().describe("How this template is used in this runbook"),
1061
1511
  });
1062
1512
 
@@ -1064,32 +1514,60 @@ server.tool(
1064
1514
  "upsert_runbook",
1065
1515
  "Create or update a runbook in VantageRegistry. Upserts by name — if a runbook with the same name exists, it is updated.",
1066
1516
  {
1067
- name: z.string().describe("Runbook slug — e.g. 'deploy-production', 'onboard-agent'"),
1517
+ name: z
1518
+ .string()
1519
+ .describe("Runbook slug — e.g. 'deploy-production', 'onboard-agent'"),
1068
1520
  description: z.string().describe("What this runbook accomplishes"),
1069
1521
  version: z.string().describe("Semantic version — e.g. '1.0.0'"),
1070
1522
  status: runbookStatusSchema,
1071
- category: z.string().describe("Category — e.g. 'deployment', 'onboarding', 'quality'"),
1523
+ category: z
1524
+ .string()
1525
+ .describe("Category — e.g. 'deployment', 'onboarding', 'quality'"),
1072
1526
  tags: z.array(z.string()).describe("Searchable tags"),
1073
1527
  content: z.string().describe("Full runbook content (markdown)"),
1074
1528
  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"),
1529
+ inputs: z
1530
+ .array(runbookInputSchema)
1531
+ .describe("Input parameters for the runbook"),
1532
+ outputs: z
1533
+ .array(runbookOutputSchema)
1534
+ .describe("Output artifacts produced by the runbook"),
1077
1535
  applicability: applicabilitySchema,
1078
1536
  author: z.string().describe("Author slug — e.g. 'omega'"),
1079
1537
  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"),
1538
+ related_skills: z
1539
+ .array(z.string())
1540
+ .describe("Skill slugs referenced by this runbook"),
1541
+ related_agents: z
1542
+ .array(z.string())
1543
+ .describe("Agent slugs referenced by this runbook"),
1544
+ linked_templates: z
1545
+ .array(linkedTemplateSchema)
1546
+ .describe("Templates used during this runbook"),
1083
1547
  },
1084
1548
  async (args) => {
1085
1549
  try {
1086
1550
  const result = await convex.mutation(api.runbooks.upsertRunbook, args);
1087
1551
  return {
1088
- content: [{ type: "text", text: JSON.stringify({ success: true, data: result }, null, 2) }],
1552
+ content: [
1553
+ {
1554
+ type: "text",
1555
+ text: JSON.stringify({ success: true, data: result }, null, 2),
1556
+ },
1557
+ ],
1089
1558
  };
1090
1559
  } catch (err) {
1091
1560
  return {
1092
- content: [{ type: "text", text: JSON.stringify({ success: false, error: String(err) }, null, 2) }],
1561
+ content: [
1562
+ {
1563
+ type: "text",
1564
+ text: JSON.stringify(
1565
+ { success: false, error: String(err) },
1566
+ null,
1567
+ 2,
1568
+ ),
1569
+ },
1570
+ ],
1093
1571
  };
1094
1572
  }
1095
1573
  },
@@ -1100,17 +1578,37 @@ server.tool(
1100
1578
  "Get a single runbook by name. Optionally guard by exact version.",
1101
1579
  {
1102
1580
  name: z.string().describe("Runbook slug — e.g. 'deploy-production'"),
1103
- version: z.string().optional().describe("If provided, returns null when stored version differs"),
1581
+ version: z
1582
+ .string()
1583
+ .optional()
1584
+ .describe("If provided, returns null when stored version differs"),
1104
1585
  },
1105
1586
  async ({ name, version }) => {
1106
1587
  try {
1107
- const result = await convex.query(api.runbooks.getRunbook, { name, version });
1588
+ const result = await convex.query(api.runbooks.getRunbook, {
1589
+ name,
1590
+ version,
1591
+ });
1108
1592
  return {
1109
- content: [{ type: "text", text: JSON.stringify({ success: true, data: result }, null, 2) }],
1593
+ content: [
1594
+ {
1595
+ type: "text",
1596
+ text: JSON.stringify({ success: true, data: result }, null, 2),
1597
+ },
1598
+ ],
1110
1599
  };
1111
1600
  } catch (err) {
1112
1601
  return {
1113
- content: [{ type: "text", text: JSON.stringify({ success: false, error: String(err) }, null, 2) }],
1602
+ content: [
1603
+ {
1604
+ type: "text",
1605
+ text: JSON.stringify(
1606
+ { success: false, error: String(err) },
1607
+ null,
1608
+ 2,
1609
+ ),
1610
+ },
1611
+ ],
1114
1612
  };
1115
1613
  }
1116
1614
  },
@@ -1120,23 +1618,55 @@ server.tool(
1120
1618
  "list_runbooks",
1121
1619
  "List runbooks in VantageRegistry. Defaults to published status. Supports applicability filters.",
1122
1620
  {
1123
- status: runbookStatusSchema.optional().describe("Filter by status — defaults to 'published'"),
1124
- category: z.string().optional().describe("Filter by category — e.g. 'deployment'"),
1621
+ status: runbookStatusSchema
1622
+ .optional()
1623
+ .describe("Filter by status — defaults to 'published'"),
1624
+ category: z
1625
+ .string()
1626
+ .optional()
1627
+ .describe("Filter by category — e.g. 'deployment'"),
1125
1628
  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"),
1629
+ applicability_orchestrator: z
1630
+ .string()
1631
+ .optional()
1632
+ .describe("Filter by orchestrator slug in applicability"),
1633
+ applicability_bu: z
1634
+ .string()
1635
+ .optional()
1636
+ .describe("Filter by business unit slug in applicability"),
1637
+ applicability_use_case: z
1638
+ .string()
1639
+ .optional()
1640
+ .describe("Filter by use case slug in applicability"),
1641
+ limit: z
1642
+ .number()
1643
+ .int()
1644
+ .optional()
1645
+ .describe("Max results to return — defaults to 50, max 200"),
1130
1646
  },
1131
1647
  async (args) => {
1132
1648
  try {
1133
1649
  const result = await convex.query(api.runbooks.listRunbooks, args);
1134
1650
  return {
1135
- content: [{ type: "text", text: JSON.stringify({ success: true, data: result }, null, 2) }],
1651
+ content: [
1652
+ {
1653
+ type: "text",
1654
+ text: JSON.stringify({ success: true, data: result }, null, 2),
1655
+ },
1656
+ ],
1136
1657
  };
1137
1658
  } catch (err) {
1138
1659
  return {
1139
- content: [{ type: "text", text: JSON.stringify({ success: false, error: String(err) }, null, 2) }],
1660
+ content: [
1661
+ {
1662
+ type: "text",
1663
+ text: JSON.stringify(
1664
+ { success: false, error: String(err) },
1665
+ null,
1666
+ 2,
1667
+ ),
1668
+ },
1669
+ ],
1140
1670
  };
1141
1671
  }
1142
1672
  },
@@ -1146,18 +1676,41 @@ server.tool(
1146
1676
  "list_runbooks_by_category",
1147
1677
  "List runbooks for a specific category using the byCategory index.",
1148
1678
  {
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"),
1679
+ category: z
1680
+ .string()
1681
+ .describe("Category to filter by — e.g. 'deployment', 'quality'"),
1682
+ status: runbookStatusSchema
1683
+ .optional()
1684
+ .describe(
1685
+ "Filter by status — omit to list all statuses in this category",
1686
+ ),
1151
1687
  },
1152
1688
  async ({ category, status }) => {
1153
1689
  try {
1154
- const result = await convex.query(api.runbooks.listRunbooksByCategory, { category, status });
1690
+ const result = await convex.query(api.runbooks.listRunbooksByCategory, {
1691
+ category,
1692
+ status,
1693
+ });
1155
1694
  return {
1156
- content: [{ type: "text", text: JSON.stringify({ success: true, data: result }, null, 2) }],
1695
+ content: [
1696
+ {
1697
+ type: "text",
1698
+ text: JSON.stringify({ success: true, data: result }, null, 2),
1699
+ },
1700
+ ],
1157
1701
  };
1158
1702
  } catch (err) {
1159
1703
  return {
1160
- content: [{ type: "text", text: JSON.stringify({ success: false, error: String(err) }, null, 2) }],
1704
+ content: [
1705
+ {
1706
+ type: "text",
1707
+ text: JSON.stringify(
1708
+ { success: false, error: String(err) },
1709
+ null,
1710
+ 2,
1711
+ ),
1712
+ },
1713
+ ],
1161
1714
  };
1162
1715
  }
1163
1716
  },
@@ -1168,17 +1721,36 @@ server.tool(
1168
1721
  "List runbooks for a specific team using the byTeam index.",
1169
1722
  {
1170
1723
  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"),
1724
+ status: runbookStatusSchema
1725
+ .optional()
1726
+ .describe("Filter by status — omit to list all statuses for this team"),
1172
1727
  },
1173
1728
  async ({ team, status }) => {
1174
1729
  try {
1175
- const result = await convex.query(api.runbooks.listRunbooksByTeam, { team, status });
1730
+ const result = await convex.query(api.runbooks.listRunbooksByTeam, {
1731
+ team,
1732
+ status,
1733
+ });
1176
1734
  return {
1177
- content: [{ type: "text", text: JSON.stringify({ success: true, data: result }, null, 2) }],
1735
+ content: [
1736
+ {
1737
+ type: "text",
1738
+ text: JSON.stringify({ success: true, data: result }, null, 2),
1739
+ },
1740
+ ],
1178
1741
  };
1179
1742
  } catch (err) {
1180
1743
  return {
1181
- content: [{ type: "text", text: JSON.stringify({ success: false, error: String(err) }, null, 2) }],
1744
+ content: [
1745
+ {
1746
+ type: "text",
1747
+ text: JSON.stringify(
1748
+ { success: false, error: String(err) },
1749
+ null,
1750
+ 2,
1751
+ ),
1752
+ },
1753
+ ],
1182
1754
  };
1183
1755
  }
1184
1756
  },
@@ -1192,13 +1764,29 @@ server.tool(
1192
1764
  },
1193
1765
  async ({ name }) => {
1194
1766
  try {
1195
- const result = await convex.mutation(api.runbooks.deleteRunbook, { name });
1767
+ const result = await convex.mutation(api.runbooks.deleteRunbook, {
1768
+ name,
1769
+ });
1196
1770
  return {
1197
- content: [{ type: "text", text: JSON.stringify({ success: true, data: result }, null, 2) }],
1771
+ content: [
1772
+ {
1773
+ type: "text",
1774
+ text: JSON.stringify({ success: true, data: result }, null, 2),
1775
+ },
1776
+ ],
1198
1777
  };
1199
1778
  } catch (err) {
1200
1779
  return {
1201
- content: [{ type: "text", text: JSON.stringify({ success: false, error: String(err) }, null, 2) }],
1780
+ content: [
1781
+ {
1782
+ type: "text",
1783
+ text: JSON.stringify(
1784
+ { success: false, error: String(err) },
1785
+ null,
1786
+ 2,
1787
+ ),
1788
+ },
1789
+ ],
1202
1790
  };
1203
1791
  }
1204
1792
  },
@@ -1416,6 +2004,306 @@ server.tool(
1416
2004
  },
1417
2005
  );
1418
2006
 
2007
+ // ═══════════════════════════════════════════════════════════════════════════════
2008
+ // COMPONENTS CATALOG (D90 — closes orphaned list_components tool)
2009
+ // ═══════════════════════════════════════════════════════════════════════════════
2010
+
2011
+ const componentKindSchema = z
2012
+ .enum(["skill", "agent", "hook", "plugin", "prompt", "runbook", "template"])
2013
+ .describe("Component kind");
2014
+
2015
+ const componentStatusSchema = z
2016
+ .enum(["active", "deprecated", "experimental"])
2017
+ .describe("Component status");
2018
+
2019
+ server.tool(
2020
+ "register_component",
2021
+ "Create or update a component in the VantageRegistry components catalog. " +
2022
+ "Upserts by name+kind — if a component with the same name and kind exists it is updated. " +
2023
+ "Use this to register any VantageOS primitive: skill, agent, hook, plugin, prompt, runbook, or template.",
2024
+ {
2025
+ name: z
2026
+ .string()
2027
+ .describe(
2028
+ "Component name slug — e.g. 'check-messages', 'dev-convex-expert'",
2029
+ ),
2030
+ kind: componentKindSchema,
2031
+ status: componentStatusSchema,
2032
+ ownerTeam: z
2033
+ .string()
2034
+ .optional()
2035
+ .describe("Team that owns this component — e.g. 'core', 'dev'"),
2036
+ description: z
2037
+ .string()
2038
+ .optional()
2039
+ .describe("Short description of what this component does"),
2040
+ vrEntityId: z
2041
+ .string()
2042
+ .optional()
2043
+ .describe("ID of the backing VR entity (skill _id, agent _id, etc.)"),
2044
+ metadata: z
2045
+ .record(z.string(), z.unknown())
2046
+ .optional()
2047
+ .describe("Arbitrary metadata bag — store extra fields here"),
2048
+ tags: z
2049
+ .array(z.string())
2050
+ .optional()
2051
+ .describe("Searchable tags — e.g. ['ai', 'backend']"),
2052
+ },
2053
+ async (args) => {
2054
+ try {
2055
+ const id = await convex.mutation(api.components.registerComponent, args);
2056
+ return {
2057
+ content: [
2058
+ {
2059
+ type: "text",
2060
+ text: JSON.stringify(
2061
+ { id, name: args.name, kind: args.kind },
2062
+ null,
2063
+ 2,
2064
+ ),
2065
+ },
2066
+ ],
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
+ "list_components",
2087
+ "List components in the VantageRegistry catalog. " +
2088
+ "Supports filtering by kind, ownerTeam, status, and tags. " +
2089
+ "fields='lite' (default) returns compact {_id, name, kind, status, ownerTeam}. " +
2090
+ "fields='full' returns the complete document including description, tags, metadata, vrEntityId. " +
2091
+ "limit defaults to 100, max 500.",
2092
+ {
2093
+ kind: componentKindSchema
2094
+ .optional()
2095
+ .describe("Filter by kind — omit to return all kinds"),
2096
+ ownerTeam: z.string().optional().describe("Filter by owning team"),
2097
+ status: componentStatusSchema
2098
+ .optional()
2099
+ .describe("Filter by status — omit to return all statuses"),
2100
+ tags: z
2101
+ .array(z.string())
2102
+ .optional()
2103
+ .describe("Filter by tags — ALL specified tags must match"),
2104
+ fields: z
2105
+ .enum(["lite", "full"])
2106
+ .optional()
2107
+ .describe("Projection: 'lite' (default) or 'full'"),
2108
+ limit: z
2109
+ .number()
2110
+ .int()
2111
+ .optional()
2112
+ .describe("Max results — defaults to 100, max 500"),
2113
+ },
2114
+ async ({ kind, ownerTeam, status, tags, fields, limit }) => {
2115
+ try {
2116
+ const filter =
2117
+ kind !== undefined ||
2118
+ ownerTeam !== undefined ||
2119
+ status !== undefined ||
2120
+ tags !== undefined
2121
+ ? { kind, ownerTeam, status, tags }
2122
+ : undefined;
2123
+ const results = await convex.query(api.components.listComponents, {
2124
+ filter,
2125
+ fields,
2126
+ limit,
2127
+ });
2128
+ return {
2129
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
2130
+ };
2131
+ } catch (err) {
2132
+ return {
2133
+ content: [
2134
+ {
2135
+ type: "text",
2136
+ text: JSON.stringify(
2137
+ { success: false, error: String(err) },
2138
+ null,
2139
+ 2,
2140
+ ),
2141
+ },
2142
+ ],
2143
+ };
2144
+ }
2145
+ },
2146
+ );
2147
+
2148
+ server.tool(
2149
+ "get_component",
2150
+ "Get a single component by its Convex document ID. Returns the full document or null if not found.",
2151
+ {
2152
+ id: z.string().describe("Convex document ID of the component"),
2153
+ },
2154
+ async ({ id }) => {
2155
+ try {
2156
+ const result = await convex.query(api.components.getComponent, {
2157
+ id: id as any,
2158
+ });
2159
+ return {
2160
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
2161
+ };
2162
+ } catch (err) {
2163
+ return {
2164
+ content: [
2165
+ {
2166
+ type: "text",
2167
+ text: JSON.stringify(
2168
+ { success: false, error: String(err) },
2169
+ null,
2170
+ 2,
2171
+ ),
2172
+ },
2173
+ ],
2174
+ };
2175
+ }
2176
+ },
2177
+ );
2178
+
2179
+ server.tool(
2180
+ "search_components",
2181
+ "Search components by name prefix. Returns up to `limit` lite results whose name starts with the query string. " +
2182
+ "Case-sensitive index range scan. Use list_components with filters for kind/status/tags filtering.",
2183
+ {
2184
+ query: z
2185
+ .string()
2186
+ .describe("Name prefix to search for — e.g. 'check', 'dev-'"),
2187
+ limit: z
2188
+ .number()
2189
+ .int()
2190
+ .optional()
2191
+ .describe("Max results — defaults to 50, max 200"),
2192
+ },
2193
+ async ({ query, limit }) => {
2194
+ try {
2195
+ const results = await convex.query(api.components.searchComponents, {
2196
+ query,
2197
+ limit,
2198
+ });
2199
+ return {
2200
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
2201
+ };
2202
+ } catch (err) {
2203
+ return {
2204
+ content: [
2205
+ {
2206
+ type: "text",
2207
+ text: JSON.stringify(
2208
+ { success: false, error: String(err) },
2209
+ null,
2210
+ 2,
2211
+ ),
2212
+ },
2213
+ ],
2214
+ };
2215
+ }
2216
+ },
2217
+ );
2218
+
2219
+ server.tool(
2220
+ "update_component",
2221
+ "Patch individual fields on an existing component. Only provided fields are updated. Throws if not found.",
2222
+ {
2223
+ id: z.string().describe("Convex document ID of the component to update"),
2224
+ name: z.string().optional().describe("New name slug"),
2225
+ kind: componentKindSchema.optional().describe("New kind"),
2226
+ ownerTeam: z.string().optional().describe("New owning team"),
2227
+ description: z.string().optional().describe("New description"),
2228
+ status: componentStatusSchema.optional().describe("New status"),
2229
+ vrEntityId: z.string().optional().describe("Updated vrEntityId reference"),
2230
+ metadata: z
2231
+ .record(z.string(), z.unknown())
2232
+ .optional()
2233
+ .describe("Updated metadata bag"),
2234
+ tags: z.array(z.string()).optional().describe("Updated tags array"),
2235
+ },
2236
+ async (args) => {
2237
+ try {
2238
+ await convex.mutation(api.components.updateComponent, {
2239
+ ...args,
2240
+ id: args.id as any,
2241
+ });
2242
+ return {
2243
+ content: [
2244
+ {
2245
+ type: "text",
2246
+ text: JSON.stringify({ success: true, id: args.id }, null, 2),
2247
+ },
2248
+ ],
2249
+ };
2250
+ } catch (err) {
2251
+ return {
2252
+ content: [
2253
+ {
2254
+ type: "text",
2255
+ text: JSON.stringify(
2256
+ { success: false, error: String(err) },
2257
+ null,
2258
+ 2,
2259
+ ),
2260
+ },
2261
+ ],
2262
+ };
2263
+ }
2264
+ },
2265
+ );
2266
+
2267
+ server.tool(
2268
+ "delete_component",
2269
+ "Soft-delete a component by ID — sets status to 'deprecated'. " +
2270
+ "The document remains in the database and is still retrievable. " +
2271
+ "Returns {deleted: true} if found, {deleted: false} if not found.",
2272
+ {
2273
+ id: z
2274
+ .string()
2275
+ .describe("Convex document ID of the component to soft-delete"),
2276
+ },
2277
+ async ({ id }) => {
2278
+ try {
2279
+ const result = await convex.mutation(api.components.deleteComponent, {
2280
+ id: id as any,
2281
+ });
2282
+ return {
2283
+ content: [
2284
+ {
2285
+ type: "text",
2286
+ text: JSON.stringify({ success: true, ...result }, null, 2),
2287
+ },
2288
+ ],
2289
+ };
2290
+ } catch (err) {
2291
+ return {
2292
+ content: [
2293
+ {
2294
+ type: "text",
2295
+ text: JSON.stringify(
2296
+ { success: false, error: String(err) },
2297
+ null,
2298
+ 2,
2299
+ ),
2300
+ },
2301
+ ],
2302
+ };
2303
+ }
2304
+ },
2305
+ );
2306
+
1419
2307
  // ═══════════════════════════════════════════════════════════════════════════════
1420
2308
  // STATS
1421
2309
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -1427,15 +2315,30 @@ server.tool(
1427
2315
  "Use this to get a quick overview of the registry contents.",
1428
2316
  {},
1429
2317
  async () => {
1430
- const [teams, agents, skills, plugins, hooks, rbDraft, rbPublished, rbDeprecated] = await Promise.all([
2318
+ const [
2319
+ teams,
2320
+ agents,
2321
+ skills,
2322
+ plugins,
2323
+ hooks,
2324
+ rbDraft,
2325
+ rbPublished,
2326
+ rbDeprecated,
2327
+ ] = await Promise.all([
1431
2328
  convex.query(api.teams.list, {}),
1432
2329
  convex.query(api.agents.list, {}),
1433
2330
  convex.query(api.skills.list, {}),
1434
2331
  convex.query(api.plugins.list, {}),
1435
2332
  convex.query(api.hooks.list, {}),
1436
2333
  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 }),
2334
+ convex.query(api.runbooks.listRunbooks, {
2335
+ status: "published",
2336
+ limit: 1000,
2337
+ }),
2338
+ convex.query(api.runbooks.listRunbooks, {
2339
+ status: "deprecated",
2340
+ limit: 1000,
2341
+ }),
1439
2342
  ]);
1440
2343
 
1441
2344
  // Build by_category map across all statuses
@@ -1444,7 +2347,8 @@ server.tool(
1444
2347
  byCategoryMap[rb.category] = (byCategoryMap[rb.category] ?? 0) + 1;
1445
2348
  }
1446
2349
 
1447
- const runbooksTotal = rbDraft.length + rbPublished.length + rbDeprecated.length;
2350
+ const runbooksTotal =
2351
+ rbDraft.length + rbPublished.length + rbDeprecated.length;
1448
2352
 
1449
2353
  const stats = {
1450
2354
  teams: teams.length,
@@ -1461,7 +2365,13 @@ server.tool(
1461
2365
  },
1462
2366
  by_category: byCategoryMap,
1463
2367
  },
1464
- total: teams.length + agents.length + skills.length + plugins.length + hooks.length + runbooksTotal,
2368
+ total:
2369
+ teams.length +
2370
+ agents.length +
2371
+ skills.length +
2372
+ plugins.length +
2373
+ hooks.length +
2374
+ runbooksTotal,
1465
2375
  };
1466
2376
 
1467
2377
  return {