@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.
- package/package.json +5 -3
- package/server.js +1695 -0
- 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-
|
|
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(
|
|
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.
|
|
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
|
|
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: [
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
225
|
-
|
|
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, {
|
|
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
|
|
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, {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
306
|
-
|
|
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, {
|
|
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
|
|
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, {
|
|
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
|
|
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, {
|
|
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
|
|
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, {
|
|
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
|
|
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(
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
|
414
|
-
|
|
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, {
|
|
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
|
|
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, {
|
|
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
|
|
455
|
-
|
|
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(
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
|
477
|
-
|
|
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, {
|
|
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
|
|
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, {
|
|
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
|
|
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(
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
|
540
|
-
|
|
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, {
|
|
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
|
|
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, {
|
|
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
|
|
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
|
|
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, {
|
|
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
|
|
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, {
|
|
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(
|
|
647
|
-
|
|
648
|
-
|
|
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
|
|
664
|
-
|
|
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, {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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
|
|
972
|
+
name: z
|
|
973
|
+
.string()
|
|
974
|
+
.describe("Hook name — e.g. 'check-pii', 'enforce-skill-references'"),
|
|
747
975
|
event: hookEventSchema,
|
|
748
|
-
matcher: z
|
|
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
|
|
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(
|
|
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
|
|
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, {
|
|
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
|
|
806
|
-
|
|
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
|
|
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
|
|
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, {
|
|
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
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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
|
|
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
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
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
|
|
932
|
-
|
|
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
|
|
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
|
|
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: [
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
990
|
-
|
|
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(
|
|
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
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
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(
|
|
1016
|
-
|
|
1017
|
-
|
|
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: [
|
|
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
|
|
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
|
|
1050
|
-
|
|
1051
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1076
|
-
|
|
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
|
|
1081
|
-
|
|
1082
|
-
|
|
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: [
|
|
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: [
|
|
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
|
|
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, {
|
|
1525
|
+
const result = await convex.query(api.runbooks.getRunbook, {
|
|
1526
|
+
name,
|
|
1527
|
+
version,
|
|
1528
|
+
});
|
|
1108
1529
|
return {
|
|
1109
|
-
content: [
|
|
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: [
|
|
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
|
|
1124
|
-
|
|
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
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
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: [
|
|
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: [
|
|
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
|
|
1150
|
-
|
|
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, {
|
|
1627
|
+
const result = await convex.query(api.runbooks.listRunbooksByCategory, {
|
|
1628
|
+
category,
|
|
1629
|
+
status,
|
|
1630
|
+
});
|
|
1155
1631
|
return {
|
|
1156
|
-
content: [
|
|
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: [
|
|
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
|
|
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, {
|
|
1667
|
+
const result = await convex.query(api.runbooks.listRunbooksByTeam, {
|
|
1668
|
+
team,
|
|
1669
|
+
status,
|
|
1670
|
+
});
|
|
1176
1671
|
return {
|
|
1177
|
-
content: [
|
|
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: [
|
|
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, {
|
|
1704
|
+
const result = await convex.mutation(api.runbooks.deleteRunbook, {
|
|
1705
|
+
name,
|
|
1706
|
+
});
|
|
1196
1707
|
return {
|
|
1197
|
-
content: [
|
|
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: [
|
|
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 [
|
|
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, {
|
|
1438
|
-
|
|
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 =
|
|
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:
|
|
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 {
|