@contextstream/mcp-server 0.4.35 → 0.4.36

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 +219 -134
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -4571,10 +4571,16 @@ var IGNORE_FILES = /* @__PURE__ */ new Set([
4571
4571
  "composer.lock"
4572
4572
  ]);
4573
4573
  var MAX_FILE_SIZE = 1024 * 1024;
4574
+ var MAX_BATCH_BYTES = 10 * 1024 * 1024;
4575
+ var LARGE_FILE_THRESHOLD = 2 * 1024 * 1024;
4576
+ var MAX_FILES_PER_BATCH = 200;
4574
4577
  async function* readAllFilesInBatches(rootPath, options = {}) {
4575
- const batchSize = options.batchSize ?? 50;
4578
+ const maxBatchBytes = options.maxBatchBytes ?? MAX_BATCH_BYTES;
4579
+ const largeFileThreshold = options.largeFileThreshold ?? LARGE_FILE_THRESHOLD;
4580
+ const maxFilesPerBatch = options.maxFilesPerBatch ?? MAX_FILES_PER_BATCH;
4576
4581
  const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
4577
4582
  let batch = [];
4583
+ let currentBatchBytes = 0;
4578
4584
  async function* walkDir(dir, relativePath = "") {
4579
4585
  let entries;
4580
4586
  try {
@@ -4596,28 +4602,46 @@ async function* readAllFilesInBatches(rootPath, options = {}) {
4596
4602
  const stat2 = await fs.promises.stat(fullPath);
4597
4603
  if (stat2.size > maxFileSize) continue;
4598
4604
  const content = await fs.promises.readFile(fullPath, "utf-8");
4599
- yield { path: relPath, content };
4605
+ yield { path: relPath, content, sizeBytes: stat2.size };
4600
4606
  } catch {
4601
4607
  }
4602
4608
  }
4603
4609
  }
4604
4610
  }
4605
4611
  for await (const file of walkDir(rootPath)) {
4606
- batch.push(file);
4607
- if (batch.length >= batchSize) {
4608
- yield batch;
4609
- batch = [];
4612
+ if (file.sizeBytes > largeFileThreshold) {
4613
+ if (batch.length > 0) {
4614
+ yield batch.map(({ sizeBytes, ...rest }) => rest);
4615
+ batch = [];
4616
+ currentBatchBytes = 0;
4617
+ }
4618
+ yield [{ path: file.path, content: file.content }];
4619
+ continue;
4610
4620
  }
4621
+ const wouldExceedBytes = currentBatchBytes + file.sizeBytes > maxBatchBytes;
4622
+ const wouldExceedFiles = batch.length >= maxFilesPerBatch;
4623
+ if (wouldExceedBytes || wouldExceedFiles) {
4624
+ if (batch.length > 0) {
4625
+ yield batch.map(({ sizeBytes, ...rest }) => rest);
4626
+ batch = [];
4627
+ currentBatchBytes = 0;
4628
+ }
4629
+ }
4630
+ batch.push(file);
4631
+ currentBatchBytes += file.sizeBytes;
4611
4632
  }
4612
4633
  if (batch.length > 0) {
4613
- yield batch;
4634
+ yield batch.map(({ sizeBytes, ...rest }) => rest);
4614
4635
  }
4615
4636
  }
