@contextstream/mcp-server 0.3.53 → 0.3.55

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 (3) hide show
  1. package/README.md +6 -0
  2. package/dist/index.js +257 -34
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -28,9 +28,15 @@ No special syntax. No commands to learn. Just ask.
28
28
  - Memory capture and recall (decisions, preferences, tasks, bugs, lessons)
29
29
  - Code search (semantic, hybrid, keyword, pattern)
30
30
  - Knowledge graph and code analysis (dependencies, impact, call paths, circular deps, unused code)
31
+ - Graph ingestion for full graph builds (`graph_ingest`)
31
32
  - Local repo ingestion for indexing (`projects_ingest_local`)
32
33
  - Auto-context: on the first tool call in a new session, the server can auto-initialize context
33
34
 
35
+ ## Graph tiers
36
+
37
+ - **Pro (Graph-Lite):** module-level import graph, dependencies, and 1-hop impact
38
+ - **Elite/Team (Full Graph):** module + call + dataflow + type layers, plus `graph_ingest`
39
+
34
40
  ## Requirements
35
41
 
36
42
  - Node.js 18+
package/dist/index.js CHANGED
@@ -4701,6 +4701,8 @@ import { createRequire } from "module";
4701
4701
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
4702
4702
  import { homedir } from "os";
4703
4703
  import { join as join3 } from "path";
