@productbrain/mcp 0.0.1-beta.26 → 0.0.1-beta.28

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.
@@ -10,10 +10,11 @@ import {
10
10
  mcpQuery,
11
11
  recordSessionActivity,
12
12
  registerSmartCaptureTools,
13
+ requireActiveSession,
13
14
  requireWriteAccess,
14
15
  setSessionOriented,
15
16
  startAgentSession
16
- } from "./chunk-EVF3CYZN.js";
17
+ } from "./chunk-UZDFQUAR.js";
17
18
 
18
19
  // src/server.ts
19
20
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -429,6 +430,9 @@ The relation was **not applied** \u2014 it will be created when the proposal is
429
430
  const created = results.filter((r) => r.ok && !r.proposalCreated);
430
431
  const proposals = results.filter((r) => r.ok && r.proposalCreated);
431
432
  const failed = results.filter((r) => !r.ok);
433
+ for (let i = 0; i < created.length; i++) {
434
+ await recordSessionActivity({ relationCreated: true });
435
+ }
432
436
  const lines = [`# Batch Link Results
433
437
  `];
434
438
  lines.push(`**${created.length}** created, **${proposals.length}** proposals, **${failed.length}** failed out of ${relations.length} total.
@@ -690,6 +694,82 @@ Use \`suggest-links\` to discover potential connections, or \`relate-entries\` t
690
694
  return { content: [{ type: "text", text: lines.join("\n") }] };
691
695
  }
692
696
  );
697
+ server.registerTool(
698
+ "get-build-context",
699
+ {
700
+ title: "Get Build Context",
701
+ description: "Assemble a structured build spec for any entry. Collection-agnostic: works with bets, features, epics, or any collection type. Returns the entry data, related entries by collection, applicable business rules, glossary terms, and chain refs to consult. Use this when starting a build to get full context in one call \u2014 no need to call orient, gather-context, and search separately.\n\nPass an entry ID (e.g. 'ENT-wc7u71', 'BET-001') or name. The output adapts to the entry's collection schema.",
702
+ inputSchema: {
703
+ entryId: z.string().describe("Entry ID or name (e.g. 'ENT-wc7u71', 'PB as Shared Memory')"),
704
+ maxHops: z.number().min(1).max(3).default(2).optional().describe("Relation traversal depth (default 2)")
705
+ },
706
+ annotations: { readOnlyHint: true }
707
+ },
708
+ async ({ entryId, maxHops }) => {
709
+ requireActiveSession();
710
+ const result = await mcpQuery("chain.assembleBuildContext", {
711
+ entryId,
712
+ maxHops: maxHops ?? 2
713
+ });
714
+ const { entry, relatedByCollection, businessRules, glossaryTerms, rulesToHonor, chainRefs, contextTruncated } = result;
715
+ const lines = [
716
+ `# Build Context: ${entry.name}`,
717
+ `**Entry:** ${entry.entryId ?? entry.name} [${entry.collectionSlug}]`,
718
+ `**Status:** ${entry.status}`,
719
+ ""
720
+ ];
721
+ if (Object.keys(entry.data).length > 0) {
722
+ lines.push("## Entry Data");
723
+ for (const [key, val] of Object.entries(entry.data)) {
724
+ const str = typeof val === "string" ? val : JSON.stringify(val);
725
+ if (str.length > 200) {
726
+ lines.push(`- **${key}:** ${str.slice(0, 200)}...`);
727
+ } else {
728
+ lines.push(`- **${key}:** ${str}`);
729
+ }
730
+ }
731
+ lines.push("");
732
+ }
733
+ if (businessRules.length > 0) {
734
+ lines.push("## Business Rules to Honor");
735
+ for (const r of businessRules) {
736
+ const id = r.entryId ?? r.name;
737
+ lines.push(`- **${id}:** ${r.name}`);
738
+ if (r.description) lines.push(` ${r.description.slice(0, 150)}${r.description.length > 150 ? "..." : ""}`);
739
+ }
740
+ lines.push("");
741
+ }
742
+ if (glossaryTerms.length > 0) {
743
+ lines.push("## Glossary Terms");
744
+ for (const t of glossaryTerms.slice(0, 15)) {
745
+ const id = t.entryId ?? t.name;
746
+ lines.push(`- **${id}:** ${t.name}`);
747
+ if (t.definition) lines.push(` ${t.definition.slice(0, 120)}${t.definition.length > 120 ? "..." : ""}`);
748
+ }
749
+ if (glossaryTerms.length > 15) lines.push(` _...and ${glossaryTerms.length - 15} more_`);
750
+ lines.push("");
751
+ }
752
+ const relatedCount = Object.values(relatedByCollection).reduce((sum, arr) => sum + arr.length, 0);
753
+ if (relatedCount > 0) {
754
+ lines.push("## Related Entries");
755
+ for (const [coll, entries] of Object.entries(relatedByCollection)) {
756
+ lines.push(`### ${coll} (${entries.length})`);
757
+ for (const e of entries.slice(0, 5)) {
758
+ lines.push(`- ${e.entryId ?? e.name}: ${e.name} [${e.relationType}]`);
759
+ }
760
+ if (entries.length > 5) lines.push(` _...and ${entries.length - 5} more_`);
761
+ }
762
+ lines.push("");
763
+ }
764
+ lines.push("## Chain Refs to Consult");
765
+ lines.push(chainRefs.slice(0, 15).map((r) => `- ${r}`).join("\n"));
766
+ if (contextTruncated) {
767
+ lines.push("");
768
+ lines.push("_Context was truncated (limits: 50 related, 20 rules, 30 glossary). Use get-entry for full details._");
769
+ }
770
+ return { content: [{ type: "text", text: lines.join("\n") }] };
771
+ }
772
+ );
693
773
  server.registerTool(
694
774
  "suggest-links",
695
775
  {
@@ -920,7 +1000,7 @@ Use \`list-collections\` to verify the result.`
920
1000
  },
921
1001
  async ({ entryId }) => {
922
1002
  requireWriteAccess();
923
- const { runContradictionCheck } = await import("./smart-capture-NX4QDLLY.js");
1003
+ const { runContradictionCheck } = await import("./smart-capture-AEPYZ2EA.js");
924
1004
  const entry = await mcpQuery("chain.getEntry", { entryId });
925
1005
  if (!entry) {
926
1006
  return {
@@ -934,7 +1014,8 @@ Use \`list-collections\` to verify the result.`
934
1014
  }
935
1015
  const result = await mcpMutation("chain.commitEntry", {
936
1016
  entryId,
937
- author: getAgentSessionId() ? `agent:${getAgentSessionId()}` : void 0
1017
+ author: getAgentSessionId() ? `agent:${getAgentSessionId()}` : void 0,
1018
+ sessionId: getAgentSessionId() ?? void 0
938
1019
  });
939
1020
  const docId = result?._id ?? entry._id;
940
1021
  await recordSessionActivity({ entryModified: docId });
@@ -1240,6 +1321,27 @@ function buildSessionSummary(log) {
1240
1321
  }
1241
1322
  return lines.join("\n");
1242
1323
  }
1324
+ function extractSessionEntryIds(priorSessions) {
1325
+ const allSeen = /* @__PURE__ */ new Set();
1326
+ const all = [];
1327
+ let lastSessionOnly = [];
1328
+ for (let i = 0; i < priorSessions.length; i++) {
1329
+ const s = priorSessions[i];
1330
+ const ids = [...s.entriesCreated ?? [], ...s.entriesModified ?? []].filter((id) => id);
1331
+ if (i === 0) {
1332
+ lastSessionOnly = [...new Set(ids)].slice(0, 5);
1333
+ }
1334
+ for (const id of ids) {
1335
+ if (!allSeen.has(id)) {
1336
+ allSeen.add(id);
1337
+ all.push(id);
1338
+ if (all.length >= 10) break;
1339
+ }
1340
+ }
1341
+ if (all.length >= 10) break;
1342
+ }
1343
+ return { all, lastSessionOnly };
1344
+ }
1243
1345
  function registerHealthTools(server) {
1244
1346
  server.registerTool(
1245
1347
  "health",
@@ -1389,9 +1491,14 @@ function registerHealthTools(server) {
1389
1491
  } catch {
1390
1492
  }
1391
1493
  }
1494
+ const { all: sessionEntryIds, lastSessionOnly } = extractSessionEntryIds(priorSessions);
1392
1495
  let orientEntries = null;
1393
1496
  try {
1394
- orientEntries = await mcpQuery("chain.getOrientEntries", task ? { task } : {});
1497
+ const orientArgs = {};
1498
+ if (task) orientArgs.task = task;
1499
+ if (sessionEntryIds.length > 0) orientArgs.sessionEntryIds = sessionEntryIds;
1500
+ if (lastSessionOnly.length > 0) orientArgs.lastSessionEntryIds = lastSessionOnly;
1501
+ orientEntries = await mcpQuery("chain.getOrientEntries", orientArgs);
1395
1502
  } catch {
1396
1503
  }
1397
1504
  let openTensions = [];
@@ -1516,6 +1623,31 @@ function registerHealthTools(server) {
1516
1623
  lines.push(`${betLine} ${sc.activeTensionCount} open tension(s).`);
1517
1624
  lines.push("");
1518
1625
  }
1626
+ if (orientEntries?.continuingFrom && orientEntries.continuingFrom.length > 0) {
1627
+ lines.push("## Continuing from");
1628
+ lines.push("_Prior-session entries most relevant to your task._");
1629
+ lines.push("");
1630
+ for (const e of orientEntries.continuingFrom) {
1631
+ const id = e.entryId ?? e.name;
1632
+ const type = e.canonicalKey ?? "generic";
1633
+ const coll = e.collectionSlug ? ` [${e.collectionSlug}]` : "";
1634
+ lines.push(`- \`${id}\` (score ${e.score}) [${type}]${coll} \u2014 ${e.name}`);
1635
+ if (e.reasoning) lines.push(` _${e.reasoning}_`);
1636
+ }
1637
+ lines.push("");
1638
+ }
1639
+ if (orientEntries?.lastSessionTouched && orientEntries.lastSessionTouched.length > 0) {
1640
+ lines.push("## Last session touched");
1641
+ lines.push("_Entries created or modified in your most recent session._");
1642
+ lines.push("");
1643
+ for (const e of orientEntries.lastSessionTouched) {
1644
+ const id = e.entryId ?? e.name;
1645
+ const type = e.canonicalKey ?? "generic";
1646
+ const coll = e.collectionSlug ? ` [${e.collectionSlug}]` : "";
1647
+ lines.push(`- \`${id}\` [${type}]${coll} \u2014 ${e.name}`);
1648
+ }
1649
+ lines.push("");
1650
+ }
1519
1651
  if (orientEntries?.taskContext && orientEntries.taskContext.context.length > 0) {
1520
1652
  const tc = orientEntries.taskContext;
1521
1653
  lines.push("## Task Context");
@@ -3418,6 +3550,43 @@ function slotSummary(slots) {
3418
3550
  }).join("\n");
3419
3551
  }
3420
3552
  function registerMapTools(server) {
3553
+ server.registerTool(
3554
+ "create-audience-map-set",
3555
+ {
3556
+ title: "Create Audience Map Set",
3557
+ description: "Create all three audience intelligence maps (Empathy, Narrowing, Jobs & Alternatives) anchored to one audience entry. The audience is pre-slotted in the anchor position of each map.",
3558
+ inputSchema: {
3559
+ audienceEntryId: z8.string().describe("Entry ID of the audience (e.g. STR-fb7hje)")
3560
+ }
3561
+ },
3562
+ async ({ audienceEntryId }) => {
3563
+ const result = await mcpMutation(
3564
+ "maps.createAudienceMapSet",
3565
+ { audienceEntryId }
3566
+ );
3567
+ const wsCtx = await getWorkspaceContext();
3568
+ const lines = result.maps.map(
3569
+ (m) => `- **${m.templateId}**: \`${m.mapEntryId}\``
3570
+ );
3571
+ return {
3572
+ content: [
3573
+ {
3574
+ type: "text",
3575
+ text: `# Audience Map Set Created
3576
+
3577
+ **Audience:** \`${result.audienceEntryId}\`
3578
+ **Workspace:** ${wsCtx.workspaceSlug}
3579
+
3580
+ ## Maps
3581
+
3582
+ ${lines.join("\n")}
3583
+
3584
+ Use map-slot to add ingredients. View at /empathy, /narrowing, and /jobs.`
3585
+ }
3586
+ ]
3587
+ };
3588
+ }
3589
+ );
3421
3590
  server.registerTool(
3422
3591
  "map",
3423
3592
  {
@@ -4727,4 +4896,4 @@ export {
4727
4896
  SERVER_VERSION,
4728
4897
  createProductBrainServer
4729
4898
  };
4730
- //# sourceMappingURL=chunk-PVWT5LIB.js.map
4899
+ //# sourceMappingURL=chunk-PTFZHZZJ.js.map