4616
4637
  async function* readChangedFilesInBatches(rootPath, sinceTimestamp, options = {}) {
4617
- const batchSize = options.batchSize ?? 50;
4638
+ const maxBatchBytes = options.maxBatchBytes ?? MAX_BATCH_BYTES;
4639
+ const largeFileThreshold = options.largeFileThreshold ?? LARGE_FILE_THRESHOLD;
4640
+ const maxFilesPerBatch = options.maxFilesPerBatch ?? MAX_FILES_PER_BATCH;
4618
4641
  const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
4619
4642
  const sinceMs = sinceTimestamp.getTime();
4620
4643
  let batch = [];
4644
+ let currentBatchBytes = 0;
4621
4645
  let filesScanned = 0;
4622
4646
  let filesChanged = 0;
4623
4647
  async function* walkDir(dir, relativePath = "") {
@@ -4644,21 +4668,36 @@ async function* readChangedFilesInBatches(rootPath, sinceTimestamp, options = {}
4644
4668
  if (stat2.size > maxFileSize) continue;
4645
4669
  const content = await fs.promises.readFile(fullPath, "utf-8");
4646
4670
  filesChanged++;
4647
- yield { path: relPath, content };
4671
+ yield { path: relPath, content, sizeBytes: stat2.size };
4648
4672
  } catch {
4649
4673
  }
4650
4674
  }
4651
4675
  }
4652
4676
  }
