@contextstream/mcp-server 0.4.62 → 0.4.63

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 +231 -11
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -17229,6 +17229,170 @@ var TASK_HINTS = {
17229
17229
  linked_to_plan: "Task linked to plan. Progress will be tracked."
17230
17230
  };
17231
17231
 
17232
+ // src/project-index-utils.ts
17233
+ var INDEX_FRESH_HOURS = 1;
17234
+ var INDEX_RECENT_HOURS = 24;
17235
+ var INDEX_STALE_HOURS = 24 * 7;
17236
+ function asRecord(value) {
17237
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
17238
+ }
17239
+ function candidateObjects(result) {
17240
+ const root = asRecord(result);
17241
+ const data = asRecord(root?.data);
17242
+ if (data && root) return [data, root];
17243
+ if (data) return [data];
17244
+ if (root) return [root];
17245
+ return [];
17246
+ }
17247
+ function readBoolean(candidates, key) {
17248
+ for (const candidate of candidates) {
17249
+ const value = candidate[key];
17250
+ if (typeof value === "boolean") {
17251
+ return value;
17252
+ }
17253
+ }
17254
+ return void 0;
17255
+ }
17256
+ function readNumber(candidates, keys) {
17257
+ for (const candidate of candidates) {
17258
+ for (const key of keys) {
17259
+ const value = candidate[key];
17260
+ if (typeof value === "number" && Number.isFinite(value)) {
17261
+ return value;
17262
+ }
17263
+ if (typeof value === "string" && value.trim()) {
17264
+ const parsed = Number(value);
17265
+ if (Number.isFinite(parsed)) {
17266
+ return parsed;
17267
+ }
17268
+ }
17269
+ }
17270
+ }
17271
+ return void 0;
17272
+ }
17273
+ function readString(candidates, key) {
17274
+ for (const candidate of candidates) {
17275
+ const value = candidate[key];
17276
+ if (typeof value === "string" && value.trim()) {
17277
+ return value.trim();
17278
+ }
17279
+ }
17280
+ return void 0;
17281
+ }
17282
+ function extractIndexTimestamp(result) {
17283
+ const candidates = candidateObjects(result);
17284
+ for (const key of ["last_updated", "indexed_at", "last_indexed"]) {
17285
+ const raw = readString(candidates, key);
17286
+ if (!raw) continue;
17287
+ const parsed = new Date(raw);
17288
+ if (!Number.isNaN(parsed.getTime())) {
17289
+ return parsed;
17290
+ }
17291
+ }
17292
+ return void 0;
17293
+ }
17294
+ function apiResultReportsIndexed(result) {
17295
+ const candidates = candidateObjects(result);
17296
+ const indexed = readBoolean(candidates, "indexed");
17297
+ if (indexed !== void 0) {
17298
+ return indexed;
17299
+ }
17300
+ const indexedFiles = readNumber(candidates, ["indexed_files", "indexed_file_count"]) ?? 0;
17301
+ if (indexedFiles > 0) {
17302
+ return true;
17303
+ }
17304
+ const totalFiles = readNumber(candidates, ["total_files"]) ?? 0;
17305
+ if (totalFiles > 0) {
17306
+ const status = readString(candidates, "status")?.toLowerCase();
17307
+ if (status === "completed" || status === "ready") {
17308
+ return true;
17309
+ }
17310
+ }
17311
+ return false;
17312
+ }
17313
+ function apiResultIsIndexing(result) {
17314
+ const candidates = candidateObjects(result);
17315
+ const projectIndexState = readString(candidates, "project_index_state")?.toLowerCase();
17316
+ if (projectIndexState === "indexing" || projectIndexState === "committing") {
17317
+ return true;
17318
+ }
17319
+ const status = readString(candidates, "status")?.toLowerCase();
17320
+ if (status === "indexing" || status === "processing") {
17321
+ return true;
17322
+ }
17323
+ const pendingFiles = readNumber(candidates, ["pending_files"]) ?? 0;
17324
+ return pendingFiles > 0;
17325
+ }
17326
+ function countFromObject(value) {
17327
+ const obj = asRecord(value);
17328
+ if (!obj) return void 0;
17329
+ if (Array.isArray(obj.entries)) {
17330
+ return obj.entries.length;
17331
+ }
17332
+ if (Array.isArray(obj.history)) {
17333
+ return obj.history.length;
17334
+ }
17335
+ return void 0;
17336
+ }
17337
+ function indexHistoryEntryCount(result) {
17338
+ const rootCount = countFromObject(result);
17339
+ if (typeof rootCount === "number") {
17340
+ return rootCount;
17341
+ }
17342
+ const root = asRecord(result);
17343
+ const dataCount = countFromObject(root?.data);
17344
+ if (typeof dataCount === "number") {
17345
+ return dataCount;
17346
+ }
17347
+ if (Array.isArray(result)) {
17348
+ return result.length;
17349
+ }
17350
+ if (Array.isArray(root?.data)) {
17351
+ return root.data.length;
17352
+ }
17353
+ return 0;
17354
+ }
17355
+ function classifyIndexFreshness(indexed, ageHours) {
17356
+ if (!indexed) {
17357
+ return "missing";
17358
+ }
17359
+ if (typeof ageHours !== "number" || Number.isNaN(ageHours)) {
17360
+ return "unknown";
17361
+ }
17362
+ if (ageHours <= INDEX_FRESH_HOURS) {
17363
+ return "fresh";
17364
+ }
17365
+ if (ageHours <= INDEX_RECENT_HOURS) {
17366
+ return "recent";
17367
+ }
17368
+ if (ageHours <= INDEX_STALE_HOURS) {
17369
+ return "aging";
17370
+ }
17371
+ return "stale";
17372
+ }
17373
+ function classifyIndexConfidence(indexed, apiIndexed, locallyIndexed, freshness) {
17374
+ if (!indexed) {
17375
+ return {
17376
+ confidence: "low",
17377
+ reason: "Neither API status nor local index metadata currently indicates a usable index."
17378
+ };
17379
+ }
17380
+ if (apiIndexed && locallyIndexed) {
17381
+ const reason = freshness === "stale" ? "API and local metadata agree, but index age indicates stale coverage." : "API and local metadata agree for this project scope.";
17382
+ return { confidence: "high", reason };
17383
+ }
17384
+ if (apiIndexed || locallyIndexed) {
17385
+ return {
17386
+ confidence: "medium",
17387
+ reason: "Only one source reports index readiness (API vs local metadata)."
17388
+ };
17389
+ }
17390
+ return {
17391
+ confidence: "low",
17392
+ reason: "Index state is inferred but lacks corroborating API/local metadata."
17393
+ };
17394
+ }
17395
+
17232
17396
  // src/tools.ts