4704
+ var UPGRADE_COMMAND = "npm update -g @contextstream/mcp-server";
4705
+ var NPM_LATEST_URL = "https://registry.npmjs.org/@contextstream/mcp-server/latest";
4704
4706
  function getVersion() {
4705
4707
  try {
4706
4708
  const require2 = createRequire(import.meta.url);
@@ -4724,6 +4726,7 @@ function compareVersions(v1, v2) {
4724
4726
  return 0;
4725
4727
  }
4726
4728
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
4729
+ var latestVersionPromise = null;
4727
4730
  function getCacheFilePath() {
4728
4731
  return join3(homedir(), ".contextstream", "version-cache.json");
4729
4732
  }
@@ -4752,33 +4755,40 @@ function writeCache(latestVersion) {
4752
4755
  } catch {
4753
4756
  }
4754
4757
  }
4755
- async function checkForUpdates() {
4756
- const currentVersion = VERSION;
4757
- if (currentVersion === "unknown") return;
4758
+ async function fetchLatestVersion() {
4758
4759
  try {
4759
- const cached = readCache();
4760
- if (cached) {
4761
- if (compareVersions(currentVersion, cached.latestVersion) < 0) {
4762
- showUpdateWarning(currentVersion, cached.latestVersion);
4763
- }
4764
- return;
4765
- }
4766
4760
  const controller = new AbortController();
4767
4761
  const timeout = setTimeout(() => controller.abort(), 5e3);
4768
- const response = await fetch("https://registry.npmjs.org/@contextstream/mcp-server/latest", {
4762
+ const response = await fetch(NPM_LATEST_URL, {
4769
4763
  signal: controller.signal,
4770
4764
  headers: { "Accept": "application/json" }
4771
4765
  });
4772
4766
  clearTimeout(timeout);
4773
- if (!response.ok) return;
4767
+ if (!response.ok) return null;
4774
4768
  const data = await response.json();
4775
- const latestVersion = data.version;
4776
- if (typeof latestVersion !== "string") return;
4777
- writeCache(latestVersion);
4778
- if (compareVersions(currentVersion, latestVersion) < 0) {
4779
- showUpdateWarning(currentVersion, latestVersion);
4780
- }
4769
+ return typeof data.version === "string" ? data.version : null;
4781
4770
  } catch {
4771
+ return null;
4772
+ }
4773
+ }
4774
+ async function resolveLatestVersion() {
4775
+ const cached = readCache();
4776
+ if (cached) return cached.latestVersion;
4777
+ if (!latestVersionPromise) {
4778
+ latestVersionPromise = fetchLatestVersion().finally(() => {
4779
+ latestVersionPromise = null;
4780
+ });
4781
+ }
4782
+ const latestVersion = await latestVersionPromise;
4783
+ if (latestVersion) {
4784
+ writeCache(latestVersion);
4785
+ }
4786
+ return latestVersion;
4787
+ }
4788
+ async function checkForUpdates() {
4789
+ const notice = await getUpdateNotice();
4790
+ if (notice?.behind) {
4791
+ showUpdateWarning(notice.current, notice.latest);
4782
4792
  }
4783
4793
  }
4784
4794
  function showUpdateWarning(currentVersion, latestVersion) {
@@ -4786,12 +4796,30 @@ function showUpdateWarning(currentVersion, latestVersion) {
4786
4796
  console.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
4787
4797
  console.error(`\u26A0\uFE0F Update available: v${currentVersion} \u2192 v${latestVersion}`);
4788
4798
  console.error("");
4789
- console.error(" Run: npm update -g @contextstream/mcp-server");
4799
+ console.error(` Run: ${UPGRADE_COMMAND}`);
4790
4800
  console.error("");
4791
4801
  console.error(" Then restart your AI tool to use the new version.");
4792
4802
  console.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
4793
4803
  console.error("");
4794
4804
  }
4805
+ async function getUpdateNotice() {
4806
+ const currentVersion = VERSION;
4807
+ if (currentVersion === "unknown") return null;
4808
+ try {
4809
+ const latestVersion = await resolveLatestVersion();
4810
+ if (!latestVersion) return null;
4811
+ if (compareVersions(currentVersion, latestVersion) < 0) {
4812
+ return {
4813
+ current: currentVersion,
4814
+ latest: latestVersion,
4815
+ behind: true,
4816
+ upgrade_command: UPGRADE_COMMAND
4817
+ };
4818
+ }
4819
+ } catch {
4820
+ }
4821
+ return null;
4822
+ }
4795
4823
 
4796
4824
  // src/client.ts
4797
4825
  var uuidSchema = external_exports.string().uuid();
@@ -4826,6 +4854,19 @@ function normalizeNodeType(input) {
4826
4854
  );
4827
4855
  }
4828
4856
  }
4857
+ function pickString(value) {
4858
+ if (typeof value !== "string") return null;
4859
+ const trimmed = value.trim();
4860
+ return trimmed ? trimmed : null;
4861
+ }
4862
+ function normalizeGraphTier(value) {
4863
+ const normalized = value.trim().toLowerCase();
4864
+ if (!normalized) return null;
4865
+ if (normalized.includes("full") || normalized.includes("elite") || normalized.includes("team")) return "full";
4866
+ if (normalized.includes("lite") || normalized.includes("light") || normalized.includes("basic") || normalized.includes("module")) return "lite";
4867
+ if (normalized.includes("none") || normalized.includes("off") || normalized.includes("disabled") || normalized.includes("free")) return "none";
4868
+ return null;
4869
+ }
4829
4870
  var AI_PLAN_TIMEOUT_MS = 5e4;
4830
4871
  var AI_PLAN_RETRIES = 0;
4831
4872
  var ContextStreamClient = class {
@@ -4920,6 +4961,26 @@ var ContextStreamClient = class {
4920
4961
  return null;
4921
4962
  }
4922
4963
  }
4964
+ async getGraphTier() {
4965
+ try {
4966
+ const balance = await this.getCreditBalance();
4967
+ const plan = balance?.plan ?? {};
4968
+ const features = plan?.features ?? {};
4969
+ const tierCandidate = pickString(plan.graph_tier) || pickString(plan.graphTier) || pickString(features.graph_tier) || pickString(features.graphTier) || pickString(balance?.graph_tier) || pickString(balance?.graphTier);
4970
+ const normalizedTier = tierCandidate ? normalizeGraphTier(tierCandidate) : null;
4971
+ if (normalizedTier) return normalizedTier;
4972
+ const planName = pickString(plan.name)?.toLowerCase() ?? null;
4973
+ if (!planName) return "none";
4974
+ if (planName.includes("elite") || planName.includes("team") || planName.includes("enterprise") || planName.includes("business")) {
4975
+ return "full";
4976
+ }
4977
+ if (planName.includes("pro")) return "lite";
4978
+ if (planName.includes("free")) return "none";
4979
+ return "lite";
4980
+ } catch {
4981
+ return "none";
4982
+ }
4983
+ }
4923
4984
  // Workspaces & Projects
4924
4985
  listWorkspaces(params) {
4925
4986
  const query = new URLSearchParams();
@@ -6611,6 +6672,11 @@ W:${wsHint}
6611
6672
  [/CTX]`;
6612
6673
  candidateContext = context;
6613
6674
  }
6675
+ let versionNotice = null;
6676
+ try {
6677
+ versionNotice = await getUpdateNotice();
6678
+ } catch {
6679
+ }
6614
6680
  this.trackTokenSavings({
6615
6681
  tool: "context_smart",
6616
6682
  workspace_id: withDefaults.workspace_id,
@@ -6631,6 +6697,7 @@ W:${wsHint}
6631
6697
  token_estimate: Math.ceil(context.length / 4),
6632
6698
  format,
6633
6699
  sources_used: items.filter((i) => context.includes(i.value.slice(0, 20))).length,
6700
+ ...versionNotice ? { version_notice: versionNotice } : {},
6634
6701
  ...errors.length > 0 && { errors }
6635
6702
  // Include errors for debugging
6636
6703
  };
@@ -7939,6 +8006,35 @@ function toStructured(data) {
7939
8006
  }
7940
8007
  return void 0;
7941
8008
  }
8009
+ function readStatNumber(payload, key) {
8010
+ if (!payload || typeof payload !== "object") return void 0;
8011
+ const direct = payload[key];
8012
+ if (typeof direct === "number") return direct;
8013
+ const nested = payload.data;
8014
+ if (nested && typeof nested === "object") {
8015
+ const nestedValue = nested[key];
8016
+ if (typeof nestedValue === "number") return nestedValue;
8017
+ }
8018
+ return void 0;
8019
+ }
8020
+ function estimateGraphIngestMinutes(stats) {
8021
+ const totalFiles = readStatNumber(stats, "total_files");
8022
+ const totalLines = readStatNumber(stats, "total_lines");
8023
+ if (!totalFiles && !totalLines) return null;
8024
+ const fileScore = totalFiles ? totalFiles / 1e3 : 0;
8025
+ const lineScore = totalLines ? totalLines / 5e4 : 0;
8026
+ const sizeScore = Math.max(fileScore, lineScore);
8027
+ const minMinutes = Math.min(45, Math.max(1, Math.round(1 + sizeScore * 1.5)));
8028
+ const maxMinutes = Math.min(60, Math.max(minMinutes + 1, Math.round(2 + sizeScore * 3)));
8029
+ const basisParts = [];
8030
+ if (totalFiles) basisParts.push(`${totalFiles.toLocaleString()} files`);
8031
+ if (totalLines) basisParts.push(`${totalLines.toLocaleString()} lines`);
8032
+ return {
8033
+ min: minMinutes,
8034
+ max: maxMinutes,
8035
+ basis: basisParts.length > 0 ? basisParts.join(" / ") : void 0
8036
+ };
8037
+ }
7942
8038
  function normalizeLessonField(value) {
7943
8039
  return value.trim().toLowerCase().replace(/\s+/g, " ");
7944
8040
  }
@@ -8013,6 +8109,9 @@ function registerTools(server, client, sessionManager) {
8013
8109
  return proTools.has(toolName) ? "pro" : "free";
8014
8110
  }
8015
8111
  function getToolAccessLabel(toolName) {
8112
+ const graphTier = graphToolTiers.get(toolName);
8113
+ if (graphTier === "lite") return "Pro (Graph-Lite)";
8114
+ if (graphTier === "full") return "Elite/Team (Full Graph)";
8016
8115
  return getToolAccessTier(toolName) === "pro" ? "PRO" : "Free";
8017
8116
  }
8018
8117
  async function gateIfProTool(toolName) {
@@ -8026,6 +8125,94 @@ function registerTools(server, client, sessionManager) {
8026
8125
  ].join("\n")
8027
8126
  );
8028
8127
  }
8128
+ const graphToolTiers = /* @__PURE__ */ new Map([
8129
+ ["graph_dependencies", "lite"],
8130
+ ["graph_impact", "lite"],
8131
+ ["graph_related", "full"],
8132
+ ["graph_decisions", "full"],
8133
+ ["graph_path", "full"],
8134
+ ["graph_call_path", "full"],
8135
+ ["graph_circular_dependencies", "full"],
8136
+ ["graph_unused_code", "full"],
8137
+ ["graph_ingest", "full"],
8138
+ ["graph_contradictions", "full"]
8139
+ ]);
8140
+ const graphLiteMaxDepth = 1;
8141
+ function normalizeGraphTargetType(value) {
8142
+ return String(value ?? "").trim().toLowerCase();
8143
+ }
8144
+ function isModuleTargetType(value) {
8145
+ return value === "module" || value === "file" || value === "path";
8146
+ }
8147
+ function graphLiteConstraintError(toolName, detail) {
8148
+ return errorResult(
8149
+ [
8150
+ `Access denied: \`${toolName}\` is limited to Graph-Lite (module-level, 1-hop queries).`,
8151
+ detail,
8152
+ `Upgrade to Elite or Team for full graph access: ${upgradeUrl}`
8153
+ ].join("\n")
8154
+ );
8155
+ }
8156
+ async function gateIfGraphTool(toolName, input) {
8157
+ const requiredTier = graphToolTiers.get(toolName);
8158
+ if (!requiredTier) return null;
8159
+ const graphTier = await client.getGraphTier();
8160
+ if (graphTier === "full") return null;
8161
+ if (graphTier === "lite") {
8162
+ if (requiredTier === "full") {
8163
+ return errorResult(
8164
+ [
8165
+ `Access denied: \`${toolName}\` requires Elite or Team (Full Graph).`,
8166
+ "Pro includes Graph-Lite (module-level dependencies and 1-hop impact only).",
8167
+ `Upgrade: ${upgradeUrl}`
8168
+ ].join("\n")
8169
+ );
8170
+ }
8171
+ if (toolName === "graph_dependencies") {
8172
+ const targetType = normalizeGraphTargetType(input?.target?.type);
8173
+ if (!isModuleTargetType(targetType)) {
8174
+ return graphLiteConstraintError(
8175
+ toolName,
8176
+ "Set target.type to module, file, or path."
8177
+ );
8178
+ }
8179
+ if (typeof input?.max_depth === "number" && input.max_depth > graphLiteMaxDepth) {
8180
+ return graphLiteConstraintError(
8181
+ toolName,
8182
+ `Set max_depth to ${graphLiteMaxDepth} or lower.`
8183
+ );
8184
+ }
8185
+ if (input?.include_transitive === true) {
8186
+ return graphLiteConstraintError(
8187
+ toolName,
8188
+ "Set include_transitive to false."
8189
+ );
8190
+ }
8191
+ }
8192
+ if (toolName === "graph_impact") {
8193
+ const targetType = normalizeGraphTargetType(input?.target?.type);
8194
+ if (!isModuleTargetType(targetType)) {
8195
+ return graphLiteConstraintError(
8196
+ toolName,
8197
+ "Set target.type to module, file, or path."
8198
+ );
8199
+ }
8200
+ if (typeof input?.max_depth === "number" && input.max_depth > graphLiteMaxDepth) {
8201
+ return graphLiteConstraintError(
8202
+ toolName,
8203
+ `Set max_depth to ${graphLiteMaxDepth} or lower.`
8204
+ );
8205
+ }
8206
+ }
8207
+ return null;
8208
+ }
8209
+ return errorResult(
8210
+ [
8211
+ `Access denied: \`${toolName}\` requires ContextStream Pro (Graph-Lite) or Elite/Team (Full Graph).`,
8212
+ `Upgrade: ${upgradeUrl}`
8213
+ ].join("\n")
8214
+ );
8215
+ }
8029
8216
  function wrapWithAutoContext(toolName, handler) {
8030
8217
  if (!sessionManager) {
8031
8218
  return handler;
@@ -8058,12 +8245,13 @@ function registerTools(server, client, sessionManager) {
8058
8245
  return;
8059
8246
  }
8060
8247
  const accessLabel = getToolAccessLabel(name);
8248
+ const showUpgrade = accessLabel !== "Free";
8061
8249
  const labeledConfig = {
8062
8250
  ...config,
8063
8251
  title: `${config.title} (${accessLabel})`,
8064
8252
  description: `${config.description}
8065
8253
 
8066
- Access: ${accessLabel}${accessLabel === "PRO" ? ` (upgrade: ${upgradeUrl})` : ""}`
8254
+ Access: ${accessLabel}${showUpgrade ? ` (upgrade: ${upgradeUrl})` : ""}`
8067
8255
  };
8068
8256
  const safeHandler = async (input) => {
8069
8257
  try {
@@ -8545,6 +8733,8 @@ Access: Free`,
8545
8733
  })
8546
8734
  },
8547
8735
  async (input) => {
8736
+ const gate = await gateIfGraphTool("graph_related", input);
8737
+ if (gate) return gate;
8548
8738
  const result = await client.graphRelated(input);
8549
8739
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8550
8740
  }
@@ -8562,6 +8752,8 @@ Access: Free`,
8562
8752
  })
8563
8753
  },
8564
8754
  async (input) => {
8755
+ const gate = await gateIfGraphTool("graph_path", input);
8756
+ if (gate) return gate;
8565
8757
  const result = await client.graphPath(input);
8566
8758
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8567
8759
  }
@@ -8578,6 +8770,8 @@ Access: Free`,
8578
8770
  })