4653
4677
  for await (const file of walkDir(rootPath)) {
4654
- batch.push(file);
4655
- if (batch.length >= batchSize) {
4656
- yield batch;
4657
- batch = [];
4678
+ if (file.sizeBytes > largeFileThreshold) {
4679
+ if (batch.length > 0) {
4680
+ yield batch.map(({ sizeBytes, ...rest }) => rest);
4681
+ batch = [];
4682
+ currentBatchBytes = 0;
4683
+ }
4684
+ yield [{ path: file.path, content: file.content }];
4685
+ continue;
4686
+ }
4687
+ const wouldExceedBytes = currentBatchBytes + file.sizeBytes > maxBatchBytes;
4688
+ const wouldExceedFiles = batch.length >= maxFilesPerBatch;
4689
+ if (wouldExceedBytes || wouldExceedFiles) {
4690
+ if (batch.length > 0) {
4691
+ yield batch.map(({ sizeBytes, ...rest }) => rest);
4692
+ batch = [];
4693
+ currentBatchBytes = 0;
4694
+ }
4658
4695
  }
4696
+ batch.push(file);
4697
+ currentBatchBytes += file.sizeBytes;
4659
4698
  }
4660
4699
  if (batch.length > 0) {
4661
- yield batch;
4700
+ yield batch.map(({ sizeBytes, ...rest }) => rest);
4662
4701
  }
4663
4702
  console.error(
4664
4703
  `[ContextStream] Incremental scan: ${filesChanged} changed files out of ${filesScanned} scanned (since ${sinceTimestamp.toISOString()})`
@@ -5078,7 +5117,7 @@ var ContextStreamClient = class {
5078
5117
  return "full";
5079
5118
  }
5080
5119
  if (planName.includes("pro")) return "lite";
5081
- if (planName.includes("free")) return "none";
5120
+ if (planName.includes("free")) return "lite";
5082
5121
  return "lite";
5083
5122
  } catch {
5084
5123
  return "none";
@@ -6610,19 +6649,6 @@ var ContextStreamClient = class {
6610
6649
  used += next.length;
6611
6650
  }
6612
6651
  const summary = finalLines.join("\n");
6613
- this.trackTokenSavings({
6614
- tool: "session_summary",
6615
- workspace_id: withDefaults.workspace_id,
6616
- project_id: withDefaults.project_id,
6617
- candidate_chars: candidateSummary.length,
6618
- context_chars: summary.length,
6619
- max_tokens: maxTokens,
6620
- metadata: {
6621
- decision_count: decisionCount,
6622
- memory_count: memoryCount
6623
- }
6624
- }).catch(() => {
6625
- });
6626
6652
  return {
6627
6653
  summary,
6628
6654
  workspace_name: workspaceName,
@@ -6864,21 +6890,6 @@ var ContextStreamClient = class {
6864
6890
  const context = parts.join("");
6865
6891
  const candidateContext = candidateParts.join("");
6866
6892
  const tokenEstimate = Math.ceil(context.length / charsPerToken);
6867
- this.trackTokenSavings({
6868
- tool: "ai_context_budget",
6869
- workspace_id: withDefaults.workspace_id,
6870
- project_id: withDefaults.project_id,
6871
- candidate_chars: candidateContext.length,
6872
- context_chars: context.length,
6873
- max_tokens: maxTokens,
6874
- metadata: {
6875
- include_decisions: params.include_decisions !== false,
6876
- include_memory: params.include_memory !== false,
6877
- include_code: !!params.include_code,
6878
- sources: sources.length
6879
- }
6880
- }).catch(() => {
6881
- });
6882
6893
  return {
6883
6894
  context,
6884
6895
  token_estimate: tokenEstimate,
@@ -7222,21 +7233,6 @@ W:${wsHint}
7222
7233
  versionNotice = await getUpdateNotice();
7223
7234
  } catch {
7224
7235
  }
7225
- this.trackTokenSavings({
7226
- tool: "context_smart",
7227
- workspace_id: withDefaults.workspace_id,
7228
- project_id: withDefaults.project_id,
7229
- candidate_chars: candidateContext.length,
7230
- context_chars: context.length,
7231
- max_tokens: maxTokens,
7232
- metadata: {
7233
- format,
7234
- items: items.length,
7235
- keywords: keywords.slice(0, 10),
7236
- errors: errors.length
7237
- }
7238
- }).catch(() => {
7239
- });
7240
7236
  return {
7241
7237
  context,
7242
7238
  token_estimate: Math.ceil(context.length / 4),
@@ -7885,6 +7881,29 @@ W:${wsHint}
7885
7881
  { method: "GET" }
7886
7882
  );
7887
7883
  }
7884
+ /**
7885
+ * Create a new Notion database
7886
+ */
7887
+ async notionCreateDatabase(params) {
7888
+ const withDefaults = this.withDefaults(params || {});
7889
+ if (!withDefaults.workspace_id) {
7890
+ throw new Error("workspace_id is required for creating Notion database");
7891
+ }
7892
+ const query = new URLSearchParams();
7893
+ query.set("workspace_id", withDefaults.workspace_id);
7894
+ return request(
7895
+ this.config,
7896
+ `/integrations/notion/databases?${query.toString()}`,
7897
+ {
7898
+ method: "POST",
7899
+ body: {
7900
+ title: params.title,
7901
+ parent_page_id: params.parent_page_id,
7902
+ description: params.description
7903
+ }
7904
+ }
7905
+ );
7906
+ }
7888
7907
  /**
7889
7908
  * Search/list pages in Notion with smart type detection filtering
7890
7909
  */
@@ -9235,6 +9254,69 @@ async function markProjectIndexed(projectPath, options) {
9235
9254
  await writeIndexStatus(status);
9236
9255
  }
9237
9256
 
9257
+ // src/token-savings.ts
9258
+ var TOKEN_SAVINGS_FORMULA_VERSION = 1;
9259
+ var MAX_CHARS_PER_EVENT = 2e7;
9260
+ var BASE_OVERHEAD_CHARS = 500;
9261
+ var CANDIDATE_MULTIPLIERS = {
9262
+ // context_smart: Replaces reading multiple files to gather context
9263
+ context_smart: 5,
9264
+ ai_context_budget: 5,
9265
+ // search: Semantic search replaces iterative Glob/Grep/Read cycles
9266
+ search_semantic: 4.5,
9267
+ search_hybrid: 4,
9268
+ search_keyword: 2.5,
9269
+ search_pattern: 3,
9270
+ search_exhaustive: 3.5,
9271
+ search_refactor: 3,
9272
+ // session: Recall/search replaces reading through history
9273
+ session_recall: 5,
9274
+ session_smart_search: 4,
9275
+ session_user_context: 3,
9276
+ session_summary: 4,
9277
+ // graph: Would require extensive file traversal
9278
+ graph_dependencies: 8,
9279
+ graph_impact: 10,
9280
+ graph_call_path: 8,
9281
+ graph_related: 6,
9282
+ // memory: Context retrieval
9283
+ memory_search: 3.5,
9284
+ memory_decisions: 3,
9285
+ memory_timeline: 3,
9286
+ memory_summary: 4
9287
+ };
9288
+ function clampCharCount(value) {
9289
+ if (!Number.isFinite(value) || value <= 0) return 0;
9290
+ return Math.min(MAX_CHARS_PER_EVENT, Math.floor(value));
9291
+ }
9292
+ function trackToolTokenSavings(client, tool, contextText, params, extraMetadata) {
9293
+ try {
9294
+ const contextChars = clampCharCount(contextText.length);
9295
+ const multiplier = CANDIDATE_MULTIPLIERS[tool] ?? 3;
9296
+ const baseOverhead = contextChars > 0 ? BASE_OVERHEAD_CHARS : 0;
9297
+ const estimatedCandidate = Math.round(contextChars * multiplier + baseOverhead);
9298
+ const candidateChars = Math.max(contextChars, clampCharCount(estimatedCandidate));
9299
+ client.trackTokenSavings({
9300
+ tool,
9301
+ workspace_id: params?.workspace_id,
9302
+ project_id: params?.project_id,
9303
+ candidate_chars: candidateChars,
9304
+ context_chars: contextChars,
9305
+ max_tokens: params?.max_tokens,
9306
+ metadata: {
9307
+ method: "multiplier_estimate",
9308
+ formula_version: TOKEN_SAVINGS_FORMULA_VERSION,
9309
+ source: "mcp-server",
9310
+ multiplier,
9311
+ base_overhead_chars: baseOverhead,
9312
+ ...extraMetadata ?? {}
9313
+ }
9314
+ }).catch(() => {
9315
+ });
9316
+ } catch {
9317
+ }
9318
+ }
9319
+
9238
9320
  // src/tools.ts
9239
9321
  var LESSON_DEDUP_WINDOW_MS = 2 * 60 * 1e3;
9240
9322
  var recentLessonCaptures = /* @__PURE__ */ new Map();
@@ -10587,52 +10669,6 @@ function toStructured(data) {
10587
10669
  }
10588
10670
  return void 0;
10589
10671
  }
10590
- var CANDIDATE_MULTIPLIERS = {
10591
- // context_smart: Replaces reading multiple files to gather context
10592
- context_smart: 5,
10593
- // search: Semantic search replaces iterative Glob/Grep/Read cycles
10594
- search_semantic: 4.5,
10595
- search_hybrid: 4,
10596
- search_keyword: 2.5,
10597
- search_pattern: 3,
10598
- search_exhaustive: 3.5,
10599
- search_refactor: 3,
10600
- // session: Recall/search replaces reading through history
10601
- session_recall: 5,
10602
- session_smart_search: 4,
10603
- session_user_context: 3,
10604
- session_summary: 4,
10605
- // graph: Would require extensive file traversal
10606
- graph_dependencies: 8,
10607
- graph_impact: 10,
10608
- graph_call_path: 8,
10609
- graph_related: 6,
10610
- // memory: Context retrieval
10611
- memory_search: 3.5,
10612
- memory_decisions: 3,
10613
- memory_timeline: 3,
10614
- memory_summary: 4
10615
- };
10616
- function trackToolTokenSavings(client, tool, resultContent, params) {
10617
- try {
10618
- const contextStr = typeof resultContent === "string" ? resultContent : JSON.stringify(resultContent ?? {});
10619
- const contextChars = contextStr.length;
10620
- const multiplier = CANDIDATE_MULTIPLIERS[tool] ?? 3;
10621
- const baseOverhead = 500;
10622
- const candidateChars = Math.round(contextChars * multiplier + baseOverhead);
10623
- client.trackTokenSavings({
10624
- tool,
10625
- workspace_id: params?.workspace_id,
10626
- project_id: params?.project_id,
10627
- candidate_chars: candidateChars,
10628
- context_chars: contextChars,
10629
- max_tokens: params?.max_tokens,
10630
- metadata: { multiplier, source: "mcp-server" }
10631
- }).catch(() => {
10632
- });
10633
- } catch {
10634
- }
10635
- }
10636
10672
  function readStatNumber(payload, key) {
10637
10673
  if (!payload || typeof payload !== "object") return void 0;
10638
10674
  const direct = payload[key];
@@ -10876,7 +10912,8 @@ function registerTools(server, client, sessionManager) {
10876
10912
  ["graph_call_path", "full"],
10877
10913
  ["graph_circular_dependencies", "full"],
10878
10914
  ["graph_unused_code", "full"],
10879
- ["graph_ingest", "full"],
10915
+ ["graph_ingest", "lite"],
10916
+ // Pro can ingest (builds module-level graph), Elite gets full graph
10880
10917
  ["graph_contradictions", "full"]
10881
10918
  ]);
10882
10919
  const graphLiteMaxDepth = 1;
@@ -15372,12 +15409,13 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
15372
15409
  default:
15373
15410
  toolType = "search_hybrid";
15374
15411
  }
15375
- trackToolTokenSavings(client, toolType, result, {
15412
+ const outputText = formatContent(result);
15413
+ trackToolTokenSavings(client, toolType, outputText, {
15376
15414
  workspace_id: params.workspace_id,
15377
15415
  project_id: params.project_id
15378
15416
  });
15379
15417
  return {
15380
- content: [{ type: "text", text: formatContent(result) }],
15418
+ content: [{ type: "text", text: outputText }],
15381
15419
  structuredContent: toStructured(result)
15382
15420
  };
15383
15421
  }
@@ -15588,12 +15626,13 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
15588
15626
  include_related: input.include_related,
15589
15627
  include_decisions: input.include_decisions
15590
15628
  });