17233
17397
  function parseBoolEnvDefault(raw, fallback) {
17234
17398
  if (raw === void 0) return fallback;
@@ -25575,6 +25739,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
25575
25739
  plan_step_id: external_exports.string().optional().describe("Which plan step this task implements"),
25576
25740
  description: external_exports.string().optional().describe("Description for task"),
25577
25741
  task_status: external_exports.enum(["pending", "in_progress", "completed", "blocked", "cancelled"]).optional().describe("Task status"),
25742
+ status: external_exports.enum(["pending", "in_progress", "completed", "blocked", "cancelled"]).optional().describe("Backward-compatible alias for task_status in task actions"),
25578
25743
  priority: external_exports.enum(["low", "medium", "high", "urgent"]).optional().describe("Task priority"),
25579
25744
  order: external_exports.number().optional().describe("Task order within plan"),
25580
25745
  task_ids: external_exports.array(external_exports.string().uuid()).optional().describe("Task IDs for reorder_tasks"),
@@ -25877,6 +26042,7 @@ ${formatContent(result)}`
25877
26042
  if (!workspaceId) {
25878
26043
  return errorResult("create_task requires workspace_id. Call session_init first.");
25879
26044
  }
26045
+ const requestedTaskStatus = input.task_status ?? input.status;
25880
26046
  const result = await client.createTask({
25881
26047
  workspace_id: workspaceId,
25882
26048
  project_id: projectId,
@@ -25885,7 +26051,7 @@ ${formatContent(result)}`
25885
26051
  description: input.description,
25886
26052
  plan_id: input.plan_id ?? void 0,
25887
26053
  plan_step_id: input.plan_step_id,
25888
- status: input.task_status,
26054
+ status: requestedTaskStatus,
25889
26055
  priority: input.priority,
25890
26056
  order: input.order,
25891
26057
  code_refs: input.code_refs,
@@ -25913,12 +26079,13 @@ ${formatContent(result)}`
25913
26079
  if (!input.task_id) {
25914
26080
  return errorResult("update_task requires: task_id");
25915
26081
  }
26082
+ const requestedTaskStatus = input.task_status ?? input.status;
25916
26083
  const result = await client.updateTask({
25917
26084
  task_id: input.task_id,
25918
26085
  title: input.title,
25919
26086
  content: input.content,
25920
26087
  description: input.description,
25921
- status: input.task_status,
26088
+ status: requestedTaskStatus,
25922
26089
  priority: input.priority,
25923
26090
  order: input.order,
25924
26091
  plan_id: input.plan_id,
@@ -25928,11 +26095,11 @@ ${formatContent(result)}`
25928
26095
  blocked_reason: input.blocked_reason
25929
26096
  });
