@productbrain/mcp 0.0.1-beta.29 → 0.0.1-beta.30

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.
@@ -15,7 +15,10 @@ import {
15
15
  requireWriteAccess,
16
16
  setSessionOriented,
17
17
  startAgentSession
18
- } from "./chunk-CKXLZBWQ.js";
18
+ } from "./chunk-P5KVCIYN.js";
19
+ import {
20
+ trackQualityVerdict
21
+ } from "./chunk-SJ2ODB3Y.js";
19
22
 
20
23
  // src/server.ts
21
24
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -1001,7 +1004,7 @@ Use \`list-collections\` to verify the result.`
1001
1004
  },
1002
1005
  async ({ entryId }) => {
1003
1006
  requireWriteAccess();
1004
- const { runContradictionCheck } = await import("./smart-capture-J27LOWAP.js");
1007
+ const { runContradictionCheck } = await import("./smart-capture-TLGJDCEQ.js");
1005
1008
  const entry = await mcpQuery("chain.getEntry", { entryId });
1006
1009
  if (!entry) {
1007
1010
  return {
@@ -1015,10 +1018,10 @@ Use \`list-collections\` to verify the result.`
1015
1018
  }
1016
1019
  let coachingResult = null;
1017
1020
  try {
1018
- coachingResult = await Promise.race([
1019
- mcpMutation("quality.evaluateAtCommit", { entryId }),
1020
- new Promise((resolve3) => setTimeout(() => resolve3(null), 6e3))
1021
- ]);
1021
+ coachingResult = await mcpMutation("quality.evaluateHeuristicAndSchedule", {
1022
+ entryId,
1023
+ context: "commit"
1024
+ });
1022
1025
  } catch {
1023
1026
  }
1024
1027
  const result = await mcpMutation("chain.commitEntry", {
@@ -1053,6 +1056,24 @@ Use \`list-collections\` to verify the result.`
1053
1056
  }
1054
1057
  lines.push("Run gather-context on these entries before committing.");
1055
1058
  }
1059
+ if (coachingResult?.verdict) {
1060
+ try {
1061
+ const v = coachingResult.verdict;
1062
+ const failedCount = (v.criteria ?? []).filter((c) => !c.passed).length;
1063
+ trackQualityVerdict(wsCtx.workspaceId, {
1064
+ entry_id: entryId,
1065
+ entry_type: v.canonicalKey ?? entry.canonicalKey ?? "",
1066
+ tier: v.tier,
1067
+ context: "commit",
1068
+ passed: v.passed,
1069
+ source: coachingResult.source ?? "heuristic",
1070
+ criteria_total: v.criteria?.length ?? 0,
1071
+ criteria_failed: failedCount,
1072
+ llm_scheduled: v.tier !== "passive"
1073
+ });
1074
+ } catch {
1075
+ }
1076
+ }
1056
1077
  if (coachingResult?.verdict && coachingResult.verdict.tier !== "passive" && coachingResult.verdict.criteria.length > 0) {
1057
1078
  const coaching = formatRubricCoaching(coachingResult);
1058
1079
  if (coaching) {
@@ -1064,7 +1085,10 @@ Use \`list-collections\` to verify the result.`
1064
1085
  }
1065
1086
  }
1066
1087
  }
1067
- return { content: [{ type: "text", text: lines.join("\n") }] };
1088
+ return {
1089
+ content: [{ type: "text", text: lines.join("\n") }],
1090
+ structuredContent: coachingResult?.verdict ? { qualityVerdict: coachingResult.verdict, source: coachingResult.source ?? "heuristic" } : void 0
1091
+ };
1068
1092
  }
1069
1093
  );
1070
1094
  }
@@ -4006,7 +4030,7 @@ var COLLECTION_PRESETS = [
4006
4030
  name: "Software Product",
4007
4031
  description: "For teams building software products \u2014 glossary, features, architecture, tech debt, and API endpoints",
4008
4032
  collections: [
4009
- { slug: "glossary", name: "Glossary", description: "Canonical terminology for the product domain", idPrefix: "GT", fields: [{ key: "canonical", label: "Canonical", type: "string", required: true, searchable: true }, { key: "category", label: "Category", type: "select", options: ["Platform & Architecture", "Knowledge Management", "AI & Developer Tools", "Governance & Process"] }, { key: "confusedWith", label: "Confused With", type: "array" }] },
4033
+ { slug: "glossary", name: "Glossary", description: "Canonical terminology for the product domain", idPrefix: "GLO", fields: [{ key: "canonical", label: "Canonical", type: "string", required: true, searchable: true }, { key: "category", label: "Category", type: "select", options: ["Platform & Architecture", "Knowledge Management", "AI & Developer Tools", "Governance & Process"] }, { key: "confusedWith", label: "Confused With", type: "array" }] },
4010
4034
  { slug: "features", name: "Features", description: "Product features and capabilities", idPrefix: "FEAT", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "scope", label: "Scope", type: "string" }, { key: "area", label: "Area", type: "string" }, { key: "status", label: "Status", type: "select", options: ["proposed", "in-progress", "shipped", "deprecated"] }] },