8579
8771
  },
8580
8772
  async (input) => {
8773
+ const gate = await gateIfGraphTool("graph_decisions", input);
8774
+ if (gate) return gate;
8581
8775
  const result = await client.graphDecisions(input);
8582
8776
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8583
8777
  }
@@ -8586,9 +8780,7 @@ Access: Free`,
8586
8780
  "graph_dependencies",
8587
8781
  {
8588
8782
  title: "Code dependencies",
8589
- description: `Dependency graph query
8590
-
8591
- Access: Free`,
8783
+ description: "Dependency graph query",
8592
8784
  inputSchema: external_exports.object({
8593
8785
  target: external_exports.object({
8594
8786
  type: external_exports.string().describe("Code element type. Accepted values: module (aliases: file, path), function (alias: method), type (aliases: struct, enum, trait, class), variable (aliases: data, const, constant). For knowledge/memory nodes, use graph_path with UUID ids instead."),
@@ -8599,6 +8791,8 @@ Access: Free`,
8599
8791
  })
8600
8792
  },
8601
8793
  async (input) => {
8794
+ const gate = await gateIfGraphTool("graph_dependencies", input);
8795
+ if (gate) return gate;
8602
8796
  const result = await client.graphDependencies(input);
8603
8797
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8604
8798
  }