25930
26097
  let taskUpdateHint = "Task updated.";
25931
- if (input.task_status === "completed") {
26098
+ if (requestedTaskStatus === "completed") {
25932
26099
  taskUpdateHint = TASK_HINTS.completed;
25933
- } else if (input.task_status === "blocked") {
26100
+ } else if (requestedTaskStatus === "blocked") {
25934
26101
  taskUpdateHint = TASK_HINTS.blocked;
25935
- } else if (input.task_status === "cancelled") {
26102
+ } else if (requestedTaskStatus === "cancelled") {
25936
26103
  taskUpdateHint = TASK_HINTS.cancelled;
25937
26104
  }
25938
26105
  const resultWithHint = { ...result, hint: taskUpdateHint };
@@ -25955,11 +26122,12 @@ ${formatContent(result)}`
25955
26122
  if (!workspaceId) {
25956
26123
  return errorResult("list_tasks requires workspace_id. Call session_init first.");
25957
26124
  }
26125
+ const requestedTaskStatus = input.task_status ?? input.status;
25958
26126
  const result = await client.listTasks({
25959
26127
  workspace_id: workspaceId,
25960
26128
  project_id: projectId,
25961
26129
  plan_id: input.plan_id ?? void 0,
25962
- status: input.task_status,
26130
+ status: requestedTaskStatus,
25963
26131
  priority: input.priority,
25964
26132
  limit: input.limit,
25965
26133
  is_personal: input.is_personal
@@ -26246,11 +26414,12 @@ ${formatContent(result)}`
26246
26414
  const teamWorkspacesForTasks = await client.listTeamWorkspaces({ page_size: 100 });
26247
26415
  const workspacesForTasks = teamWorkspacesForTasks?.items || teamWorkspacesForTasks?.data?.items || [];
26248
26416
  const allTasks = [];
26417
+ const requestedTaskStatus = input.task_status ?? input.status;
26249
26418
  for (const ws of workspacesForTasks.slice(0, 10)) {
26250
26419
  try {
26251
26420
  const tasks = await client.listTasks({
26252
26421
  workspace_id: ws.id,
26253
- status: input.task_status,
26422
+ status: requestedTaskStatus,
26254
26423
  limit: input.limit ? Math.ceil(input.limit / workspacesForTasks.length) : 10
26255
26424
  });
26256
26425
  const items = tasks?.data?.tasks || tasks?.tasks || tasks?.data?.items || tasks?.items || [];
@@ -26866,8 +27035,7 @@ ${formatContent(result)}`
26866
27035
  for (const [index, candidateId] of candidateIds.entries()) {
26867
27036
  try {
26868
27037
  const statusResult = await client.projectIndexStatus(candidateId);
26869
- const data = statusResult?.data ?? statusResult;
26870
- const apiIndexed = Boolean(data?.indexed);
27038
+ const apiIndexed = apiResultReportsIndexed(statusResult);
26871
27039
  if (apiIndexed) {
26872
27040
  selected = { index, projectId: candidateId, result: statusResult, apiIndexed };
26873
27041
  break;
@@ -26897,7 +27065,17 @@ ${formatContent(result)}`
26897
27065
  });
26898
27066
  }
26899
27067
  const locallyIndexed = localIndexProjectId !== void 0 ? localIndexProjectId === selected.projectId : Boolean(folderPath && await indexedProjectIdForFolder(folderPath));
27068
+ const apiIndexing = apiResultIsIndexing(selected.result);
26900
27069
  const indexed = selected.apiIndexed || locallyIndexed;