15591
- trackToolTokenSavings(client, "session_recall", result, {
15629
+ const outputText = formatContent(result);
15630
+ trackToolTokenSavings(client, "session_recall", outputText, {
15592
15631
  workspace_id: workspaceId,
15593
15632
  project_id: projectId
15594
15633
  });
15595
15634
  return {
15596
- content: [{ type: "text", text: formatContent(result) }],
15635
+ content: [{ type: "text", text: outputText }],
15597
15636
  structuredContent: toStructured(result)
15598
15637
  };
15599
15638
  }
@@ -15615,11 +15654,12 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
15615
15654
  }
15616
15655
  case "user_context": {
15617
15656
  const result = await client.getUserContext({ workspace_id: workspaceId });
15618
- trackToolTokenSavings(client, "session_user_context", result, {
15657
+ const outputText = formatContent(result);
15658
+ trackToolTokenSavings(client, "session_user_context", outputText, {
15619
15659
  workspace_id: workspaceId
15620
15660
  });
15621
15661
  return {
15622
- content: [{ type: "text", text: formatContent(result) }],
15662
+ content: [{ type: "text", text: outputText }],
15623
15663
  structuredContent: toStructured(result)
15624
15664
  };
15625
15665
  }
@@ -15629,13 +15669,14 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
15629
15669
  project_id: projectId,
15630
15670
  max_tokens: input.max_tokens
15631
15671
  });
