@productbrain/mcp 0.0.1-beta.46 → 0.0.1-beta.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-6O4EVIVI.js → chunk-FMEZXUP5.js} +1372 -811
- package/dist/chunk-FMEZXUP5.js.map +1 -0
- package/dist/{chunk-5ZCY6NLZ.js → chunk-FYFF4QKF.js} +388 -119
- package/dist/chunk-FYFF4QKF.js.map +1 -0
- package/dist/http.js +2 -2
- package/dist/index.js +2 -2
- package/dist/{smart-capture-QDF6MKRP.js → smart-capture-XLBFE252.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-5ZCY6NLZ.js.map +0 -1
- package/dist/chunk-6O4EVIVI.js.map +0 -1
- /package/dist/{smart-capture-QDF6MKRP.js.map → smart-capture-XLBFE252.js.map} +0 -0
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
checkEntryQuality,
|
|
5
5
|
closeAgentSession,
|
|
6
6
|
extractPreview,
|
|
7
|
+
failure,
|
|
8
|
+
failureResult,
|
|
7
9
|
formatRubricCoaching,
|
|
8
10
|
formatRubricVerdictSection,
|
|
9
11
|
getAgentSessionId,
|
|
@@ -16,6 +18,8 @@ import {
|
|
|
16
18
|
mcpCall,
|
|
17
19
|
mcpMutation,
|
|
18
20
|
mcpQuery,
|
|
21
|
+
notFoundResult,
|
|
22
|
+
parseOrFail,
|
|
19
23
|
recordSessionActivity,
|
|
20
24
|
registerSmartCaptureTools,
|
|
21
25
|
requireActiveSession,
|
|
@@ -23,9 +27,14 @@ import {
|
|
|
23
27
|
runWithToolContext,
|
|
24
28
|
setSessionOriented,
|
|
25
29
|
startAgentSession,
|
|
30
|
+
success,
|
|
31
|
+
successResult,
|
|
26
32
|
trackWriteTool,
|
|
27
|
-
translateStaleToolNames
|
|
28
|
-
|
|
33
|
+
translateStaleToolNames,
|
|
34
|
+
unknownAction,
|
|
35
|
+
validationResult,
|
|
36
|
+
withEnvelope
|
|
37
|
+
} from "./chunk-FYFF4QKF.js";
|
|
29
38
|
import {
|
|
30
39
|
trackQualityCheck,
|
|
31
40
|
trackQualityVerdict
|
|
@@ -94,7 +103,7 @@ function registerKnowledgeTools(server) {
|
|
|
94
103
|
inputSchema: updateEntrySchema,
|
|
95
104
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
96
105
|
},
|
|
97
|
-
async ({ entryId, name, status: rawStatus, workflowStatus: rawWorkflowStatus, data, order, canonicalKey, autoPublish, changeNote }) => {
|
|
106
|
+
withEnvelope(async ({ entryId, name, status: rawStatus, workflowStatus: rawWorkflowStatus, data, order, canonicalKey, autoPublish, changeNote }) => {
|
|
98
107
|
requireWriteAccess();
|
|
99
108
|
const PROMOTED_FIELDS = ["status", "workflowStatus", "name", "order", "canonicalKey"];
|
|
100
109
|
const topLevelByField = { status: rawStatus, workflowStatus: rawWorkflowStatus, name, order, canonicalKey };
|
|
@@ -164,11 +173,16 @@ function registerKnowledgeTools(server) {
|
|
|
164
173
|
responseLines.push(`\u26A0\uFE0F \`data.${field}\` detected \u2014 did you mean the top-level \`${field}\` parameter?`);
|
|
165
174
|
}
|
|
166
175
|
}
|
|
176
|
+
const summary = `Updated ${entryId} \u2014 ${versionMode}. Fields: ${fieldsProvided.join(", ") || "none"}.`;
|
|
177
|
+
const next = [
|
|
178
|
+
{ tool: "entries", description: "View updated entry", parameters: { action: "get", entryId } },
|
|
179
|
+
{ tool: "commit-entry", description: "Commit to Chain", parameters: { entryId } }
|
|
180
|
+
];
|
|
167
181
|
return {
|
|
168
182
|
content: [{ type: "text", text: responseLines.join("\n") }],
|
|
169
|
-
structuredContent: { entryId: id, versionMode, fieldsProvided, confusedFields }
|
|
183
|
+
structuredContent: success(summary, { entryId: id, versionMode, fieldsProvided, confusedFields }, next)
|
|
170
184
|
};
|
|
171
|
-
}
|
|
185
|
+
})
|
|
172
186
|
);
|
|
173
187
|
trackWriteTool(updateTool);
|
|
174
188
|
server.registerTool(
|
|
@@ -179,10 +193,14 @@ function registerKnowledgeTools(server) {
|
|
|
179
193
|
inputSchema: getHistorySchema,
|
|
180
194
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
181
195
|
},
|
|
182
|
-
async ({ entryId }) => {
|
|
196
|
+
withEnvelope(async ({ entryId }) => {
|
|
183
197
|
const history = await mcpQuery("chain.listEntryHistory", { entryId });
|
|
184
198
|
if (history.length === 0) {
|
|
185
|
-
return
|
|
199
|
+
return successResult(
|
|
200
|
+
`No history found for ${entryId}.`,
|
|
201
|
+
`No history events recorded for ${entryId}.`,
|
|
202
|
+
{ entryId, eventCount: 0, events: [] }
|
|
203
|
+
);
|
|
186
204
|
}
|
|
187
205
|
const formatted = history.map((h) => {
|
|
188
206
|
const date = new Date(h.timestamp).toISOString();
|
|
@@ -192,9 +210,13 @@ function registerKnowledgeTools(server) {
|
|
|
192
210
|
return {
|
|
193
211
|
content: [{ type: "text", text: `# History for \`${entryId}\` (${history.length} events)
|
|
194
212
|
|
|
195
|
-
${formatted}` }]
|
|
213
|
+
${formatted}` }],
|
|
214
|
+
structuredContent: success(
|
|
215
|
+
`Found ${history.length} history events for ${entryId}.`,
|
|
216
|
+
{ entryId, eventCount: history.length, events: history }
|
|
217
|
+
)
|
|
196
218
|
};
|
|
197
|
-
}
|
|
219
|
+
})
|
|
198
220
|
);
|
|
199
221
|
const commitTool = server.registerTool(
|
|
200
222
|
"commit-entry",
|
|
@@ -204,14 +226,12 @@ ${formatted}` }]
|
|
|
204
226
|
inputSchema: commitEntrySchema,
|
|
205
227
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
206
228
|
},
|
|
207
|
-
async ({ entryId }) => {
|
|
229
|
+
withEnvelope(async ({ entryId }) => {
|
|
208
230
|
requireWriteAccess();
|
|
209
|
-
const { runContradictionCheck } = await import("./smart-capture-
|
|
231
|
+
const { runContradictionCheck } = await import("./smart-capture-XLBFE252.js");
|
|
210
232
|
const entry = await mcpQuery("chain.getEntry", { entryId });
|
|
211
233
|
if (!entry) {
|
|
212
|
-
return {
|
|
213
|
-
content: [{ type: "text", text: `Entry \`${entryId}\` not found. Try search to find the right ID.` }]
|
|
214
|
-
};
|
|
234
|
+
return notFoundResult(entryId, `Entry '${entryId}' not found. Try search to find the right ID.`);
|
|
215
235
|
}
|
|
216
236
|
const descField = entry.data?.description ?? entry.data?.canonical ?? entry.data?.rationale ?? "";
|
|
217
237
|
const warnings = await runContradictionCheck(entry.name, descField);
|
|
@@ -234,8 +254,9 @@ ${formatted}` }]
|
|
|
234
254
|
const docId = result?._id ?? entry._id;
|
|
235
255
|
await recordSessionActivity({ entryModified: docId });
|
|
236
256
|
const wsCtx = await getWorkspaceContext();
|
|
257
|
+
const isProposal = result?.status === "proposal_created";
|
|
237
258
|
let lines;
|
|
238
|
-
if (
|
|
259
|
+
if (isProposal) {
|
|
239
260
|
lines = [
|
|
240
261
|
`# Proposal created: ${result.entryId ?? entryId}`,
|
|
241
262
|
`**${result.name ?? entry.name}** \u2014 commit requires consent. A proposal was created instead of committing directly.`,
|
|
@@ -287,11 +308,29 @@ ${formatted}` }]
|
|
|
287
308
|
}
|
|
288
309
|
}
|
|
289
310
|
}
|
|
311
|
+
const next = isProposal ? [
|
|
312
|
+
{ tool: "entries", description: "View entry", parameters: { action: "get", entryId } }
|
|
313
|
+
] : [
|
|
314
|
+
{ tool: "graph", description: "Discover connections", parameters: { action: "suggest", entryId } },
|
|
315
|
+
{ tool: "entries", description: "View committed entry", parameters: { action: "get", entryId } }
|
|
316
|
+
];
|
|
317
|
+
const summary = isProposal ? `Proposal created for ${entryId} (${entry.name}). Awaiting consent.` : `Committed ${entryId} (${entry.name}) to the Chain.`;
|
|
290
318
|
return {
|
|
291
319
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
292
|
-
structuredContent:
|
|
320
|
+
structuredContent: success(
|
|
321
|
+
summary,
|
|
322
|
+
{
|
|
323
|
+
entryId,
|
|
324
|
+
name: entry.name,
|
|
325
|
+
outcome: isProposal ? "proposal_created" : "committed",
|
|
326
|
+
qualityVerdict: coachingResult?.verdict ?? void 0,
|
|
327
|
+
source: coachingResult?.source ?? void 0,
|
|
328
|
+
contradictions: warnings.length
|
|
329
|
+
},
|
|
330
|
+
next
|
|
331
|
+
)
|
|
293
332
|
};
|
|
294
|
-
}
|
|
333
|
+
})
|
|
295
334
|
);
|
|
296
335
|
trackWriteTool(commitTool);
|
|
297
336
|
}
|
|
@@ -365,49 +404,41 @@ function registerEntriesTools(server) {
|
|
|
365
404
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
366
405
|
_meta: { ui: { resourceUri: "ui://entries/entry-cards.html" } }
|
|
367
406
|
},
|
|
368
|
-
async (args) => {
|
|
369
|
-
const parsed = entriesSchema
|
|
370
|
-
if (!parsed.
|
|
371
|
-
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
372
|
-
return { content: [{ type: "text", text: `Invalid arguments: ${issues}` }] };
|
|
373
|
-
}
|
|
407
|
+
withEnvelope(async (args) => {
|
|
408
|
+
const parsed = parseOrFail(entriesSchema, args);
|
|
409
|
+
if (!parsed.ok) return parsed.result;
|
|
374
410
|
const { action, entryId, entryIds, collection, status, tag, label, query } = parsed.data;
|
|
375
411
|
return runWithToolContext({ tool: "entries", action }, async () => {
|
|
376
412
|
if (action === "get") {
|
|
377
413
|
if (!entryId) {
|
|
378
|
-
return
|
|
414
|
+
return validationResult("entryId is required when action is 'get'.");
|
|
379
415
|
}
|
|
380
416
|
return handleGet(entryId);
|
|
381
417
|
}
|
|
382
418
|
if (action === "batch") {
|
|
383
419
|
if (!entryIds || entryIds.length === 0) {
|
|
384
|
-
return
|
|
420
|
+
return validationResult("entryIds is required when action is 'batch'.");
|
|
385
421
|
}
|
|
386
422
|
return handleBatch(entryIds);
|
|
387
423
|
}
|
|
388
424
|
if (action === "search") {
|
|
389
425
|
if (!query || query.length < 2) {
|
|
390
|
-
return
|
|
426
|
+
return validationResult("query is required for search (min 2 characters).");
|
|
391
427
|
}
|
|
392
428
|
return handleSearch(server, query, collection, status);
|
|
393
429
|
}
|
|
394
430
|
if (action === "list") {
|
|
395
431
|
return handleList(collection, status, tag, label);
|
|
396
432
|
}
|
|
397
|
-
return
|
|
398
|
-
content: [{
|
|
399
|
-
type: "text",
|
|
400
|
-
text: `Unknown action '${action}'. Valid actions: ${ENTRIES_ACTIONS.join(", ")}.`
|
|
401
|
-
}]
|
|
402
|
-
};
|
|
433
|
+
return unknownAction(action, ENTRIES_ACTIONS);
|
|
403
434
|
});
|
|
404
|
-
}
|
|
435
|
+
})
|
|
405
436
|
);
|
|
406
437
|
}
|
|
407
438
|
async function handleGet(entryId) {
|
|
408
439
|
const entry = await mcpQuery("chain.getEntry", { entryId });
|
|
409
440
|
if (!entry) {
|
|
410
|
-
return
|
|
441
|
+
return notFoundResult(entryId, `Entry '${entryId}' not found. Try search to find the right ID.`);
|
|
411
442
|
}
|
|
412
443
|
const e = entry;
|
|
413
444
|
const lines = [
|
|
@@ -447,7 +478,7 @@ async function handleGet(entryId) {
|
|
|
447
478
|
lines.push(`- ${date}: ${h.event}${h.changedBy ? ` _(${h.changedBy})_` : ""}`);
|
|
448
479
|
}
|
|
449
480
|
}
|
|
450
|
-
const
|
|
481
|
+
const entryData = {
|
|
451
482
|
entryId: String(e.entryId ?? ""),
|
|
452
483
|
name: String(e.name ?? ""),
|
|
453
484
|
collection: String(e.canonicalKey ?? ""),
|
|
@@ -467,7 +498,14 @@ async function handleGet(entryId) {
|
|
|
467
498
|
};
|
|
468
499
|
return {
|
|
469
500
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
470
|
-
structuredContent
|
|
501
|
+
structuredContent: success(
|
|
502
|
+
`Found ${entryId}: ${String(e.name)} [${String(e.canonicalKey ?? "")}].`,
|
|
503
|
+
entryData,
|
|
504
|
+
[
|
|
505
|
+
{ tool: "graph", description: "Explore connections", parameters: { action: "suggest", entryId } },
|
|
506
|
+
{ tool: "context", description: "Gather related context", parameters: { action: "gather", entryId } }
|
|
507
|
+
]
|
|
508
|
+
)
|
|
471
509
|
};
|
|
472
510
|
}
|
|
473
511
|
async function handleBatch(entryIds) {
|
|
@@ -530,9 +568,10 @@ async function handleBatch(entryIds) {
|
|
|
530
568
|
...entry.data && typeof entry.data === "object" ? { data: entry.data } : {}
|
|
531
569
|
};
|
|
532
570
|
});
|
|
571
|
+
const total = structuredEntries.length;
|
|
533
572
|
return {
|
|
534
573
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
535
|
-
structuredContent: { entries: structuredEntries, total
|
|
574
|
+
structuredContent: success(`Fetched ${total} entries in batch.`, { entries: structuredEntries, total })
|
|
536
575
|
};
|
|
537
576
|
}
|
|
538
577
|
async function handleList(collection, status, tag, label) {
|
|
@@ -550,7 +589,14 @@ async function handleList(collection, status, tag, label) {
|
|
|
550
589
|
});
|
|
551
590
|
}
|
|
552
591
|
if (entries.length === 0) {
|
|
553
|
-
return {
|
|
592
|
+
return {
|
|
593
|
+
content: [{ type: "text", text: "No entries match the given filters." }],
|
|
594
|
+
structuredContent: success(
|
|
595
|
+
"No entries match the given filters.",
|
|
596
|
+
{ entries: [], total: 0 },
|
|
597
|
+
[{ tool: "collections", description: "List collections", parameters: { action: "list" } }]
|
|
598
|
+
)
|
|
599
|
+
};
|
|
554
600
|
}
|
|
555
601
|
const formatted = entries.map((e) => {
|
|
556
602
|
const id = e.entryId ? `**${e.entryId}:** ` : "";
|
|
@@ -567,13 +613,14 @@ ${dataPreview}` : ""}`;
|
|
|
567
613
|
status: String(e.status ?? ""),
|
|
568
614
|
...e.workflowStatus ? { workflowStatus: e.workflowStatus } : {}
|
|
569
615
|
}));
|
|
616
|
+
const total = structuredEntries.length;
|
|
570
617
|
return {
|
|
571
618
|
content: [{ type: "text", text: `## List Result
|
|
572
619
|
|
|
573
620
|
# Entries${scope} (${entries.length})
|
|
574
621
|
|
|
575
622
|
${formatted}` }],
|
|
576
|
-
structuredContent: { entries: structuredEntries, total
|
|
623
|
+
structuredContent: success(`Found ${total} entries${scope}.`, { entries: structuredEntries, total })
|
|
577
624
|
};
|
|
578
625
|
}
|
|
579
626
|
async function handleSearch(server, query, collection, status) {
|
|
@@ -592,7 +639,12 @@ async function handleSearch(server, query, collection, status) {
|
|
|
592
639
|
content: [{
|
|
593
640
|
type: "text",
|
|
594
641
|
text: `No results for "${query}"${scope}. Try a broader search or use \`collections action=list\` to see available data.`
|
|
595
|
-
}]
|
|
642
|
+
}],
|
|
643
|
+
structuredContent: success(
|
|
644
|
+
`No results for '${query}'${scope}.`,
|
|
645
|
+
{ entries: [], total: 0, query },
|
|
646
|
+
[{ tool: "collections", description: "List collections", parameters: { action: "list" } }]
|
|
647
|
+
)
|
|
596
648
|
};
|
|
597
649
|
}
|
|
598
650
|
const collMap = /* @__PURE__ */ new Map();
|
|
@@ -637,18 +689,19 @@ async function handleSearch(server, query, collection, status) {
|
|
|
637
689
|
status: e.status,
|
|
638
690
|
collectionName: e.collection
|
|
639
691
|
}));
|
|
692
|
+
const total = structuredResults.length;
|
|
640
693
|
return {
|
|
641
694
|
content: [{ type: "text", text: `${header}
|
|
642
695
|
|
|
643
696
|
${formatted}
|
|
644
697
|
|
|
645
698
|
${footer}` }],
|
|
646
|
-
structuredContent: {
|
|
699
|
+
structuredContent: success(`Found ${total} entries matching '${query}'.`, {
|
|
647
700
|
results: structuredResults,
|
|
648
701
|
entries: entriesForView,
|
|
649
|
-
total
|
|
702
|
+
total,
|
|
650
703
|
query
|
|
651
|
-
}
|
|
704
|
+
})
|
|
652
705
|
};
|
|
653
706
|
}
|
|
654
707
|
|
|
@@ -696,12 +749,9 @@ function registerGraphTools(server) {
|
|
|
696
749
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
697
750
|
_meta: { ui: { resourceUri: "ui://graph/constellation.html" } }
|
|
698
751
|
},
|
|
699
|
-
async (args) => {
|
|
700
|
-
const parsed = graphSchema
|
|
701
|
-
if (!parsed.
|
|
702
|
-
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
703
|
-
return { content: [{ type: "text", text: `Invalid arguments: ${issues}` }] };
|
|
704
|
-
}
|
|
752
|
+
withEnvelope(async (args) => {
|
|
753
|
+
const parsed = parseOrFail(graphSchema, args);
|
|
754
|
+
if (!parsed.ok) return parsed.result;
|
|
705
755
|
const { action, entryId, direction, limit, depth } = parsed.data;
|
|
706
756
|
return runWithToolContext({ tool: "graph", action }, async () => {
|
|
707
757
|
if (action === "find") {
|
|
@@ -710,24 +760,29 @@ function registerGraphTools(server) {
|
|
|
710
760
|
if (action === "suggest") {
|
|
711
761
|
return handleSuggest(entryId, limit ?? 10, depth ?? 2);
|
|
712
762
|
}
|
|
713
|
-
return
|
|
714
|
-
content: [{
|
|
715
|
-
type: "text",
|
|
716
|
-
text: `Unknown action '${action}'. Valid actions: ${GRAPH_ACTIONS.join(", ")}.`
|
|
717
|
-
}]
|
|
718
|
-
};
|
|
763
|
+
return unknownAction(action, GRAPH_ACTIONS);
|
|
719
764
|
});
|
|
720
|
-
}
|
|
765
|
+
})
|
|
721
766
|
);
|
|
722
767
|
}
|
|
723
768
|
async function handleFind(entryId, direction) {
|
|
724
769
|
const relations = await mcpQuery("chain.listEntryRelations", { entryId });
|
|
725
770
|
if (relations.length === 0) {
|
|
726
|
-
return {
|
|
771
|
+
return {
|
|
772
|
+
content: [{ type: "text", text: `No relations found for \`${entryId}\`. Use relations action=create to create connections.` }],
|
|
773
|
+
structuredContent: success(
|
|
774
|
+
`No relations found for '${entryId}'.`,
|
|
775
|
+
{ entryId, relations: [], nodes: [], edges: [], total: 0 },
|
|
776
|
+
[
|
|
777
|
+
{ tool: "relations", description: "Create a relation", parameters: { action: "create", from: entryId } },
|
|
778
|
+
{ tool: "graph", description: "Get suggestions", parameters: { action: "suggest", entryId } }
|
|
779
|
+
]
|
|
780
|
+
)
|
|
781
|
+
};
|
|
727
782
|
}
|
|
728
783
|
const sourceEntry = await mcpQuery("chain.getEntry", { entryId });
|
|
729
784
|
if (!sourceEntry) {
|
|
730
|
-
return
|
|
785
|
+
return notFoundResult(entryId, `Entry '${entryId}' not found. Try entries action=search to find the right ID.`);
|
|
731
786
|
}
|
|
732
787
|
const sourceInternalId = sourceEntry._id;
|
|
733
788
|
const MAX_RELATIONS = 25;
|
|
@@ -801,13 +856,11 @@ async function handleFind(entryId, direction) {
|
|
|
801
856
|
}));
|
|
802
857
|
return {
|
|
803
858
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
804
|
-
structuredContent:
|
|
805
|
-
entryId
|
|
806
|
-
relations: structuredRelations,
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
total: relations.length
|
|
810
|
-
}
|
|
859
|
+
structuredContent: success(
|
|
860
|
+
`Found ${relations.length} relations for ${entryId}.`,
|
|
861
|
+
{ entryId, relations: structuredRelations, nodes, edges, total: relations.length },
|
|
862
|
+
[{ tool: "entries", description: "View entry details", parameters: { action: "get", entryId } }]
|
|
863
|
+
)
|
|
811
864
|
};
|
|
812
865
|
}
|
|
813
866
|
async function handleSuggest(entryId, limit, depth) {
|
|
@@ -817,7 +870,13 @@ async function handleSuggest(entryId, limit, depth) {
|
|
|
817
870
|
limit
|
|
818
871
|
});
|
|
819
872
|
if (!result?.suggestions || result.suggestions.length === 0) {
|
|
820
|
-
return
|
|
873
|
+
return failureResult(
|
|
874
|
+
`No suggestions found for '${entryId}' \u2014 it may already be well-connected, or no similar entries exist.`,
|
|
875
|
+
"NOT_FOUND",
|
|
876
|
+
`No suggestions found for '${entryId}'.`,
|
|
877
|
+
"Entry may already be well-connected.",
|
|
878
|
+
[{ tool: "graph", description: "Check existing relations", parameters: { action: "find", entryId } }]
|
|
879
|
+
);
|
|
821
880
|
}
|
|
822
881
|
const resolved = result.resolvedEntry;
|
|
823
882
|
const resolvedLabel = resolved ? `\`${resolved.entryId ?? resolved.name}\` (${resolved.name})` : `\`${entryId}\``;
|
|
@@ -885,13 +944,11 @@ async function handleSuggest(entryId, limit, depth) {
|
|
|
885
944
|
}));
|
|
886
945
|
return {
|
|
887
946
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
888
|
-
structuredContent:
|
|
889
|
-
|
|
890
|
-
suggestions: structuredSuggestions,
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
total: suggestions.length
|
|
894
|
-
}
|
|
947
|
+
structuredContent: success(
|
|
948
|
+
`Found ${suggestions.length} link suggestions for ${resolvedLabel}.`,
|
|
949
|
+
{ entryId: sourceId, suggestions: structuredSuggestions, nodes, edges, total: suggestions.length },
|
|
950
|
+
[{ tool: "relations", description: "Create links", parameters: { action: "batch-create" } }]
|
|
951
|
+
)
|
|
895
952
|
};
|
|
896
953
|
}
|
|
897
954
|
|
|
@@ -921,40 +978,32 @@ function registerRelationsTools(server) {
|
|
|
921
978
|
inputSchema: relationsSchema,
|
|
922
979
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
923
980
|
},
|
|
924
|
-
async (args) => {
|
|
925
|
-
const parsed = relationsSchema
|
|
926
|
-
if (!parsed.
|
|
927
|
-
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
928
|
-
return { content: [{ type: "text", text: `Invalid arguments: ${issues}` }] };
|
|
929
|
-
}
|
|
981
|
+
withEnvelope(async (args) => {
|
|
982
|
+
const parsed = parseOrFail(relationsSchema, args);
|
|
983
|
+
if (!parsed.ok) return parsed.result;
|
|
930
984
|
const { action, from, to, type, score, relations } = parsed.data;
|
|
931
985
|
return runWithToolContext({ tool: "relations", action }, async () => {
|
|
932
986
|
if (action === "create") {
|
|
933
987
|
if (!from || !to || !type) {
|
|
934
|
-
return
|
|
988
|
+
return validationResult("from, to, and type are required when action is 'create'.");
|
|
935
989
|
}
|
|
936
990
|
return handleCreate(from, to, type, score);
|
|
937
991
|
}
|
|
938
992
|
if (action === "batch-create") {
|
|
939
993
|
if (!relations || relations.length === 0) {
|
|
940
|
-
return
|
|
994
|
+
return validationResult("relations is required when action is 'batch-create'.");
|
|
941
995
|
}
|
|
942
996
|
return handleBatchCreate(relations);
|
|
943
997
|
}
|
|
944
998
|
if (action === "dismiss") {
|
|
945
999
|
if (!from || !to) {
|
|
946
|
-
return
|
|
1000
|
+
return validationResult("from and to are required when action is 'dismiss'.");
|
|
947
1001
|
}
|
|
948
1002
|
return handleDismiss(from, to, type ?? "related_to", score);
|
|
949
1003
|
}
|
|
950
|
-
return
|
|
951
|
-
content: [{
|
|
952
|
-
type: "text",
|
|
953
|
-
text: `Unknown action '${action}'. Valid actions: ${RELATIONS_ACTIONS.join(", ")}.`
|
|
954
|
-
}]
|
|
955
|
-
};
|
|
1004
|
+
return unknownAction(action, RELATIONS_ACTIONS);
|
|
956
1005
|
});
|
|
957
|
-
}
|
|
1006
|
+
})
|
|
958
1007
|
);
|
|
959
1008
|
trackWriteTool(tool);
|
|
960
1009
|
}
|
|
@@ -983,14 +1032,24 @@ ${result.message ?? "Governs relation requires async consent. A proposal was cre
|
|
|
983
1032
|
**Workspace:** ${wsCtx.workspaceSlug} (${wsCtx.workspaceId})
|
|
984
1033
|
|
|
985
1034
|
The relation was **not applied** \u2014 it will be created when the proposal is approved.`
|
|
986
|
-
}]
|
|
1035
|
+
}],
|
|
1036
|
+
structuredContent: success(
|
|
1037
|
+
`Proposal created for ${from} \u2192 ${to} (${type}). Awaiting consent.`,
|
|
1038
|
+
{ status: "proposal_created", from, to, type, proposalId: result.proposalId },
|
|
1039
|
+
[{ tool: "entries", description: "Check proposal", parameters: { action: "get", entryId: result.proposalId } }]
|
|
1040
|
+
)
|
|
987
1041
|
};
|
|
988
1042
|
}
|
|
989
1043
|
return {
|
|
990
1044
|
content: [{ type: "text", text: `# Relation Created
|
|
991
1045
|
|
|
992
1046
|
**${from}** \u2014[${type}]\u2192 **${to}**
|
|
993
|
-
**Workspace:** ${wsCtx.workspaceSlug} (${wsCtx.workspaceId})` }]
|
|
1047
|
+
**Workspace:** ${wsCtx.workspaceSlug} (${wsCtx.workspaceId})` }],
|
|
1048
|
+
structuredContent: success(
|
|
1049
|
+
`Created relation ${from} \u2192 ${to} (${type}).`,
|
|
1050
|
+
{ status: "created", from, to, type },
|
|
1051
|
+
[{ tool: "graph", description: "See connections", parameters: { action: "find", entryId: from } }]
|
|
1052
|
+
)
|
|
994
1053
|
};
|
|
995
1054
|
}
|
|
996
1055
|
async function handleBatchCreate(relations) {
|
|
@@ -1043,17 +1102,25 @@ async function handleBatchCreate(relations) {
|
|
|
1043
1102
|
lines.push(`- **${r.from}** \u2192 **${r.to}** (${r.type}): _${r.error}_`);
|
|
1044
1103
|
}
|
|
1045
1104
|
}
|
|
1046
|
-
|
|
1105
|
+
const next = failed.length === 0 ? [{ tool: "graph", description: "Check connections", parameters: { action: "find", entryId: relations[0].from } }] : [];
|
|
1106
|
+
return {
|
|
1107
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
1108
|
+
structuredContent: success(
|
|
1109
|
+
`Batch: ${created.length} created, ${proposals.length} proposals, ${failed.length} failed of ${relations.length}.`,
|
|
1110
|
+
{ created: created.length, proposals: proposals.length, failed: failed.length, total: relations.length, results },
|
|
1111
|
+
next.length ? next : void 0
|
|
1112
|
+
)
|
|
1113
|
+
};
|
|
1047
1114
|
}
|
|
1048
1115
|
async function handleDismiss(from, to, type, score) {
|
|
1049
1116
|
requireWriteAccess();
|
|
1050
1117
|
const fromEntry = await mcpQuery("chain.getEntry", { entryId: from });
|
|
1051
1118
|
if (!fromEntry) {
|
|
1052
|
-
return
|
|
1119
|
+
return notFoundResult(from);
|
|
1053
1120
|
}
|
|
1054
1121
|
const toEntry = await mcpQuery("chain.getEntry", { entryId: to });
|
|
1055
1122
|
if (!toEntry) {
|
|
1056
|
-
return
|
|
1123
|
+
return notFoundResult(to);
|
|
1057
1124
|
}
|
|
1058
1125
|
await mcpMutation("chain.dismissSuggestion", {
|
|
1059
1126
|
fromEntryId: fromEntry._id,
|
|
@@ -1061,12 +1128,11 @@ async function handleDismiss(from, to, type, score) {
|
|
|
1061
1128
|
recommendedType: type,
|
|
1062
1129
|
score: score ?? 0
|
|
1063
1130
|
});
|
|
1064
|
-
return
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
};
|
|
1131
|
+
return successResult(
|
|
1132
|
+
`Dismissed suggestion: ${from} \u2192 ${to} (${type}). Feedback recorded for scoring improvements.`,
|
|
1133
|
+
`Dismissed suggestion ${from} \u2192 ${to} (${type}). Feedback recorded.`,
|
|
1134
|
+
{ from, to, type }
|
|
1135
|
+
);
|
|
1070
1136
|
}
|
|
1071
1137
|
|
|
1072
1138
|
// src/tools/context.ts
|
|
@@ -1099,12 +1165,9 @@ function registerContextTools(server) {
|
|
|
1099
1165
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
1100
1166
|
_meta: { ui: { resourceUri: "ui://graph/constellation.html" } }
|
|
1101
1167
|
},
|
|
1102
|
-
async (args) => {
|
|
1103
|
-
const parsed = contextSchema
|
|
1104
|
-
if (!parsed.
|
|
1105
|
-
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
1106
|
-
return { content: [{ type: "text", text: `Invalid arguments: ${issues}` }] };
|
|
1107
|
-
}
|
|
1168
|
+
withEnvelope(async (args) => {
|
|
1169
|
+
const parsed = parseOrFail(contextSchema, args);
|
|
1170
|
+
if (!parsed.ok) return parsed.result;
|
|
1108
1171
|
const { action, entryId, mapEntryId, task, mode, maxHops, maxResults } = parsed.data;
|
|
1109
1172
|
return runWithToolContext({ tool: "context", action }, async () => {
|
|
1110
1173
|
if (action === "gather") {
|
|
@@ -1115,23 +1178,18 @@ function registerContextTools(server) {
|
|
|
1115
1178
|
}
|
|
1116
1179
|
if (action === "build") {
|
|
1117
1180
|
if (!entryId) {
|
|
1118
|
-
return
|
|
1181
|
+
return validationResult("entryId is required when action is 'build'.");
|
|
1119
1182
|
}
|
|
1120
1183
|
return handleBuild(server, entryId, maxHops ?? 2);
|
|
1121
1184
|
}
|
|
1122
|
-
return
|
|
1123
|
-
content: [{
|
|
1124
|
-
type: "text",
|
|
1125
|
-
text: `Unknown action '${action}'. Valid actions: ${CONTEXT_ACTIONS.join(", ")}.`
|
|
1126
|
-
}]
|
|
1127
|
-
};
|
|
1185
|
+
return unknownAction(action, CONTEXT_ACTIONS);
|
|
1128
1186
|
});
|
|
1129
|
-
}
|
|
1187
|
+
})
|
|
1130
1188
|
);
|
|
1131
1189
|
}
|
|
1132
1190
|
async function handleGather(server, entryId, task, mode, maxHops, maxResults) {
|
|
1133
1191
|
if (!entryId && !task) {
|
|
1134
|
-
return
|
|
1192
|
+
return validationResult("Provide either entryId (graph traversal) or task (auto-load context).");
|
|
1135
1193
|
}
|
|
1136
1194
|
if (entryId && mode === "graph") {
|
|
1137
1195
|
await server.sendLoggingMessage({
|
|
@@ -1144,7 +1202,7 @@ async function handleGather(server, entryId, task, mode, maxHops, maxResults) {
|
|
|
1144
1202
|
maxHops
|
|
1145
1203
|
});
|
|
1146
1204
|
if (!result2?.root) {
|
|
1147
|
-
return
|
|
1205
|
+
return notFoundResult(entryId, `Entry '${entryId}' not found. Try search to find the right ID.`);
|
|
1148
1206
|
}
|
|
1149
1207
|
if (result2.context.length === 0) {
|
|
1150
1208
|
return {
|
|
@@ -1155,7 +1213,12 @@ async function handleGather(server, entryId, task, mode, maxHops, maxResults) {
|
|
|
1155
1213
|
_No relations found._ This entry is not yet connected to the knowledge graph.
|
|
1156
1214
|
|
|
1157
1215
|
Use \`graph action=suggest\` to discover potential connections.`
|
|
1158
|
-
}]
|
|
1216
|
+
}],
|
|
1217
|
+
structuredContent: success(
|
|
1218
|
+
`No relations found for ${result2.root.entryId}. Entry not yet connected.`,
|
|
1219
|
+
{ root: result2.root, context: [], totalFound: 0 },
|
|
1220
|
+
[{ tool: "graph", description: "Discover connections", parameters: { action: "suggest", entryId: result2.root.entryId } }]
|
|
1221
|
+
)
|
|
1159
1222
|
};
|
|
1160
1223
|
}
|
|
1161
1224
|
const byCollection2 = /* @__PURE__ */ new Map();
|
|
@@ -1201,7 +1264,10 @@ Use \`graph action=suggest\` to discover potential connections.`
|
|
|
1201
1264
|
}));
|
|
1202
1265
|
return {
|
|
1203
1266
|
content: [{ type: "text", text: lines2.join("\n") }],
|
|
1204
|
-
structuredContent:
|
|
1267
|
+
structuredContent: success(
|
|
1268
|
+
`Gathered ${result2.totalFound} related entries for ${result2.root.entryId} (${result2.hopsTraversed} hops, graph mode).`,
|
|
1269
|
+
{ nodes: nodes2, edges: edges2, root: result2.root, totalFound: result2.totalFound }
|
|
1270
|
+
)
|
|
1205
1271
|
};
|
|
1206
1272
|
}
|
|
1207
1273
|
if (task && !entryId) {
|
|
@@ -1226,7 +1292,13 @@ Use \`graph action=suggest\` to discover potential connections.`
|
|
|
1226
1292
|
No context found for this task. The chain may not cover this area yet.
|
|
1227
1293
|
|
|
1228
1294
|
_Consider capturing domain knowledge discovered during this task via \`capture\`._`
|
|
1229
|
-
}]
|
|
1295
|
+
}],
|
|
1296
|
+
structuredContent: failure(
|
|
1297
|
+
"NOT_FOUND",
|
|
1298
|
+
"No context found for this task.",
|
|
1299
|
+
"The chain may not cover this area. Capture domain knowledge via capture.",
|
|
1300
|
+
[{ tool: "capture", description: "Capture knowledge", parameters: {} }]
|
|
1301
|
+
)
|
|
1230
1302
|
};
|
|
1231
1303
|
}
|
|
1232
1304
|
const byCollection2 = /* @__PURE__ */ new Map();
|
|
@@ -1262,7 +1334,10 @@ _Consider capturing domain knowledge discovered during this task via \`capture\`
|
|
|
1262
1334
|
}));
|
|
1263
1335
|
return {
|
|
1264
1336
|
content: [{ type: "text", text: lines2.join("\n") }],
|
|
1265
|
-
structuredContent:
|
|
1337
|
+
structuredContent: success(
|
|
1338
|
+
`Loaded ${result2.totalFound} entries for task (${result2.confidence} confidence).`,
|
|
1339
|
+
{ entries: taskEntries, totalFound: result2.totalFound, confidence: result2.confidence }
|
|
1340
|
+
)
|
|
1266
1341
|
};
|
|
1267
1342
|
}
|
|
1268
1343
|
await server.sendLoggingMessage({
|
|
@@ -1272,7 +1347,7 @@ _Consider capturing domain knowledge discovered during this task via \`capture\`
|
|
|
1272
1347
|
});
|
|
1273
1348
|
const result = await mcpQuery("chain.gatherContext", { entryId, maxHops });
|
|
1274
1349
|
if (!result?.root) {
|
|
1275
|
-
return
|
|
1350
|
+
return notFoundResult(entryId, `Entry '${entryId}' not found. Try search to find the right ID.`);
|
|
1276
1351
|
}
|
|
1277
1352
|
if (result.related.length === 0) {
|
|
1278
1353
|
return {
|
|
@@ -1283,7 +1358,12 @@ _Consider capturing domain knowledge discovered during this task via \`capture\`
|
|
|
1283
1358
|
_No relations found._ This entry is not yet connected to the knowledge graph.
|
|
1284
1359
|
|
|
1285
1360
|
Use \`graph action=suggest\` to discover potential connections, or \`relations action=create\` to link manually.`
|
|
1286
|
-
}]
|
|
1361
|
+
}],
|
|
1362
|
+
structuredContent: success(
|
|
1363
|
+
`No relations found for ${result.root.entryId}. Entry not yet connected.`,
|
|
1364
|
+
{ root: result.root, related: [], totalRelations: 0 },
|
|
1365
|
+
[{ tool: "graph", description: "Discover connections", parameters: { action: "suggest", entryId: result.root.entryId } }]
|
|
1366
|
+
)
|
|
1287
1367
|
};
|
|
1288
1368
|
}
|
|
1289
1369
|
const byCollection = /* @__PURE__ */ new Map();
|
|
@@ -1323,7 +1403,10 @@ Use \`graph action=suggest\` to discover potential connections, or \`relations a
|
|
|
1323
1403
|
}));
|
|
1324
1404
|
return {
|
|
1325
1405
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
1326
|
-
structuredContent:
|
|
1406
|
+
structuredContent: success(
|
|
1407
|
+
`Gathered ${result.totalRelations} related entries for ${result.root.entryId} (${result.hopsTraversed} hops).`,
|
|
1408
|
+
{ nodes, edges, root: result.root, totalRelations: result.totalRelations }
|
|
1409
|
+
)
|
|
1327
1410
|
};
|
|
1328
1411
|
}
|
|
1329
1412
|
async function handleJourneyGather(server, mapEntryId, maxHops, maxResults) {
|
|
@@ -1338,7 +1421,11 @@ async function handleJourneyGather(server, mapEntryId, maxHops, maxResults) {
|
|
|
1338
1421
|
maxResults
|
|
1339
1422
|
});
|
|
1340
1423
|
if (!result) {
|
|
1341
|
-
return
|
|
1424
|
+
return notFoundResult(
|
|
1425
|
+
mapEntryId,
|
|
1426
|
+
`Journey map '${mapEntryId}' not found or has no slot data. Verify the map entry ID.`,
|
|
1427
|
+
"Verify the map entry ID is correct."
|
|
1428
|
+
);
|
|
1342
1429
|
}
|
|
1343
1430
|
if (result.stages.length === 0 || result.seedCount === 0) {
|
|
1344
1431
|
return {
|
|
@@ -1349,7 +1436,11 @@ async function handleJourneyGather(server, mapEntryId, maxHops, maxResults) {
|
|
|
1349
1436
|
_No ingredients found in this journey map._
|
|
1350
1437
|
|
|
1351
1438
|
Add journey steps via MCP: \`map-slot action=add mapEntryId="${mapEntryId}" slotId="..." ingredientEntryId="..."\``
|
|
1352
|
-
}]
|
|
1439
|
+
}],
|
|
1440
|
+
structuredContent: success(
|
|
1441
|
+
`Journey map ${result.map.name} has no ingredients.`,
|
|
1442
|
+
{ map: result.map, stages: result.stages, context: [] }
|
|
1443
|
+
)
|
|
1353
1444
|
};
|
|
1354
1445
|
}
|
|
1355
1446
|
const lines = [
|
|
@@ -1389,15 +1480,18 @@ Add journey steps via MCP: \`map-slot action=add mapEntryId="${mapEntryId}" slot
|
|
|
1389
1480
|
lines.push(`_Use \`entries action=get\` for full details. Link governance to steps: \`relations action=create\`._`);
|
|
1390
1481
|
return {
|
|
1391
1482
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
1392
|
-
structuredContent:
|
|
1393
|
-
map: result.
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1483
|
+
structuredContent: success(
|
|
1484
|
+
`Journey context for ${result.map.name}: ${result.totalFound} related entries across ${result.stages.length} stages.`,
|
|
1485
|
+
{
|
|
1486
|
+
map: result.map,
|
|
1487
|
+
stages: result.stages,
|
|
1488
|
+
context: result.context.map((e) => ({
|
|
1489
|
+
entryId: e.entryId ?? "",
|
|
1490
|
+
name: e.name,
|
|
1491
|
+
collectionName: e.collectionName
|
|
1492
|
+
}))
|
|
1493
|
+
}
|
|
1494
|
+
)
|
|
1401
1495
|
};
|
|
1402
1496
|
}
|
|
1403
1497
|
async function handleBuild(server, entryId, maxHops) {
|
|
@@ -1467,7 +1561,13 @@ async function handleBuild(server, entryId, maxHops) {
|
|
|
1467
1561
|
lines.push("");
|
|
1468
1562
|
lines.push("_Context was truncated (limits: 50 related, 20 rules, 30 glossary). Use entries action=get for full details._");
|
|
1469
1563
|
}
|
|
1470
|
-
return {
|
|
1564
|
+
return {
|
|
1565
|
+
content: [{ type: "text", text: annotateStaleNames(lines.join("\n")) }],
|
|
1566
|
+
structuredContent: success(
|
|
1567
|
+
`Build context for ${entry.name}: ${relatedCount} related, ${businessRules.length} rules, ${glossaryTerms.length} terms.`,
|
|
1568
|
+
{ entry, relatedByCollection, businessRulesCount: businessRules.length, glossaryTermsCount: glossaryTerms.length, contextTruncated }
|
|
1569
|
+
)
|
|
1570
|
+
};
|
|
1471
1571
|
}
|
|
1472
1572
|
|
|
1473
1573
|
// src/tools/collections.ts
|
|
@@ -1502,12 +1602,9 @@ function registerCollectionsTools(server) {
|
|
|
1502
1602
|
inputSchema: collectionsSchema,
|
|
1503
1603
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
1504
1604
|
},
|
|
1505
|
-
async (args) => {
|
|
1506
|
-
const parsed = collectionsSchema
|
|
1507
|
-
if (!parsed.
|
|
1508
|
-
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
1509
|
-
return { content: [{ type: "text", text: `Invalid arguments: ${issues}` }] };
|
|
1510
|
-
}
|
|
1605
|
+
withEnvelope(async (args) => {
|
|
1606
|
+
const parsed = parseOrFail(collectionsSchema, args);
|
|
1607
|
+
if (!parsed.ok) return parsed.result;
|
|
1511
1608
|
const { action, slug, name, description, purpose, icon, navGroup, fields } = parsed.data;
|
|
1512
1609
|
return runWithToolContext({ tool: "collections", action }, async () => {
|
|
1513
1610
|
if (action === "list") {
|
|
@@ -1515,39 +1612,34 @@ function registerCollectionsTools(server) {
|
|
|
1515
1612
|
}
|
|
1516
1613
|
if (action === "create") {
|
|
1517
1614
|
if (!slug || !name || !fields || fields.length === 0) {
|
|
1518
|
-
return
|
|
1519
|
-
content: [{
|
|
1520
|
-
type: "text",
|
|
1521
|
-
text: "`slug`, `name`, and `fields` are required when action is 'create'."
|
|
1522
|
-
}]
|
|
1523
|
-
};
|
|
1615
|
+
return validationResult("slug, name, and fields are required when action is 'create'.");
|
|
1524
1616
|
}
|
|
1525
1617
|
return handleCreate2(slug, name, description, purpose, icon, navGroup ?? "collections", fields);
|
|
1526
1618
|
}
|
|
1527
1619
|
if (action === "update") {
|
|
1528
1620
|
if (!slug) {
|
|
1529
|
-
return
|
|
1621
|
+
return validationResult("slug is required when action is 'update'.");
|
|
1530
1622
|
}
|
|
1531
1623
|
if (!name && !description && !purpose && !icon && !navGroup && !fields) {
|
|
1532
|
-
return
|
|
1624
|
+
return validationResult("Provide at least one field to update (name, description, purpose, icon, navGroup, or fields).");
|
|
1533
1625
|
}
|
|
1534
1626
|
return handleUpdate(slug, name, description, purpose, icon, navGroup, fields);
|
|
1535
1627
|
}
|
|
1536
|
-
return
|
|
1537
|
-
content: [{
|
|
1538
|
-
type: "text",
|
|
1539
|
-
text: `Unknown action '${action}'. Valid actions: ${COLLECTIONS_ACTIONS.join(", ")}.`
|
|
1540
|
-
}]
|
|
1541
|
-
};
|
|
1628
|
+
return unknownAction(action, COLLECTIONS_ACTIONS);
|
|
1542
1629
|
});
|
|
1543
|
-
}
|
|
1630
|
+
})
|
|
1544
1631
|
);
|
|
1545
1632
|
trackWriteTool(tool);
|
|
1546
1633
|
}
|
|
1547
1634
|
async function handleList2() {
|
|
1548
1635
|
const collections = await mcpQuery("chain.listCollections");
|
|
1549
1636
|
if (collections.length === 0) {
|
|
1550
|
-
return
|
|
1637
|
+
return successResult(
|
|
1638
|
+
"No collections found in this workspace.",
|
|
1639
|
+
"No collections found yet.",
|
|
1640
|
+
{ collections: [], total: 0 },
|
|
1641
|
+
[{ tool: "collections", description: "Create collection", parameters: { action: "create" } }]
|
|
1642
|
+
);
|
|
1551
1643
|
}
|
|
1552
1644
|
const formatted = collections.map((c) => {
|
|
1553
1645
|
const fieldList = c.fields.map((f) => ` - \`${f.key}\` (${f.type}${f.required ? ", required" : ""}${f.searchable ? ", searchable" : ""})`).join("\n");
|
|
@@ -1565,7 +1657,19 @@ ${fieldList}`;
|
|
|
1565
1657
|
return {
|
|
1566
1658
|
content: [{ type: "text", text: `# Knowledge Collections (${collections.length})
|
|
1567
1659
|
|
|
1568
|
-
${formatted}` }]
|
|
1660
|
+
${formatted}` }],
|
|
1661
|
+
structuredContent: success(
|
|
1662
|
+
`Found ${collections.length} collections.`,
|
|
1663
|
+
{
|
|
1664
|
+
collections: collections.map((c) => ({
|
|
1665
|
+
slug: c.slug,
|
|
1666
|
+
name: c.name,
|
|
1667
|
+
description: c.description,
|
|
1668
|
+
fieldCount: c.fields.length
|
|
1669
|
+
})),
|
|
1670
|
+
total: collections.length
|
|
1671
|
+
}
|
|
1672
|
+
)
|
|
1569
1673
|
};
|
|
1570
1674
|
}
|
|
1571
1675
|
async function handleCreate2(slug, name, description, purpose, icon, navGroup, fields) {
|
|
@@ -1596,17 +1700,23 @@ async function handleCreate2(slug, name, description, purpose, icon, navGroup, f
|
|
|
1596
1700
|
${fieldList}
|
|
1597
1701
|
|
|
1598
1702
|
You can now capture entries: \`capture collection="${slug}" name="..." description="..."\``
|
|
1599
|
-
}]
|
|
1703
|
+
}],
|
|
1704
|
+
structuredContent: success(
|
|
1705
|
+
`Created collection '${name}' (${slug}) with ${fields.length} fields.`,
|
|
1706
|
+
{ slug, name, description, purpose, navGroup, fieldCount: fields.length },
|
|
1707
|
+
[{ tool: "capture", description: "Capture an entry", parameters: { collection: slug } }]
|
|
1708
|
+
)
|
|
1600
1709
|
};
|
|
1601
1710
|
} catch (error) {
|
|
1602
1711
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1603
1712
|
if (msg.includes("already exists")) {
|
|
1604
|
-
return
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1713
|
+
return failureResult(
|
|
1714
|
+
`Collection '${slug}' already exists. Use collections action=update to modify it, or choose a different slug.`,
|
|
1715
|
+
"DUPLICATE",
|
|
1716
|
+
`Collection '${slug}' already exists.`,
|
|
1717
|
+
"Use collections action=update to modify it.",
|
|
1718
|
+
[{ tool: "collections", description: "Update collection", parameters: { action: "update", slug } }]
|
|
1719
|
+
);
|
|
1610
1720
|
}
|
|
1611
1721
|
throw error;
|
|
1612
1722
|
}
|
|
@@ -1631,7 +1741,11 @@ async function handleUpdate(slug, name, description, purpose, icon, navGroup, fi
|
|
|
1631
1741
|
Changed: ${changes || "no changes"}.
|
|
1632
1742
|
|
|
1633
1743
|
Use \`collections action=list\` to verify the result.`
|
|
1634
|
-
}]
|
|
1744
|
+
}],
|
|
1745
|
+
structuredContent: success(
|
|
1746
|
+
`Updated collection '${slug}'. Changed: ${changes || "no changes"}.`,
|
|
1747
|
+
{ slug, changes }
|
|
1748
|
+
)
|
|
1635
1749
|
};
|
|
1636
1750
|
}
|
|
1637
1751
|
|
|
@@ -1657,11 +1771,16 @@ function registerLabelTools(server) {
|
|
|
1657
1771
|
inputSchema: labelsSchema,
|
|
1658
1772
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
1659
1773
|
},
|
|
1660
|
-
async ({ action, slug, name, color, description, parentSlug, isGroup, order, entryId }) => {
|
|
1774
|
+
withEnvelope(async ({ action, slug, name, color, description, parentSlug, isGroup, order, entryId }) => {
|
|
1661
1775
|
if (action === "list") {
|
|
1662
1776
|
const labels = await mcpQuery("chain.listLabels");
|
|
1663
1777
|
if (labels.length === 0) {
|
|
1664
|
-
return
|
|
1778
|
+
return successResult(
|
|
1779
|
+
"No labels defined in this workspace yet.",
|
|
1780
|
+
"No labels defined yet.",
|
|
1781
|
+
{ labels: [], total: 0 },
|
|
1782
|
+
[{ tool: "labels", description: "Create a label", parameters: { action: "create" } }]
|
|
1783
|
+
);
|
|
1665
1784
|
}
|
|
1666
1785
|
const groups = labels.filter((l) => l.isGroup);
|
|
1667
1786
|
const ungrouped = labels.filter((l) => !l.isGroup && !l.parentId);
|
|
@@ -1683,54 +1802,74 @@ function registerLabelTools(server) {
|
|
|
1683
1802
|
lines.push(`- \`${label.slug}\` ${label.name}${c}${label.description ? ` \u2014 _${label.description}_` : ""}`);
|
|
1684
1803
|
}
|
|
1685
1804
|
}
|
|
1686
|
-
return {
|
|
1805
|
+
return {
|
|
1806
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
1807
|
+
structuredContent: success(
|
|
1808
|
+
`Found ${labels.length} labels in ${groups.length} groups.`,
|
|
1809
|
+
{ labels, total: labels.length }
|
|
1810
|
+
)
|
|
1811
|
+
};
|
|
1687
1812
|
}
|
|
1688
1813
|
if (!slug) {
|
|
1689
|
-
return
|
|
1814
|
+
return validationResult("A slug is required for this action.");
|
|
1690
1815
|
}
|
|
1691
1816
|
if (action === "create") {
|
|
1692
1817
|
if (!name) {
|
|
1693
|
-
return
|
|
1818
|
+
return validationResult("Cannot create a label without a name.");
|
|
1694
1819
|
}
|
|
1695
1820
|
let parentId;
|
|
1696
1821
|
if (parentSlug) {
|
|
1697
1822
|
const labels = await mcpQuery("chain.listLabels");
|
|
1698
1823
|
const parent = labels.find((l) => l.slug === parentSlug);
|
|
1699
1824
|
if (!parent) {
|
|
1700
|
-
return
|
|
1825
|
+
return failureResult(
|
|
1826
|
+
`Parent label '${parentSlug}' not found. Use labels action=list to see available groups.`,
|
|
1827
|
+
"NOT_FOUND",
|
|
1828
|
+
`Parent label '${parentSlug}' not found.`,
|
|
1829
|
+
"Use labels action=list to see available groups.",
|
|
1830
|
+
[{ tool: "labels", description: "List labels", parameters: { action: "list" } }]
|
|
1831
|
+
);
|
|
1701
1832
|
}
|
|
1702
1833
|
parentId = parent._id;
|
|
1703
1834
|
}
|
|
1704
1835
|
await mcpMutation("chain.createLabel", { slug, name, color, description, parentId, isGroup, order });
|
|
1705
|
-
return
|
|
1706
|
-
|
|
1707
|
-
|
|
1836
|
+
return successResult(
|
|
1837
|
+
`Label '${name}' (${slug}) created.`,
|
|
1838
|
+
`Created label '${name}' (${slug}).`,
|
|
1839
|
+
{ slug, name, color, parentSlug },
|
|
1840
|
+
[{ tool: "labels", description: "List labels", parameters: { action: "list" } }]
|
|
1841
|
+
);
|
|
1708
1842
|
}
|
|
1709
1843
|
if (action === "update") {
|
|
1710
1844
|
await mcpMutation("chain.updateLabel", { slug, name, color, description, isGroup, order });
|
|
1711
|
-
return
|
|
1712
|
-
|
|
1713
|
-
|
|
1845
|
+
return successResult(
|
|
1846
|
+
`Label '${slug}' updated.`,
|
|
1847
|
+
`Updated label '${slug}'.`,
|
|
1848
|
+
{ slug },
|
|
1849
|
+
[{ tool: "labels", description: "List labels", parameters: { action: "list" } }]
|
|
1850
|
+
);
|
|
1714
1851
|
}
|
|
1715
1852
|
if (action === "delete") {
|
|
1716
1853
|
await mcpMutation("chain.deleteLabel", { slug });
|
|
1717
|
-
return
|
|
1718
|
-
|
|
1719
|
-
|
|
1854
|
+
return successResult(
|
|
1855
|
+
`Label '${slug}' removed from all entries and deleted.`,
|
|
1856
|
+
`Deleted label '${slug}'.`,
|
|
1857
|
+
{ slug }
|
|
1858
|
+
);
|
|
1720
1859
|
}
|
|
1721
1860
|
if (action === "apply" || action === "remove") {
|
|
1722
1861
|
if (!entryId) {
|
|
1723
|
-
return
|
|
1862
|
+
return validationResult("An entryId is required for apply/remove actions.");
|
|
1724
1863
|
}
|
|
1725
1864
|
if (action === "apply") {
|
|
1726
1865
|
await mcpMutation("chain.applyLabel", { entryId, labelSlug: slug });
|
|
1727
|
-
return
|
|
1866
|
+
return successResult(`Label '${slug}' applied to ${entryId}.`, `Applied label '${slug}' to ${entryId}.`, { slug, entryId });
|
|
1728
1867
|
}
|
|
1729
1868
|
await mcpMutation("chain.removeLabel", { entryId, labelSlug: slug });
|
|
1730
|
-
return
|
|
1869
|
+
return successResult(`Label '${slug}' removed from ${entryId}.`, `Removed label '${slug}' from ${entryId}.`, { slug, entryId });
|
|
1731
1870
|
}
|
|
1732
|
-
return
|
|
1733
|
-
}
|
|
1871
|
+
return validationResult("Unknown action.");
|
|
1872
|
+
})
|
|
1734
1873
|
);
|
|
1735
1874
|
trackWriteTool(tool);
|
|
1736
1875
|
}
|
|
@@ -1792,17 +1931,17 @@ async function suggestLinksForEntries(entries) {
|
|
|
1792
1931
|
async function runWrapupReview() {
|
|
1793
1932
|
const sessionId = getAgentSessionId();
|
|
1794
1933
|
if (!sessionId) {
|
|
1795
|
-
return { text: "No active agent session. Call `session action=start` first.", data: null, suggestions: [] };
|
|
1934
|
+
return { text: "No active agent session. Call `session action=start` first.", data: null, suggestions: [], failureCode: "SESSION_REQUIRED" };
|
|
1796
1935
|
}
|
|
1797
1936
|
if (getApiKeyScope() === "read") {
|
|
1798
|
-
return { text: "Read-only session \u2014 nothing to review.", data: null, suggestions: [] };
|
|
1937
|
+
return { text: "Read-only session \u2014 nothing to review.", data: null, suggestions: [], failureCode: "READONLY_SCOPE" };
|
|
1799
1938
|
}
|
|
1800
1939
|
const data = await mcpCall("agent.getSessionWrapup", { sessionId });
|
|
1801
1940
|
if (data.wrapupCompletedAt) {
|
|
1802
|
-
return { text: "Wrapup already completed this session.", data, suggestions: [] };
|
|
1941
|
+
return { text: "Wrapup already completed this session.", data, suggestions: [], failureCode: "ALREADY_COMPLETED" };
|
|
1803
1942
|
}
|
|
1804
1943
|
if (data.drafts.length === 0 && data.committed.length === 0) {
|
|
1805
|
-
return { text: "Empty session \u2014 no entries to review.", data, suggestions: [] };
|
|
1944
|
+
return { text: "Empty session \u2014 no entries to review.", data, suggestions: [], failureCode: "NOT_FOUND" };
|
|
1806
1945
|
}
|
|
1807
1946
|
const lines = [];
|
|
1808
1947
|
lines.push(
|
|
@@ -1922,50 +2061,62 @@ function registerWrapupTools(server) {
|
|
|
1922
2061
|
let lastReviewData = null;
|
|
1923
2062
|
let lastReviewSuggestions = [];
|
|
1924
2063
|
const wrapupHandler = async ({ action }) => {
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
return
|
|
2064
|
+
if (action === "commit-all") {
|
|
2065
|
+
const sessionId = getAgentSessionId();
|
|
2066
|
+
if (!sessionId) {
|
|
2067
|
+
return failureResult(
|
|
2068
|
+
"No active agent session.",
|
|
2069
|
+
"SESSION_REQUIRED",
|
|
2070
|
+
"No active agent session.",
|
|
2071
|
+
"Start a session first.",
|
|
2072
|
+
[{ tool: "session", description: "Start a session", parameters: { action: "start" } }]
|
|
2073
|
+
);
|
|
2074
|
+
}
|
|
2075
|
+
const data2 = lastReviewData ?? await mcpCall("agent.getSessionWrapup", { sessionId });
|
|
2076
|
+
if (data2.drafts.length === 0) {
|
|
2077
|
+
return failureResult("No uncommitted drafts to commit.", "NOT_FOUND", "No uncommitted drafts to commit.", "Nothing to commit.");
|
|
1939
2078
|
}
|
|
1940
|
-
const
|
|
1941
|
-
lastReviewData =
|
|
1942
|
-
lastReviewSuggestions =
|
|
1943
|
-
|
|
2079
|
+
const result = await runWrapupCommitAll(data2, lastReviewSuggestions);
|
|
2080
|
+
lastReviewData = null;
|
|
2081
|
+
lastReviewSuggestions = [];
|
|
2082
|
+
return successResult(result, "Wrapup complete. Committed drafts and created links.", { committed: true });
|
|
2083
|
+
}
|
|
2084
|
+
const { text, data, suggestions, failureCode } = await runWrapupReview();
|
|
2085
|
+
lastReviewData = data;
|
|
2086
|
+
lastReviewSuggestions = suggestions;
|
|
2087
|
+
const fullText = data && (data.drafts.length > 0 || data.committed.length > 0) ? `## Session Wrapup
|
|
1944
2088
|
|
|
1945
2089
|
${text}` : text;
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
2090
|
+
if (failureCode) {
|
|
2091
|
+
return failureResult(fullText, failureCode, text);
|
|
2092
|
+
}
|
|
2093
|
+
const next = data.drafts.length > 0 ? [{ tool: "session-wrapup", description: "Commit all drafts", parameters: { action: "commit-all" } }] : void 0;
|
|
2094
|
+
return successResult(
|
|
2095
|
+
fullText,
|
|
2096
|
+
`Session review: ${data.drafts.length} uncommitted, ${data.committed.length} committed, ${suggestions.length} link suggestions.`,
|
|
2097
|
+
{
|
|
2098
|
+
drafts: data.drafts.length,
|
|
2099
|
+
committed: data.committed.length,
|
|
2100
|
+
uncommitted: data.summary.uncommitted,
|
|
2101
|
+
suggestedLinks: suggestions.length
|
|
2102
|
+
},
|
|
2103
|
+
next
|
|
2104
|
+
);
|
|
1951
2105
|
};
|
|
1952
2106
|
const tool = server.registerTool("session-wrapup", {
|
|
1953
2107
|
title: "Session Wrapup",
|
|
1954
2108
|
description: WRAPUP_TOOL_DESCRIPTION + " Also known as: wrapup, finish.",
|
|
1955
2109
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
1956
2110
|
inputSchema: wrapupSchema
|
|
1957
|
-
}, async (args) => {
|
|
1958
|
-
const parsed = wrapupSchema
|
|
1959
|
-
if (!parsed.
|
|
1960
|
-
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
1961
|
-
return { content: [{ type: "text", text: `Invalid arguments: ${issues}` }] };
|
|
1962
|
-
}
|
|
2111
|
+
}, withEnvelope(async (args) => {
|
|
2112
|
+
const parsed = parseOrFail(wrapupSchema, args);
|
|
2113
|
+
if (!parsed.ok) return parsed.result;
|
|
1963
2114
|
const act = parsed.data.action ?? "review";
|
|
1964
2115
|
return runWithToolContext(
|
|
1965
2116
|
{ tool: "session-wrapup", action: act },
|
|
1966
2117
|
() => wrapupHandler({ action: act })
|
|
1967
2118
|
);
|
|
1968
|
-
});
|
|
2119
|
+
}));
|
|
1969
2120
|
trackWriteTool(tool);
|
|
1970
2121
|
}
|
|
1971
2122
|
|
|
@@ -1985,12 +2136,9 @@ function registerSessionTools(server) {
|
|
|
1985
2136
|
inputSchema: sessionSchema,
|
|
1986
2137
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
1987
2138
|
},
|
|
1988
|
-
async (args) => {
|
|
1989
|
-
const parsed = sessionSchema
|
|
1990
|
-
if (!parsed.
|
|
1991
|
-
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
1992
|
-
return { content: [{ type: "text", text: `Invalid arguments: ${issues}` }] };
|
|
1993
|
-
}
|
|
2139
|
+
withEnvelope(async (args) => {
|
|
2140
|
+
const parsed = parseOrFail(sessionSchema, args);
|
|
2141
|
+
if (!parsed.ok) return parsed.result;
|
|
1994
2142
|
const { action } = parsed.data;
|
|
1995
2143
|
return runWithToolContext({ tool: "session", action }, async () => {
|
|
1996
2144
|
if (action === "start") {
|
|
@@ -2002,141 +2150,159 @@ function registerSessionTools(server) {
|
|
|
2002
2150
|
if (action === "status") {
|
|
2003
2151
|
return handleStatus();
|
|
2004
2152
|
}
|
|
2005
|
-
return
|
|
2006
|
-
content: [{
|
|
2007
|
-
type: "text",
|
|
2008
|
-
text: `Unknown action '${action}'. Valid actions: ${SESSION_ACTIONS.join(", ")}.`
|
|
2009
|
-
}]
|
|
2010
|
-
};
|
|
2153
|
+
return unknownAction(action, SESSION_ACTIONS);
|
|
2011
2154
|
});
|
|
2012
|
-
}
|
|
2155
|
+
})
|
|
2013
2156
|
);
|
|
2014
2157
|
}
|
|
2015
2158
|
async function handleStart() {
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
if (result.superseded) {
|
|
2020
|
-
lines.push(
|
|
2021
|
-
`Previous session superseded. Session ${result.superseded.previousSessionId} (started ${result.superseded.startedAt}, initiated by ${result.superseded.initiatedBy}) was closed.`,
|
|
2022
|
-
""
|
|
2023
|
-
);
|
|
2024
|
-
}
|
|
2159
|
+
const result = await startAgentSession();
|
|
2160
|
+
const lines = [];
|
|
2161
|
+
if (result.superseded) {
|
|
2025
2162
|
lines.push(
|
|
2026
|
-
`
|
|
2163
|
+
`Previous session superseded. Session ${result.superseded.previousSessionId} (started ${result.superseded.startedAt}, initiated by ${result.superseded.initiatedBy}) was closed.`,
|
|
2164
|
+
""
|
|
2027
2165
|
);
|
|
2028
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
2029
|
-
} catch (err) {
|
|
2030
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2031
|
-
return {
|
|
2032
|
-
content: [{ type: "text", text: `Failed to start agent session: ${msg}` }],
|
|
2033
|
-
isError: true
|
|
2034
|
-
};
|
|
2035
2166
|
}
|
|
2167
|
+
lines.push(
|
|
2168
|
+
`Session ${result.sessionId} active. Initiated by ${result.initiatedBy}. Workspace ${result.workspaceName}. Scope: ${result.toolsScope}. Write tools available after orient.`
|
|
2169
|
+
);
|
|
2170
|
+
const summary = `Session ${result.sessionId} active. Workspace ${result.workspaceName}. Write tools available after orient.`;
|
|
2171
|
+
return {
|
|
2172
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
2173
|
+
structuredContent: success(summary, {
|
|
2174
|
+
sessionId: result.sessionId,
|
|
2175
|
+
initiatedBy: result.initiatedBy,
|
|
2176
|
+
workspaceName: result.workspaceName,
|
|
2177
|
+
toolsScope: result.toolsScope,
|
|
2178
|
+
superseded: !!result.superseded
|
|
2179
|
+
}, [{ tool: "orient", description: "Orient session", parameters: {} }])
|
|
2180
|
+
};
|
|
2036
2181
|
}
|
|
2037
2182
|
async function handleClose() {
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2183
|
+
const sessionId = getAgentSessionId();
|
|
2184
|
+
if (!sessionId) {
|
|
2185
|
+
return failureResult(
|
|
2186
|
+
"No active agent session to close.",
|
|
2187
|
+
"SESSION_REQUIRED",
|
|
2188
|
+
"No active agent session to close.",
|
|
2189
|
+
"Start a session first.",
|
|
2190
|
+
[{ tool: "session", description: "Start a session", parameters: { action: "start" } }]
|
|
2191
|
+
);
|
|
2192
|
+
}
|
|
2193
|
+
const session = await mcpCall("agent.getSession", {
|
|
2194
|
+
sessionId
|
|
2195
|
+
});
|
|
2196
|
+
let wrapupNudge = "";
|
|
2197
|
+
if (session && !session.wrapupCompletedAt) {
|
|
2198
|
+
try {
|
|
2199
|
+
const { text, data } = await runWrapupReview();
|
|
2200
|
+
if (data && data.drafts.length > 0) {
|
|
2201
|
+
wrapupNudge = [
|
|
2202
|
+
"## Session Wrapup",
|
|
2203
|
+
"",
|
|
2204
|
+
text,
|
|
2205
|
+
"",
|
|
2206
|
+
`> **${data.drafts.length} uncommitted draft(s) remain.** Run \`session-wrapup\` with action \`commit-all\` before closing next time.`,
|
|
2207
|
+
"",
|
|
2208
|
+
"---",
|
|
2209
|
+
""
|
|
2210
|
+
].join("\n");
|
|
2065
2211
|
}
|
|
2212
|
+
} catch {
|
|
2066
2213
|
}
|
|
2067
|
-
await closeAgentSession();
|
|
2068
|
-
const lines = [
|
|
2069
|
-
`Session ${sessionId} closed.`,
|
|
2070
|
-
""
|
|
2071
|
-
];
|
|
2072
|
-
if (session) {
|
|
2073
|
-
const created = session.entriesCreated?.length ?? 0;
|
|
2074
|
-
const modified = session.entriesModified?.length ?? 0;
|
|
2075
|
-
const relations = session.relationsCreated ?? 0;
|
|
2076
|
-
const gates = session.gateFailures ?? 0;
|
|
2077
|
-
const warnings = session.contradictionWarnings ?? 0;
|
|
2078
|
-
lines.push(
|
|
2079
|
-
`| Metric | Count |`,
|
|
2080
|
-
`|--------|-------|`,
|
|
2081
|
-
`| Entries created | ${created} |`,
|
|
2082
|
-
`| Entries modified | ${modified} |`,
|
|
2083
|
-
`| Relations created | ${relations} |`,
|
|
2084
|
-
`| Gate failures | ${gates} |`,
|
|
2085
|
-
`| Contradiction warnings | ${warnings} |`
|
|
2086
|
-
);
|
|
2087
|
-
}
|
|
2088
|
-
lines.push("", "Write tools are now blocked. Session data saved for future orientation.");
|
|
2089
|
-
const fullResponse = wrapupNudge + lines.join("\n");
|
|
2090
|
-
return { content: [{ type: "text", text: fullResponse }] };
|
|
2091
|
-
} catch (err) {
|
|
2092
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2093
|
-
return {
|
|
2094
|
-
content: [{ type: "text", text: `Failed to close agent session: ${msg}` }],
|
|
2095
|
-
isError: true
|
|
2096
|
-
};
|
|
2097
2214
|
}
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
return {
|
|
2112
|
-
content: [{ type: "text", text: "Session ID cached but not found in Convex. Call `session action=start` to create a new session." }]
|
|
2113
|
-
};
|
|
2114
|
-
}
|
|
2115
|
-
const oriented = isSessionOriented();
|
|
2116
|
-
const created = session.entriesCreated?.length ?? 0;
|
|
2117
|
-
const modified = session.entriesModified?.length ?? 0;
|
|
2118
|
-
const lines = [
|
|
2119
|
-
`Session ${sessionId} ${session.status}.`,
|
|
2120
|
-
`Initiated by ${session.initiatedBy}.`,
|
|
2121
|
-
`Scope: ${session.toolsScope}.`,
|
|
2122
|
-
`Oriented: ${oriented ? "yes" : "no"}.`,
|
|
2123
|
-
"",
|
|
2124
|
-
`| Metric | Value |`,
|
|
2215
|
+
await closeAgentSession();
|
|
2216
|
+
const lines = [
|
|
2217
|
+
`Session ${sessionId} closed.`,
|
|
2218
|
+
""
|
|
2219
|
+
];
|
|
2220
|
+
const created = session?.entriesCreated?.length ?? 0;
|
|
2221
|
+
const modified = session?.entriesModified?.length ?? 0;
|
|
2222
|
+
const relations = session?.relationsCreated ?? 0;
|
|
2223
|
+
const gates = session?.gateFailures ?? 0;
|
|
2224
|
+
const warnings = session?.contradictionWarnings ?? 0;
|
|
2225
|
+
if (session) {
|
|
2226
|
+
lines.push(
|
|
2227
|
+
`| Metric | Count |`,
|
|
2125
2228
|
`|--------|-------|`,
|
|
2126
2229
|
`| Entries created | ${created} |`,
|
|
2127
2230
|
`| Entries modified | ${modified} |`,
|
|
2128
|
-
`| Relations created | ${
|
|
2129
|
-
`|
|
|
2130
|
-
`|
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2231
|
+
`| Relations created | ${relations} |`,
|
|
2232
|
+
`| Gate failures | ${gates} |`,
|
|
2233
|
+
`| Contradiction warnings | ${warnings} |`
|
|
2234
|
+
);
|
|
2235
|
+
}
|
|
2236
|
+
lines.push("", "Write tools are now blocked. Session data saved for future orientation.");
|
|
2237
|
+
const fullResponse = wrapupNudge + lines.join("\n");
|
|
2238
|
+
const summary = `Session ${sessionId} closed. ${created} created, ${modified} modified.`;
|
|
2239
|
+
return {
|
|
2240
|
+
content: [{ type: "text", text: fullResponse }],
|
|
2241
|
+
structuredContent: success(summary, {
|
|
2242
|
+
sessionId,
|
|
2243
|
+
entriesCreated: created,
|
|
2244
|
+
entriesModified: modified,
|
|
2245
|
+
relationsCreated: relations,
|
|
2246
|
+
gateFailures: gates,
|
|
2247
|
+
contradictionWarnings: warnings,
|
|
2248
|
+
wrapupNudge: wrapupNudge.length > 0
|
|
2249
|
+
})
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
async function handleStatus() {
|
|
2253
|
+
const sessionId = getAgentSessionId();
|
|
2254
|
+
if (!sessionId) {
|
|
2255
|
+
return failureResult(
|
|
2256
|
+
"No active agent session. Call session action=start to begin.",
|
|
2257
|
+
"SESSION_REQUIRED",
|
|
2258
|
+
"No active agent session.",
|
|
2259
|
+
"Call session action=start to begin.",
|
|
2260
|
+
[{ tool: "session", description: "Start a session", parameters: { action: "start" } }]
|
|
2261
|
+
);
|
|
2139
2262
|
}
|
|
2263
|
+
const session = await mcpCall("agent.getSession", {
|
|
2264
|
+
sessionId
|
|
2265
|
+
});
|
|
2266
|
+
if (!session) {
|
|
2267
|
+
return failureResult(
|
|
2268
|
+
"Session not found in Convex. Call session action=start to create a new session.",
|
|
2269
|
+
"NOT_FOUND",
|
|
2270
|
+
"Session not found in Convex.",
|
|
2271
|
+
"Call session action=start to create a new session.",
|
|
2272
|
+
[{ tool: "session", description: "Start a session", parameters: { action: "start" } }]
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
const oriented = isSessionOriented();
|
|
2276
|
+
const created = session.entriesCreated?.length ?? 0;
|
|
2277
|
+
const modified = session.entriesModified?.length ?? 0;
|
|
2278
|
+
const lines = [
|
|
2279
|
+
`Session ${sessionId} ${session.status}.`,
|
|
2280
|
+
`Initiated by ${session.initiatedBy}.`,
|
|
2281
|
+
`Scope: ${session.toolsScope}.`,
|
|
2282
|
+
`Oriented: ${oriented ? "yes" : "no"}.`,
|
|
2283
|
+
"",
|
|
2284
|
+
`| Metric | Value |`,
|
|
2285
|
+
`|--------|-------|`,
|
|
2286
|
+
`| Entries created | ${created} |`,
|
|
2287
|
+
`| Entries modified | ${modified} |`,
|
|
2288
|
+
`| Relations created | ${session.relationsCreated ?? 0} |`,
|
|
2289
|
+
`| Started | ${new Date(session.startedAt).toISOString()} |`,
|
|
2290
|
+
`| Expires | ${new Date(session.expiresAt).toISOString()} |`
|
|
2291
|
+
];
|
|
2292
|
+
const summary = `Session ${sessionId} ${session.status}. ${created} created, ${modified} modified.`;
|
|
2293
|
+
return {
|
|
2294
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
2295
|
+
structuredContent: success(summary, {
|
|
2296
|
+
sessionId,
|
|
2297
|
+
status: session.status,
|
|
2298
|
+
initiatedBy: session.initiatedBy,
|
|
2299
|
+
toolsScope: session.toolsScope,
|
|
2300
|
+
oriented,
|
|
2301
|
+
entriesCreated: created,
|
|
2302
|
+
entriesModified: modified,
|
|
2303
|
+
relationsCreated: session.relationsCreated ?? 0
|
|
2304
|
+
})
|
|
2305
|
+
};
|
|
2140
2306
|
}
|
|
2141
2307
|
|
|
2142
2308
|
// src/tools/quality.ts
|
|
@@ -2176,12 +2342,9 @@ function registerQualityTools(server) {
|
|
|
2176
2342
|
inputSchema: qualitySchema,
|
|
2177
2343
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2178
2344
|
},
|
|
2179
|
-
async (args) => {
|
|
2180
|
-
const parsed = qualitySchema
|
|
2181
|
-
if (!parsed.
|
|
2182
|
-
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
2183
|
-
return { content: [{ type: "text", text: `Invalid arguments: ${issues}` }] };
|
|
2184
|
-
}
|
|
2345
|
+
withEnvelope(async (args) => {
|
|
2346
|
+
const parsed = parseOrFail(qualitySchema, args);
|
|
2347
|
+
if (!parsed.ok) return parsed.result;
|
|
2185
2348
|
const { action, entryId, context } = parsed.data;
|
|
2186
2349
|
return runWithToolContext({ tool: "quality", action }, async () => {
|
|
2187
2350
|
if (action === "check") {
|
|
@@ -2190,14 +2353,9 @@ function registerQualityTools(server) {
|
|
|
2190
2353
|
if (action === "re-evaluate") {
|
|
2191
2354
|
return handleReEvaluate(entryId, context ?? "review");
|
|
2192
2355
|
}
|
|
2193
|
-
return
|
|
2194
|
-
content: [{
|
|
2195
|
-
type: "text",
|
|
2196
|
-
text: `Unknown action '${action}'. Valid actions: ${QUALITY_ACTIONS.join(", ")}.`
|
|
2197
|
-
}]
|
|
2198
|
-
};
|
|
2356
|
+
return unknownAction(action, QUALITY_ACTIONS);
|
|
2199
2357
|
});
|
|
2200
|
-
}
|
|
2358
|
+
})
|
|
2201
2359
|
);
|
|
2202
2360
|
trackWriteTool(qualityTool);
|
|
2203
2361
|
}
|
|
@@ -2247,17 +2405,23 @@ ${linkHints}`;
|
|
|
2247
2405
|
}
|
|
2248
2406
|
return {
|
|
2249
2407
|
content: [{ type: "text", text: result.text }],
|
|
2250
|
-
structuredContent:
|
|
2251
|
-
entryId
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2408
|
+
structuredContent: success(
|
|
2409
|
+
`Quality check for ${entryId}: ${result.quality.score}/${result.quality.maxScore}.`,
|
|
2410
|
+
{
|
|
2411
|
+
entryId,
|
|
2412
|
+
score: result.quality.score,
|
|
2413
|
+
maxScore: result.quality.maxScore,
|
|
2414
|
+
criteria: result.quality.checks.map((c) => ({
|
|
2415
|
+
name: c.label,
|
|
2416
|
+
score: c.passed ? 1 : 0,
|
|
2417
|
+
maxScore: 1,
|
|
2418
|
+
met: c.passed
|
|
2419
|
+
}))
|
|
2420
|
+
},
|
|
2421
|
+
[
|
|
2422
|
+
{ tool: "graph", description: "Suggest connections", parameters: { action: "suggest", entryId } }
|
|
2423
|
+
]
|
|
2424
|
+
)
|
|
2261
2425
|
};
|
|
2262
2426
|
}
|
|
2263
2427
|
async function handleReEvaluate(entryId, context) {
|
|
@@ -2268,10 +2432,13 @@ async function handleReEvaluate(entryId, context) {
|
|
|
2268
2432
|
context
|
|
2269
2433
|
});
|
|
2270
2434
|
if (!result) {
|
|
2271
|
-
return
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2435
|
+
return failureResult(
|
|
2436
|
+
`Entry '${entryId}' not found or has no rubric.`,
|
|
2437
|
+
"NOT_FOUND",
|
|
2438
|
+
`Entry '${entryId}' not found or has no rubric.`,
|
|
2439
|
+
"Use entries action=search to find the correct ID.",
|
|
2440
|
+
[{ tool: "entries", description: "Search entries", parameters: { action: "search", query: entryId } }]
|
|
2441
|
+
);
|
|
2275
2442
|
}
|
|
2276
2443
|
const llmScheduled = result.verdict.tier !== "passive";
|
|
2277
2444
|
const lines = [`Re-evaluated \`${entryId}\` in \`${context}\` context.`];
|
|
@@ -2300,13 +2467,16 @@ async function handleReEvaluate(entryId, context) {
|
|
|
2300
2467
|
const reEvalMaxScore = criteria.length;
|
|
2301
2468
|
return {
|
|
2302
2469
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
2303
|
-
structuredContent:
|
|
2304
|
-
entryId
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2470
|
+
structuredContent: success(
|
|
2471
|
+
`Re-evaluated ${entryId} in ${context} context. Score: ${reEvalScore}/${reEvalMaxScore}.`,
|
|
2472
|
+
{
|
|
2473
|
+
entryId,
|
|
2474
|
+
context,
|
|
2475
|
+
score: reEvalScore,
|
|
2476
|
+
maxScore: reEvalMaxScore,
|
|
2477
|
+
improved: result.verdict.passed ?? false
|
|
2478
|
+
}
|
|
2479
|
+
)
|
|
2310
2480
|
};
|
|
2311
2481
|
}
|
|
2312
2482
|
|
|
@@ -2714,12 +2884,9 @@ function registerWorkflowTools(server) {
|
|
|
2714
2884
|
inputSchema: workflowsSchema,
|
|
2715
2885
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
2716
2886
|
},
|
|
2717
|
-
async (args) => {
|
|
2718
|
-
const parsed = workflowsSchema
|
|
2719
|
-
if (!parsed.
|
|
2720
|
-
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
2721
|
-
return { content: [{ type: "text", text: `Invalid arguments: ${issues}` }] };
|
|
2722
|
-
}
|
|
2887
|
+
withEnvelope(async (args) => {
|
|
2888
|
+
const parsed = parseOrFail(workflowsSchema, args);
|
|
2889
|
+
if (!parsed.ok) return parsed.result;
|
|
2723
2890
|
const { action, workflowId, roundId, output, isFinal, summaryName, summaryDescription } = parsed.data;
|
|
2724
2891
|
return runWithToolContext({ tool: "workflows", action }, async () => {
|
|
2725
2892
|
if (action === "list") {
|
|
@@ -2727,35 +2894,26 @@ function registerWorkflowTools(server) {
|
|
|
2727
2894
|
}
|
|
2728
2895
|
if (action === "checkpoint") {
|
|
2729
2896
|
if (!workflowId || !roundId || output === void 0) {
|
|
2730
|
-
return
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
text: "`workflowId`, `roundId`, and `output` are required when action is 'checkpoint'."
|
|
2734
|
-
}]
|
|
2735
|
-
};
|
|
2897
|
+
return validationResult(
|
|
2898
|
+
"workflowId, roundId, and output are required when action is 'checkpoint'."
|
|
2899
|
+
);
|
|
2736
2900
|
}
|
|
2737
2901
|
return handleCheckpoint(workflowId, roundId, output, isFinal, summaryName, summaryDescription);
|
|
2738
2902
|
}
|
|
2739
|
-
return
|
|
2740
|
-
content: [{
|
|
2741
|
-
type: "text",
|
|
2742
|
-
text: `Unknown action '${action}'. Valid actions: ${WORKFLOWS_ACTIONS.join(", ")}.`
|
|
2743
|
-
}]
|
|
2744
|
-
};
|
|
2903
|
+
return unknownAction(action, WORKFLOWS_ACTIONS);
|
|
2745
2904
|
});
|
|
2746
|
-
}
|
|
2905
|
+
})
|
|
2747
2906
|
);
|
|
2748
2907
|
trackWriteTool(workflowsTool);
|
|
2749
2908
|
}
|
|
2750
2909
|
async function handleList3() {
|
|
2751
2910
|
const workflows = listWorkflows();
|
|
2752
2911
|
if (workflows.length === 0) {
|
|
2753
|
-
return
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
};
|
|
2912
|
+
return successResult(
|
|
2913
|
+
"No workflows registered yet. Check back after the workflow definitions are configured.",
|
|
2914
|
+
"No workflows registered yet.",
|
|
2915
|
+
{ workflows: [], total: 0 }
|
|
2916
|
+
);
|
|
2759
2917
|
}
|
|
2760
2918
|
const cards = workflows.map(formatWorkflowCard).join("\n\n---\n\n");
|
|
2761
2919
|
return {
|
|
@@ -2768,7 +2926,19 @@ ${workflows.length} workflow(s) available. Use the \`run-workflow\` prompt to la
|
|
|
2768
2926
|
---
|
|
2769
2927
|
|
|
2770
2928
|
${cards}`
|
|
2771
|
-
}]
|
|
2929
|
+
}],
|
|
2930
|
+
structuredContent: success(
|
|
2931
|
+
`Found ${workflows.length} workflow(s).`,
|
|
2932
|
+
{
|
|
2933
|
+
workflows: workflows.map((w) => ({
|
|
2934
|
+
id: w.id,
|
|
2935
|
+
name: w.name,
|
|
2936
|
+
rounds: w.rounds.length,
|
|
2937
|
+
outputCollection: w.kbOutputCollection
|
|
2938
|
+
})),
|
|
2939
|
+
total: workflows.length
|
|
2940
|
+
}
|
|
2941
|
+
)
|
|
2772
2942
|
};
|
|
2773
2943
|
}
|
|
2774
2944
|
async function handleCheckpoint(workflowId, roundId, output, isFinal, summaryName, summaryDescription) {
|
|
@@ -2780,18 +2950,31 @@ async function handleCheckpoint(workflowId, roundId, output, isFinal, summaryNam
|
|
|
2780
2950
|
text: `Workflow "${workflowId}" not found. Available: ${listWorkflows().map((w) => w.id).join(", ")}.
|
|
2781
2951
|
|
|
2782
2952
|
This checkpoint was NOT saved. Continue the conversation \u2014 the facilitator has the context.`
|
|
2783
|
-
}]
|
|
2953
|
+
}],
|
|
2954
|
+
structuredContent: failure(
|
|
2955
|
+
"NOT_FOUND",
|
|
2956
|
+
`Workflow '${workflowId}' not found.`,
|
|
2957
|
+
"Use workflows action=list to see available workflows.",
|
|
2958
|
+
[{ tool: "workflows", description: "List workflows", parameters: { action: "list" } }]
|
|
2959
|
+
)
|
|
2784
2960
|
};
|
|
2785
2961
|
}
|
|
2786
2962
|
const round = wf.rounds.find((r) => r.id === roundId);
|
|
2787
2963
|
if (!round) {
|
|
2964
|
+
const availableRounds = wf.rounds.map((r) => r.id).join(", ");
|
|
2788
2965
|
return {
|
|
2789
2966
|
content: [{
|
|
2790
2967
|
type: "text",
|
|
2791
|
-
text: `Round "${roundId}" not found in workflow "${workflowId}". Available rounds: ${
|
|
2968
|
+
text: `Round "${roundId}" not found in workflow "${workflowId}". Available rounds: ${availableRounds}.
|
|
2792
2969
|
|
|
2793
2970
|
This checkpoint was NOT saved. The conversation context is preserved \u2014 continue facilitating.`
|
|
2794
|
-
}]
|
|
2971
|
+
}],
|
|
2972
|
+
structuredContent: failure(
|
|
2973
|
+
"NOT_FOUND",
|
|
2974
|
+
`Round '${roundId}' not found in workflow '${workflowId}'.`,
|
|
2975
|
+
`Available rounds: ${availableRounds}`,
|
|
2976
|
+
[{ tool: "workflows", description: "List workflows", parameters: { action: "list" } }]
|
|
2977
|
+
)
|
|
2795
2978
|
};
|
|
2796
2979
|
}
|
|
2797
2980
|
const lines = [
|
|
@@ -2822,6 +3005,31 @@ This checkpoint was NOT saved. The conversation context is preserved \u2014 cont
|
|
|
2822
3005
|
`The summary is captured as a draft. Use \`commit-entry entryId="${result.entryId}"\` to promote it to the Chain.`,
|
|
2823
3006
|
`Use \`graph action=suggest entryId="${result.entryId}"\` to discover connections.`
|
|
2824
3007
|
);
|
|
3008
|
+
return {
|
|
3009
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
3010
|
+
structuredContent: success(
|
|
3011
|
+
`Draft '${summaryName}' created (${result.entryId}) for workflow ${workflowId}.`,
|
|
3012
|
+
{
|
|
3013
|
+
entryId: result.entryId,
|
|
3014
|
+
workflowId,
|
|
3015
|
+
roundId,
|
|
3016
|
+
collection: wf.kbOutputCollection,
|
|
3017
|
+
isFinal: true
|
|
3018
|
+
},
|
|
3019
|
+
[
|
|
3020
|
+
{
|
|
3021
|
+
tool: "commit-entry",
|
|
3022
|
+
description: "Promote draft to Chain",
|
|
3023
|
+
parameters: { entryId: result.entryId }
|
|
3024
|
+
},
|
|
3025
|
+
{
|
|
3026
|
+
tool: "graph",
|
|
3027
|
+
description: "Discover connections",
|
|
3028
|
+
parameters: { action: "suggest", entryId: result.entryId }
|
|
3029
|
+
}
|
|
3030
|
+
]
|
|
3031
|
+
)
|
|
3032
|
+
};
|
|
2825
3033
|
} catch (err) {
|
|
2826
3034
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2827
3035
|
lines.push(
|
|
@@ -2833,6 +3041,15 @@ This checkpoint was NOT saved. The conversation context is preserved \u2014 cont
|
|
|
2833
3041
|
`- Name: ${summaryName}`,
|
|
2834
3042
|
`- Description: (copy from the conversation summary above)`
|
|
2835
3043
|
);
|
|
3044
|
+
return {
|
|
3045
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
3046
|
+
structuredContent: failure(
|
|
3047
|
+
"BACKEND_ERROR",
|
|
3048
|
+
`Chain commit failed: ${msg}.`,
|
|
3049
|
+
"Capture manually with the capture tool.",
|
|
3050
|
+
[{ tool: "capture", description: "Capture manually", parameters: {} }]
|
|
3051
|
+
)
|
|
3052
|
+
};
|
|
2836
3053
|
}
|
|
2837
3054
|
} else {
|
|
2838
3055
|
lines.push(
|
|
@@ -2840,8 +3057,10 @@ This checkpoint was NOT saved. The conversation context is preserved \u2014 cont
|
|
|
2840
3057
|
`Output: ${output.substring(0, 200)}${output.length > 200 ? "..." : ""}`
|
|
2841
3058
|
);
|
|
2842
3059
|
const currentIdx = wf.rounds.findIndex((r) => r.id === roundId);
|
|
3060
|
+
let nextRound;
|
|
2843
3061
|
if (currentIdx < wf.rounds.length - 1) {
|
|
2844
3062
|
const next = wf.rounds[currentIdx + 1];
|
|
3063
|
+
nextRound = { id: next.id, num: next.num, label: next.label };
|
|
2845
3064
|
lines.push(
|
|
2846
3065
|
"",
|
|
2847
3066
|
`**Next**: Round ${next.num} \u2014 ${next.label}`,
|
|
@@ -2853,8 +3072,21 @@ This checkpoint was NOT saved. The conversation context is preserved \u2014 cont
|
|
|
2853
3072
|
`**All rounds complete.** Call this tool again with \`isFinal: true\` to commit to the Chain.`
|
|
2854
3073
|
);
|
|
2855
3074
|
}
|
|
3075
|
+
return {
|
|
3076
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
3077
|
+
structuredContent: success(
|
|
3078
|
+
`Round ${round.num} (${round.label}) output recorded for workflow ${workflowId}.`,
|
|
3079
|
+
{
|
|
3080
|
+
workflowId,
|
|
3081
|
+
roundId,
|
|
3082
|
+
roundNum: round.num,
|
|
3083
|
+
roundLabel: round.label,
|
|
3084
|
+
isFinal: false,
|
|
3085
|
+
nextRound
|
|
3086
|
+
}
|
|
3087
|
+
)
|
|
3088
|
+
};
|
|
2856
3089
|
}
|
|
2857
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
2858
3090
|
}
|
|
2859
3091
|
|
|
2860
3092
|
// src/tools/facilitate.ts
|
|
@@ -3810,13 +4042,11 @@ function registerFacilitateTools(server) {
|
|
|
3810
4042
|
openWorldHint: false
|
|
3811
4043
|
}
|
|
3812
4044
|
},
|
|
3813
|
-
async (args) => {
|
|
4045
|
+
withEnvelope(async (args) => {
|
|
3814
4046
|
const parsed = facilitateSchema.safeParse(args);
|
|
3815
4047
|
if (!parsed.success) {
|
|
3816
4048
|
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
3817
|
-
return {
|
|
3818
|
-
content: [{ type: "text", text: `Invalid arguments: ${issues}` }]
|
|
3819
|
-
};
|
|
4049
|
+
return validationResult(`Invalid arguments: ${issues}`);
|
|
3820
4050
|
}
|
|
3821
4051
|
const { action } = parsed.data;
|
|
3822
4052
|
return runWithToolContext({ tool: "facilitate", action }, async () => {
|
|
@@ -3832,15 +4062,10 @@ function registerFacilitateTools(server) {
|
|
|
3832
4062
|
case "commit-constellation":
|
|
3833
4063
|
return handleCommitConstellation(parsed.data);
|
|
3834
4064
|
default:
|
|
3835
|
-
return {
|
|
3836
|
-
content: [{
|
|
3837
|
-
type: "text",
|
|
3838
|
-
text: `Unknown action '${action}'. Valid: ${FACILITATE_ACTIONS.join(", ")}.`
|
|
3839
|
-
}]
|
|
3840
|
-
};
|
|
4065
|
+
return validationResult(`Unknown action '${action}'. Valid: ${FACILITATE_ACTIONS.join(", ")}.`);
|
|
3841
4066
|
}
|
|
3842
4067
|
});
|
|
3843
|
-
}
|
|
4068
|
+
})
|
|
3844
4069
|
);
|
|
3845
4070
|
}
|
|
3846
4071
|
function buildStudioUrl(workspaceSlug, entryId) {
|
|
@@ -4178,7 +4403,11 @@ async function handleStart2(args) {
|
|
|
4178
4403
|
].filter(Boolean).join("\n");
|
|
4179
4404
|
return {
|
|
4180
4405
|
content: [{ type: "text", text: output }],
|
|
4181
|
-
structuredContent:
|
|
4406
|
+
structuredContent: success(
|
|
4407
|
+
`Shaping session started for "${betName}". Phase: context.`,
|
|
4408
|
+
response,
|
|
4409
|
+
[{ tool: "facilitate", description: "Describe the problem", parameters: { action: "respond", betName, dimension: "problem_clarity" } }]
|
|
4410
|
+
)
|
|
4182
4411
|
};
|
|
4183
4412
|
}
|
|
4184
4413
|
async function processCaptures(opts) {
|
|
@@ -4566,9 +4795,14 @@ function assembleResponse(opts) {
|
|
|
4566
4795
|
sessionDrafts.length > 0 ? `**Session drafts (${sessionDrafts.length}):** ${sessionDrafts.map((d) => `\`${d.entryId}\` ${d.name}`).join(", ")}` : "",
|
|
4567
4796
|
captureErrors.length > 0 ? `**Warnings:** ${captureErrors.map((e) => e.detail).join("; ")}` : ""
|
|
4568
4797
|
];
|
|
4798
|
+
const summaryText = `${PHASE_LABELS[phase]} \u2014 ${DIMENSION_LABELS[activeDimension]}: ${scorecard[activeDimension]}/10.${coaching.captureReady ? " Capture ready." : ""}`;
|
|
4569
4799
|
return {
|
|
4570
4800
|
content: [{ type: "text", text: summaryParts.filter(Boolean).join("\n") }],
|
|
4571
|
-
structuredContent:
|
|
4801
|
+
structuredContent: success(
|
|
4802
|
+
summaryText,
|
|
4803
|
+
response,
|
|
4804
|
+
[{ tool: "facilitate", description: "Continue shaping", parameters: { action: "respond", betEntryId, dimension: nextDimension } }]
|
|
4805
|
+
)
|
|
4572
4806
|
};
|
|
4573
4807
|
}
|
|
4574
4808
|
async function handleRespond(args) {
|
|
@@ -4577,9 +4811,7 @@ async function handleRespond(args) {
|
|
|
4577
4811
|
const source = argSource ?? "user";
|
|
4578
4812
|
const captureItems = rawCapture ? Array.isArray(rawCapture) ? rawCapture : [rawCapture] : [];
|
|
4579
4813
|
if (!userInput) {
|
|
4580
|
-
return
|
|
4581
|
-
content: [{ type: "text", text: "`userInput` is required for respond action." }]
|
|
4582
|
-
};
|
|
4814
|
+
return validationResult("`userInput` is required for respond action.");
|
|
4583
4815
|
}
|
|
4584
4816
|
let betId = argBetId;
|
|
4585
4817
|
if (!betId) {
|
|
@@ -4614,7 +4846,7 @@ async function handleRespond(args) {
|
|
|
4614
4846
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4615
4847
|
return {
|
|
4616
4848
|
content: [{ type: "text", text: `Failed to create bet entry: ${msg}` }],
|
|
4617
|
-
|
|
4849
|
+
structuredContent: failure("BACKEND_ERROR", `Failed to create bet entry: ${msg}`, "Check workspace access and try again.")
|
|
4618
4850
|
};
|
|
4619
4851
|
}
|
|
4620
4852
|
}
|
|
@@ -4625,7 +4857,13 @@ async function handleRespond(args) {
|
|
|
4625
4857
|
]);
|
|
4626
4858
|
if (!betEntry) {
|
|
4627
4859
|
return {
|
|
4628
|
-
content: [{ type: "text", text: `Bet \`${betId}\` not found. Use start to create a session.` }]
|
|
4860
|
+
content: [{ type: "text", text: `Bet \`${betId}\` not found. Use start to create a session.` }],
|
|
4861
|
+
structuredContent: failure(
|
|
4862
|
+
"NOT_FOUND",
|
|
4863
|
+
`Bet '${betId}' not found.`,
|
|
4864
|
+
"Use facilitate action=start to begin a new session.",
|
|
4865
|
+
[{ tool: "facilitate", description: "Start session", parameters: { action: "start" } }]
|
|
4866
|
+
)
|
|
4629
4867
|
};
|
|
4630
4868
|
}
|
|
4631
4869
|
const betData = betEntry.data ?? {};
|
|
@@ -4687,14 +4925,18 @@ async function handleRespond(args) {
|
|
|
4687
4925
|
async function handleScore(args) {
|
|
4688
4926
|
const betId = args.betEntryId;
|
|
4689
4927
|
if (!betId) {
|
|
4690
|
-
return
|
|
4691
|
-
content: [{ type: "text", text: "`betEntryId` is required for score action." }]
|
|
4692
|
-
};
|
|
4928
|
+
return validationResult("`betEntryId` is required for score action.");
|
|
4693
4929
|
}
|
|
4694
4930
|
const betEntry = await loadBetEntry(betId);
|
|
4695
4931
|
if (!betEntry) {
|
|
4696
4932
|
return {
|
|
4697
|
-
content: [{ type: "text", text: `Bet \`${betId}\` not found.` }]
|
|
4933
|
+
content: [{ type: "text", text: `Bet \`${betId}\` not found.` }],
|
|
4934
|
+
structuredContent: failure(
|
|
4935
|
+
"NOT_FOUND",
|
|
4936
|
+
`Bet '${betId}' not found.`,
|
|
4937
|
+
"Use facilitate action=start to begin a session.",
|
|
4938
|
+
[{ tool: "facilitate", description: "Start session", parameters: { action: "start" } }]
|
|
4939
|
+
)
|
|
4698
4940
|
};
|
|
4699
4941
|
}
|
|
4700
4942
|
const constellation = await loadConstellationState(betId, betEntry._id);
|
|
@@ -4764,31 +5006,38 @@ async function handleScore(args) {
|
|
|
4764
5006
|
}
|
|
4765
5007
|
return {
|
|
4766
5008
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
4767
|
-
structuredContent:
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
5009
|
+
structuredContent: success(
|
|
5010
|
+
`Scorecard for ${betEntry.name}: ${completed.length}/${dims.length} complete. Phase: ${PHASE_LABELS[phase]}.${captureReady ? " Capture ready." : ""}`,
|
|
5011
|
+
{
|
|
5012
|
+
scorecard,
|
|
5013
|
+
phase,
|
|
5014
|
+
phaseLabel: PHASE_LABELS[phase],
|
|
5015
|
+
completedDimensions: completed,
|
|
5016
|
+
nextDimension: active,
|
|
5017
|
+
sessionDrafts,
|
|
5018
|
+
captureReady,
|
|
5019
|
+
constellationDrafts,
|
|
5020
|
+
commitBlockers: commitBlockers.length > 0 ? commitBlockers : void 0,
|
|
5021
|
+
scorecardCriteria
|
|
5022
|
+
}
|
|
5023
|
+
)
|
|
4779
5024
|
};
|
|
4780
5025
|
}
|
|
4781
5026
|
async function handleResume(args) {
|
|
4782
5027
|
const betId = args.betEntryId;
|
|
4783
5028
|
if (!betId) {
|
|
4784
|
-
return
|
|
4785
|
-
content: [{ type: "text", text: "`betEntryId` is required for resume action." }]
|
|
4786
|
-
};
|
|
5029
|
+
return validationResult("`betEntryId` is required for resume action.");
|
|
4787
5030
|
}
|
|
4788
5031
|
const betEntry = await loadBetEntry(betId);
|
|
4789
5032
|
if (!betEntry) {
|
|
4790
5033
|
return {
|
|
4791
|
-
content: [{ type: "text", text: `Bet \`${betId}\` not found. Cannot resume.` }]
|
|
5034
|
+
content: [{ type: "text", text: `Bet \`${betId}\` not found. Cannot resume.` }],
|
|
5035
|
+
structuredContent: failure(
|
|
5036
|
+
"NOT_FOUND",
|
|
5037
|
+
`Bet '${betId}' not found.`,
|
|
5038
|
+
"Use facilitate action=start to begin a new session.",
|
|
5039
|
+
[{ tool: "facilitate", description: "Start session", parameters: { action: "start" } }]
|
|
5040
|
+
)
|
|
4792
5041
|
};
|
|
4793
5042
|
}
|
|
4794
5043
|
const constellation = await loadConstellationState(betId, betEntry._id);
|
|
@@ -4875,21 +5124,29 @@ ${directive}` : ""
|
|
|
4875
5124
|
].filter(Boolean).join("\n");
|
|
4876
5125
|
return {
|
|
4877
5126
|
content: [{ type: "text", text: output }],
|
|
4878
|
-
structuredContent:
|
|
5127
|
+
structuredContent: success(
|
|
5128
|
+
`Resumed session for "${betEntry.name}". Phase: ${PHASE_LABELS[phase]}. Next: ${DIMENSION_LABELS[active]}.`,
|
|
5129
|
+
response,
|
|
5130
|
+
[{ tool: "facilitate", description: `Continue with ${DIMENSION_LABELS[active]}`, parameters: { action: "respond", betEntryId: betId, dimension: active } }]
|
|
5131
|
+
)
|
|
4879
5132
|
};
|
|
4880
5133
|
}
|
|
4881
5134
|
async function handleCommitConstellation(args) {
|
|
4882
5135
|
requireWriteAccess();
|
|
4883
5136
|
const betId = args.betEntryId;
|
|
4884
5137
|
if (!betId) {
|
|
4885
|
-
return
|
|
4886
|
-
content: [{ type: "text", text: "`betEntryId` is required for commit-constellation action." }]
|
|
4887
|
-
};
|
|
5138
|
+
return validationResult("`betEntryId` is required for commit-constellation action.");
|
|
4888
5139
|
}
|
|
4889
5140
|
const betEntry = await loadBetEntry(betId);
|
|
4890
5141
|
if (!betEntry) {
|
|
4891
5142
|
return {
|
|
4892
|
-
content: [{ type: "text", text: `Bet \`${betId}\` not found.` }]
|
|
5143
|
+
content: [{ type: "text", text: `Bet \`${betId}\` not found.` }],
|
|
5144
|
+
structuredContent: failure(
|
|
5145
|
+
"NOT_FOUND",
|
|
5146
|
+
`Bet '${betId}' not found.`,
|
|
5147
|
+
"Use facilitate action=start to begin a session.",
|
|
5148
|
+
[{ tool: "facilitate", description: "Start session", parameters: { action: "start" } }]
|
|
5149
|
+
)
|
|
4893
5150
|
};
|
|
4894
5151
|
}
|
|
4895
5152
|
const betData = betEntry.data ?? {};
|
|
@@ -4934,21 +5191,23 @@ async function handleCommitConstellation(args) {
|
|
|
4934
5191
|
"No entries were committed."
|
|
4935
5192
|
].join("\n")
|
|
4936
5193
|
}],
|
|
4937
|
-
structuredContent:
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
5194
|
+
structuredContent: failure(
|
|
5195
|
+
"VALIDATION_ERROR",
|
|
5196
|
+
`${commitBlockerItems.length} blocker(s) found. Fix before committing.`,
|
|
5197
|
+
commitBlockerItems.map((b) => `${b.blocker} \u2192 ${b.fix}`).join("; "),
|
|
5198
|
+
void 0,
|
|
5199
|
+
{
|
|
5200
|
+
operationId,
|
|
5201
|
+
blockers: commitBlockerItems,
|
|
5202
|
+
committedIds: [],
|
|
5203
|
+
totalRelations: relations.length
|
|
5204
|
+
}
|
|
5205
|
+
)
|
|
4947
5206
|
};
|
|
4948
5207
|
}
|
|
4949
5208
|
let contradictionWarnings = [];
|
|
4950
5209
|
try {
|
|
4951
|
-
const { runContradictionCheck } = await import("./smart-capture-
|
|
5210
|
+
const { runContradictionCheck } = await import("./smart-capture-XLBFE252.js");
|
|
4952
5211
|
const descField = betData.problem ?? betData.description ?? "";
|
|
4953
5212
|
contradictionWarnings = await runContradictionCheck(
|
|
4954
5213
|
betEntry.name ?? betId,
|
|
@@ -4980,7 +5239,12 @@ async function handleCommitConstellation(args) {
|
|
|
4980
5239
|
|
|
4981
5240
|
No constellation entries were committed.`
|
|
4982
5241
|
}],
|
|
4983
|
-
|
|
5242
|
+
structuredContent: failure(
|
|
5243
|
+
"BACKEND_ERROR",
|
|
5244
|
+
`Bet commit failed: ${msg}`,
|
|
5245
|
+
"Retry or check for commit blockers.",
|
|
5246
|
+
[{ tool: "facilitate", description: "Check scorecard", parameters: { action: "score", betEntryId: betId } }]
|
|
5247
|
+
)
|
|
4984
5248
|
};
|
|
4985
5249
|
}
|
|
4986
5250
|
result = {
|
|
@@ -5043,9 +5307,13 @@ No constellation entries were committed.`
|
|
|
5043
5307
|
lines.push(`- \`${f.entryId}\` ${f.name}: ${f.error}`);
|
|
5044
5308
|
}
|
|
5045
5309
|
}
|
|
5310
|
+
const summaryText = result.proposalCreated ? `Proposal created for ${betId}. ${result.committedIds.length} entries committed.` : `Published ${result.committedIds.length} entries for ${betId}. ${result.totalRelations} connections preserved.${hasIssues ? ` ${result.failedIds.length} failed, ${result.conflictIds.length} conflicts.` : ""}`;
|
|
5046
5311
|
return {
|
|
5047
5312
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
5048
|
-
structuredContent:
|
|
5313
|
+
structuredContent: success(
|
|
5314
|
+
summaryText,
|
|
5315
|
+
{ ...result, contradictionWarnings }
|
|
5316
|
+
)
|
|
5049
5317
|
};
|
|
5050
5318
|
}
|
|
5051
5319
|
|
|
@@ -5209,15 +5477,14 @@ function registerVerifyTools(server) {
|
|
|
5209
5477
|
inputSchema: verifySchema,
|
|
5210
5478
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
5211
5479
|
},
|
|
5212
|
-
async ({ collection, mode }) => {
|
|
5480
|
+
withEnvelope(async ({ collection, mode }) => {
|
|
5213
5481
|
const projectRoot = resolveProjectRoot();
|
|
5214
5482
|
if (!projectRoot) {
|
|
5215
|
-
return
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
};
|
|
5483
|
+
return failureResult(
|
|
5484
|
+
"# Verification Failed\n\nCannot find project root (looked for `convex/schema.ts` in cwd and parent directory).\n\nSet `WORKSPACE_PATH` in `.env.mcp` to the absolute path of the Product Brain project root.",
|
|
5485
|
+
"VALIDATION_ERROR",
|
|
5486
|
+
"Cannot find project root. Set WORKSPACE_PATH in .env.mcp."
|
|
5487
|
+
);
|
|
5221
5488
|
}
|
|
5222
5489
|
const schema = parseConvexSchema(resolve(projectRoot, "convex/schema.ts"));
|
|
5223
5490
|
await server.sendLoggingMessage({
|
|
@@ -5227,9 +5494,13 @@ function registerVerifyTools(server) {
|
|
|
5227
5494
|
});
|
|
5228
5495
|
const scopedEntries = await mcpQuery("chain.listEntries", { collectionSlug: collection });
|
|
5229
5496
|
if (scopedEntries.length === 0) {
|
|
5230
|
-
return
|
|
5231
|
-
|
|
5232
|
-
|
|
5497
|
+
return failureResult(
|
|
5498
|
+
`No entries found in '${collection}'. Nothing to verify.`,
|
|
5499
|
+
"NOT_FOUND",
|
|
5500
|
+
`No entries found in '${collection}'. Nothing to verify.`,
|
|
5501
|
+
"Capture entries first, then verify.",
|
|
5502
|
+
[{ tool: "capture", description: "Capture an entry", parameters: {} }]
|
|
5503
|
+
);
|
|
5233
5504
|
}
|
|
5234
5505
|
await server.sendLoggingMessage({
|
|
5235
5506
|
level: "info",
|
|
@@ -5323,6 +5594,14 @@ function registerVerifyTools(server) {
|
|
|
5323
5594
|
fixes.push(entry.entryId);
|
|
5324
5595
|
}
|
|
5325
5596
|
}
|
|
5597
|
+
const verified = mappingChecks.filter((c) => c.result === "verified").length;
|
|
5598
|
+
const drifted = mappingChecks.filter((c) => c.result === "drifted").length;
|
|
5599
|
+
const unverifiable = mappingChecks.filter((c) => c.result === "unverifiable").length;
|
|
5600
|
+
const refsValid = refChecks.filter((c) => c.found).length;
|
|
5601
|
+
const refsBroken = refChecks.filter((c) => !c.found).length;
|
|
5602
|
+
const totalChecks = mappingChecks.length + refChecks.length;
|
|
5603
|
+
const totalPassed = verified + refsValid;
|
|
5604
|
+
const trustScore = totalChecks > 0 ? Math.round(totalPassed / totalChecks * 100) : 100;
|
|
5326
5605
|
const report = formatTrustReport(
|
|
5327
5606
|
collection,
|
|
5328
5607
|
scopedEntries.length,
|
|
@@ -5333,8 +5612,27 @@ function registerVerifyTools(server) {
|
|
|
5333
5612
|
schema.size,
|
|
5334
5613
|
projectRoot
|
|
5335
5614
|
);
|
|
5336
|
-
return {
|
|
5337
|
-
|
|
5615
|
+
return {
|
|
5616
|
+
content: [{ type: "text", text: report }],
|
|
5617
|
+
structuredContent: success(
|
|
5618
|
+
`Trust report for ${collection}: ${totalPassed}/${totalChecks} checks passed (${trustScore}%).`,
|
|
5619
|
+
{
|
|
5620
|
+
collection,
|
|
5621
|
+
entryCount: scopedEntries.length,
|
|
5622
|
+
trustScore,
|
|
5623
|
+
totalChecks,
|
|
5624
|
+
totalPassed,
|
|
5625
|
+
verified,
|
|
5626
|
+
drifted,
|
|
5627
|
+
unverifiable,
|
|
5628
|
+
refsValid,
|
|
5629
|
+
refsBroken,
|
|
5630
|
+
fixesApplied: fixes.length,
|
|
5631
|
+
mode
|
|
5632
|
+
}
|
|
5633
|
+
)
|
|
5634
|
+
};
|
|
5635
|
+
})
|
|
5338
5636
|
);
|
|
5339
5637
|
trackWriteTool(verifyTool);
|
|
5340
5638
|
}
|
|
@@ -5815,7 +6113,7 @@ function registerStartTools(server) {
|
|
|
5815
6113
|
inputSchema: startSchema,
|
|
5816
6114
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
5817
6115
|
},
|
|
5818
|
-
async ({ preset }) => {
|
|
6116
|
+
withEnvelope(async ({ preset }) => {
|
|
5819
6117
|
const errors = [];
|
|
5820
6118
|
const agentSessionId = getAgentSessionId();
|
|
5821
6119
|
let wsCtx = null;
|
|
@@ -5825,13 +6123,14 @@ function registerStartTools(server) {
|
|
|
5825
6123
|
errors.push(`Workspace: ${e.message}`);
|
|
5826
6124
|
}
|
|
5827
6125
|
if (!wsCtx) {
|
|
6126
|
+
const text = "# Could not connect to Product Brain\n\n" + (errors.length > 0 ? errors.map((e) => `- ${e}`).join("\n") : "Check your API key and CONVEX_SITE_URL.");
|
|
5828
6127
|
return {
|
|
5829
|
-
content: [
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
6128
|
+
content: [{ type: "text", text }],
|
|
6129
|
+
structuredContent: failure(
|
|
6130
|
+
"BACKEND_UNAVAILABLE",
|
|
6131
|
+
"Could not connect to Product Brain.",
|
|
6132
|
+
"Check your API key and CONVEX_SITE_URL."
|
|
6133
|
+
)
|
|
5835
6134
|
};
|
|
5836
6135
|
}
|
|
5837
6136
|
let stage = null;
|
|
@@ -5851,15 +6150,14 @@ function registerStartTools(server) {
|
|
|
5851
6150
|
}
|
|
5852
6151
|
if (stage === "blank") {
|
|
5853
6152
|
const { oriented, orientationStatus } = await tryMarkOriented(agentSessionId);
|
|
5854
|
-
const blankResult = buildBlankResponse(wsCtx
|
|
6153
|
+
const blankResult = buildBlankResponse(wsCtx, {
|
|
6154
|
+
oriented,
|
|
6155
|
+
orientationStatus,
|
|
6156
|
+
...agentSessionId ? { sessionId: agentSessionId } : {}
|
|
6157
|
+
});
|
|
5855
6158
|
return {
|
|
5856
6159
|
content: [{ type: "text", text: blankResult.text }],
|
|
5857
|
-
structuredContent:
|
|
5858
|
-
...blankResult.structuredContent,
|
|
5859
|
-
oriented,
|
|
5860
|
-
orientationStatus,
|
|
5861
|
-
...agentSessionId ? { sessionId: agentSessionId } : {}
|
|
5862
|
-
}
|
|
6160
|
+
structuredContent: blankResult.structuredContent
|
|
5863
6161
|
};
|
|
5864
6162
|
}
|
|
5865
6163
|
if (stage === "seeded") {
|
|
@@ -5880,10 +6178,10 @@ function registerStartTools(server) {
|
|
|
5880
6178
|
content: [{ type: "text", text: orientResult.text }],
|
|
5881
6179
|
structuredContent: orientResult.structuredContent
|
|
5882
6180
|
};
|
|
5883
|
-
}
|
|
6181
|
+
})
|
|
5884
6182
|
);
|
|
5885
6183
|
}
|
|
5886
|
-
function buildBlankResponse(wsCtx) {
|
|
6184
|
+
function buildBlankResponse(wsCtx, sessionCtx) {
|
|
5887
6185
|
const instructions = getInterviewInstructions(wsCtx.workspaceName);
|
|
5888
6186
|
const text = [
|
|
5889
6187
|
`# Welcome to ${wsCtx.workspaceName}`,
|
|
@@ -5947,7 +6245,14 @@ function buildBlankResponse(wsCtx) {
|
|
|
5947
6245
|
].join("\n");
|
|
5948
6246
|
return {
|
|
5949
6247
|
text,
|
|
5950
|
-
structuredContent:
|
|
6248
|
+
structuredContent: success(
|
|
6249
|
+
`Workspace is blank. Choose: tell me about your product, scan codebase, or use a preset.`,
|
|
6250
|
+
{ stage: "blank", ...sessionCtx },
|
|
6251
|
+
[
|
|
6252
|
+
{ tool: "capture", description: "Capture product knowledge", parameters: {} },
|
|
6253
|
+
{ tool: "start", description: "Seed with a preset", parameters: { preset: "software-product" } }
|
|
6254
|
+
]
|
|
6255
|
+
)
|
|
5951
6256
|
};
|
|
5952
6257
|
}
|
|
5953
6258
|
async function buildSeededResponse(wsCtx, readiness, agentSessionId) {
|
|
@@ -5974,17 +6279,22 @@ async function buildSeededResponse(wsCtx, readiness, agentSessionId) {
|
|
|
5974
6279
|
lines.push("No gaps detected \u2014 your workspace is filling up nicely. What would you like to work on?");
|
|
5975
6280
|
}
|
|
5976
6281
|
const { oriented, orientationStatus } = await tryMarkOriented(agentSessionId);
|
|
6282
|
+
const next = gaps.length > 0 ? [{ tool: "capture", description: `Fill gap: ${gaps[0].label}`, parameters: {} }] : [];
|
|
5977
6283
|
return {
|
|
5978
6284
|
text: lines.join("\n"),
|
|
5979
|
-
structuredContent:
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
6285
|
+
structuredContent: success(
|
|
6286
|
+
gaps.length > 0 ? `Workspace seeded. ${gaps.length} gap${gaps.length === 1 ? "" : "s"} to fill. Top: ${gaps[0].label}.` : `Workspace seeded. No gaps detected.`,
|
|
6287
|
+
{
|
|
6288
|
+
stage,
|
|
6289
|
+
readinessScore: score ?? null,
|
|
6290
|
+
totalGaps: gaps.length,
|
|
6291
|
+
topGapLabels: gaps.slice(0, 3).map((g) => g.label),
|
|
6292
|
+
oriented,
|
|
6293
|
+
orientationStatus,
|
|
6294
|
+
...agentSessionId ? { sessionId: agentSessionId } : {}
|
|
6295
|
+
},
|
|
6296
|
+
next.length > 0 ? next : void 0
|
|
6297
|
+
)
|
|
5988
6298
|
};
|
|
5989
6299
|
}
|
|
5990
6300
|
async function seedPreset(wsCtx, presetId, agentSessionId) {
|
|
@@ -5994,7 +6304,12 @@ async function seedPreset(wsCtx, presetId, agentSessionId) {
|
|
|
5994
6304
|
text: `Preset "${presetId}" not found.
|
|
5995
6305
|
|
|
5996
6306
|
Available presets: ${listPresets().map((p) => `\`${p.id}\``).join(", ")}`,
|
|
5997
|
-
structuredContent:
|
|
6307
|
+
structuredContent: failure(
|
|
6308
|
+
"NOT_FOUND",
|
|
6309
|
+
`Preset '${presetId}' not found.`,
|
|
6310
|
+
`Available presets: ${listPresets().map((p) => p.id).join(", ")}.`,
|
|
6311
|
+
[{ tool: "start", description: "Start with a preset", parameters: { preset: "software-product" } }]
|
|
6312
|
+
)
|
|
5998
6313
|
};
|
|
5999
6314
|
}
|
|
6000
6315
|
const seeded = [];
|
|
@@ -6039,15 +6354,19 @@ Available presets: ${listPresets().map((p) => `\`${p.id}\``).join(", ")}`,
|
|
|
6039
6354
|
);
|
|
6040
6355
|
return {
|
|
6041
6356
|
text: lines.join("\n"),
|
|
6042
|
-
structuredContent:
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6357
|
+
structuredContent: success(
|
|
6358
|
+
`Seeded '${preset.name}' preset with ${seeded.length} collection${seeded.length === 1 ? "" : "s"}.`,
|
|
6359
|
+
{
|
|
6360
|
+
stage: "blank",
|
|
6361
|
+
preset: presetId,
|
|
6362
|
+
seededCollections: seeded,
|
|
6363
|
+
skippedCollections: skipped,
|
|
6364
|
+
oriented,
|
|
6365
|
+
orientationStatus,
|
|
6366
|
+
...agentSessionId ? { sessionId: agentSessionId } : {}
|
|
6367
|
+
},
|
|
6368
|
+
[{ tool: "capture", description: "Capture your first entry", parameters: {} }]
|
|
6369
|
+
)
|
|
6051
6370
|
};
|
|
6052
6371
|
}
|
|
6053
6372
|
function computeWorkspaceAge(createdAt) {
|
|
@@ -6255,17 +6574,33 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors) {
|
|
|
6255
6574
|
lines.push("");
|
|
6256
6575
|
}
|
|
6257
6576
|
const { oriented, orientationStatus } = await tryMarkOriented(agentSessionId);
|
|
6577
|
+
const stageLabel = stage ?? "active";
|
|
6578
|
+
const gapCount = readiness?.gaps?.length ?? 0;
|
|
6579
|
+
const summaryParts = [`Product Brain ready. Stage: ${stageLabel}.`];
|
|
6580
|
+
if (gapCount > 0) summaryParts.push(`${gapCount} gap${gapCount === 1 ? "" : "s"}.`);
|
|
6581
|
+
if (openTensions.length > 0) summaryParts.push(`${openTensions.length} open tension${openTensions.length === 1 ? "" : "s"}.`);
|
|
6582
|
+
const next = [];
|
|
6583
|
+
if (gapCount > 0) {
|
|
6584
|
+
next.push({ tool: "capture", description: "Fill a gap", parameters: {} });
|
|
6585
|
+
}
|
|
6586
|
+
if (openTensions.length > 0) {
|
|
6587
|
+
next.push({ tool: "entries", description: "View open tensions", parameters: { action: "list", collection: "tensions" } });
|
|
6588
|
+
}
|
|
6258
6589
|
return {
|
|
6259
6590
|
text: lines.join("\n"),
|
|
6260
|
-
structuredContent:
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6591
|
+
structuredContent: success(
|
|
6592
|
+
summaryParts.join(" "),
|
|
6593
|
+
{
|
|
6594
|
+
stage,
|
|
6595
|
+
readinessScore: readiness?.score ?? null,
|
|
6596
|
+
totalGaps: gapCount,
|
|
6597
|
+
openTensions: openTensions.length,
|
|
6598
|
+
oriented,
|
|
6599
|
+
orientationStatus,
|
|
6600
|
+
...agentSessionId ? { sessionId: agentSessionId } : {}
|
|
6601
|
+
},
|
|
6602
|
+
next.length > 0 ? next : void 0
|
|
6603
|
+
)
|
|
6269
6604
|
};
|
|
6270
6605
|
}
|
|
6271
6606
|
|
|
@@ -6283,21 +6618,19 @@ function registerUsageTools(server) {
|
|
|
6283
6618
|
inputSchema: usageSummarySchema,
|
|
6284
6619
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
6285
6620
|
},
|
|
6286
|
-
async ({ periodDays }) => {
|
|
6621
|
+
withEnvelope(async ({ periodDays }) => {
|
|
6287
6622
|
const ws = await getWorkspaceContext();
|
|
6288
6623
|
const sessionId = getAgentSessionId();
|
|
6289
6624
|
const summary = await mcpQuery("usage.getWorkspaceSummary", {
|
|
6290
6625
|
periodDays: periodDays ?? 30
|
|
6291
6626
|
});
|
|
6292
6627
|
if (!summary) {
|
|
6293
|
-
return
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
]
|
|
6300
|
-
};
|
|
6628
|
+
return failureResult(
|
|
6629
|
+
"No usage data available yet. LLM tracking may not be active or no calls have been made in this period.",
|
|
6630
|
+
"NOT_FOUND",
|
|
6631
|
+
"No usage data available yet.",
|
|
6632
|
+
"LLM tracking may not be active or no calls have been made in this period."
|
|
6633
|
+
);
|
|
6301
6634
|
}
|
|
6302
6635
|
const lines = [
|
|
6303
6636
|
`## Usage Summary \u2014 ${ws.workspaceName}`,
|
|
@@ -6328,9 +6661,21 @@ function registerUsageTools(server) {
|
|
|
6328
6661
|
type: "text",
|
|
6329
6662
|
text: lines.join("\n")
|
|
6330
6663
|
}
|
|
6331
|
-
]
|
|
6664
|
+
],
|
|
6665
|
+
structuredContent: success(
|
|
6666
|
+
`Usage for ${ws.workspaceName}: $${summary.totalCostUsd.toFixed(4)} over ${summary.periodDays} days, ${summary.totalRequests} requests.`,
|
|
6667
|
+
{
|
|
6668
|
+
periodDays: summary.periodDays,
|
|
6669
|
+
totalTokens: summary.totalTokens,
|
|
6670
|
+
totalCostUsd: summary.totalCostUsd,
|
|
6671
|
+
totalRequests: summary.totalRequests,
|
|
6672
|
+
cacheHitRate: summary.cacheHitRate,
|
|
6673
|
+
modelBreakdown: summary.modelBreakdown,
|
|
6674
|
+
featureBreakdown: summary.featureBreakdown
|
|
6675
|
+
}
|
|
6676
|
+
)
|
|
6332
6677
|
};
|
|
6333
|
-
}
|
|
6678
|
+
})
|
|
6334
6679
|
);
|
|
6335
6680
|
}
|
|
6336
6681
|
|
|
@@ -6389,9 +6734,11 @@ function registerGitChainTools(server) {
|
|
|
6389
6734
|
inputSchema: chainSchema,
|
|
6390
6735
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
6391
6736
|
},
|
|
6392
|
-
async ({ action, chainEntryId, title, chainTypeId, description, linkId, content, status, author }) => {
|
|
6737
|
+
withEnvelope(async ({ action, chainEntryId, title, chainTypeId, description, linkId, content, status, author }) => {
|
|
6393
6738
|
if (action === "create") {
|
|
6394
|
-
if (!title)
|
|
6739
|
+
if (!title) {
|
|
6740
|
+
return validationResult("A `title` is required to create a process.");
|
|
6741
|
+
}
|
|
6395
6742
|
const result = await mcpMutation(
|
|
6396
6743
|
"gitchain.createChain",
|
|
6397
6744
|
{ title, chainTypeId, description, author }
|
|
@@ -6407,14 +6754,29 @@ function registerGitChainTools(server) {
|
|
|
6407
6754
|
- **Status:** draft
|
|
6408
6755
|
|
|
6409
6756
|
Use \`chain action=edit chainEntryId="${result.entryId}" linkId="problem" content="..."\` to start filling in links.`
|
|
6410
|
-
}]
|
|
6757
|
+
}],
|
|
6758
|
+
structuredContent: success(
|
|
6759
|
+
`Created process '${title}' (${result.entryId}), type: ${chainTypeId}.`,
|
|
6760
|
+
{ entryId: result.entryId, title, chainTypeId, status: "draft" },
|
|
6761
|
+
[{ tool: "chain", description: "Edit a link", parameters: { action: "edit", chainEntryId: result.entryId, linkId: "problem" } }]
|
|
6762
|
+
)
|
|
6411
6763
|
};
|
|
6412
6764
|
}
|
|
6413
6765
|
if (action === "get") {
|
|
6414
|
-
if (!chainEntryId)
|
|
6766
|
+
if (!chainEntryId) {
|
|
6767
|
+
return validationResult("A `chainEntryId` is required.");
|
|
6768
|
+
}
|
|
6415
6769
|
const chain = await mcpQuery("gitchain.getChain", { chainEntryId });
|
|
6416
6770
|
if (!chain) {
|
|
6417
|
-
return {
|
|
6771
|
+
return {
|
|
6772
|
+
content: [{ type: "text", text: `Process "${chainEntryId}" not found.` }],
|
|
6773
|
+
structuredContent: failure(
|
|
6774
|
+
"NOT_FOUND",
|
|
6775
|
+
`Process "${chainEntryId}" not found.`,
|
|
6776
|
+
"Use chain action=list to find available processes.",
|
|
6777
|
+
[{ tool: "chain", description: "List processes", parameters: { action: "list" } }]
|
|
6778
|
+
)
|
|
6779
|
+
};
|
|
6418
6780
|
}
|
|
6419
6781
|
const scoreSection = chain.scores ? `
|
|
6420
6782
|
## Coherence: ${chain.coherenceScore}%
|
|
@@ -6437,28 +6799,48 @@ Use \`chain action=edit chainEntryId="${result.entryId}" linkId="problem" conten
|
|
|
6437
6799
|
## Links
|
|
6438
6800
|
|
|
6439
6801
|
` + linkSummary(chain.links)
|
|
6440
|
-
}]
|
|
6802
|
+
}],
|
|
6803
|
+
structuredContent: success(
|
|
6804
|
+
`${chain.name}: ${chain.filledCount}/${chain.totalCount} links, coherence ${chain.coherenceScore}%, v${chain.currentVersion}.`,
|
|
6805
|
+
{ entryId: chain.entryId, name: chain.name, status: chain.status, filledCount: chain.filledCount, totalCount: chain.totalCount, coherenceScore: chain.coherenceScore, currentVersion: chain.currentVersion }
|
|
6806
|
+
)
|
|
6441
6807
|
};
|
|
6442
6808
|
}
|
|
6443
6809
|
if (action === "list") {
|
|
6444
|
-
const chains = await mcpQuery("gitchain.listChains", {
|
|
6445
|
-
chainTypeId,
|
|
6446
|
-
status
|
|
6447
|
-
});
|
|
6810
|
+
const chains = await mcpQuery("gitchain.listChains", { chainTypeId, status });
|
|
6448
6811
|
if (chains.length === 0) {
|
|
6449
|
-
return {
|
|
6812
|
+
return {
|
|
6813
|
+
content: [{ type: "text", text: "No processes found. Use `chain action=create` to create one." }],
|
|
6814
|
+
structuredContent: success(
|
|
6815
|
+
"No processes found.",
|
|
6816
|
+
{ chains: [], total: 0 },
|
|
6817
|
+
[{ tool: "chain", description: "Create a process", parameters: { action: "create" } }]
|
|
6818
|
+
)
|
|
6819
|
+
};
|
|
6450
6820
|
}
|
|
6451
6821
|
const formatted = chains.map(
|
|
6452
6822
|
(c) => `- **\`${c.entryId}\`** ${c.name} \u2014 ${c.chainTypeId} \xB7 ${c.filledCount}/${c.totalCount} links \xB7 coherence: ${c.coherenceScore}% \xB7 status: ${c.status}`
|
|
6453
6823
|
).join("\n");
|
|
6454
|
-
return {
|
|
6824
|
+
return {
|
|
6825
|
+
content: [{ type: "text", text: `# Chains (${chains.length})
|
|
6455
6826
|
|
|
6456
|
-
${formatted}` }]
|
|
6827
|
+
${formatted}` }],
|
|
6828
|
+
structuredContent: success(
|
|
6829
|
+
`Found ${chains.length} process(es).`,
|
|
6830
|
+
{ total: chains.length }
|
|
6831
|
+
)
|
|
6832
|
+
};
|
|
6457
6833
|
}
|
|
6458
6834
|
if (action === "edit") {
|
|
6459
|
-
if (!chainEntryId)
|
|
6460
|
-
|
|
6461
|
-
|
|
6835
|
+
if (!chainEntryId) {
|
|
6836
|
+
return validationResult("A `chainEntryId` is required.");
|
|
6837
|
+
}
|
|
6838
|
+
if (!linkId) {
|
|
6839
|
+
return validationResult("A `linkId` is required (e.g. problem, insight, choice, action, outcome).");
|
|
6840
|
+
}
|
|
6841
|
+
if (!content) {
|
|
6842
|
+
return validationResult("The `content` for the link is required.");
|
|
6843
|
+
}
|
|
6462
6844
|
const result = await mcpMutation("gitchain.editLink", { chainEntryId, linkId, content, author });
|
|
6463
6845
|
return {
|
|
6464
6846
|
content: [{
|
|
@@ -6471,11 +6853,16 @@ ${formatted}` }] };
|
|
|
6471
6853
|
- **Content length:** ${content.length} chars
|
|
6472
6854
|
|
|
6473
6855
|
Use \`chain action=get\` to see the full chain with updated scores.`
|
|
6474
|
-
}]
|
|
6856
|
+
}],
|
|
6857
|
+
structuredContent: success(
|
|
6858
|
+
`Updated link "${result.linkId}" on ${result.entryId}. Status: ${result.status}.`,
|
|
6859
|
+
{ entryId: result.entryId, linkId: result.linkId, status: result.status, contentLength: content.length },
|
|
6860
|
+
[{ tool: "chain", description: "View full chain", parameters: { action: "get", chainEntryId } }]
|
|
6861
|
+
)
|
|
6475
6862
|
};
|
|
6476
6863
|
}
|
|
6477
|
-
return
|
|
6478
|
-
}
|
|
6864
|
+
return unknownAction(action, ["create", "get", "list", "edit"]);
|
|
6865
|
+
})
|
|
6479
6866
|
);
|
|
6480
6867
|
trackWriteTool(chainTool);
|
|
6481
6868
|
const chainVersionTool = server.registerTool(
|
|
@@ -6486,9 +6873,11 @@ Use \`chain action=get\` to see the full chain with updated scores.`
|
|
|
6486
6873
|
inputSchema: chainVersionSchema,
|
|
6487
6874
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
6488
6875
|
},
|
|
6489
|
-
async ({ action, chainEntryId, commitMessage, versionA, versionB, toVersion, author }) => {
|
|
6876
|
+
withEnvelope(async ({ action, chainEntryId, commitMessage, versionA, versionB, toVersion, author }) => {
|
|
6490
6877
|
if (action === "commit") {
|
|
6491
|
-
if (!commitMessage)
|
|
6878
|
+
if (!commitMessage) {
|
|
6879
|
+
return validationResult("A `commitMessage` is required.");
|
|
6880
|
+
}
|
|
6492
6881
|
const result = await mcpMutation("gitchain.commitChain", { chainEntryId, commitMessage, author });
|
|
6493
6882
|
const warning = result.commitLintWarning ? `
|
|
6494
6883
|
|
|
@@ -6504,13 +6893,24 @@ Use \`chain action=get\` to see the full chain with updated scores.`
|
|
|
6504
6893
|
- **Coherence:** ${result.coherenceScore}%
|
|
6505
6894
|
- **Links modified:** ${result.linksModified.length > 0 ? result.linksModified.join(", ") : "none"}
|
|
6506
6895
|
` + warning
|
|
6507
|
-
}]
|
|
6896
|
+
}],
|
|
6897
|
+
structuredContent: success(
|
|
6898
|
+
`Committed v${result.version} on ${result.entryId}. Coherence: ${result.coherenceScore}%.`,
|
|
6899
|
+
{ entryId: result.entryId, version: result.version, coherenceScore: result.coherenceScore, linksModified: result.linksModified, lintWarning: result.commitLintWarning }
|
|
6900
|
+
)
|
|
6508
6901
|
};
|
|
6509
6902
|
}
|
|
6510
6903
|
if (action === "list") {
|
|
6511
6904
|
const commits = await mcpQuery("gitchain.listCommits", { chainEntryId });
|
|
6512
6905
|
if (commits.length === 0) {
|
|
6513
|
-
return {
|
|
6906
|
+
return {
|
|
6907
|
+
content: [{ type: "text", text: `No commits found for chain "${chainEntryId}". Use \`chain-version action=commit\` to create the first snapshot.` }],
|
|
6908
|
+
structuredContent: success(
|
|
6909
|
+
`No commits for ${chainEntryId}.`,
|
|
6910
|
+
{ chainEntryId, commits: [], total: 0 },
|
|
6911
|
+
[{ tool: "chain-version", description: "Commit chain", parameters: { action: "commit", chainEntryId } }]
|
|
6912
|
+
)
|
|
6913
|
+
};
|
|
6514
6914
|
}
|
|
6515
6915
|
const formatted = commits.map((c) => {
|
|
6516
6916
|
const date = new Date(c.createdAt).toISOString().replace("T", " ").substring(0, 19);
|
|
@@ -6518,13 +6918,19 @@ Use \`chain action=get\` to see the full chain with updated scores.`
|
|
|
6518
6918
|
const links = c.linksModified?.length > 0 ? ` [${c.linksModified.join(", ")}]` : "";
|
|
6519
6919
|
return `- **v${c.version}** ${date} by ${c.author} \u2014 ${msg}${links} (${c.versionStatus})`;
|
|
6520
6920
|
}).join("\n");
|
|
6521
|
-
return {
|
|
6921
|
+
return {
|
|
6922
|
+
content: [{ type: "text", text: `# Commits for ${chainEntryId} (${commits.length})
|
|
6522
6923
|
|
|
6523
|
-
${formatted}` }]
|
|
6924
|
+
${formatted}` }],
|
|
6925
|
+
structuredContent: success(
|
|
6926
|
+
`${commits.length} commit(s) for ${chainEntryId}.`,
|
|
6927
|
+
{ chainEntryId, total: commits.length }
|
|
6928
|
+
)
|
|
6929
|
+
};
|
|
6524
6930
|
}
|
|
6525
6931
|
if (action === "diff") {
|
|
6526
6932
|
if (versionA == null || versionB == null) {
|
|
6527
|
-
return
|
|
6933
|
+
return validationResult("Both `versionA` and `versionB` are required for diff.");
|
|
6528
6934
|
}
|
|
6529
6935
|
const diff = await mcpMutation("gitchain.diffVersions", { chainEntryId, versionA, versionB });
|
|
6530
6936
|
let text = `# Diff: v${versionA} \u2192 v${versionB}
|
|
@@ -6549,10 +6955,18 @@ ${formatted}` }] };
|
|
|
6549
6955
|
text += "\n";
|
|
6550
6956
|
}
|
|
6551
6957
|
}
|
|
6552
|
-
return {
|
|
6958
|
+
return {
|
|
6959
|
+
content: [{ type: "text", text }],
|
|
6960
|
+
structuredContent: success(
|
|
6961
|
+
`Diff v${versionA} \u2192 v${versionB}: coherence ${diff.coherenceBefore}% \u2192 ${diff.coherenceAfter}%, ${diff.linksChanged.length} link(s) changed.`,
|
|
6962
|
+
{ chainEntryId: diff.chainEntryId, versionA, versionB, coherenceBefore: diff.coherenceBefore, coherenceAfter: diff.coherenceAfter, coherenceDelta: diff.coherenceDelta, linksChanged: diff.linksChanged }
|
|
6963
|
+
)
|
|
6964
|
+
};
|
|
6553
6965
|
}
|
|
6554
6966
|
if (action === "revert") {
|
|
6555
|
-
if (toVersion == null)
|
|
6967
|
+
if (toVersion == null) {
|
|
6968
|
+
return validationResult("A `toVersion` is required for revert.");
|
|
6969
|
+
}
|
|
6556
6970
|
const result = await mcpMutation("gitchain.revertChain", { chainEntryId, toVersion, author });
|
|
6557
6971
|
return {
|
|
6558
6972
|
content: [{
|
|
@@ -6565,24 +6979,40 @@ ${formatted}` }] };
|
|
|
6565
6979
|
- **Links affected:** ${result.linksModified.length > 0 ? result.linksModified.join(", ") : "none"}
|
|
6566
6980
|
|
|
6567
6981
|
History is preserved \u2014 this created a new version, not a destructive reset.`
|
|
6568
|
-
}]
|
|
6982
|
+
}],
|
|
6983
|
+
structuredContent: success(
|
|
6984
|
+
`Reverted ${result.entryId} to v${result.revertedTo}. New version: v${result.newVersion}.`,
|
|
6985
|
+
{ entryId: result.entryId, revertedTo: result.revertedTo, newVersion: result.newVersion, linksModified: result.linksModified }
|
|
6986
|
+
)
|
|
6569
6987
|
};
|
|
6570
6988
|
}
|
|
6571
6989
|
if (action === "history") {
|
|
6572
6990
|
const history = await mcpQuery("gitchain.getHistory", { chainEntryId });
|
|
6573
6991
|
if (history.length === 0) {
|
|
6574
|
-
return {
|
|
6992
|
+
return {
|
|
6993
|
+
content: [{ type: "text", text: `No history found for chain "${chainEntryId}".` }],
|
|
6994
|
+
structuredContent: success(
|
|
6995
|
+
`No history for ${chainEntryId}.`,
|
|
6996
|
+
{ chainEntryId, events: [], total: 0 }
|
|
6997
|
+
)
|
|
6998
|
+
};
|
|
6575
6999
|
}
|
|
6576
7000
|
const formatted = history.sort((a, b) => b.timestamp - a.timestamp).map((h) => {
|
|
6577
7001
|
const date = new Date(h.timestamp).toISOString().replace("T", " ").substring(0, 19);
|
|
6578
7002
|
return `- **${date}** [${h.event}] by ${h.changedBy ?? "unknown"} \u2014 ${h.note ?? ""}`;
|
|
6579
7003
|
}).join("\n");
|
|
6580
|
-
return {
|
|
7004
|
+
return {
|
|
7005
|
+
content: [{ type: "text", text: `# History for ${chainEntryId} (${history.length} events)
|
|
6581
7006
|
|
|
6582
|
-
${formatted}` }]
|
|
7007
|
+
${formatted}` }],
|
|
7008
|
+
structuredContent: success(
|
|
7009
|
+
`${history.length} history event(s) for ${chainEntryId}.`,
|
|
7010
|
+
{ chainEntryId, total: history.length }
|
|
7011
|
+
)
|
|
7012
|
+
};
|
|
6583
7013
|
}
|
|
6584
|
-
return
|
|
6585
|
-
}
|
|
7014
|
+
return unknownAction(action, ["commit", "list", "diff", "revert", "history"]);
|
|
7015
|
+
})
|
|
6586
7016
|
);
|
|
6587
7017
|
trackWriteTool(chainVersionTool);
|
|
6588
7018
|
const chainBranchTool = server.registerTool(
|
|
@@ -6593,7 +7023,7 @@ ${formatted}` }] };
|
|
|
6593
7023
|
inputSchema: chainBranchSchema,
|
|
6594
7024
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
6595
7025
|
},
|
|
6596
|
-
async ({ action, chainEntryId, branchName, strategy, author }) => {
|
|
7026
|
+
withEnvelope(async ({ action, chainEntryId, branchName, strategy, author }) => {
|
|
6597
7027
|
if (action === "create") {
|
|
6598
7028
|
const result = await mcpMutation("gitchain.createBranch", { chainEntryId, name: branchName, author });
|
|
6599
7029
|
return {
|
|
@@ -6606,21 +7036,41 @@ ${formatted}` }] };
|
|
|
6606
7036
|
- **Chain:** \`${chainEntryId}\`
|
|
6607
7037
|
|
|
6608
7038
|
Edit links and commit on this branch, then use \`chain-branch action=merge\` to land changes.`
|
|
6609
|
-
}]
|
|
7039
|
+
}],
|
|
7040
|
+
structuredContent: success(
|
|
7041
|
+
`Created branch "${result.name}" based on v${result.baseVersion}.`,
|
|
7042
|
+
{ branchId: result.branchId, name: result.name, baseVersion: result.baseVersion, chainEntryId },
|
|
7043
|
+
[{ tool: "chain-branch", description: "Merge branch", parameters: { action: "merge", chainEntryId, branchName: result.name } }]
|
|
7044
|
+
)
|
|
6610
7045
|
};
|
|
6611
7046
|
}
|
|
6612
7047
|
if (action === "list") {
|
|
6613
7048
|
const branches = await mcpMutation("gitchain.listBranches", { chainEntryId });
|
|
6614
7049
|
if (branches.length === 0) {
|
|
6615
|
-
return {
|
|
7050
|
+
return {
|
|
7051
|
+
content: [{ type: "text", text: `No branches found for chain "${chainEntryId}".` }],
|
|
7052
|
+
structuredContent: success(
|
|
7053
|
+
`No branches for ${chainEntryId}.`,
|
|
7054
|
+
{ chainEntryId, branches: [], total: 0 },
|
|
7055
|
+
[{ tool: "chain-branch", description: "Create a branch", parameters: { action: "create", chainEntryId } }]
|
|
7056
|
+
)
|
|
7057
|
+
};
|
|
6616
7058
|
}
|
|
6617
7059
|
const formatted = branches.map((b) => `- **${b.name}** (${b.status}) \u2014 based on v${b.baseVersion}, by ${b.createdBy}`).join("\n");
|
|
6618
|
-
return {
|
|
7060
|
+
return {
|
|
7061
|
+
content: [{ type: "text", text: `# Branches for ${chainEntryId} (${branches.length})
|
|
6619
7062
|
|
|
6620
|
-
${formatted}` }]
|
|
7063
|
+
${formatted}` }],
|
|
7064
|
+
structuredContent: success(
|
|
7065
|
+
`${branches.length} branch(es) for ${chainEntryId}.`,
|
|
7066
|
+
{ chainEntryId, total: branches.length }
|
|
7067
|
+
)
|
|
7068
|
+
};
|
|
6621
7069
|
}
|
|
6622
7070
|
if (action === "merge") {
|
|
6623
|
-
if (!branchName)
|
|
7071
|
+
if (!branchName) {
|
|
7072
|
+
return validationResult("A `branchName` is required for merge.");
|
|
7073
|
+
}
|
|
6624
7074
|
const result = await mcpMutation("gitchain.mergeBranch", { chainEntryId, branchName, strategy, author });
|
|
6625
7075
|
return {
|
|
6626
7076
|
content: [{
|
|
@@ -6633,11 +7083,17 @@ ${formatted}` }] };
|
|
|
6633
7083
|
- **Strategy:** ${result.strategy}
|
|
6634
7084
|
|
|
6635
7085
|
Main is now at v${result.version}. The branch has been closed.`
|
|
6636
|
-
}]
|
|
7086
|
+
}],
|
|
7087
|
+
structuredContent: success(
|
|
7088
|
+
`Merged branch "${result.branchName}" into ${result.entryId} at v${result.version}.`,
|
|
7089
|
+
{ entryId: result.entryId, branchName: result.branchName, version: result.version, strategy: result.strategy }
|
|
7090
|
+
)
|
|
6637
7091
|
};
|
|
6638
7092
|
}
|
|
6639
7093
|
if (action === "conflicts") {
|
|
6640
|
-
if (!branchName)
|
|
7094
|
+
if (!branchName) {
|
|
7095
|
+
return validationResult("A `branchName` is required for conflict check.");
|
|
7096
|
+
}
|
|
6641
7097
|
const result = await mcpMutation("gitchain.checkConflicts", { chainEntryId, branchName });
|
|
6642
7098
|
if (!result.hasConflicts) {
|
|
6643
7099
|
return {
|
|
@@ -6646,7 +7102,12 @@ Main is now at v${result.version}. The branch has been closed.`
|
|
|
6646
7102
|
text: `# No Conflicts
|
|
6647
7103
|
|
|
6648
7104
|
Branch "${branchName}" on \`${chainEntryId}\` has no conflicts. Safe to merge.`
|
|
6649
|
-
}]
|
|
7105
|
+
}],
|
|
7106
|
+
structuredContent: success(
|
|
7107
|
+
`No conflicts on branch "${branchName}". Safe to merge.`,
|
|
7108
|
+
{ chainEntryId, branchName, hasConflicts: false },
|
|
7109
|
+
[{ tool: "chain-branch", description: "Merge branch", parameters: { action: "merge", chainEntryId, branchName } }]
|
|
7110
|
+
)
|
|
6650
7111
|
};
|
|
6651
7112
|
}
|
|
6652
7113
|
const conflictLines = result.conflicts.map(
|
|
@@ -6662,11 +7123,16 @@ Branch "${branchName}" conflicts with other branches on these links:
|
|
|
6662
7123
|
` + conflictLines + `
|
|
6663
7124
|
|
|
6664
7125
|
Resolve conflicts before merging.`
|
|
6665
|
-
}]
|
|
7126
|
+
}],
|
|
7127
|
+
structuredContent: failure(
|
|
7128
|
+
"CONFLICT",
|
|
7129
|
+
`Branch "${branchName}" has ${result.conflicts.length} conflict(s).`,
|
|
7130
|
+
"Resolve conflicts on the conflicting links before merging."
|
|
7131
|
+
)
|
|
6666
7132
|
};
|
|
6667
7133
|
}
|
|
6668
|
-
return
|
|
6669
|
-
}
|
|
7134
|
+
return unknownAction(action, ["create", "list", "merge", "conflicts"]);
|
|
7135
|
+
})
|
|
6670
7136
|
);
|
|
6671
7137
|
trackWriteTool(chainBranchTool);
|
|
6672
7138
|
const chainReviewTool = server.registerTool(
|
|
@@ -6677,7 +7143,7 @@ Resolve conflicts before merging.`
|
|
|
6677
7143
|
inputSchema: chainReviewSchema,
|
|
6678
7144
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
6679
7145
|
},
|
|
6680
|
-
async ({ action, chainEntryId, commitMessage, versionNumber, linkId, body, commentId, author }) => {
|
|
7146
|
+
withEnvelope(async ({ action, chainEntryId, commitMessage, versionNumber, linkId, body, commentId, author }) => {
|
|
6681
7147
|
if (action === "gate") {
|
|
6682
7148
|
const gate = await mcpQuery("gitchain.runGate", { chainEntryId, commitMessage });
|
|
6683
7149
|
const checkLines = gate.checks.map((c) => `- ${c.pass ? "PASS" : "FAIL"} **${c.name}**: ${c.detail}`).join("\n");
|
|
@@ -6693,12 +7159,20 @@ Resolve conflicts before merging.`
|
|
|
6693
7159
|
## Checks
|
|
6694
7160
|
|
|
6695
7161
|
${checkLines}`
|
|
6696
|
-
}]
|
|
7162
|
+
}],
|
|
7163
|
+
structuredContent: success(
|
|
7164
|
+
`Gate ${icon}: ${gate.score}% (threshold ${gate.threshold}%).`,
|
|
7165
|
+
{ pass: gate.pass, score: gate.score, threshold: gate.threshold, checks: gate.checks }
|
|
7166
|
+
)
|
|
6697
7167
|
};
|
|
6698
7168
|
}
|
|
6699
7169
|
if (action === "comment") {
|
|
6700
|
-
if (!versionNumber)
|
|
6701
|
-
|
|
7170
|
+
if (!versionNumber) {
|
|
7171
|
+
return validationResult("A `versionNumber` is required.");
|
|
7172
|
+
}
|
|
7173
|
+
if (!body) {
|
|
7174
|
+
return validationResult("A `body` is required.");
|
|
7175
|
+
}
|
|
6702
7176
|
const result = await mcpMutation("gitchain.addComment", {
|
|
6703
7177
|
chainEntryId,
|
|
6704
7178
|
versionNumber,
|
|
@@ -6715,13 +7189,19 @@ ${checkLines}`
|
|
|
6715
7189
|
- **Version:** v${result.versionNumber}
|
|
6716
7190
|
` + (result.linkId ? `- **Link:** ${result.linkId}
|
|
6717
7191
|
` : "") + `- **Body:** ${body.substring(0, 200)}`
|
|
6718
|
-
}]
|
|
7192
|
+
}],
|
|
7193
|
+
structuredContent: success(
|
|
7194
|
+
`Comment added on ${result.chainEntryId} v${result.versionNumber}${result.linkId ? ` [${result.linkId}]` : ""}.`,
|
|
7195
|
+
{ chainEntryId: result.chainEntryId, versionNumber: result.versionNumber, linkId: result.linkId }
|
|
7196
|
+
)
|
|
6719
7197
|
};
|
|
6720
7198
|
}
|
|
6721
7199
|
if (action === "resolve-comment") {
|
|
6722
|
-
if (!commentId)
|
|
7200
|
+
if (!commentId) {
|
|
7201
|
+
return validationResult("A `commentId` is required.");
|
|
7202
|
+
}
|
|
6723
7203
|
await mcpMutation("gitchain.resolveComment", { commentId });
|
|
6724
|
-
return {
|
|
7204
|
+
return successResult("Comment resolved.", `Comment ${commentId} resolved.`, { commentId, resolved: true });
|
|
6725
7205
|
}
|
|
6726
7206
|
if (action === "list-comments") {
|
|
6727
7207
|
const comments = await mcpMutation("gitchain.listComments", {
|
|
@@ -6729,7 +7209,13 @@ ${checkLines}`
|
|
|
6729
7209
|
versionNumber
|
|
6730
7210
|
});
|
|
6731
7211
|
if (comments.length === 0) {
|
|
6732
|
-
return {
|
|
7212
|
+
return {
|
|
7213
|
+
content: [{ type: "text", text: `No comments found for chain "${chainEntryId}"${versionNumber ? ` v${versionNumber}` : ""}.` }],
|
|
7214
|
+
structuredContent: success(
|
|
7215
|
+
`No comments for ${chainEntryId}${versionNumber ? ` v${versionNumber}` : ""}.`,
|
|
7216
|
+
{ chainEntryId, comments: [], total: 0, unresolved: 0 }
|
|
7217
|
+
)
|
|
7218
|
+
};
|
|
6733
7219
|
}
|
|
6734
7220
|
const formatted = comments.map((c) => {
|
|
6735
7221
|
const date = new Date(c.createdAt).toISOString().replace("T", " ").substring(0, 19);
|
|
@@ -6744,11 +7230,15 @@ ${checkLines}`
|
|
|
6744
7230
|
text: `# Comments for ${chainEntryId} (${comments.length}, ${unresolvedCount} unresolved)
|
|
6745
7231
|
|
|
6746
7232
|
${formatted}`
|
|
6747
|
-
}]
|
|
7233
|
+
}],
|
|
7234
|
+
structuredContent: success(
|
|
7235
|
+
`${comments.length} comment(s) for ${chainEntryId}, ${unresolvedCount} unresolved.`,
|
|
7236
|
+
{ chainEntryId, total: comments.length, unresolved: unresolvedCount }
|
|
7237
|
+
)
|
|
6748
7238
|
};
|
|
6749
7239
|
}
|
|
6750
|
-
return
|
|
6751
|
-
}
|
|
7240
|
+
return unknownAction(action, ["gate", "comment", "resolve-comment", "list-comments"]);
|
|
7241
|
+
})
|
|
6752
7242
|
);
|
|
6753
7243
|
trackWriteTool(chainReviewTool);
|
|
6754
7244
|
}
|
|
@@ -6808,7 +7298,7 @@ function registerMapTools(server) {
|
|
|
6808
7298
|
inputSchema: createAudienceMapSetSchema,
|
|
6809
7299
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
6810
7300
|
},
|
|
6811
|
-
async ({ audienceEntryId }) => {
|
|
7301
|
+
withEnvelope(async ({ audienceEntryId }) => {
|
|
6812
7302
|
const result = await mcpMutation(
|
|
6813
7303
|
"maps.createAudienceMapSet",
|
|
6814
7304
|
{ audienceEntryId }
|
|
@@ -6832,9 +7322,18 @@ ${lines.join("\n")}
|
|
|
6832
7322
|
|
|
6833
7323
|
Use map-slot to add ingredients. View at /empathy, /narrowing, and /jobs.`
|
|
6834
7324
|
}
|
|
6835
|
-
]
|
|
7325
|
+
],
|
|
7326
|
+
structuredContent: success(
|
|
7327
|
+
`Created ${result.maps.length} audience maps for ${result.audienceEntryId}.`,
|
|
7328
|
+
{ audienceEntryId: result.audienceEntryId, maps: result.maps },
|
|
7329
|
+
result.maps.map((m) => ({
|
|
7330
|
+
tool: "map-slot",
|
|
7331
|
+
description: `Add ingredients to ${m.templateId}`,
|
|
7332
|
+
parameters: { action: "add", mapEntryId: m.mapEntryId }
|
|
7333
|
+
}))
|
|
7334
|
+
)
|
|
6836
7335
|
};
|
|
6837
|
-
}
|
|
7336
|
+
})
|
|
6838
7337
|
);
|
|
6839
7338
|
trackWriteTool(createAudienceMapSetTool);
|
|
6840
7339
|
const mapTool = server.registerTool(
|
|
@@ -6845,14 +7344,10 @@ Use map-slot to add ingredients. View at /empathy, /narrowing, and /jobs.`
|
|
|
6845
7344
|
inputSchema: mapSchema,
|
|
6846
7345
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
6847
7346
|
},
|
|
6848
|
-
async ({ action, mapEntryId, title, templateId, description, slotIds, status }) => {
|
|
7347
|
+
withEnvelope(async ({ action, mapEntryId, title, templateId, description, slotIds, status }) => {
|
|
6849
7348
|
if (action === "create") {
|
|
6850
7349
|
if (!title) {
|
|
6851
|
-
return
|
|
6852
|
-
content: [
|
|
6853
|
-
{ type: "text", text: "A `title` is required to create a map." }
|
|
6854
|
-
]
|
|
6855
|
-
};
|
|
7350
|
+
return validationResult("A `title` is required to create a map.");
|
|
6856
7351
|
}
|
|
6857
7352
|
const result = await mcpMutation(
|
|
6858
7353
|
"maps.createMap",
|
|
@@ -6873,19 +7368,28 @@ Use map-slot to add ingredients. View at /empathy, /narrowing, and /jobs.`
|
|
|
6873
7368
|
|
|
6874
7369
|
Use \`map-slot action=add mapEntryId="${result.entryId}" slotId="problem" ingredientEntryId="..."\` to start filling slots.`
|
|
6875
7370
|
}
|
|
6876
|
-
]
|
|
7371
|
+
],
|
|
7372
|
+
structuredContent: success(
|
|
7373
|
+
`Created map '${title}' (${result.entryId}) using ${templateId} template.`,
|
|
7374
|
+
{ entryId: result.entryId, title, templateId, status: "draft" },
|
|
7375
|
+
[{ tool: "map-slot", description: "Add ingredient to slot", parameters: { action: "add", mapEntryId: result.entryId } }]
|
|
7376
|
+
)
|
|
6877
7377
|
};
|
|
6878
7378
|
}
|
|
6879
7379
|
if (action === "get") {
|
|
6880
7380
|
if (!mapEntryId) {
|
|
6881
|
-
return
|
|
6882
|
-
content: [{ type: "text", text: "A `mapEntryId` is required." }]
|
|
6883
|
-
};
|
|
7381
|
+
return validationResult("A `mapEntryId` is required.");
|
|
6884
7382
|
}
|
|
6885
7383
|
const map = await mcpQuery("maps.getMap", { mapEntryId });
|
|
6886
7384
|
if (!map) {
|
|
6887
7385
|
return {
|
|
6888
|
-
content: [{ type: "text", text: `Map "${mapEntryId}" not found.` }]
|
|
7386
|
+
content: [{ type: "text", text: `Map "${mapEntryId}" not found.` }],
|
|
7387
|
+
structuredContent: failure(
|
|
7388
|
+
"NOT_FOUND",
|
|
7389
|
+
`Map "${mapEntryId}" not found.`,
|
|
7390
|
+
"Use map action=list to find available maps.",
|
|
7391
|
+
[{ tool: "map", description: "List maps", parameters: { action: "list" } }]
|
|
7392
|
+
)
|
|
6889
7393
|
};
|
|
6890
7394
|
}
|
|
6891
7395
|
return {
|
|
@@ -6904,7 +7408,12 @@ Use \`map-slot action=add mapEntryId="${result.entryId}" slotId="problem" ingred
|
|
|
6904
7408
|
|
|
6905
7409
|
` + slotSummary(map.slots)
|
|
6906
7410
|
}
|
|
6907
|
-
]
|
|
7411
|
+
],
|
|
7412
|
+
structuredContent: success(
|
|
7413
|
+
`${map.name}: ${map.completionScore}% complete (${map.filledSlots}/${map.totalSlots} slots), v${map.currentVersion}.`,
|
|
7414
|
+
{ entryId: map.entryId, name: map.name, templateName: map.templateName, status: map.status, completionScore: map.completionScore, filledSlots: map.filledSlots, totalSlots: map.totalSlots, currentVersion: map.currentVersion },
|
|
7415
|
+
[{ tool: "map-slot", description: "Manage slot ingredients", parameters: { action: "list", mapEntryId } }]
|
|
7416
|
+
)
|
|
6908
7417
|
};
|
|
6909
7418
|
}
|
|
6910
7419
|
if (action === "list") {
|
|
@@ -6914,32 +7423,29 @@ Use \`map-slot action=add mapEntryId="${result.entryId}" slotId="problem" ingred
|
|
|
6914
7423
|
});
|
|
6915
7424
|
if (!maps || maps.length === 0) {
|
|
6916
7425
|
return {
|
|
6917
|
-
content: [
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
}
|
|
6922
|
-
|
|
7426
|
+
content: [{ type: "text", text: 'No maps found. Create one with `map action=create title="My Canvas"`.' }],
|
|
7427
|
+
structuredContent: success(
|
|
7428
|
+
"No maps found.",
|
|
7429
|
+
{ maps: [], total: 0 },
|
|
7430
|
+
[{ tool: "map", description: "Create a map", parameters: { action: "create" } }]
|
|
7431
|
+
)
|
|
6923
7432
|
};
|
|
6924
7433
|
}
|
|
6925
7434
|
const lines = maps.map(
|
|
6926
7435
|
(m) => `- **${m.name}** (\`${m.entryId}\`) \u2014 ${m.templateId}, ${m.completionScore}% complete, v${m.currentVersion}`
|
|
6927
7436
|
);
|
|
6928
7437
|
return {
|
|
6929
|
-
content: [
|
|
6930
|
-
{
|
|
6931
|
-
type: "text",
|
|
6932
|
-
text: `# Maps (${maps.length})
|
|
7438
|
+
content: [{ type: "text", text: `# Maps (${maps.length})
|
|
6933
7439
|
|
|
6934
|
-
${lines.join("\n")}`
|
|
6935
|
-
|
|
6936
|
-
|
|
7440
|
+
${lines.join("\n")}` }],
|
|
7441
|
+
structuredContent: success(
|
|
7442
|
+
`Found ${maps.length} map(s).`,
|
|
7443
|
+
{ maps: maps.map((m) => ({ entryId: m.entryId, name: m.name, templateId: m.templateId, completionScore: m.completionScore })), total: maps.length }
|
|
7444
|
+
)
|
|
6937
7445
|
};
|
|
6938
7446
|
}
|
|
6939
|
-
return
|
|
6940
|
-
|
|
6941
|
-
};
|
|
6942
|
-
}
|
|
7447
|
+
return unknownAction(action, ["create", "get", "list"]);
|
|
7448
|
+
})
|
|
6943
7449
|
);
|
|
6944
7450
|
trackWriteTool(mapTool);
|
|
6945
7451
|
const mapSlotTool = server.registerTool(
|
|
@@ -6950,7 +7456,7 @@ ${lines.join("\n")}`
|
|
|
6950
7456
|
inputSchema: mapSlotSchema,
|
|
6951
7457
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
6952
7458
|
},
|
|
6953
|
-
async ({
|
|
7459
|
+
withEnvelope(async ({
|
|
6954
7460
|
action,
|
|
6955
7461
|
mapEntryId,
|
|
6956
7462
|
slotId,
|
|
@@ -6963,116 +7469,88 @@ ${lines.join("\n")}`
|
|
|
6963
7469
|
const map = await mcpQuery("maps.getMap", { mapEntryId });
|
|
6964
7470
|
if (!map) {
|
|
6965
7471
|
return {
|
|
6966
|
-
content: [{ type: "text", text: `Map "${mapEntryId}" not found.` }]
|
|
7472
|
+
content: [{ type: "text", text: `Map "${mapEntryId}" not found.` }],
|
|
7473
|
+
structuredContent: failure(
|
|
7474
|
+
"NOT_FOUND",
|
|
7475
|
+
`Map "${mapEntryId}" not found.`,
|
|
7476
|
+
"Use map action=list to find available maps.",
|
|
7477
|
+
[{ tool: "map", description: "List maps", parameters: { action: "list" } }]
|
|
7478
|
+
)
|
|
6967
7479
|
};
|
|
6968
7480
|
}
|
|
6969
7481
|
if (slotId && map.slots[slotId]) {
|
|
7482
|
+
const refs = map.slots[slotId];
|
|
6970
7483
|
return {
|
|
6971
7484
|
content: [
|
|
6972
7485
|
{
|
|
6973
7486
|
type: "text",
|
|
6974
7487
|
text: `# Slot: ${slotId}
|
|
6975
7488
|
|
|
6976
|
-
` + (
|
|
7489
|
+
` + (refs.length === 0 ? "(empty)" : refs.map(
|
|
6977
7490
|
(r) => `- **${r.ingredientName ?? r.entryId}** (\`${r.entryId}\`)${r.pinnedVersion ? ` v${r.pinnedVersion}` : ""}`
|
|
6978
7491
|
).join("\n"))
|
|
6979
7492
|
}
|
|
6980
|
-
]
|
|
7493
|
+
],
|
|
7494
|
+
structuredContent: success(
|
|
7495
|
+
refs.length === 0 ? `Slot "${slotId}" is empty.` : `Slot "${slotId}": ${refs.length} ingredient(s).`,
|
|
7496
|
+
{ mapEntryId, slotId, ingredients: refs, count: refs.length }
|
|
7497
|
+
)
|
|
6981
7498
|
};
|
|
6982
7499
|
}
|
|
6983
7500
|
return {
|
|
6984
|
-
content: [
|
|
6985
|
-
{ type: "text", text: `## All Slots
|
|
7501
|
+
content: [{ type: "text", text: `## All Slots
|
|
6986
7502
|
|
|
6987
|
-
${slotSummary(map.slots)}` }
|
|
6988
|
-
|
|
7503
|
+
${slotSummary(map.slots)}` }],
|
|
7504
|
+
structuredContent: success(
|
|
7505
|
+
`${map.name}: ${Object.keys(map.slots).length} slots.`,
|
|
7506
|
+
{ mapEntryId, slotCount: Object.keys(map.slots).length }
|
|
7507
|
+
)
|
|
6989
7508
|
};
|
|
6990
7509
|
}
|
|
6991
7510
|
if (action === "add") {
|
|
6992
7511
|
if (!slotId || !ingredientEntryId) {
|
|
6993
|
-
return
|
|
6994
|
-
content: [
|
|
6995
|
-
{
|
|
6996
|
-
type: "text",
|
|
6997
|
-
text: "Both `slotId` and `ingredientEntryId` are required for add."
|
|
6998
|
-
}
|
|
6999
|
-
]
|
|
7000
|
-
};
|
|
7512
|
+
return validationResult("Both `slotId` and `ingredientEntryId` are required for add.");
|
|
7001
7513
|
}
|
|
7002
|
-
await mcpMutation("maps.addToSlot", {
|
|
7003
|
-
mapEntryId,
|
|
7004
|
-
slotId,
|
|
7005
|
-
ingredientEntryId,
|
|
7006
|
-
label,
|
|
7007
|
-
author
|
|
7008
|
-
});
|
|
7514
|
+
await mcpMutation("maps.addToSlot", { mapEntryId, slotId, ingredientEntryId, label, author });
|
|
7009
7515
|
return {
|
|
7010
|
-
content: [
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
}
|
|
7015
|
-
|
|
7516
|
+
content: [{ type: "text", text: `Added \`${ingredientEntryId}\` to slot "${slotId}" on map \`${mapEntryId}\`.` }],
|
|
7517
|
+
structuredContent: success(
|
|
7518
|
+
`Added ${ingredientEntryId} to slot "${slotId}".`,
|
|
7519
|
+
{ mapEntryId, slotId, ingredientEntryId },
|
|
7520
|
+
[{ tool: "map", description: "View updated map", parameters: { action: "get", mapEntryId } }]
|
|
7521
|
+
)
|
|
7016
7522
|
};
|
|
7017
7523
|
}
|
|
7018
7524
|
if (action === "remove") {
|
|
7019
7525
|
if (!slotId || !ingredientEntryId) {
|
|
7020
|
-
return
|
|
7021
|
-
content: [
|
|
7022
|
-
{
|
|
7023
|
-
type: "text",
|
|
7024
|
-
text: "Both `slotId` and `ingredientEntryId` are required for remove."
|
|
7025
|
-
}
|
|
7026
|
-
]
|
|
7027
|
-
};
|
|
7526
|
+
return validationResult("Both `slotId` and `ingredientEntryId` are required for remove.");
|
|
7028
7527
|
}
|
|
7029
|
-
await mcpMutation("maps.removeFromSlot", {
|
|
7030
|
-
mapEntryId,
|
|
7031
|
-
slotId,
|
|
7032
|
-
ingredientEntryId,
|
|
7033
|
-
author
|
|
7034
|
-
});
|
|
7528
|
+
await mcpMutation("maps.removeFromSlot", { mapEntryId, slotId, ingredientEntryId, author });
|
|
7035
7529
|
return {
|
|
7036
|
-
content: [
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
}
|
|
7041
|
-
|
|
7530
|
+
content: [{ type: "text", text: `Removed \`${ingredientEntryId}\` from slot "${slotId}" on map \`${mapEntryId}\`.` }],
|
|
7531
|
+
structuredContent: success(
|
|
7532
|
+
`Removed ${ingredientEntryId} from slot "${slotId}".`,
|
|
7533
|
+
{ mapEntryId, slotId, ingredientEntryId },
|
|
7534
|
+
[{ tool: "map", description: "View updated map", parameters: { action: "get", mapEntryId } }]
|
|
7535
|
+
)
|
|
7042
7536
|
};
|
|
7043
7537
|
}
|
|
7044
7538
|
if (action === "replace") {
|
|
7045
7539
|
if (!slotId || !ingredientEntryId || !newIngredientEntryId) {
|
|
7046
|
-
return
|
|
7047
|
-
content: [
|
|
7048
|
-
{
|
|
7049
|
-
type: "text",
|
|
7050
|
-
text: "`slotId`, `ingredientEntryId`, and `newIngredientEntryId` are required for replace."
|
|
7051
|
-
}
|
|
7052
|
-
]
|
|
7053
|
-
};
|
|
7540
|
+
return validationResult("`slotId`, `ingredientEntryId`, and `newIngredientEntryId` are required for replace.");
|
|
7054
7541
|
}
|
|
7055
|
-
await mcpMutation("maps.replaceInSlot", {
|
|
7056
|
-
mapEntryId,
|
|
7057
|
-
slotId,
|
|
7058
|
-
oldIngredientEntryId: ingredientEntryId,
|
|
7059
|
-
newIngredientEntryId,
|
|
7060
|
-
label,
|
|
7061
|
-
author
|
|
7062
|
-
});
|
|
7542
|
+
await mcpMutation("maps.replaceInSlot", { mapEntryId, slotId, oldIngredientEntryId: ingredientEntryId, newIngredientEntryId, label, author });
|
|
7063
7543
|
return {
|
|
7064
|
-
content: [
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
}
|
|
7069
|
-
|
|
7544
|
+
content: [{ type: "text", text: `Replaced \`${ingredientEntryId}\` with \`${newIngredientEntryId}\` in slot "${slotId}".` }],
|
|
7545
|
+
structuredContent: success(
|
|
7546
|
+
`Replaced ${ingredientEntryId} with ${newIngredientEntryId} in slot "${slotId}".`,
|
|
7547
|
+
{ mapEntryId, slotId, removed: ingredientEntryId, added: newIngredientEntryId },
|
|
7548
|
+
[{ tool: "map", description: "View updated map", parameters: { action: "get", mapEntryId } }]
|
|
7549
|
+
)
|
|
7070
7550
|
};
|
|
7071
7551
|
}
|
|
7072
|
-
return
|
|
7073
|
-
|
|
7074
|
-
};
|
|
7075
|
-
}
|
|
7552
|
+
return unknownAction(action, ["add", "remove", "replace", "list"]);
|
|
7553
|
+
})
|
|
7076
7554
|
);
|
|
7077
7555
|
trackWriteTool(mapSlotTool);
|
|
7078
7556
|
const mapVersionTool = server.registerTool(
|
|
@@ -7083,7 +7561,7 @@ ${slotSummary(map.slots)}` }
|
|
|
7083
7561
|
inputSchema: mapVersionSchema,
|
|
7084
7562
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
7085
7563
|
},
|
|
7086
|
-
async ({ action, mapEntryId, commitMessage, author }) => {
|
|
7564
|
+
withEnvelope(async ({ action, mapEntryId, commitMessage, author }) => {
|
|
7087
7565
|
if (action === "commit") {
|
|
7088
7566
|
const result = await mcpMutation("maps.commitMap", {
|
|
7089
7567
|
mapEntryId,
|
|
@@ -7102,21 +7580,23 @@ ${slotSummary(map.slots)}` }
|
|
|
7102
7580
|
|
|
7103
7581
|
All ingredient references have been pinned at their current versions.`
|
|
7104
7582
|
}
|
|
7105
|
-
]
|
|
7583
|
+
],
|
|
7584
|
+
structuredContent: success(
|
|
7585
|
+
`Committed map ${mapEntryId} at v${result.version} (${result.completionScore}% complete).`,
|
|
7586
|
+
{ mapEntryId, version: result.version, completionScore: result.completionScore, slotsModified: result.slotsModified }
|
|
7587
|
+
)
|
|
7106
7588
|
};
|
|
7107
7589
|
}
|
|
7108
7590
|
if (action === "list" || action === "history") {
|
|
7109
|
-
const commits = await mcpQuery("maps.listMapCommits", {
|
|
7110
|
-
mapEntryId
|
|
7111
|
-
});
|
|
7591
|
+
const commits = await mcpQuery("maps.listMapCommits", { mapEntryId });
|
|
7112
7592
|
if (!commits || commits.length === 0) {
|
|
7113
7593
|
return {
|
|
7114
|
-
content: [
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
}
|
|
7119
|
-
|
|
7594
|
+
content: [{ type: "text", text: `No commits yet for map \`${mapEntryId}\`. Use \`map-version action=commit\` to create the first snapshot.` }],
|
|
7595
|
+
structuredContent: success(
|
|
7596
|
+
`No commits yet for ${mapEntryId}.`,
|
|
7597
|
+
{ mapEntryId, commits: [], total: 0 },
|
|
7598
|
+
[{ tool: "map-version", description: "Commit the map", parameters: { action: "commit", mapEntryId } }]
|
|
7599
|
+
)
|
|
7120
7600
|
};
|
|
7121
7601
|
}
|
|
7122
7602
|
const lines = commits.map(
|
|
@@ -7124,20 +7604,17 @@ All ingredient references have been pinned at their current versions.`
|
|
|
7124
7604
|
Slots changed: ${c.slotsModified.join(", ")}` : "")
|
|
7125
7605
|
);
|
|
7126
7606
|
return {
|
|
7127
|
-
content: [
|
|
7128
|
-
{
|
|
7129
|
-
type: "text",
|
|
7130
|
-
text: `# Map History: \`${mapEntryId}\` (${commits.length} commits)
|
|
7607
|
+
content: [{ type: "text", text: `# Map History: \`${mapEntryId}\` (${commits.length} commits)
|
|
7131
7608
|
|
|
7132
|
-
${lines.join("\n")}`
|
|
7133
|
-
|
|
7134
|
-
|
|
7609
|
+
${lines.join("\n")}` }],
|
|
7610
|
+
structuredContent: success(
|
|
7611
|
+
`${commits.length} commit(s) for ${mapEntryId}.`,
|
|
7612
|
+
{ mapEntryId, total: commits.length }
|
|
7613
|
+
)
|
|
7135
7614
|
};
|
|
7136
7615
|
}
|
|
7137
|
-
return
|
|
7138
|
-
|
|
7139
|
-
};
|
|
7140
|
-
}
|
|
7616
|
+
return unknownAction(action, ["commit", "list", "history"]);
|
|
7617
|
+
})
|
|
7141
7618
|
);
|
|
7142
7619
|
trackWriteTool(mapVersionTool);
|
|
7143
7620
|
server.registerTool(
|
|
@@ -7148,22 +7625,25 @@ ${lines.join("\n")}`
|
|
|
7148
7625
|
inputSchema: mapSuggestSchema,
|
|
7149
7626
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
7150
7627
|
},
|
|
7151
|
-
async ({ mapEntryId, slotId, query }) => {
|
|
7628
|
+
withEnvelope(async ({ mapEntryId, slotId, query }) => {
|
|
7152
7629
|
const map = await mcpQuery("maps.getMap", { mapEntryId });
|
|
7153
7630
|
if (!map) {
|
|
7154
7631
|
return {
|
|
7155
|
-
content: [{ type: "text", text: `Map "${mapEntryId}" not found.` }]
|
|
7632
|
+
content: [{ type: "text", text: `Map "${mapEntryId}" not found.` }],
|
|
7633
|
+
structuredContent: failure(
|
|
7634
|
+
"NOT_FOUND",
|
|
7635
|
+
`Map "${mapEntryId}" not found.`,
|
|
7636
|
+
"Use map action=list to find available maps.",
|
|
7637
|
+
[{ tool: "map", description: "List maps", parameters: { action: "list" } }]
|
|
7638
|
+
)
|
|
7156
7639
|
};
|
|
7157
7640
|
}
|
|
7158
7641
|
const emptySlots = slotId ? [slotId].filter((id) => (map.slots[id]?.length ?? 0) === 0) : Object.entries(map.slots).filter(([, refs]) => refs.length === 0).map(([id]) => id);
|
|
7159
7642
|
if (emptySlots.length === 0) {
|
|
7643
|
+
const msg = slotId ? `Slot "${slotId}" already has ingredients.` : "All slots have ingredients. Nothing to suggest.";
|
|
7160
7644
|
return {
|
|
7161
|
-
content: [
|
|
7162
|
-
|
|
7163
|
-
type: "text",
|
|
7164
|
-
text: slotId ? `Slot "${slotId}" already has ingredients.` : "All slots have ingredients. Nothing to suggest."
|
|
7165
|
-
}
|
|
7166
|
-
]
|
|
7645
|
+
content: [{ type: "text", text: msg }],
|
|
7646
|
+
structuredContent: success(msg, { mapEntryId, emptySlots: 0 })
|
|
7167
7647
|
};
|
|
7168
7648
|
}
|
|
7169
7649
|
const slotDefs = map.slotDefs ?? [];
|
|
@@ -7202,9 +7682,14 @@ _No matching entries found. Create ingredients first._`
|
|
|
7202
7682
|
|
|
7203
7683
|
Use \`map-slot action=add mapEntryId="${mapEntryId}" slotId="..." ingredientEntryId="..."\` to add ingredients.`
|
|
7204
7684
|
}
|
|
7205
|
-
]
|
|
7685
|
+
],
|
|
7686
|
+
structuredContent: success(
|
|
7687
|
+
`Suggestions for ${emptySlots.length} empty slot(s) on "${map.name}".`,
|
|
7688
|
+
{ mapEntryId, emptySlots: emptySlots.length },
|
|
7689
|
+
[{ tool: "map-slot", description: "Add ingredient", parameters: { action: "add", mapEntryId } }]
|
|
7690
|
+
)
|
|
7206
7691
|
};
|
|
7207
|
-
}
|
|
7692
|
+
})
|
|
7208
7693
|
);
|
|
7209
7694
|
}
|
|
7210
7695
|
|
|
@@ -7386,7 +7871,7 @@ function registerArchitectureTools(server) {
|
|
|
7386
7871
|
inputSchema: architectureSchema,
|
|
7387
7872
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
7388
7873
|
},
|
|
7389
|
-
async ({ action, template, layer, flow }) => {
|
|
7874
|
+
withEnvelope(async ({ action, template, layer, flow }) => {
|
|
7390
7875
|
await ensureCollection();
|
|
7391
7876
|
const all = await listArchEntries();
|
|
7392
7877
|
if (action === "show") {
|
|
@@ -7402,7 +7887,13 @@ function registerArchitectureTools(server) {
|
|
|
7402
7887
|
content: [{
|
|
7403
7888
|
type: "text",
|
|
7404
7889
|
text: "# Architecture Explorer\n\nNo architecture data found. Use `architecture-admin action=seed` to populate the default architecture."
|
|
7405
|
-
}]
|
|
7890
|
+
}],
|
|
7891
|
+
structuredContent: failure(
|
|
7892
|
+
"NOT_FOUND",
|
|
7893
|
+
"No architecture data found.",
|
|
7894
|
+
"Seed the default architecture first.",
|
|
7895
|
+
[{ tool: "architecture-admin", description: "Seed architecture", parameters: { action: "seed" } }]
|
|
7896
|
+
)
|
|
7406
7897
|
};
|
|
7407
7898
|
}
|
|
7408
7899
|
const textLayers = layers.map((l) => formatLayerText(l, nodes)).join("\n\n");
|
|
@@ -7417,18 +7908,31 @@ ${textLayers}${textFlows}`;
|
|
|
7417
7908
|
content: [
|
|
7418
7909
|
{ type: "text", text },
|
|
7419
7910
|
{ type: "resource", resource: { uri: `ui://product-os/architecture`, mimeType: "text/html", text: html } }
|
|
7420
|
-
]
|
|
7911
|
+
],
|
|
7912
|
+
structuredContent: success(
|
|
7913
|
+
`Showing ${templateName}: ${layers.length} layers, ${nodes.length} nodes, ${flows.length} flows.`,
|
|
7914
|
+
{ templateName, layerCount: layers.length, nodeCount: nodes.length, flowCount: flows.length }
|
|
7915
|
+
)
|
|
7421
7916
|
};
|
|
7422
7917
|
}
|
|
7423
7918
|
if (action === "explore") {
|
|
7424
|
-
if (!layer)
|
|
7919
|
+
if (!layer) {
|
|
7920
|
+
return validationResult("A `layer` is required for explore.");
|
|
7921
|
+
}
|
|
7425
7922
|
const layers = byTag(all, "layer");
|
|
7426
7923
|
const target = layers.find(
|
|
7427
7924
|
(l) => l.name.toLowerCase() === layer.toLowerCase() || l.entryId === layer
|
|
7428
7925
|
);
|
|
7429
7926
|
if (!target) {
|
|
7430
7927
|
const available = layers.map((l) => `\`${l.name}\``).join(", ");
|
|
7431
|
-
return {
|
|
7928
|
+
return {
|
|
7929
|
+
content: [{ type: "text", text: `Layer "${layer}" not found. Available layers: ${available}` }],
|
|
7930
|
+
structuredContent: failure(
|
|
7931
|
+
"NOT_FOUND",
|
|
7932
|
+
`Layer "${layer}" not found.`,
|
|
7933
|
+
`Available layers: ${available}`
|
|
7934
|
+
)
|
|
7935
|
+
};
|
|
7432
7936
|
}
|
|
7433
7937
|
const nodes = byTag(all, "node").filter((n) => n.data?.layerRef === target.entryId);
|
|
7434
7938
|
const flows = byTag(all, "flow").filter(
|
|
@@ -7459,18 +7963,31 @@ ${target.data?.description ?? ""}${depRule}${layerRationale}
|
|
|
7459
7963
|
**${nodes.length} components**
|
|
7460
7964
|
|
|
7461
7965
|
${nodeDetail}${flowLines}`
|
|
7462
|
-
}]
|
|
7966
|
+
}],
|
|
7967
|
+
structuredContent: success(
|
|
7968
|
+
`${target.name} layer: ${nodes.length} components, ${flows.length} connected flows.`,
|
|
7969
|
+
{ layerName: target.name, entryId: target.entryId, nodeCount: nodes.length, flowCount: flows.length }
|
|
7970
|
+
)
|
|
7463
7971
|
};
|
|
7464
7972
|
}
|
|
7465
7973
|
if (action === "flow") {
|
|
7466
|
-
if (!flow)
|
|
7974
|
+
if (!flow) {
|
|
7975
|
+
return validationResult("A `flow` name or entry ID is required.");
|
|
7976
|
+
}
|
|
7467
7977
|
const flows = byTag(all, "flow");
|
|
7468
7978
|
const target = flows.find(
|
|
7469
7979
|
(f) => f.name.toLowerCase() === flow.toLowerCase() || f.entryId === flow
|
|
7470
7980
|
);
|
|
7471
7981
|
if (!target) {
|
|
7472
7982
|
const available = flows.map((f) => `\`${f.name}\``).join(", ");
|
|
7473
|
-
return {
|
|
7983
|
+
return {
|
|
7984
|
+
content: [{ type: "text", text: `Flow "${flow}" not found. Available flows: ${available}` }],
|
|
7985
|
+
structuredContent: failure(
|
|
7986
|
+
"NOT_FOUND",
|
|
7987
|
+
`Flow "${flow}" not found.`,
|
|
7988
|
+
`Available flows: ${available}`
|
|
7989
|
+
)
|
|
7990
|
+
};
|
|
7474
7991
|
}
|
|
7475
7992
|
const nodes = byTag(all, "node");
|
|
7476
7993
|
const source = nodes.find((n) => n.entryId === target.data?.sourceNode);
|
|
@@ -7490,10 +8007,16 @@ ${nodeDetail}${flowLines}`
|
|
|
7490
8007
|
lines.push("", `### Target: ${dest.name}`, String(dest.data?.description ?? ""));
|
|
7491
8008
|
if (dest.data?.filePaths) lines.push(`Files: \`${dest.data.filePaths}\``);
|
|
7492
8009
|
}
|
|
7493
|
-
return {
|
|
8010
|
+
return {
|
|
8011
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
8012
|
+
structuredContent: success(
|
|
8013
|
+
`${target.name}: ${source?.name ?? "?"} \u2192 ${dest?.name ?? "?"}.`,
|
|
8014
|
+
{ flowName: target.name, entryId: target.entryId, source: source?.name, target: dest?.name }
|
|
8015
|
+
)
|
|
8016
|
+
};
|
|
7494
8017
|
}
|
|
7495
|
-
return
|
|
7496
|
-
}
|
|
8018
|
+
return unknownAction(action, ["show", "explore", "flow"]);
|
|
8019
|
+
})
|
|
7497
8020
|
);
|
|
7498
8021
|
const archAdminTool = server.registerTool(
|
|
7499
8022
|
"architecture-admin",
|
|
@@ -7503,7 +8026,7 @@ ${nodeDetail}${flowLines}`
|
|
|
7503
8026
|
inputSchema: architectureAdminSchema,
|
|
7504
8027
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
7505
8028
|
},
|
|
7506
|
-
async ({ action }) => {
|
|
8029
|
+
withEnvelope(async ({ action }) => {
|
|
7507
8030
|
if (action === "seed") {
|
|
7508
8031
|
await ensureCollection();
|
|
7509
8032
|
const existing = await listArchEntries();
|
|
@@ -7554,7 +8077,12 @@ ${nodeDetail}${flowLines}`
|
|
|
7554
8077
|
**Unchanged:** ${unchanged}
|
|
7555
8078
|
|
|
7556
8079
|
Use \`architecture action=show\` to view the map.`
|
|
7557
|
-
}]
|
|
8080
|
+
}],
|
|
8081
|
+
structuredContent: success(
|
|
8082
|
+
`Architecture seeded: ${created} created, ${updated} updated, ${unchanged} unchanged.`,
|
|
8083
|
+
{ created, updated, unchanged },
|
|
8084
|
+
[{ tool: "architecture", description: "View the map", parameters: { action: "show" } }]
|
|
8085
|
+
)
|
|
7558
8086
|
};
|
|
7559
8087
|
}
|
|
7560
8088
|
if (action === "check") {
|
|
@@ -7564,7 +8092,12 @@ Use \`architecture action=show\` to view the map.`
|
|
|
7564
8092
|
content: [{
|
|
7565
8093
|
type: "text",
|
|
7566
8094
|
text: "# Scan Failed\n\nCannot find project root (looked for `convex/schema.ts` in cwd and parent). Set `WORKSPACE_PATH` env var to the absolute path of the Product OS project root."
|
|
7567
|
-
}]
|
|
8095
|
+
}],
|
|
8096
|
+
structuredContent: failure(
|
|
8097
|
+
"VALIDATION_ERROR",
|
|
8098
|
+
"Cannot find project root.",
|
|
8099
|
+
"Set WORKSPACE_PATH env var to the absolute path of the Product OS project root."
|
|
8100
|
+
)
|
|
7568
8101
|
};
|
|
7569
8102
|
}
|
|
7570
8103
|
await ensureCollection();
|
|
@@ -7572,10 +8105,21 @@ Use \`architecture action=show\` to view the map.`
|
|
|
7572
8105
|
const layers = byTag(all, "layer");
|
|
7573
8106
|
const nodes = byTag(all, "node");
|
|
7574
8107
|
const result = scanDependencies(projectRoot, layers, nodes);
|
|
7575
|
-
return {
|
|
8108
|
+
return {
|
|
8109
|
+
content: [{ type: "text", text: formatScanReport(result) }],
|
|
8110
|
+
structuredContent: success(
|
|
8111
|
+
result.violations.length === 0 ? `Health check passed: 0 violations across ${result.filesScanned} files.` : `Health check found ${result.violations.length} violation(s) across ${result.filesScanned} files.`,
|
|
8112
|
+
{
|
|
8113
|
+
violations: result.violations.length,
|
|
8114
|
+
filesScanned: result.filesScanned,
|
|
8115
|
+
importsChecked: result.importsChecked,
|
|
8116
|
+
unmappedImports: result.unmappedImports
|
|
8117
|
+
}
|
|
8118
|
+
)
|
|
8119
|
+
};
|
|
7576
8120
|
}
|
|
7577
|
-
return
|
|
7578
|
-
}
|
|
8121
|
+
return unknownAction(action, ["seed", "check"]);
|
|
8122
|
+
})
|
|
7579
8123
|
);
|
|
7580
8124
|
trackWriteTool(archAdminTool);
|
|
7581
8125
|
}
|
|
@@ -7937,15 +8481,19 @@ async function handleHealthCheck() {
|
|
|
7937
8481
|
lines.push(`- ${err}`);
|
|
7938
8482
|
}
|
|
7939
8483
|
}
|
|
8484
|
+
const healthData = {
|
|
8485
|
+
healthy,
|
|
8486
|
+
collections: collections.length,
|
|
8487
|
+
entries: totalEntries,
|
|
8488
|
+
latencyMs: durationMs,
|
|
8489
|
+
workspace: workspaceId ?? "unresolved"
|
|
8490
|
+
};
|
|
7940
8491
|
return {
|
|
7941
8492
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
7942
|
-
structuredContent:
|
|
7943
|
-
healthy,
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
latencyMs: durationMs,
|
|
7947
|
-
workspace: workspaceId ?? "unresolved"
|
|
7948
|
-
}
|
|
8493
|
+
structuredContent: success(
|
|
8494
|
+
healthy ? `Healthy. ${collections.length} collections, ${totalEntries} entries, ${durationMs}ms.` : `Degraded. ${errors.length} error(s).`,
|
|
8495
|
+
healthData
|
|
8496
|
+
)
|
|
7949
8497
|
};
|
|
7950
8498
|
}
|
|
7951
8499
|
async function handleWhoami() {
|
|
@@ -7962,13 +8510,10 @@ async function handleWhoami() {
|
|
|
7962
8510
|
];
|
|
7963
8511
|
return {
|
|
7964
8512
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
7965
|
-
structuredContent:
|
|
7966
|
-
|
|
7967
|
-
workspaceName: ctx.workspaceName,
|
|
7968
|
-
|
|
7969
|
-
sessionId,
|
|
7970
|
-
oriented
|
|
7971
|
-
}
|
|
8513
|
+
structuredContent: success(
|
|
8514
|
+
`Session: ${ctx.workspaceName} (${scope}). ${oriented ? "Oriented." : "Not oriented."}`,
|
|
8515
|
+
{ workspaceId: ctx.workspaceId, workspaceName: ctx.workspaceName, scope, sessionId, oriented }
|
|
8516
|
+
)
|
|
7972
8517
|
};
|
|
7973
8518
|
}
|
|
7974
8519
|
var STAGE_LABELS = {
|
|
@@ -8021,31 +8566,32 @@ async function handleWorkspaceStatus() {
|
|
|
8021
8566
|
lines.push(`- [x] ${check.label} (${check.current}/${check.required})`);
|
|
8022
8567
|
}
|
|
8023
8568
|
}
|
|
8569
|
+
const statusData = {
|
|
8570
|
+
stage,
|
|
8571
|
+
scoringVersion,
|
|
8572
|
+
readinessScore: score,
|
|
8573
|
+
activeEntries: stats.activeCount,
|
|
8574
|
+
totalRelations: stats.totalRelations,
|
|
8575
|
+
orphanedEntries: stats.orphanedCount,
|
|
8576
|
+
gaps: gaps.map((g) => ({
|
|
8577
|
+
id: g.id,
|
|
8578
|
+
label: g.label,
|
|
8579
|
+
guidance: g.capabilityGuidance ?? g.guidance
|
|
8580
|
+
}))
|
|
8581
|
+
};
|
|
8024
8582
|
return {
|
|
8025
8583
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
8026
|
-
structuredContent:
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
activeEntries: stats.activeCount,
|
|
8031
|
-
totalRelations: stats.totalRelations,
|
|
8032
|
-
orphanedEntries: stats.orphanedCount,
|
|
8033
|
-
gaps: gaps.map((g) => ({
|
|
8034
|
-
id: g.id,
|
|
8035
|
-
label: g.label,
|
|
8036
|
-
guidance: g.capabilityGuidance ?? g.guidance
|
|
8037
|
-
}))
|
|
8038
|
-
}
|
|
8584
|
+
structuredContent: success(
|
|
8585
|
+
`Brain: ${stageLabel} (${score}%). ${stats.activeCount} active entries, ${gaps.length} gap(s).`,
|
|
8586
|
+
statusData
|
|
8587
|
+
)
|
|
8039
8588
|
};
|
|
8040
8589
|
}
|
|
8041
8590
|
async function handleAudit(limit) {
|
|
8042
8591
|
const log = getAuditLog();
|
|
8043
8592
|
const recent = log.slice(-limit);
|
|
8044
8593
|
if (recent.length === 0) {
|
|
8045
|
-
return {
|
|
8046
|
-
content: [{ type: "text", text: "No calls recorded yet this session." }],
|
|
8047
|
-
structuredContent: { totalCalls: 0, calls: [] }
|
|
8048
|
-
};
|
|
8594
|
+
return successResult("No calls recorded yet this session.", "No calls recorded yet this session.", { totalCalls: 0, calls: [] });
|
|
8049
8595
|
}
|
|
8050
8596
|
const summary = buildSessionSummary(log);
|
|
8051
8597
|
const logLines = [`# Audit Log (last ${recent.length} of ${log.length} total)
|
|
@@ -8056,21 +8602,25 @@ async function handleAudit(limit) {
|
|
|
8056
8602
|
const toolPart = entry.toolContext ? ` [${entry.toolContext.tool}${entry.toolContext.action ? ` action=${entry.toolContext.action}` : ""}]` : "";
|
|
8057
8603
|
logLines.push(`${icon} \`${entry.fn}\`${toolPart} ${entry.durationMs}ms ${entry.status}${errPart}`);
|
|
8058
8604
|
}
|
|
8605
|
+
const auditData = {
|
|
8606
|
+
totalCalls: log.length,
|
|
8607
|
+
calls: recent.map((entry) => ({
|
|
8608
|
+
tool: entry.fn,
|
|
8609
|
+
...entry.toolContext?.action && { action: entry.toolContext.action },
|
|
8610
|
+
timestamp: entry.ts,
|
|
8611
|
+
...entry.durationMs != null && { durationMs: entry.durationMs }
|
|
8612
|
+
}))
|
|
8613
|
+
};
|
|
8059
8614
|
return {
|
|
8060
8615
|
content: [{ type: "text", text: `${summary}
|
|
8061
8616
|
|
|
8062
8617
|
---
|
|
8063
8618
|
|
|
8064
8619
|
${logLines.join("\n")}` }],
|
|
8065
|
-
structuredContent:
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
...entry.toolContext?.action && { action: entry.toolContext.action },
|
|
8070
|
-
timestamp: entry.ts,
|
|
8071
|
-
...entry.durationMs != null && { durationMs: entry.durationMs }
|
|
8072
|
-
}))
|
|
8073
|
-
}
|
|
8620
|
+
structuredContent: success(
|
|
8621
|
+
`Audit: ${log.length} total calls, showing last ${recent.length}.`,
|
|
8622
|
+
auditData
|
|
8623
|
+
)
|
|
8074
8624
|
};
|
|
8075
8625
|
}
|
|
8076
8626
|
var HEALTH_ACTIONS = ["check", "whoami", "status", "audit", "self-test"];
|
|
@@ -8199,7 +8749,10 @@ function handleSelfTest() {
|
|
|
8199
8749
|
}
|
|
8200
8750
|
return {
|
|
8201
8751
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
8202
|
-
structuredContent:
|
|
8752
|
+
structuredContent: success(
|
|
8753
|
+
failed === 0 ? `Self-test: all ${total} schemas valid.` : `Self-test: ${failed}/${total} schemas failed.`,
|
|
8754
|
+
{ passed, failed, total, results }
|
|
8755
|
+
)
|
|
8203
8756
|
};
|
|
8204
8757
|
}
|
|
8205
8758
|
function registerHealthTools(server) {
|
|
@@ -8211,12 +8764,9 @@ function registerHealthTools(server) {
|
|
|
8211
8764
|
inputSchema: healthSchema,
|
|
8212
8765
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
8213
8766
|
},
|
|
8214
|
-
async (args) => {
|
|
8215
|
-
const parsed = healthSchema
|
|
8216
|
-
if (!parsed.
|
|
8217
|
-
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
8218
|
-
return { content: [{ type: "text", text: `Invalid arguments: ${issues}` }] };
|
|
8219
|
-
}
|
|
8767
|
+
withEnvelope(async (args) => {
|
|
8768
|
+
const parsed = parseOrFail(healthSchema, args);
|
|
8769
|
+
if (!parsed.ok) return parsed.result;
|
|
8220
8770
|
const { action, limit } = parsed.data;
|
|
8221
8771
|
return runWithToolContext({ tool: "health", action }, async () => {
|
|
8222
8772
|
if (action === "check") return handleHealthCheck();
|
|
@@ -8224,11 +8774,9 @@ function registerHealthTools(server) {
|
|
|
8224
8774
|
if (action === "status") return handleWorkspaceStatus();
|
|
8225
8775
|
if (action === "audit") return handleAudit(limit ?? 20);
|
|
8226
8776
|
if (action === "self-test") return handleSelfTest();
|
|
8227
|
-
return
|
|
8228
|
-
content: [{ type: "text", text: `Unknown action '${action}'. Valid actions: ${HEALTH_ACTIONS.join(", ")}.` }]
|
|
8229
|
-
};
|
|
8777
|
+
return unknownAction(action, HEALTH_ACTIONS);
|
|
8230
8778
|
});
|
|
8231
|
-
}
|
|
8779
|
+
})
|
|
8232
8780
|
);
|
|
8233
8781
|
server.registerTool(
|
|
8234
8782
|
"orient",
|
|
@@ -8238,13 +8786,16 @@ function registerHealthTools(server) {
|
|
|
8238
8786
|
inputSchema: orientSchema,
|
|
8239
8787
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
8240
8788
|
},
|
|
8241
|
-
async ({ mode = "full", task } = {}) => {
|
|
8789
|
+
withEnvelope(async ({ mode = "full", task } = {}) => {
|
|
8242
8790
|
const errors = [];
|
|
8243
8791
|
const agentSessionId = getAgentSessionId();
|
|
8244
8792
|
if (isSessionOriented() && mode === "brief" && !task) {
|
|
8245
8793
|
return {
|
|
8246
8794
|
content: [{ type: "text", text: "Already oriented. Session active, writes unlocked. Use `orient mode='full'` or `orient task='...'` for full context." }],
|
|
8247
|
-
structuredContent:
|
|
8795
|
+
structuredContent: success(
|
|
8796
|
+
"Already oriented. Session active, writes unlocked.",
|
|
8797
|
+
{ alreadyOriented: true, sessionId: agentSessionId }
|
|
8798
|
+
)
|
|
8248
8799
|
};
|
|
8249
8800
|
}
|
|
8250
8801
|
let wsCtx = null;
|
|
@@ -8310,13 +8861,11 @@ function registerHealthTools(server) {
|
|
|
8310
8861
|
}
|
|
8311
8862
|
return {
|
|
8312
8863
|
content: [{ type: "text", text: scanLines.join("\n") }],
|
|
8313
|
-
structuredContent:
|
|
8314
|
-
|
|
8315
|
-
readinessScore: readiness?.score ?? 0,
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
redirectHint: "Use the `start` tool for the full guided setup experience."
|
|
8319
|
-
}
|
|
8864
|
+
structuredContent: success(
|
|
8865
|
+
"Workspace is blank. Use the start tool for guided setup.",
|
|
8866
|
+
{ stage: "blank", readinessScore: readiness?.score ?? 0, oriented, orientationStatus, redirectHint: "Use the `start` tool for the full guided setup experience." },
|
|
8867
|
+
[{ tool: "start", description: "Guided workspace setup", parameters: {} }]
|
|
8868
|
+
)
|
|
8320
8869
|
};
|
|
8321
8870
|
}
|
|
8322
8871
|
const lines = [];
|
|
@@ -8390,7 +8939,13 @@ function registerHealthTools(server) {
|
|
|
8390
8939
|
lines.push("");
|
|
8391
8940
|
for (const err of errors) lines.push(`- ${err}`);
|
|
8392
8941
|
}
|
|
8393
|
-
return {
|
|
8942
|
+
return {
|
|
8943
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
8944
|
+
structuredContent: success(
|
|
8945
|
+
`Oriented (brief). Stage: ${readiness?.stage ?? "unknown"}.`,
|
|
8946
|
+
{ mode: "brief", stage: readiness?.stage ?? "unknown", oriented: isSessionOriented(), sessionId: agentSessionId }
|
|
8947
|
+
)
|
|
8948
|
+
};
|
|
8394
8949
|
}
|
|
8395
8950
|
const orientStage = readiness?.stage ?? "seeded";
|
|
8396
8951
|
if (isLowReadiness && wsCtx?.createdAt) {
|
|
@@ -8628,8 +9183,14 @@ function registerHealthTools(server) {
|
|
|
8628
9183
|
lines.push("---");
|
|
8629
9184
|
lines.push("_No active agent session. Call `session action=start` to begin a tracked session._");
|
|
8630
9185
|
}
|
|
8631
|
-
return {
|
|
8632
|
-
|
|
9186
|
+
return {
|
|
9187
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
9188
|
+
structuredContent: success(
|
|
9189
|
+
`Oriented (full). Stage: ${orientStage}. ${isLowReadiness ? "Low readiness \u2014 gaps remain." : "Ready."}`,
|
|
9190
|
+
{ mode: "full", stage: orientStage, oriented: isSessionOriented(), sessionId: agentSessionId }
|
|
9191
|
+
)
|
|
9192
|
+
};
|
|
9193
|
+
})
|
|
8633
9194
|
);
|
|
8634
9195
|
}
|
|
8635
9196
|
|
|
@@ -9955,4 +10516,4 @@ export {
|
|
|
9955
10516
|
SERVER_VERSION,
|
|
9956
10517
|
createProductBrainServer
|
|
9957
10518
|
};
|
|
9958
|
-
//# sourceMappingURL=chunk-
|
|
10519
|
+
//# sourceMappingURL=chunk-FMEZXUP5.js.map
|