@contextstream/mcp-server 0.3.18 → 0.3.19

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.
Files changed (2) hide show
  1. package/dist/index.js +125 -22
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5431,6 +5431,17 @@ var ContextStreamClient = class {
5431
5431
  // ============================================
5432
5432
  // Token-Saving Context Tools
5433
5433
  // ============================================
5434
+ /**
5435
+ * Record a token savings event for user-facing dashboard analytics.
5436
+ * Best-effort: callers should not await this in latency-sensitive paths.
5437
+ */
5438
+ trackTokenSavings(body) {
5439
+ const payload = this.withDefaults({
5440
+ source: "mcp",
5441
+ ...body
5442
+ });
5443
+ return request(this.config, "/analytics/token-savings", { body: payload });
5444
+ }
5434
5445
  /**
5435
5446
  * Get a compact, token-efficient summary of workspace context.
5436
5447
  * Designed to be included in every AI prompt without consuming many tokens.
@@ -5519,7 +5530,31 @@ var ContextStreamClient = class {
5519
5530
  }
5520
5531
  parts.push("");
5521
5532
  parts.push('\u{1F4A1} Use session_recall("topic") for specific context');
5522
- const summary = parts.join("\n");
5533
+ const candidateSummary = parts.join("\n");
5534
+ const maxChars = maxTokens * 4;
5535
+ const candidateLines = candidateSummary.split("\n");
5536
+ const finalLines = [];
5537
+ let used = 0;
5538
+ for (const line of candidateLines) {
5539
+ const next = (finalLines.length ? "\n" : "") + line;
5540
+ if (used + next.length > maxChars) break;
5541
+ finalLines.push(line);
5542
+ used += next.length;
5543
+ }
5544
+ const summary = finalLines.join("\n");
5545
+ this.trackTokenSavings({
5546
+ tool: "session_summary",
5547
+ workspace_id: withDefaults.workspace_id,
5548
+ project_id: withDefaults.project_id,
5549
+ candidate_chars: candidateSummary.length,
5550
+ context_chars: summary.length,
5551
+ max_tokens: maxTokens,
5552
+ metadata: {
5553
+ decision_count: decisionCount,
5554
+ memory_count: memoryCount
5555
+ }
5556
+ }).catch(() => {
5557
+ });
5523
5558
  return {
5524
5559
  summary,
5525
5560
  workspace_name: workspaceName,
@@ -5654,6 +5689,7 @@ var ContextStreamClient = class {
5654
5689
  const charsPerToken = 4;
5655
5690
  const maxChars = maxTokens * charsPerToken;
5656
5691
  const parts = [];
5692
+ const candidateParts = [];
5657
5693
  const sources = [];
5658
5694
  let currentChars = 0;
5659
5695
  if (params.include_decisions !== false && withDefaults.workspace_id) {
@@ -5665,14 +5701,22 @@ var ContextStreamClient = class {
5665
5701
  });
5666
5702
  if (decisions.items) {
5667
5703
  parts.push("## Relevant Decisions\n");
5704
+ candidateParts.push("## Relevant Decisions\n");
5668
5705
  currentChars += 25;
5669
- for (const d of decisions.items) {
5670
- const entry = `\u2022 ${d.title || "Decision"}
5671
- `;
5672
- if (currentChars + entry.length > maxChars * 0.4) break;
5673
- parts.push(entry);
5674
- currentChars += entry.length;
5675
- sources.push({ type: "decision", title: d.title || "Decision" });
5706
+ const decisionEntries = decisions.items.map((d) => {
5707
+ const title = d.title || "Decision";
5708
+ return { title, entry: `\u2022 ${title}
5709
+ ` };
5710
+ });
5711
+ for (const d of decisionEntries) {
5712
+ candidateParts.push(d.entry);
5713
+ }
5714
+ candidateParts.push("\n");
5715
+ for (const d of decisionEntries) {
5716
+ if (currentChars + d.entry.length > maxChars * 0.4) break;
5717
+ parts.push(d.entry);
5718
+ currentChars += d.entry.length;
5719
+ sources.push({ type: "decision", title: d.title });
5676
5720
  }
5677
5721
  parts.push("\n");
5678
5722
  }
@@ -5689,16 +5733,23 @@ var ContextStreamClient = class {
5689
5733
  });
5690
5734
  if (memory.results) {
5691
5735
  parts.push("## Related Context\n");
5736
+ candidateParts.push("## Related Context\n");
5692
5737
  currentChars += 20;
5693
- for (const m of memory.results) {
5738
+ const memoryEntries = memory.results.map((m) => {
5694
5739
  const title = m.title || "Context";
5695
5740
  const content = m.content?.slice(0, 200) || "";
5696
- const entry = `\u2022 ${title}: ${content}...
5697
- `;
5698
- if (currentChars + entry.length > maxChars * 0.7) break;
5699
- parts.push(entry);
5700
- currentChars += entry.length;
5701
- sources.push({ type: "memory", title });
5741
+ return { title, entry: `\u2022 ${title}: ${content}...
5742
+ ` };
5743
+ });
5744
+ for (const m of memoryEntries) {
5745
+ candidateParts.push(m.entry);
5746
+ }
5747
+ candidateParts.push("\n");
5748
+ for (const m of memoryEntries) {
5749
+ if (currentChars + m.entry.length > maxChars * 0.7) break;
5750
+ parts.push(m.entry);
5751
+ currentChars += m.entry.length;
5752
+ sources.push({ type: "memory", title: m.title });
5702
5753
  }
5703
5754
  parts.push("\n");
5704
5755
  }
@@ -5715,23 +5766,45 @@ var ContextStreamClient = class {
5715
5766
  });
5716
5767
  if (code.results) {
5717
5768
  parts.push("## Relevant Code\n");
5769
+ candidateParts.push("## Relevant Code\n");
5718
5770
  currentChars += 18;
5719
- for (const c of code.results) {
5771
+ const codeEntries = code.results.map((c) => {
5720
5772
  const path3 = c.file_path || "file";
5721
5773
  const content = c.content?.slice(0, 150) || "";
5722
- const entry = `\u2022 ${path3}: ${content}...
5723
- `;
5724
- if (currentChars + entry.length > maxChars) break;
5725
- parts.push(entry);
5726
- currentChars += entry.length;
5727
- sources.push({ type: "code", title: path3 });
5774
+ return { path: path3, entry: `\u2022 ${path3}: ${content}...
5775
+ ` };
5776
+ });
5777
+ for (const c of codeEntries) {
5778
+ candidateParts.push(c.entry);
5779
+ }
5780
+ for (const c of codeEntries) {
5781
+ if (currentChars + c.entry.length > maxChars) break;
5782
+ parts.push(c.entry);
5783
+ currentChars += c.entry.length;
5784
+ sources.push({ type: "code", title: c.path });
5728
5785
  }
5729
5786
  }
5730
5787
  } catch {
5731
5788
  }
5732
5789
  }
5733
5790
  const context = parts.join("");
5791
+ const candidateContext = candidateParts.join("");
5734
5792
  const tokenEstimate = Math.ceil(context.length / charsPerToken);
5793
+ this.trackTokenSavings({
5794
+ tool: "ai_context_budget",
5795
+ workspace_id: withDefaults.workspace_id,
5796
+ project_id: withDefaults.project_id,
5797
+ candidate_chars: candidateContext.length,
5798
+ context_chars: context.length,
5799
+ max_tokens: maxTokens,
5800
+ metadata: {
5801
+ include_decisions: params.include_decisions !== false,
5802
+ include_memory: params.include_memory !== false,
5803
+ include_code: !!params.include_code,
5804
+ sources: sources.length
5805
+ }
5806
+ }).catch(() => {
5807
+ });
5735
5808
  return {
5736
5809
  context,
5737
5810
  token_estimate: tokenEstimate,
@@ -5908,6 +5981,7 @@ var ContextStreamClient = class {
5908
5981
  let context;
5909
5982
  let charsUsed = 0;
5910
5983
  const maxChars = maxTokens * 4;
5984
+ let candidateContext;
5911
5985
  if (format === "minified") {
5912
5986
  const parts = [];
5913
5987
  for (const item of items) {
@@ -5917,6 +5991,7 @@ var ContextStreamClient = class {
5917
5991
  charsUsed += entry.length + 1;
5918
5992
  }
5919
5993
  context = parts.join("|");
5994
+ candidateContext = items.map((i) => `${i.type}:${i.value}`).join("|");
5920
5995
  } else if (format === "structured") {
5921
5996
  const grouped = {};
5922
5997
  for (const item of items) {
@@ -5926,6 +6001,12 @@ var ContextStreamClient = class {
5926
6001
  charsUsed += item.value.length + 5;
5927
6002
  }
5928
6003
  context = JSON.stringify(grouped);
6004
+ const candidateGrouped = {};
6005
+ for (const item of items) {
6006
+ if (!candidateGrouped[item.type]) candidateGrouped[item.type] = [];
6007
+ candidateGrouped[item.type].push(item.value);
6008
+ }
6009
+ candidateContext = JSON.stringify(candidateGrouped);
5929
6010
  } else {
5930
6011
  const lines = ["[CTX]"];
5931
6012
  for (const item of items) {
@@ -5936,6 +6017,12 @@ var ContextStreamClient = class {
5936
6017
  }
5937
6018
  lines.push("[/CTX]");
5938
6019
  context = lines.join("\n");
6020
+ const candidateLines = ["[CTX]"];
6021
+ for (const item of items) {
6022
+ candidateLines.push(`${item.type}:${item.value}`);
6023
+ }
6024
+ candidateLines.push("[/CTX]");
6025
+ candidateContext = candidateLines.join("\n");
5939
6026
  }
5940
6027
  if (context.length === 0 && withDefaults.workspace_id) {
5941
6028
  const wsHint = items.find((i) => i.type === "W")?.value || withDefaults.workspace_id.slice(0, 8);
@@ -5943,7 +6030,23 @@ var ContextStreamClient = class {
5943
6030
  W:${wsHint}
5944
6031
  [NO_MATCHES]
5945
6032
  [/CTX]`;
6033
+ candidateContext = context;
5946
6034
  }
6035
+ this.trackTokenSavings({
6036
+ tool: "context_smart",
6037
+ workspace_id: withDefaults.workspace_id,
6038
+ project_id: withDefaults.project_id,
6039
+ candidate_chars: candidateContext.length,
6040
+ context_chars: context.length,
6041
+ max_tokens: maxTokens,
6042
+ metadata: {
6043
+ format,
6044
+ items: items.length,
6045
+ keywords: keywords.slice(0, 10),
6046
+ errors: errors.length
6047
+ }
6048
+ }).catch(() => {
6049
+ });
5947
6050
  return {
5948
6051
  context,
5949
6052
  token_estimate: Math.ceil(context.length / 4),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contextstream/mcp-server",
3
- "version": "0.3.18",
3
+ "version": "0.3.19",
4
4
  "description": "MCP server exposing ContextStream public API - code context, memory, search, and AI tools for developers",
5
5
  "type": "module",
6
6
  "license": "MIT",