@usewhisper/mcp-server 2.11.0 → 2.12.0

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/server.js +143 -27
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -3799,6 +3799,7 @@ var API_KEY = process.env.WHISPER_API_KEY || "";
3799
3799
  var DEFAULT_PROJECT = process.env.WHISPER_PROJECT || "";
3800
3800
  var BASE_URL = process.env.WHISPER_BASE_URL;
3801
3801
  var RUNTIME_MODE = (process.env.WHISPER_MCP_MODE || "remote").toLowerCase();
3802
+ var MCP_RETRIEVAL_PRECISION_V1_DEFAULT = /^true$/i.test(process.env.MCP_RETRIEVAL_PRECISION_V1_DEFAULT || "false");
3802
3803
  var CLI_ARGS = process.argv.slice(2);
3803
3804
  var IS_MANAGEMENT_ONLY = CLI_ARGS.includes("--print-tool-map") || CLI_ARGS[0] === "scope";
3804
3805
  function createWhisperMcpClient(options) {
@@ -3833,6 +3834,16 @@ var server = new McpServer({
3833
3834
  function createMcpServer() {
3834
3835
  return server;
3835
3836
  }
3837
+ var WORKSPACE_HEALTH_VALUES = ["unbound", "unindexed", "stale", "drifted", "healthy"];
3838
+ var RETRIEVAL_READINESS_VALUES = [
3839
+ "no_project",
3840
+ "project_unverified",
3841
+ "project_bound_no_repo_source",
3842
+ "project_repo_source_stale",
3843
+ "project_repo_source_ready",
3844
+ "local_fallback"
3845
+ ];
3846
+ var RETRIEVAL_ROUTE_VALUES = ["project_repo", "local_workspace_fallback", "memory_only", "none"];
3836
3847
  var STATE_DIR = join(homedir(), ".whisper-mcp");
3837
3848
  var STATE_PATH = join(STATE_DIR, "state.json");
3838
3849
  var AUDIT_LOG_PATH = join(STATE_DIR, "forget-audit.log");
@@ -3874,6 +3885,55 @@ var ALIAS_TOOL_MAP = [
3874
3885
  { alias: "learn", target: "context.add_text | context.add_source | context.add_document" },
3875
3886
  { alias: "share_context", target: "context.share" }
3876
3887
  ];
3888
+ var MCP_RETRIEVAL_PROFILE_VALUES = ["legacy", "precision_v1"];
3889
+ var RECOMMENDED_NEXT_CALL_ALLOWLIST = /* @__PURE__ */ new Set([
3890
+ "index.workspace_resolve",
3891
+ "context.list_projects",
3892
+ "index.workspace_status",
3893
+ "index.workspace_run",
3894
+ "search_code",
3895
+ "grep",
3896
+ "context.list_sources",
3897
+ "index.local_scan_ingest",
3898
+ "context.get_relevant"
3899
+ ]);
3900
+ var UNTRUSTED_CODEBASE_HEALTH = /* @__PURE__ */ new Set(["unbound", "unindexed"]);
3901
+ var UNTRUSTED_CODEBASE_PROJECT_READINESS = /* @__PURE__ */ new Set([
3902
+ "project_unverified",
3903
+ "project_bound_no_repo_source"
3904
+ ]);
3905
+ function sanitizeRecommendedNextCalls(nextCalls) {
3906
+ return Array.from(
3907
+ new Set(
3908
+ nextCalls.map((entry) => String(entry || "").trim()).filter((entry) => RECOMMENDED_NEXT_CALL_ALLOWLIST.has(entry))
3909
+ )
3910
+ );
3911
+ }
3912
+ function resolveMcpRetrievalProfile(requested) {
3913
+ if (requested && MCP_RETRIEVAL_PROFILE_VALUES.includes(requested)) {
3914
+ return requested;
3915
+ }
3916
+ return MCP_RETRIEVAL_PRECISION_V1_DEFAULT ? "precision_v1" : "legacy";
3917
+ }
3918
+ function shouldAbstainForCodebaseTrust(args) {
3919
+ if (args.retrievalProfile !== "precision_v1" || !args.codebaseIntent) return false;
3920
+ return UNTRUSTED_CODEBASE_HEALTH.has(args.preflight.trust_state.health) || UNTRUSTED_CODEBASE_PROJECT_READINESS.has(args.preflight.project_retrieval_readiness);
3921
+ }
3922
+ function buildCodebaseTrustAbstainPayload(args) {
3923
+ return {
3924
+ status: "abstained",
3925
+ reason: "stale_index",
3926
+ message: args.preflight.warnings[0] || "Codebase retrieval is not trusted yet for this workspace/project. Resolve index trust before answering.",
3927
+ query: args.query,
3928
+ trust_state: args.preflight.trust_state,
3929
+ retrieval_readiness: args.preflight.retrieval_readiness,
3930
+ project_retrieval_readiness: args.preflight.project_retrieval_readiness,
3931
+ retrieval_route: args.preflight.retrieval_route,
3932
+ retrieval_profile: args.retrievalProfile,
3933
+ warnings: args.preflight.warnings,
3934
+ recommended_next_calls: sanitizeRecommendedNextCalls(args.preflight.recommended_next_calls)
3935
+ };
3936
+ }
3877
3937
  function ensureStateDir() {
3878
3938
  if (!existsSync(STATE_DIR)) {
3879
3939
  mkdirSync(STATE_DIR, { recursive: true });
@@ -4044,16 +4104,25 @@ function classifyProjectRepoReadiness(args) {
4044
4104
  function classifyRepoGroundedQuery(args) {
4045
4105
  const query = args.query.toLowerCase();
4046
4106
  let score = 0;
4047
- if (/(^|[\s"'])where is\b|which file|what file|show me|wiring|handler|middleware|implementation|route|endpoint|function|class|module|repo|workspace|code/.test(query)) {
4107
+ if (/(^|[\s"'])where is\b|which file|what file|show me|wiring|handler|middleware|implementation|route|endpoint|function|class|module|repo|workspace|code|project/.test(query)) {
4048
4108
  score += 1;
4049
4109
  }
4050
4110
  if (/\bauth\b|\burl\b|\bcookie\b|\bsession\b|\bapi\b/.test(query)) {
4051
4111
  score += 1;
4052
4112
  }
4113
+ if (/\berror\b|\berrors\b|\bexception\b|\bexceptions\b/.test(query)) {
4114
+ score += 1;
4115
+ }
4116
+ if (/\bretry\b/.test(query)) {
4117
+ score += 1;
4118
+ }
4119
+ if (/\bflow\b|\blogic\b|\bstack\b|\btrace\b|\blogging\b|\bfailure\b|\bfailures\b|\bthrow\b|\bcatch\b|\bdebug\b/.test(query)) {
4120
+ score += 1;
4121
+ }
4053
4122
  if (/[A-Za-z0-9/_-]+\.[A-Za-z0-9]+/.test(args.query) || /\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]{2,}/.test(args.query)) {
4054
4123
  score += 2;
4055
4124
  }
4056
- if (/[A-Za-z_$][A-Za-z0-9_$-]*\(/.test(args.query) || /[A-Z][A-Za-z0-9]+/.test(args.query)) {
4125
+ if (/[A-Za-z_$][A-Za-z0-9_$-]*\(/.test(args.query) || /\b[A-Z][a-z0-9]+[A-Z][A-Za-z0-9]*\b/.test(args.query)) {
4057
4126
  score += 1;
4058
4127
  }
4059
4128
  if ((args.chunk_types || []).some((chunkType) => ["code", "function", "class", "config", "schema", "api_spec"].includes(chunkType))) {
@@ -4181,9 +4250,9 @@ async function resolveProjectDescriptor(projectRef) {
4181
4250
  }
4182
4251
  }
4183
4252
  function getWorkspaceRecommendedNextCalls(health) {
4184
- if (health === "unbound") return ["index.workspace_resolve", "context.list_projects", "index.workspace_status"];
4185
- if (health === "unindexed") return ["index.workspace_status", "index.workspace_run", "search_code", "grep"];
4186
- if (health === "stale" || health === "drifted") return ["index.workspace_status", "index.workspace_run", "grep", "search_code"];
4253
+ if (health === "unbound") return sanitizeRecommendedNextCalls(["index.workspace_resolve", "context.list_projects", "index.workspace_status"]);
4254
+ if (health === "unindexed") return sanitizeRecommendedNextCalls(["index.workspace_status", "index.workspace_run", "search_code", "grep"]);
4255
+ if (health === "stale" || health === "drifted") return sanitizeRecommendedNextCalls(["index.workspace_status", "index.workspace_run", "grep", "search_code"]);
4187
4256
  return [];
4188
4257
  }
4189
4258
  function getWorkspaceWarnings(args) {
@@ -4385,12 +4454,13 @@ async function resolveRepoGroundingPreflight(args) {
4385
4454
  return {
4386
4455
  repo_grounded: repoGrounded,
4387
4456
  trust_state: trust,
4457
+ project_retrieval_readiness: sourceVerification.retrieval_readiness,
4388
4458
  retrieval_readiness: retrievalReadiness,
4389
4459
  retrieval_route: retrievalRoute,
4390
4460
  matched_sources: sourceVerification.matched_sources,
4391
4461
  matched_source_ids: sourceVerification.matched_source_ids,
4392
4462
  warnings: Array.from(new Set(warnings)),
4393
- recommended_next_calls: Array.from(new Set(recommendedNextCalls))
4463
+ recommended_next_calls: sanitizeRecommendedNextCalls(recommendedNextCalls)
4394
4464
  };
4395
4465
  }
4396
4466
  async function ingestSessionWithSyncFallback(params) {
@@ -4485,7 +4555,9 @@ function buildAbstain(args) {
4485
4555
  closest_evidence: args.closest_evidence,
4486
4556
  warnings: args.warnings || [],
4487
4557
  trust_state: args.trust_state,
4488
- recommended_next_calls: args.recommended_next_calls || ["index.workspace_status", "index.workspace_run", "grep", "context.get_relevant"],
4558
+ recommended_next_calls: sanitizeRecommendedNextCalls(
4559
+ args.recommended_next_calls || ["index.workspace_status", "index.workspace_run", "grep", "context.get_relevant"]
4560
+ ),
4489
4561
  diagnostics: {
4490
4562
  claims_evaluated: args.claims_evaluated,
4491
4563
  evidence_items_found: args.evidence_items_found,
@@ -4656,6 +4728,8 @@ async function queryWithDegradedFallback(params) {
4656
4728
  user_id: params.user_id,
4657
4729
  session_id: params.session_id,
4658
4730
  source_ids: params.source_ids,
4731
+ retrieval_profile: params.retrieval_profile,
4732
+ include_parent_content: params.include_parent_content,
4659
4733
  hybrid: true,
4660
4734
  rerank: true
4661
4735
  });
@@ -4671,6 +4745,8 @@ async function queryWithDegradedFallback(params) {
4671
4745
  user_id: params.user_id,
4672
4746
  session_id: params.session_id,
4673
4747
  source_ids: params.source_ids,
4748
+ retrieval_profile: params.retrieval_profile,
4749
+ include_parent_content: params.include_parent_content,
4674
4750
  hybrid: false,
4675
4751
  rerank: false,
4676
4752
  vector_weight: 0,
@@ -5311,12 +5387,10 @@ server.tool(
5311
5387
  health: trust.health,
5312
5388
  retrieval_readiness: repoVerification.retrieval_readiness,
5313
5389
  warnings: Array.from(/* @__PURE__ */ new Set([...trust.warnings, ...repoVerification.warnings])),
5314
- recommended_next_calls: Array.from(
5315
- /* @__PURE__ */ new Set([
5316
- ...trust.recommended_next_calls,
5317
- ...repoVerification.retrieval_readiness === "project_bound_no_repo_source" ? ["context.list_sources", "index.local_scan_ingest"] : []
5318
- ])
5319
- ),
5390
+ recommended_next_calls: sanitizeRecommendedNextCalls([
5391
+ ...trust.recommended_next_calls,
5392
+ ...repoVerification.retrieval_readiness === "project_bound_no_repo_source" ? ["context.list_sources", "index.local_scan_ingest"] : []
5393
+ ]),
5320
5394
  freshness: trust.freshness,
5321
5395
  coverage: trust.coverage,
5322
5396
  last_indexed_commit: trust.last_indexed_commit,
@@ -5420,11 +5494,25 @@ server.tool(
5420
5494
  include_memories: z.boolean().optional().default(true),
5421
5495
  include_graph: z.boolean().optional().default(true),
5422
5496
  session_id: z.string().optional(),
5423
- user_id: z.string().optional()
5497
+ user_id: z.string().optional(),
5498
+ include_parent_content: z.boolean().optional().default(false),
5499
+ retrieval_profile: z.enum(MCP_RETRIEVAL_PROFILE_VALUES).optional()
5424
5500
  },
5425
- async ({ question, path, workspace_id, project, top_k, include_memories, include_graph, session_id, user_id }) => {
5501
+ async ({ question, path, workspace_id, project, top_k, include_memories, include_graph, session_id, user_id, include_parent_content, retrieval_profile }) => {
5426
5502
  try {
5427
5503
  const preflight = await resolveRepoGroundingPreflight({ query: question, path, workspace_id, project });
5504
+ const retrievalProfile = resolveMcpRetrievalProfile(retrieval_profile);
5505
+ if (shouldAbstainForCodebaseTrust({
5506
+ retrievalProfile,
5507
+ codebaseIntent: preflight.repo_grounded,
5508
+ preflight
5509
+ })) {
5510
+ return toTextResult(buildCodebaseTrustAbstainPayload({
5511
+ query: question,
5512
+ preflight,
5513
+ retrievalProfile
5514
+ }));
5515
+ }
5428
5516
  if (preflight.retrieval_route === "local_workspace_fallback") {
5429
5517
  const localRetrieval = await runLocalWorkspaceRetrieval({
5430
5518
  query: question,
@@ -5480,7 +5568,9 @@ server.tool(
5480
5568
  include_graph,
5481
5569
  session_id,
5482
5570
  user_id,
5483
- source_ids: preflight.repo_grounded ? preflight.matched_source_ids : void 0
5571
+ source_ids: preflight.repo_grounded ? preflight.matched_source_ids : void 0,
5572
+ retrieval_profile: retrievalProfile,
5573
+ include_parent_content
5484
5574
  });
5485
5575
  const response = queryResult.response;
5486
5576
  const rawResults = preflight.repo_grounded ? filterProjectRepoResults(response.results || [], preflight.matched_source_ids) : response.results || [];
@@ -5752,11 +5842,25 @@ server.tool(
5752
5842
  include_graph: z.boolean().optional().default(false).describe("Include knowledge graph traversal"),
5753
5843
  user_id: z.string().optional().describe("User ID for memory scoping"),
5754
5844
  session_id: z.string().optional().describe("Session ID for memory scoping"),
5755
- max_tokens: z.number().optional().describe("Max tokens for packed context")
5845
+ max_tokens: z.number().optional().describe("Max tokens for packed context"),
5846
+ include_parent_content: z.boolean().optional().default(false),
5847
+ retrieval_profile: z.enum(MCP_RETRIEVAL_PROFILE_VALUES).optional()
5756
5848
  },
5757
- async ({ project, query, path, top_k, chunk_types, include_memories, include_graph, user_id, session_id, max_tokens }) => {
5849
+ async ({ project, query, path, top_k, chunk_types, include_memories, include_graph, user_id, session_id, max_tokens, include_parent_content, retrieval_profile }) => {
5758
5850
  try {
5759
5851
  const preflight = await resolveRepoGroundingPreflight({ query, path, project, chunk_types });
5852
+ const retrievalProfile = resolveMcpRetrievalProfile(retrieval_profile);
5853
+ if (shouldAbstainForCodebaseTrust({
5854
+ retrievalProfile,
5855
+ codebaseIntent: preflight.repo_grounded,
5856
+ preflight
5857
+ })) {
5858
+ return toTextResult(buildCodebaseTrustAbstainPayload({
5859
+ query,
5860
+ preflight,
5861
+ retrievalProfile
5862
+ }));
5863
+ }
5760
5864
  const resolvedProject = preflight.trust_state.project_ref || await resolveProjectRef(project);
5761
5865
  if (!resolvedProject && !preflight.repo_grounded) {
5762
5866
  return { content: [{ type: "text", text: "Error: No project resolved. Set WHISPER_PROJECT or pass project." }] };
@@ -5795,7 +5899,9 @@ server.tool(
5795
5899
  include_graph,
5796
5900
  user_id: user_id || scope.userId,
5797
5901
  session_id: session_id || scope.sessionId,
5798
- source_ids: preflight.matched_source_ids
5902
+ source_ids: preflight.matched_source_ids,
5903
+ retrieval_profile: retrievalProfile,
5904
+ include_parent_content
5799
5905
  });
5800
5906
  const repoResults = filterProjectRepoResults(queryResult2.response.results || [], preflight.matched_source_ids);
5801
5907
  if (repoResults.length > 0) {
@@ -5867,8 +5973,8 @@ workspace=${preflight.trust_state.workspace_id} identity_source=${preflight.trus
5867
5973
  const memoryRescue = include_memories !== false ? await runContextQueryMemoryRescue({
5868
5974
  project: resolvedProject,
5869
5975
  query,
5870
- user_id: user_id ? scope2.userId : void 0,
5871
- session_id: session_id ? scope2.sessionId : void 0,
5976
+ user_id: user_id ? scope.userId : void 0,
5977
+ session_id: session_id ? scope.sessionId : void 0,
5872
5978
  top_k
5873
5979
  }) : { results: [], rescue_mode: null };
5874
5980
  if (memoryRescue.results.length && memoryRescue.rescue_mode) {
@@ -5878,7 +5984,7 @@ workspace=${preflight.trust_state.workspace_id} identity_source=${preflight.trus
5878
5984
  text: renderContextQueryMemoryRescue({
5879
5985
  project: resolvedProject,
5880
5986
  query,
5881
- scope: scope2,
5987
+ scope,
5882
5988
  results: memoryRescue.results,
5883
5989
  rescue_mode: memoryRescue.rescue_mode
5884
5990
  })
@@ -5897,11 +6003,11 @@ ${prepared.retrieval.warnings.join("\n")}` : "";
5897
6003
  `deduped=${prepared.retrieval.dedupedCount}`,
5898
6004
  `dropped_below_floor=${prepared.retrieval.droppedBelowFloor}`
5899
6005
  ].join(" ");
5900
- const scope2 = `project=${prepared.scope.project} user=${prepared.scope.userId} session=${prepared.scope.sessionId}`;
6006
+ const scopeLabel = `project=${prepared.scope.project} user=${prepared.scope.userId} session=${prepared.scope.sessionId}`;
5901
6007
  return {
5902
6008
  content: [{
5903
6009
  type: "text",
5904
- text: `Found ${prepared.items.length} runtime-ranked items (${prepared.retrieval.durationMs}ms, ${scope2}, ${diagnostics}):
6010
+ text: `Found ${prepared.items.length} runtime-ranked items (${prepared.retrieval.durationMs}ms, ${scopeLabel}, ${diagnostics}):
5905
6011
 
5906
6012
  ${prepared.context}${warnings}`
5907
6013
  }]
@@ -5915,7 +6021,9 @@ ${prepared.context}${warnings}`
5915
6021
  include_memories: include_memories === true,
5916
6022
  include_graph,
5917
6023
  user_id: user_id || scope.userId,
5918
- session_id: session_id || scope.sessionId
6024
+ session_id: session_id || scope.sessionId,
6025
+ retrieval_profile: retrievalProfile,
6026
+ include_parent_content
5919
6027
  });
5920
6028
  const response2 = queryResult2.response;
5921
6029
  if (response2.results.length === 0) {
@@ -5968,7 +6076,9 @@ ${automaticWarning}${suffix2}` }] };
5968
6076
  include_memories: include_memories === true,
5969
6077
  include_graph,
5970
6078
  user_id: user_id || scope.userId,
5971
- session_id: session_id || scope.sessionId
6079
+ session_id: session_id || scope.sessionId,
6080
+ retrieval_profile: retrievalProfile,
6081
+ include_parent_content
5972
6082
  });
5973
6083
  const response = queryResult.response;
5974
6084
  if (response.results.length === 0) {
@@ -7861,6 +7971,10 @@ if (process.argv[1] && /server\.(mjs|cjs|js|ts)$/.test(process.argv[1])) {
7861
7971
  main().catch(console.error);
7862
7972
  }
7863
7973
  export {
7974
+ RECOMMENDED_NEXT_CALL_ALLOWLIST,
7975
+ RETRIEVAL_READINESS_VALUES,
7976
+ RETRIEVAL_ROUTE_VALUES,
7977
+ WORKSPACE_HEALTH_VALUES,
7864
7978
  canonicalizeWorkspacePath,
7865
7979
  chooseWorkspaceProjectSource,
7866
7980
  classifyProjectRepoReadiness,
@@ -7873,5 +7987,7 @@ export {
7873
7987
  renderScopedMcpConfig,
7874
7988
  resolveForgetQueryCandidates,
7875
7989
  resolveWorkspaceIdentity,
7876
- runSearchCodeSearch
7990
+ runSearchCodeSearch,
7991
+ sanitizeRecommendedNextCalls,
7992
+ shouldAbstainForCodebaseTrust
7877
7993
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usewhisper/mcp-server",
3
- "version": "2.11.0",
3
+ "version": "2.12.0",
4
4
  "whisperContractVersion": "2026.03.10",
5
5
  "scripts": {
6
6
  "build": "tsup ../src/mcp/server.ts --format esm --out-dir dist",