15632
- trackToolTokenSavings(client, "session_summary", result, {
15672
+ const outputText = formatContent(result);
15673
+ trackToolTokenSavings(client, "session_summary", outputText, {
15633
15674
  workspace_id: workspaceId,
15634
15675
  project_id: projectId,
15635
15676
  max_tokens: input.max_tokens
15636
15677
  });
15637
15678
  return {
15638
- content: [{ type: "text", text: formatContent(result) }],
15679
+ content: [{ type: "text", text: outputText }],
15639
15680
  structuredContent: toStructured(result)
15640
15681
  };
15641
15682
  }
@@ -15679,12 +15720,13 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
15679
15720
  include_decisions: input.include_decisions,
15680
15721
  include_related: input.include_related
15681
15722
  });
15682
- trackToolTokenSavings(client, "session_smart_search", result, {
15723
+ const outputText = formatContent(result);
15724
+ trackToolTokenSavings(client, "session_smart_search", outputText, {
15683
15725
  workspace_id: workspaceId,
15684
15726
  project_id: projectId
15685
15727
  });
15686
15728
  return {
15687
- content: [{ type: "text", text: formatContent(result) }],
15729
+ content: [{ type: "text", text: outputText }],
15688
15730
  structuredContent: toStructured(result)
15689
15731
  };
15690
15732
  }
@@ -16031,12 +16073,13 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
16031
16073
  query: input.query,
16032
16074
  limit: input.limit
16033
16075
  });