@@ -8607,9 +8801,7 @@ Access: Free`,
8607
8801
  "graph_call_path",
8608
8802
  {
8609
8803
  title: "Call path",
8610
- description: `Find call path between two targets
8611
-
8612
- Access: Free`,
8804
+ description: "Find call path between two targets",
8613
8805
  inputSchema: external_exports.object({
8614
8806
  source: external_exports.object({
8615
8807
  type: external_exports.string().describe('Must be "function" (alias: method). Only function types are supported for call path analysis. For knowledge/memory nodes, use graph_path with UUID ids instead.'),
@@ -8623,6 +8815,8 @@ Access: Free`,
8623
8815
  })
8624
8816
  },
8625
8817
  async (input) => {
8818
+ const gate = await gateIfGraphTool("graph_call_path", input);
8819
+ if (gate) return gate;
8626
8820
  const result = await client.graphCallPath(input);
8627
8821
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8628
8822
  }
@@ -8631,9 +8825,7 @@ Access: Free`,
8631
8825
  "graph_impact",
8632
8826
  {
8633
8827
  title: "Impact analysis",
8634
- description: `Analyze impact of a target node
8635
-
8636
- Access: Free`,
8828
+ description: "Analyze impact of a target node",
8637
8829
  inputSchema: external_exports.object({
8638
8830
  target: external_exports.object({
8639
8831
  type: external_exports.string().describe("Code element type. Accepted values: module (aliases: file, path), function (alias: method), type (aliases: struct, enum, trait, class), variable (aliases: data, const, constant). For knowledge/memory nodes, use graph_path with UUID ids instead."),
@@ -8643,6 +8835,8 @@ Access: Free`,
8643
8835
  })