4011
4035
  { slug: "architecture", name: "Architecture", description: "System architecture layers and components", idPrefix: "ARCH", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "layer", label: "Layer", type: "string" }, { key: "dependencies", label: "Dependencies", type: "array" }] },
4012
4036
  { slug: "tech-debt", name: "Tech Debt", description: "Technical debt items to track and address", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "severity", label: "Severity", type: "select", options: ["low", "medium", "high", "critical"] }, { key: "area", label: "Area", type: "string" }, { key: "effort", label: "Effort", type: "select", options: ["small", "medium", "large"] }] },
@@ -4360,6 +4384,72 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors) {
4360
4384
  return lines.join("\n");
4361
4385
  }
4362
4386
 
4387
+ // src/tools/usage.ts
4388
+ import { z as z10 } from "zod";
4389
+ function registerUsageTools(server) {
4390
+ server.registerTool(
4391
+ "get-usage-summary",
4392
+ {
4393
+ title: "Get Usage Summary",
4394
+ description: "Returns LLM usage and cost summary for the current workspace. Includes total spend, token counts, model breakdown, and feature breakdown for the specified period. If called within an agent session, also includes the current session's cost. Use this to understand AI spend, check budgets, or analyze which features and models drive cost.",
4395
+ inputSchema: {
4396
+ periodDays: z10.number().min(1).max(90).optional().describe("Number of days to look back (default 30, max 90)")
4397
+ },
4398
+ annotations: {
4399
+ readOnlyHint: true
4400
+ }
4401
+ },
4402
+ async ({ periodDays }) => {
4403
+ const ws = await getWorkspaceContext();
4404
+ const sessionId = getAgentSessionId();
4405
+ const summary = await mcpQuery("usage.getWorkspaceSummary", {
4406
+ periodDays: periodDays ?? 30
4407
+ });
4408
+ if (!summary) {
4409
+ return {
4410
+ content: [
4411
+ {
4412
+ type: "text",
4413
+ text: "No usage data available yet. LLM tracking may not be active or no calls have been made in this period."
4414
+ }
4415
+ ]
4416
+ };
4417
+ }
4418
+ const lines = [
4419
+ `## Usage Summary \u2014 ${ws.workspaceName}`,
4420
+ `Period: last ${summary.periodDays} days`,
4421
+ "",
4422
+ `**Total spend:** $${summary.totalCostUsd.toFixed(4)} USD (${summary.totalRequests} requests)`,
4423
+ `**Total tokens:** ${summary.totalTokens.toLocaleString()} (cached: ${summary.cachedTokens.toLocaleString()}, reasoning: ${summary.reasoningTokens.toLocaleString()})`,
4424
+ `**Cache hit rate:** ${(summary.cacheHitRate * 100).toFixed(1)}%`
4425
+ ];
4426
+ if (summary.modelBreakdown.length > 0) {
4427
+ lines.push("", "### Model Breakdown");
4428
+ for (const m of summary.modelBreakdown) {
4429
+ lines.push(`- **${m.model}**: $${m.costUsd.toFixed(4)} (${m.requests} requests)`);
4430
+ }
4431
+ }
4432
+ if (summary.featureBreakdown.length > 0) {
4433
+ lines.push("", "### Feature Breakdown");
4434
+ for (const f of summary.featureBreakdown) {
4435
+ lines.push(`- **${f.feature}**: $${f.costUsd.toFixed(4)} (${f.requests} requests)`);
4436
+ }
4437
+ }
4438
+ if (sessionId) {
4439
+ lines.push("", `_Current agent session: ${sessionId}_`);
4440
+ }
4441
+ return {
4442
+ content: [
4443
+ {
4444
+ type: "text",
4445
+ text: lines.join("\n")
4446
+ }
4447
+ ]
4448
+ };
4449
+ }
4450
+ );
4451
+ }
4452
+
4363
4453
  // src/resources/index.ts