16034
- trackToolTokenSavings(client, "memory_search", result, {
16076
+ const outputText = formatContent(result);
16077
+ trackToolTokenSavings(client, "memory_search", outputText, {
16035
16078
  workspace_id: workspaceId,
16036
16079
  project_id: projectId
16037
16080
  });
16038
16081
  return {
16039
- content: [{ type: "text", text: formatContent(result) }],
16082
+ content: [{ type: "text", text: outputText }],
16040
16083
  structuredContent: toStructured(result)
16041
16084
  };
16042
16085
  }
@@ -16047,12 +16090,13 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
16047
16090
  category: input.category,
16048
16091
  limit: input.limit
16049
16092
  });
16050
- trackToolTokenSavings(client, "memory_decisions", result, {
16093
+ const outputText = formatContent(result);
16094
+ trackToolTokenSavings(client, "memory_decisions", outputText, {
16051
16095
  workspace_id: workspaceId,
16052
16096
  project_id: projectId
16053
16097
  });
16054
16098
  return {
16055
- content: [{ type: "text", text: formatContent(result) }],
16099
+ content: [{ type: "text", text: outputText }],
16056
16100
  structuredContent: toStructured(result)
16057
16101
  };
16058
16102
  }
@@ -16061,11 +16105,12 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
16061
16105
  return errorResult("timeline requires workspace_id. Call session_init first.");
16062
16106
  }
16063
16107
  const result = await client.memoryTimeline(workspaceId);
16064
- trackToolTokenSavings(client, "memory_timeline", result, {
16108
+ const outputText = formatContent(result);
16109
+ trackToolTokenSavings(client, "memory_timeline", outputText, {
16065
16110
  workspace_id: workspaceId
16066
16111
  });
16067
16112
  return {
16068
- content: [{ type: "text", text: formatContent(result) }],
16113
+ content: [{ type: "text", text: outputText }],
16069
16114
  structuredContent: toStructured(result)
16070
16115
  };
16071
16116
  }
@@ -16074,11 +16119,12 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
16074
16119
  return errorResult("summary requires workspace_id. Call session_init first.");
16075
16120
  }
16076
16121
  const result = await client.memorySummary(workspaceId);
16077
- trackToolTokenSavings(client, "memory_summary", result, {
16122
+ const outputText = formatContent(result);
16123
+ trackToolTokenSavings(client, "memory_summary", outputText, {
16078
16124
  workspace_id: workspaceId
16079
16125
  });
16080
16126
  return {
16081
- content: [{ type: "text", text: formatContent(result) }],
16127
+ content: [{ type: "text", text: outputText }],
16082
16128
  structuredContent: toStructured(result)
16083
16129
  };
16084
16130
  }
@@ -16263,12 +16309,13 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
16263
16309
  max_depth: input.max_depth,
16264
16310
  include_transitive: input.include_transitive
16265
16311
  });