8644
8836
  },
8645
8837
  async (input) => {
8838
+ const gate = await gateIfGraphTool("graph_impact", input);
8839
+ if (gate) return gate;
8646
8840
  const result = await client.graphImpact(input);
8647
8841
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8648
8842
  }
@@ -8651,19 +8845,40 @@ Access: Free`,
8651
8845
  "graph_ingest",
8652
8846
  {
8653
8847
  title: "Ingest code graph",
8654
- description: "Build and persist the dependency graph for a project",
8848
+ description: "Build and persist the dependency graph for a project. Runs async by default (wait=false) and can take a few minutes for larger repos.",
8655
8849
  inputSchema: external_exports.object({
8656
8850
  project_id: external_exports.string().uuid().optional(),
8657
- wait: external_exports.boolean().optional().describe("If true, wait for ingestion to finish before returning.")
8851
+ wait: external_exports.boolean().optional().describe("If true, wait for ingestion to finish before returning. Defaults to false (async).")
8658
8852
  })
8659
8853
  },
8660
8854
  async (input) => {
8855
+ const gate = await gateIfGraphTool("graph_ingest", input);
8856
+ if (gate) return gate;
8661
8857
  const projectId = resolveProjectId(input.project_id);
8662
8858
  if (!projectId) {
8663
8859
  return errorResult("Error: project_id is required. Please call session_init first or provide project_id explicitly.");
8664
8860
  }
8665
- const result = await client.graphIngest({ project_id: projectId, wait: input.wait });
8666
- return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8861
+ const wait = input.wait ?? false;
8862
+ let estimate = null;
8863
+ try {
8864
+ const stats = await client.projectStatistics(projectId);
8865
+ estimate = estimateGraphIngestMinutes(stats);
8866
+ } catch (error) {
8867
+ console.error("[ContextStream] Failed to fetch project statistics for graph ingest estimate:", error);
8868
+ }
8869
+ const result = await client.graphIngest({ project_id: projectId, wait });
8870
+ const estimateText = estimate ? `Estimated time: ${estimate.min}-${estimate.max} min${estimate.basis ? ` (based on ${estimate.basis})` : ""}.` : "Estimated time varies with repo size.";
8871
+ const note = `Graph ingestion is running ${wait ? "synchronously" : "asynchronously"} and can take a few minutes. ${estimateText}`;
8872
+ const structured = toStructured(result);
8873
+ const structuredContent = structured && typeof structured === "object" ? { ...structured, wait, note, ...estimate ? { estimate_minutes: { min: estimate.min, max: estimate.max }, estimate_basis: estimate.basis } : {} } : { wait, note, ...estimate ? { estimate_minutes: { min: estimate.min, max: estimate.max }, estimate_basis: estimate.basis } : {} };
8874
+ return {
8875
+ content: [{
8876
+ type: "text",
8877
+ text: `${note}
8878
+ ${formatContent(result)}`
8879
+ }],
8880
+ structuredContent
8881
+ };
8667
8882
  }
8668
8883
  );
8669
8884
  registerTool(
@@ -9114,6 +9329,8 @@ Automatically detects code files and skips ignored directories like node_modules
9114
9329
  inputSchema: external_exports.object({ project_id: external_exports.string().uuid().optional() })
9115
9330
  },
9116
9331
  async (input) => {
9332
+ const gate = await gateIfGraphTool("graph_circular_dependencies", input);
9333
+ if (gate) return gate;
9117
9334
  const projectId = resolveProjectId(input.project_id);
9118
9335
  if (!projectId) {
9119
9336
  return errorResult("Error: project_id is required. Please call session_init first or provide project_id explicitly.");
@@ -9130,6 +9347,8 @@ Automatically detects code files and skips ignored directories like node_modules
9130
9347
  inputSchema: external_exports.object({ project_id: external_exports.string().uuid().optional() })
9131
9348
  },
9132
9349
  async (input) => {
9350
+ const gate = await gateIfGraphTool("graph_unused_code", input);
9351
+ if (gate) return gate;
9133
9352
  const projectId = resolveProjectId(input.project_id);
9134
9353
  if (!projectId) {
9135
9354
  return errorResult("Error: project_id is required. Please call session_init first or provide project_id explicitly.");
@@ -9146,6 +9365,8 @@ Automatically detects code files and skips ignored directories like node_modules
9146
9365
  inputSchema: external_exports.object({ node_id: external_exports.string().uuid() })
9147
9366
  },
9148
9367
  async (input) => {
9368
+ const gate = await gateIfGraphTool("graph_contradictions", input);
9369
+ if (gate) return gate;
9149
9370
  const result = await client.findContradictions(input.node_id);
9150
9371
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
9151
9372
  }
@@ -10180,8 +10401,10 @@ This saves ~80% tokens compared to including full chat history.`,
10180
10401
  const footer = `
10181
10402
  ---
10182
10403
  \u{1F3AF} ${result.sources_used} sources | ~${result.token_estimate} tokens | format: ${result.format}`;
10404
+ const versionNoticeLine = result.version_notice?.behind ? `
10405
+ [VERSION_NOTICE] current=${result.version_notice.current} latest=${result.version_notice.latest} upgrade="${result.version_notice.upgrade_command}"` : "";
10183
10406
  return {
10184
- content: [{ type: "text", text: result.context + footer }],
10407
+ content: [{ type: "text", text: result.context + footer + versionNoticeLine }],
10185
10408
  structuredContent: toStructured(result)
10186
10409
  };
10187
10410
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contextstream/mcp-server",
3
- "version": "0.3.53",
3
+ "version": "0.3.55",
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",