27070
+ const indexedAt = extractIndexTimestamp(selected.result);
27071
+ const ageHours = indexedAt !== void 0 ? Math.floor((Date.now() - indexedAt.getTime()) / (1e3 * 60 * 60)) : void 0;
27072
+ const freshness = classifyIndexFreshness(indexed, ageHours);
27073
+ const { confidence: confidenceLevel, reason: confidenceReason } = classifyIndexConfidence(
27074
+ indexed,
27075
+ selected.apiIndexed,
27076
+ locallyIndexed,
27077
+ freshness
27078
+ );
26901
27079
  const response = selected.result && typeof selected.result === "object" ? { ...selected.result } : {};
26902
27080
  const responseData = response.data && typeof response.data === "object" ? { ...response.data } : {};
26903
27081
  responseData.indexed = indexed;
@@ -26909,8 +27087,30 @@ ${formatContent(result)}`
26909
27087
  }
26910
27088
  responseData.resolved_project_id = selected.projectId;
26911
27089
  responseData.resolution_rank = selected.index;
27090
+ responseData.index_freshness = freshness;
27091
+ responseData.index_age_hours = ageHours ?? null;
27092
+ responseData.index_confidence = confidenceLevel;
27093
+ responseData.index_confidence_reason = confidenceReason;
27094
+ responseData.index_timestamp = indexedAt ? indexedAt.toISOString() : null;
27095
+ responseData.index_in_progress = apiIndexing;
26912
27096
  response.data = responseData;
26913
- const text = indexed ? locallyIndexed && !selected.apiIndexed ? "Project index is ready (local state). Semantic search is available." : "Project index is ready. Semantic search is available." : 'Project index not found. Run project(action="ingest_local", path="<folder>") to start indexing.';
27097
+ let text = "";
27098
+ if (apiIndexing) {
27099
+ text = indexed ? "Project indexing is in progress. Search is using the latest committed generation." : "Project indexing is in progress. Semantic search will be available after the first commit.";
27100
+ } else if (indexed) {
27101
+ text = locallyIndexed && !selected.apiIndexed ? "Project index is ready (local state). Semantic search is available." : "Project index is ready. Semantic search is available.";
27102
+ } else {
27103
+ text = 'Project index not found. Run project(action="ingest_local", path="<folder>") to start indexing.';
27104
+ }
27105
+ const ageDisplay = typeof ageHours === "number" ? `${ageHours}h` : "unknown";
27106
+ text += ` Freshness: ${freshness} (${ageDisplay}). Confidence: ${confidenceLevel}.`;
27107
+ if (confidenceLevel !== "high") {
27108
+ text += ` ${confidenceReason}`;
27109
+ }
27110
+ if (freshness === "stale" || freshness === "missing") {
27111
+ const ingestPath = folderPath || "<folder>";
27112
+ text += ` Refresh with project(action="ingest_local", path="${ingestPath}").`;
27113
+ }
26914
27114
  return {
26915
27115
  content: [{ type: "text", text: `${text}
26916
27116
 
@@ -26933,8 +27133,28 @@ ${formatContent(response)}` }],
26933
27133
  page: input.page,
26934
27134
  limit: input.page_size
26935
27135
  });
27136
+ const count = indexHistoryEntryCount(result);
27137
+ const response = result && typeof result === "object" ? Array.isArray(result) ? [...result] : { ...result } : result;
27138
+ if (response && typeof response === "object" && !Array.isArray(response)) {
27139
+ response.entries_count = count;
27140
+ if (response.data && typeof response.data === "object") {
27141
+ response.data = {
27142
+ ...response.data,
27143
+ entries_count: count
27144
+ };
27145
+ }
27146
+ }
27147
+ const structuredHistory = response && typeof response === "object" && !Array.isArray(response) ? response : void 0;
26936
27148
  return {
26937
- content: [{ type: "text", text: formatContent(result) }]
27149
+ content: [
27150
+ {
27151
+ type: "text",
27152
+ text: `Found ${count} index history entries.
27153
+
27154
+ ${formatContent(response)}`
27155
+ }
27156
+ ],
27157
+ ...structuredHistory ? { structuredContent: structuredHistory } : {}
26938
27158
  };
26939
27159
  }
26940
27160
  case "ingest_local": {
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.62",
4
+ "version": "0.4.63",
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",