16266
- trackToolTokenSavings(client, "graph_dependencies", result, {
16312
+ const outputText = formatContent(result);
16313
+ trackToolTokenSavings(client, "graph_dependencies", outputText, {
16267
16314
  workspace_id: workspaceId,
16268
16315
  project_id: projectId
16269
16316
  });
16270
16317
  return {
16271
- content: [{ type: "text", text: formatContent(result) }],
16318
+ content: [{ type: "text", text: outputText }],
16272
16319
  structuredContent: toStructured(result)
16273
16320
  };
16274
16321
  }
@@ -16280,12 +16327,13 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
16280
16327
  target: input.target,
16281
16328
  max_depth: input.max_depth
16282
16329
  });
16283
- trackToolTokenSavings(client, "graph_impact", result, {
16330
+ const outputText = formatContent(result);
16331
+ trackToolTokenSavings(client, "graph_impact", outputText, {
16284
16332
  workspace_id: workspaceId,
16285
16333
  project_id: projectId
16286
16334
  });
16287
16335
  return {
16288
- content: [{ type: "text", text: formatContent(result) }],
16336
+ content: [{ type: "text", text: outputText }],
16289
16337
  structuredContent: toStructured(result)
16290
16338
  };
16291
16339
  }
@@ -16298,12 +16346,13 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
16298
16346
  target: input.target,
16299
16347
  max_depth: input.max_depth
16300
16348
  });
16301
- trackToolTokenSavings(client, "graph_call_path", result, {
16349
+ const outputText = formatContent(result);
16350
+ trackToolTokenSavings(client, "graph_call_path", outputText, {
16302
16351
  workspace_id: workspaceId,
16303
16352
  project_id: projectId
16304
16353
  });
16305
16354
  return {
16306
- content: [{ type: "text", text: formatContent(result) }],
16355
+ content: [{ type: "text", text: outputText }],
16307
16356
  structuredContent: toStructured(result)
16308
16357
  };
16309
16358
  }
@@ -16317,12 +16366,13 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
16317
16366
  project_id: projectId,
16318
16367
  limit: input.limit
16319
16368
  });
