@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.
- package/package.json +5 -3
- package/server.js +1732 -0
- 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-
|
|
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"])
|
|
@@ -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.
|
|
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
|
|
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'"),
|
|
@@ -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
|
|
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
|
|
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, {
|
|
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
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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
|
|
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
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
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
|
|
932
|
-
|
|
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
|
|
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
|
|
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: [
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
990
|
-
|
|
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(
|
|
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
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
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(
|
|
1016
|
-
|
|
1017
|
-
|
|
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: [
|
|
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
|
|
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
|
|
1050
|
-
|
|
1051
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1076
|
-
|
|
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
|
|
1081
|
-
|
|
1082
|
-
|
|
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: [
|
|
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: [
|
|
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
|
|
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, {
|
|
1588
|
+
const result = await convex.query(api.runbooks.getRunbook, {
|
|
1589
|
+
name,
|
|
1590
|
+
version,
|
|
1591
|
+
});
|
|
1108
1592
|
return {
|
|
1109
|
-
content: [
|
|
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: [
|
|
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
|
|
1124
|
-
|
|
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
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
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: [
|
|
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: [
|
|
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
|
|
1150
|
-
|
|
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, {
|
|
1690
|
+
const result = await convex.query(api.runbooks.listRunbooksByCategory, {
|
|
1691
|
+
category,
|
|
1692
|
+
status,
|
|
1693
|
+
});
|
|
1155
1694
|
return {
|
|
1156
|
-
content: [
|
|
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: [
|
|
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
|
|
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, {
|
|
1730
|
+
const result = await convex.query(api.runbooks.listRunbooksByTeam, {
|
|
1731
|
+
team,
|
|
1732
|
+
status,
|
|
1733
|
+
});
|
|
1176
1734
|
return {
|
|
1177
|
-
content: [
|
|
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: [
|
|
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, {
|
|
1767
|
+
const result = await convex.mutation(api.runbooks.deleteRunbook, {
|
|
1768
|
+
name,
|
|
1769
|
+
});
|
|
1196
1770
|
return {
|
|
1197
|
-
content: [
|
|
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: [
|
|
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 [
|
|
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, {
|
|
1438
|
-
|
|
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 =
|
|
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:
|
|
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 {
|