4364
4454
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
4365
4455
  function formatEntryMarkdown(entry) {
@@ -4572,12 +4662,12 @@ ${lines.join("\n")}`, mimeType: "text/markdown" }]
4572
4662
  }
4573
4663
 
4574
4664
  // src/prompts/index.ts
4575
- import { z as z10 } from "zod";
4665
+ import { z as z11 } from "zod";
4576
4666
  function registerPrompts(server) {
4577
4667
  server.prompt(
4578
4668
  "review-against-rules",
4579
4669
  "Review code or a design decision against all business rules for a given domain. Fetches the rules and asks you to do a structured compliance review.",
4580
- { domain: z10.string().describe("Business rule domain (e.g. 'Identity & Access', 'Governance & Decision-Making')") },
4670
+ { domain: z11.string().describe("Business rule domain (e.g. 'Identity & Access', 'Governance & Decision-Making')") },
4581
4671
  async ({ domain }) => {
4582
4672
  const entries = await mcpQuery("chain.listEntries", { collectionSlug: "business-rules" });
4583
4673
  const rules = entries.filter((e) => e.data?.domain === domain);
@@ -4630,7 +4720,7 @@ Provide a structured review with a compliance status for each rule (COMPLIANT /
4630
4720
  server.prompt(
4631
4721
  "name-check",
4632
4722
  "Check variable names, field names, or API names against the glossary for terminology alignment. Flags drift from canonical terms.",
4633
- { names: z10.string().describe("Comma-separated list of names to check (e.g. 'vendor_id, compliance_level, formulator_type')") },
4723
+ { names: z11.string().describe("Comma-separated list of names to check (e.g. 'vendor_id, compliance_level, formulator_type')") },
4634
4724
  async ({ names }) => {
4635
4725
  const terms = await mcpQuery("chain.listEntries", { collectionSlug: "glossary" });
4636
4726
  const glossaryContext = terms.map(
@@ -4666,7 +4756,7 @@ Format as a table: Name | Status | Canonical Form | Action Needed`
4666
4756
  server.prompt(
4667
4757
  "draft-decision-record",
4668
4758
  "Draft a structured decision record from a description of what was decided. Includes context from recent decisions and relevant rules.",
4669
- { context: z10.string().describe("Description of the decision (e.g. 'We decided to use MRSL v3.1 as the conformance baseline because...')") },
4759
+ { context: z11.string().describe("Description of the decision (e.g. 'We decided to use MRSL v3.1 as the conformance baseline because...')") },
4670
4760
  async ({ context }) => {
4671
4761
  const recentDecisions = await mcpQuery("chain.listEntries", { collectionSlug: "decisions" });
4672
4762
  const sorted = [...recentDecisions].sort((a, b) => (b.data?.date ?? "") > (a.data?.date ?? "") ? 1 : -1).slice(0, 5);
@@ -4704,8 +4794,8 @@ After drafting, I can log it using the capture tool with collection "decisions".
4704
4794
  "draft-rule-from-context",
4705
4795
  "Draft a new business rule from an observation or discovery made while coding. Fetches existing rules for the domain to ensure consistency.",
4706
4796
  {
4707
- observation: z10.string().describe("What you observed or discovered (e.g. 'Suppliers can have multiple org types in Gateway')"),
4708
- domain: z10.string().describe("Which domain this rule belongs to (e.g. 'Governance & Decision-Making')")
4797
+ observation: z11.string().describe("What you observed or discovered (e.g. 'Suppliers can have multiple org types in Gateway')"),
4798
+ domain: z11.string().describe("Which domain this rule belongs to (e.g. 'Governance & Decision-Making')")
4709
4799
  },
4710
4800
  async ({ observation, domain }) => {
4711
4801
  const allRules = await mcpQuery("chain.listEntries", { collectionSlug: "business-rules" });
@@ -4746,10 +4836,10 @@ Make sure the rule is consistent with existing rules and doesn't contradict them
4746
4836
  "run-workflow",
4747
4837
  "Launch a Chainwork workflow (retro, shape, IDM) in Facilitator Mode. Returns the full workflow definition, facilitation instructions, and round structure. The agent enters Facilitator Mode and guides the participant through each round.",
4748
4838
  {
4749
- workflow: z10.string().describe(
4839
+ workflow: z11.string().describe(
4750
4840
  "Workflow ID to run. Available: " + listWorkflows().map((w) => `'${w.id}' (${w.name})`).join(", ")
4751
4841
  ),
4752
- context: z10.string().optional().describe(
4842
+ context: z11.string().optional().describe(
4753
4843
  "Optional context from the participant (e.g., 'retro on last sprint', 'shape the Chainwork API bet')"
4754
4844
  )
4755
4845
  },
@@ -4904,6 +4994,7 @@ function createProductBrainServer() {
4904
4994
  if (enabledModules.has("gitchain")) registerMapTools(server);
4905
4995
  if (enabledModules.has("arch")) registerArchitectureTools(server);
4906
4996
  registerStartTools(server);
4997
+ registerUsageTools(server);
4907
4998
  registerResources(server);
4908
4999
  registerPrompts(server);
4909
5000
  return server;
@@ -4913,4 +5004,4 @@ export {
4913
5004
  SERVER_VERSION,
4914
5005
  createProductBrainServer
4915
5006
  };
4916
- //# sourceMappingURL=chunk-ILOHGZZC.js.map
5007
+ //# sourceMappingURL=chunk-IUSCWY4O.js.map