16320
- trackToolTokenSavings(client, "graph_related", result, {
16369
+ const outputText = formatContent(result);
16370
+ trackToolTokenSavings(client, "graph_related", outputText, {
16321
16371
  workspace_id: workspaceId,
16322
16372
  project_id: projectId
16323
16373
  });
16324
16374
  return {
16325
- content: [{ type: "text", text: formatContent(result) }],
16375
+ content: [{ type: "text", text: outputText }],
16326
16376
  structuredContent: toStructured(result)
16327
16377
  };
16328
16378
  }
@@ -16786,7 +16836,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
16786
16836
  "integration",
16787
16837
  {
16788
16838
  title: "Integration",
16789
- description: `Integration operations for Slack, GitHub, and Notion. Provider: slack, github, notion, all. Actions: status, search, stats, activity, contributors, knowledge, summary, channels (slack), discussions (slack), repos (github), issues (github), create_page (notion), list_databases (notion), search_pages (notion with smart type detection - filter by event_type, status, priority, has_due_date, tags), get_page (notion), query_database (notion), update_page (notion).`,
16839
+ description: `Integration operations for Slack, GitHub, and Notion. Provider: slack, github, notion, all. Actions: status, search, stats, activity, contributors, knowledge, summary, channels (slack), discussions (slack), repos (github), issues (github), create_page (notion), create_database (notion), list_databases (notion), search_pages (notion with smart type detection - filter by event_type, status, priority, has_due_date, tags), get_page (notion), query_database (notion), update_page (notion).`,
16790
16840
  inputSchema: external_exports.object({
16791
16841
  provider: external_exports.enum(["slack", "github", "notion", "all"]).describe("Integration provider"),
16792
16842
  action: external_exports.enum([
@@ -16804,6 +16854,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
16804
16854
  "issues",
16805
16855
  // Notion-specific actions
16806
16856
  "create_page",
16857
+ "create_database",
16807
16858
  "list_databases",
16808
16859
  "search_pages",
16809
16860
  "get_page",
@@ -16817,10 +16868,11 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
16817
16868
  since: external_exports.string().optional(),
16818
16869
  until: external_exports.string().optional(),
16819
16870
  // Notion-specific parameters
16820
- title: external_exports.string().optional().describe("Page title (for Notion create_page/update_page)"),
16871
+ title: external_exports.string().optional().describe("Page/database title (for Notion create_page/update_page/create_database)"),
16821
16872
  content: external_exports.string().optional().describe("Page content in Markdown (for Notion create_page/update_page)"),
16873
+ description: external_exports.string().optional().describe("Database description (for Notion create_database)"),
16822
16874
  parent_database_id: external_exports.string().optional().describe("Parent database ID (for Notion create_page)"),
16823
- parent_page_id: external_exports.string().optional().describe("Parent page ID (for Notion create_page)"),
16875
+ parent_page_id: external_exports.string().optional().describe("Parent page ID (for Notion create_page/create_database)"),
16824
16876
  page_id: external_exports.string().optional().describe("Page ID (for Notion get_page/update_page)"),
16825
16877
  database_id: external_exports.string().optional().describe("Database ID (for Notion query_database/search_pages/activity)"),
16826
16878
  days: external_exports.number().optional().describe("Number of days for stats/summary (default: 7)"),
@@ -17112,6 +17164,39 @@ Created: ${result.created_time}`
17112
17164
  structuredContent: toStructured(result)
17113
17165
  };
17114
17166
  }
17167
+ case "create_database": {
17168
+ if (input.provider !== "notion") {
17169
+ return errorResult("create_database is only available for notion provider");
17170
+ }
17171
+ if (!input.title) {
17172
+ return errorResult("title is required for create_database action");
17173
+ }
17174
+ if (!input.parent_page_id) {
17175
+ return errorResult("parent_page_id is required for create_database action");
17176
+ }
17177
+ if (!workspaceId) {
17178
+ return errorResult(
17179
+ "Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly."
17180
+ );
17181
+ }
17182
+ const newDatabase = await client.notionCreateDatabase({
17183
+ workspace_id: workspaceId,
17184
+ title: input.title,
17185
+ parent_page_id: input.parent_page_id,
17186
+ description: input.description
17187
+ });
17188
+ return {
17189
+ content: [
17190
+ {
17191
+ type: "text",
17192
+ text: `Created database "${newDatabase.title}"
17193
+ ID: ${newDatabase.id}
17194
+ URL: ${newDatabase.url}`
17195
+ }
17196
+ ],
17197
+ structuredContent: toStructured(newDatabase)
17198
+ };
17199
+ }
17115
17200
  case "list_databases": {
17116
17201
  if (input.provider !== "notion") {
17117
17202
  return errorResult("list_databases is only available for notion provider");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@contextstream/mcp-server",
3
3
  "mcpName": "io.github.contextstreamio/mcp-server",
4
- "version": "0.4.35",
4
+ "version": "0.4.36",
5
5
  "description": "ContextStream MCP server - v0.4.x with consolidated domain tools (~11 tools, ~75% token reduction). Code context, memory, search, and AI tools.",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -20,8 +20,8 @@
20
20
  "test-server": "tsx src/test-server.ts",
21
21
  "test-server:start": "node dist/test-server.js",
22
22
  "typecheck": "tsc --noEmit",
23
- "test": "vitest run",
24
- "test:watch": "vitest",
23
+ "test": "vitest run --config vitest.config.cjs",
24
+ "test:watch": "vitest --config vitest.config.cjs",
25
25
  "lint": "eslint src/",
26
26
  "lint:fix": "eslint src/ --fix",
27
27
  "format": "prettier --write src/",