@usewhisper/mcp-server 2.0.0 → 2.2.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 (3) hide show
  1. package/README.md +16 -0
  2. package/dist/server.js +164 -31
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -158,6 +158,12 @@ Print config to stdout:
158
158
  whisper-context-mcp scope --project my-project --source github --client claude
159
159
  ```
160
160
 
161
+ OpenCode project config generation:
162
+
163
+ ```text
164
+ whisper-context-mcp scope --project my-project --source github --client opencode
165
+ ```
166
+
161
167
  Write the generated config to a file:
162
168
 
163
169
  ```text
@@ -211,6 +217,16 @@ Flow:
211
217
  1. Run wizard and complete auth/project setup.
212
218
  2. Add a source with MCP: `context.add_source`
213
219
  3. Ask a grounded question: `context.query` or `context.evidence_answer`
220
+ 4. If you selected OpenCode, wizard also updates `opencode.json` and creates `.opencode/plugins/whisper-context.ts`
221
+
222
+ ## OpenCode Plugin Notes
223
+
224
+ When using OpenCode, the plugin runtime adds two OpenCode-only helper tools:
225
+
226
+ - `whisper_status`
227
+ - `whisper_flush_session`
228
+
229
+ These are plugin tools, not MCP tools. Continue using Whisper MCP for retrieval and persistence verbs.
214
230
 
215
231
  ## License
216
232
 
package/dist/server.js CHANGED
@@ -144,6 +144,7 @@ var RuntimeClient = class {
144
144
  diagnostics;
145
145
  inFlight = /* @__PURE__ */ new Map();
146
146
  sendApiKeyHeader;
147
+ fetchImpl;
147
148
  constructor(options, diagnostics) {
148
149
  if (!options.apiKey) {
149
150
  throw new RuntimeClientError({
@@ -168,6 +169,7 @@ var RuntimeClient = class {
168
169
  ...options.timeouts || {}
169
170
  };
170
171
  this.sendApiKeyHeader = process.env.WHISPER_SEND_X_API_KEY === "1";
172
+ this.fetchImpl = options.fetchImpl || fetch;
171
173
  this.diagnostics = diagnostics || new DiagnosticsStore(1e3);
172
174
  }
173
175
  getDiagnosticsStore() {
@@ -288,7 +290,7 @@ var RuntimeClient = class {
288
290
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
289
291
  try {
290
292
  const attachApiKeyHeader = this.shouldAttachApiKeyHeader(normalizedEndpoint);
291
- const response = await fetch(`${this.baseUrl}${normalizedEndpoint}`, {
293
+ const response = await this.fetchImpl(`${this.baseUrl}${normalizedEndpoint}`, {
292
294
  method,
293
295
  signal: controller.signal,
294
296
  keepalive: method !== "GET",
@@ -774,6 +776,9 @@ var WhisperContext = class _WhisperContext {
774
776
  if (params.auth_ref) config.auth_ref = params.auth_ref;
775
777
  }
776
778
  if (params.metadata) config.metadata = params.metadata;
779
+ if (params.ingestion_profile) config.ingestion_profile = params.ingestion_profile;
780
+ if (params.strategy_override) config.strategy_override = params.strategy_override;
781
+ if (params.profile_config) config.profile_config = params.profile_config;
777
782
  config.auto_index = params.auto_index ?? true;
778
783
  const created = await this.addSource(project, {
779
784
  name: params.name || `${params.type}-source-${Date.now()}`,
@@ -855,15 +860,42 @@ var WhisperContext = class _WhisperContext {
855
860
  importance: params.importance,
856
861
  metadata: params.metadata,
857
862
  async: params.async,
858
- write_mode: params.write_mode
863
+ write_mode: params.write_mode || (params.async === true ? "async" : "sync")
859
864
  })
860
865
  });
861
- const id2 = direct?.memory?.id || direct?.id || direct?.memory_id || direct?.job_id;
866
+ const mode = direct?.mode === "async" ? "async" : direct?.mode === "sync" ? "sync" : void 0;
867
+ const memoryId = direct?.memory?.id || direct?.memory_id || (mode !== "async" ? direct?.id : void 0);
868
+ const jobId = direct?.job_id || (mode === "async" ? direct?.id : void 0);
869
+ const id2 = memoryId || jobId || "";
862
870
  if (id2) {
863
- return { id: id2, success: true, path: "sota", fallback_used: false };
871
+ return {
872
+ id: id2,
873
+ success: true,
874
+ path: "sota",
875
+ fallback_used: false,
876
+ mode,
877
+ ...memoryId ? { memory_id: memoryId } : {},
878
+ ...jobId ? { job_id: jobId } : {},
879
+ ...direct?.status_url ? { status_url: direct.status_url } : {},
880
+ ...direct?.accepted_at ? { accepted_at: direct.accepted_at } : {},
881
+ ...direct?.visibility_sla_ms ? { visibility_sla_ms: direct.visibility_sla_ms } : {},
882
+ ...direct?.pending_visibility !== void 0 ? { pending_visibility: Boolean(direct.pending_visibility) } : {},
883
+ ...direct?.semantic_status ? { semantic_status: direct.semantic_status } : {}
884
+ };
864
885
  }
865
886
  if (direct?.success === true) {
866
- return { id: "", success: true, path: "sota", fallback_used: false };
887
+ return {
888
+ id: "",
889
+ success: true,
890
+ path: "sota",
891
+ fallback_used: false,
892
+ mode,
893
+ ...direct?.status_url ? { status_url: direct.status_url } : {},
894
+ ...direct?.accepted_at ? { accepted_at: direct.accepted_at } : {},
895
+ ...direct?.visibility_sla_ms ? { visibility_sla_ms: direct.visibility_sla_ms } : {},
896
+ ...direct?.pending_visibility !== void 0 ? { pending_visibility: Boolean(direct.pending_visibility) } : {},
897
+ ...direct?.semantic_status ? { semantic_status: direct.semantic_status } : {}
898
+ };
867
899
  }
868
900
  } catch (error) {
869
901
  if (params.allow_legacy_fallback === false) {
@@ -891,7 +923,15 @@ var WhisperContext = class _WhisperContext {
891
923
  message: "Memory create succeeded but no memory id was returned by the API"
892
924
  });
893
925
  }
894
- return { id, success: true, path: "legacy", fallback_used: true };
926
+ return {
927
+ id,
928
+ success: true,
929
+ path: "legacy",
930
+ fallback_used: true,
931
+ mode: "sync",
932
+ memory_id: id,
933
+ semantic_status: "ready"
934
+ };
895
935
  });
896
936
  }
897
937
  async addMemoriesBulk(params) {
@@ -1408,15 +1448,25 @@ var BASE_URL = process.env.WHISPER_BASE_URL;
1408
1448
  var RUNTIME_MODE = (process.env.WHISPER_MCP_MODE || "remote").toLowerCase();
1409
1449
  var CLI_ARGS = process.argv.slice(2);
1410
1450
  var IS_MANAGEMENT_ONLY = CLI_ARGS.includes("--print-tool-map") || CLI_ARGS[0] === "scope";
1411
- var whisper = !IS_MANAGEMENT_ONLY && API_KEY ? new WhisperContext({
1412
- apiKey: API_KEY,
1413
- project: DEFAULT_PROJECT,
1414
- ...BASE_URL && { baseUrl: BASE_URL }
1415
- }) : null;
1451
+ function createWhisperMcpClient(options) {
1452
+ const apiKey = options?.apiKey ?? API_KEY;
1453
+ if (!apiKey || IS_MANAGEMENT_ONLY) {
1454
+ return null;
1455
+ }
1456
+ return new WhisperContext({
1457
+ apiKey,
1458
+ project: options?.project ?? DEFAULT_PROJECT,
1459
+ ...(options?.baseUrl ?? BASE_URL) && { baseUrl: options?.baseUrl ?? BASE_URL }
1460
+ });
1461
+ }
1462
+ var whisper = createWhisperMcpClient();
1416
1463
  var server = new McpServer({
1417
1464
  name: "whisper-context",
1418
1465
  version: "0.2.8"
1419
1466
  });
1467
+ function createMcpServer() {
1468
+ return server;
1469
+ }
1420
1470
  var STATE_DIR = join(homedir(), ".whisper-mcp");
1421
1471
  var STATE_PATH = join(STATE_DIR, "state.json");
1422
1472
  var AUDIT_LOG_PATH = join(STATE_DIR, "forget-audit.log");
@@ -1877,6 +1927,9 @@ async function createSourceByType(params) {
1877
1927
  if (params.max_chunks !== void 0) config.max_chunks = params.max_chunks;
1878
1928
  }
1879
1929
  if (params.metadata) config.metadata = params.metadata;
1930
+ if (params.ingestion_profile) config.ingestion_profile = params.ingestion_profile;
1931
+ if (params.strategy_override) config.strategy_override = params.strategy_override;
1932
+ if (params.profile_config) config.profile_config = params.profile_config;
1880
1933
  config.auto_index = params.auto_index ?? true;
1881
1934
  const created = await whisper.addSource(params.project, {
1882
1935
  name: params.name || `${params.type}-source-${Date.now()}`,
@@ -1921,11 +1974,17 @@ async function learnFromInput(input) {
1921
1974
  throw new Error("No project resolved. Set WHISPER_PROJECT or provide project.");
1922
1975
  }
1923
1976
  if (input.content) {
1977
+ const mergedMetadata = {
1978
+ ...input.metadata || {},
1979
+ ...input.ingestion_profile ? { ingestion_profile: input.ingestion_profile } : {},
1980
+ ...input.strategy_override ? { strategy_override: input.strategy_override } : {},
1981
+ ...input.profile_config ? { profile_config: input.profile_config } : {}
1982
+ };
1924
1983
  const result = await whisper.addContext({
1925
1984
  project: resolvedProject,
1926
1985
  content: input.content,
1927
1986
  title: input.title || "Learned Context",
1928
- metadata: input.metadata
1987
+ metadata: mergedMetadata
1929
1988
  });
1930
1989
  return {
1931
1990
  mode: "text",
@@ -1942,7 +2001,10 @@ async function learnFromInput(input) {
1942
2001
  branch: input.branch,
1943
2002
  name: input.name,
1944
2003
  auto_index: true,
1945
- metadata: input.metadata
2004
+ metadata: input.metadata,
2005
+ ingestion_profile: input.ingestion_profile,
2006
+ strategy_override: input.strategy_override,
2007
+ profile_config: input.profile_config
1946
2008
  });
1947
2009
  }
1948
2010
  if (input.path) {
@@ -1953,7 +2015,10 @@ async function learnFromInput(input) {
1953
2015
  glob: input.glob,
1954
2016
  max_files: input.max_files,
1955
2017
  name: input.name,
1956
- metadata: input.metadata
2018
+ metadata: input.metadata,
2019
+ ingestion_profile: input.ingestion_profile,
2020
+ strategy_override: input.strategy_override,
2021
+ profile_config: input.profile_config
1957
2022
  });
1958
2023
  }
1959
2024
  if (input.file_path) {
@@ -1962,7 +2027,10 @@ async function learnFromInput(input) {
1962
2027
  type: "pdf",
1963
2028
  file_path: input.file_path,
1964
2029
  name: input.name,
1965
- metadata: input.metadata
2030
+ metadata: input.metadata,
2031
+ ingestion_profile: input.ingestion_profile,
2032
+ strategy_override: input.strategy_override,
2033
+ profile_config: input.profile_config
1966
2034
  });
1967
2035
  }
1968
2036
  if (input.url) {
@@ -1972,6 +2040,9 @@ async function learnFromInput(input) {
1972
2040
  url: input.url,
1973
2041
  name: input.name,
1974
2042
  metadata: input.metadata,
2043
+ ingestion_profile: input.ingestion_profile,
2044
+ strategy_override: input.strategy_override,
2045
+ profile_config: input.profile_config,
1975
2046
  crawl_depth: input.crawl_depth,
1976
2047
  channel_ids: input.channel_ids,
1977
2048
  token: input.token,
@@ -1982,7 +2053,7 @@ async function learnFromInput(input) {
1982
2053
  }
1983
2054
  throw new Error("Provide content, owner+repo, path, file_path, or url.");
1984
2055
  }
1985
- function scopeConfigJson(project, source, client) {
2056
+ function renderScopedMcpConfig(project, source, client) {
1986
2057
  const serverDef = {
1987
2058
  command: "npx",
1988
2059
  args: ["-y", "@usewhisper/mcp-server"],
@@ -2445,7 +2516,13 @@ server.tool(
2445
2516
  agent_id,
2446
2517
  importance
2447
2518
  });
2448
- return { content: [{ type: "text", text: `Memory stored (id: ${result.id}, type: ${memory_type}).` }] };
2519
+ const memoryId = result?.memory_id || result.id;
2520
+ const jobId = result?.job_id;
2521
+ const mode = result?.mode;
2522
+ const semanticStatus = result?.semantic_status;
2523
+ const typeLabel = memory_type || "factual";
2524
+ const text = mode === "async" || jobId ? `Memory queued (job_id: ${jobId || result.id}, type: ${typeLabel}).` : `Memory stored (id: ${memoryId}, type: ${typeLabel}${semanticStatus ? `, semantic_status: ${semanticStatus}` : ""}).`;
2525
+ return { content: [{ type: "text", text }] };
2449
2526
  } catch (error) {
2450
2527
  return { content: [{ type: "text", text: `Error: ${error.message}` }] };
2451
2528
  }
@@ -2522,6 +2599,9 @@ server.tool(
2522
2599
  name: z.string().optional(),
2523
2600
  auto_index: z.boolean().optional().default(true),
2524
2601
  metadata: z.record(z.string()).optional(),
2602
+ ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
2603
+ strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
2604
+ profile_config: z.record(z.any()).optional(),
2525
2605
  owner: z.string().optional(),
2526
2606
  repo: z.string().optional(),
2527
2607
  branch: z.string().optional(),
@@ -2557,6 +2637,9 @@ server.tool(
2557
2637
  name: input.name,
2558
2638
  auto_index: input.auto_index,
2559
2639
  metadata: input.metadata,
2640
+ ingestion_profile: input.ingestion_profile,
2641
+ strategy_override: input.strategy_override,
2642
+ profile_config: input.profile_config,
2560
2643
  owner: input.owner,
2561
2644
  repo: input.repo,
2562
2645
  branch: input.branch,
@@ -2607,14 +2690,22 @@ server.tool(
2607
2690
  {
2608
2691
  project: z.string().optional().describe("Project name or slug"),
2609
2692
  title: z.string().describe("Title for this content"),
2610
- content: z.string().describe("The text content to index")
2693
+ content: z.string().describe("The text content to index"),
2694
+ ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
2695
+ strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
2696
+ profile_config: z.record(z.any()).optional()
2611
2697
  },
2612
- async ({ project, title, content }) => {
2698
+ async ({ project, title, content, ingestion_profile, strategy_override, profile_config }) => {
2613
2699
  try {
2614
2700
  await whisper.addContext({
2615
2701
  project,
2616
2702
  title,
2617
- content
2703
+ content,
2704
+ metadata: {
2705
+ ...ingestion_profile ? { ingestion_profile } : {},
2706
+ ...strategy_override ? { strategy_override } : {},
2707
+ ...profile_config ? { profile_config } : {}
2708
+ }
2618
2709
  });
2619
2710
  return { content: [{ type: "text", text: `Indexed "${title}" (${content.length} chars).` }] };
2620
2711
  } catch (error) {
@@ -2634,9 +2725,12 @@ server.tool(
2634
2725
  auto_sync: z.boolean().optional().default(true),
2635
2726
  tags: z.array(z.string()).optional(),
2636
2727
  platform: z.enum(["youtube", "loom", "generic"]).optional(),
2637
- language: z.string().optional()
2728
+ language: z.string().optional(),
2729
+ ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
2730
+ strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
2731
+ profile_config: z.record(z.any()).optional()
2638
2732
  },
2639
- async ({ project, source_type, title, content, url, auto_sync, tags, platform, language }) => {
2733
+ async ({ project, source_type, title, content, url, auto_sync, tags, platform, language, ingestion_profile, strategy_override, profile_config }) => {
2640
2734
  try {
2641
2735
  const resolvedProject = await resolveProjectRef(project);
2642
2736
  if (!resolvedProject) {
@@ -2652,7 +2746,10 @@ server.tool(
2652
2746
  auto_sync,
2653
2747
  tags,
2654
2748
  platform,
2655
- language
2749
+ language,
2750
+ ingestion_profile,
2751
+ strategy_override,
2752
+ profile_config
2656
2753
  });
2657
2754
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2658
2755
  }
@@ -2663,7 +2760,13 @@ server.tool(
2663
2760
  project: resolvedProject,
2664
2761
  title: title || "Document",
2665
2762
  content,
2666
- metadata: { source: "mcp:add_document", tags: tags || [] }
2763
+ metadata: {
2764
+ source: "mcp:add_document",
2765
+ tags: tags || [],
2766
+ ...ingestion_profile ? { ingestion_profile } : {},
2767
+ ...strategy_override ? { strategy_override } : {},
2768
+ ...profile_config ? { profile_config } : {}
2769
+ }
2667
2770
  });
2668
2771
  return { content: [{ type: "text", text: `Indexed "${title || "Document"}" (${content.length} chars).` }] };
2669
2772
  } catch (error) {
@@ -2725,16 +2828,21 @@ server.tool(
2725
2828
  },
2726
2829
  async ({ project, session_id, user_id, messages }) => {
2727
2830
  try {
2831
+ const normalizedMessages = messages.map((message) => ({
2832
+ role: message.role,
2833
+ content: message.content,
2834
+ timestamp: message.timestamp
2835
+ }));
2728
2836
  const result = await whisper.ingestSession({
2729
2837
  project,
2730
2838
  session_id,
2731
2839
  user_id,
2732
- messages
2840
+ messages: normalizedMessages
2733
2841
  });
2734
2842
  return {
2735
2843
  content: [{
2736
2844
  type: "text",
2737
- text: `Processed ${messages.length} messages:
2845
+ text: `Processed ${normalizedMessages.length} messages:
2738
2846
  - Created ${result.memories_created} memories
2739
2847
  - Detected ${result.relations_created} relations
2740
2848
  - Updated ${result.memories_invalidated} outdated memories` + (result.errors && result.errors.length > 0 ? `
@@ -4016,11 +4124,17 @@ server.tool(
4016
4124
  async ({ project, content, memory_type, user_id, session_id, agent_id, importance }) => {
4017
4125
  try {
4018
4126
  const result = await whisper.addMemory({ project, content, memory_type, user_id, session_id, agent_id, importance });
4127
+ const memoryId = result?.memory_id || (result.mode === "sync" ? result.id : null);
4128
+ const jobId = result?.job_id || (result.mode === "async" ? result.id : null);
4019
4129
  return primaryToolSuccess({
4020
4130
  tool: "remember",
4021
- id: result.id || null,
4131
+ id: memoryId || jobId || null,
4132
+ memory_id: memoryId,
4133
+ job_id: jobId,
4134
+ mode: result?.mode || null,
4022
4135
  memory_type,
4023
- stored: result.success === true
4136
+ stored: result.success === true,
4137
+ queued: result?.mode === "async" || Boolean(jobId)
4024
4138
  });
4025
4139
  } catch (error) {
4026
4140
  return primaryToolError(error.message);
@@ -4045,7 +4159,16 @@ server.tool(
4045
4159
  },
4046
4160
  async ({ project, session_id, user_id, messages, role, content, timestamp }) => {
4047
4161
  try {
4048
- const normalizedMessages = normalizeRecordMessages({ messages, role, content, timestamp });
4162
+ const normalizedMessages = normalizeRecordMessages({
4163
+ messages: messages?.map((message) => ({
4164
+ role: message.role,
4165
+ content: message.content,
4166
+ timestamp: message.timestamp
4167
+ })),
4168
+ role,
4169
+ content,
4170
+ timestamp
4171
+ });
4049
4172
  const result = await whisper.ingestSession({ project, session_id, user_id, messages: normalizedMessages });
4050
4173
  return primaryToolSuccess({
4051
4174
  tool: "record",
@@ -4074,6 +4197,9 @@ server.tool(
4074
4197
  file_path: z.string().optional().describe("Single file path to learn from"),
4075
4198
  name: z.string().optional().describe("Optional source name"),
4076
4199
  metadata: z.record(z.string()).optional(),
4200
+ ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
4201
+ strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
4202
+ profile_config: z.record(z.any()).optional(),
4077
4203
  max_files: z.number().optional(),
4078
4204
  glob: z.string().optional(),
4079
4205
  crawl_depth: z.number().optional()
@@ -4130,7 +4256,7 @@ async function main() {
4130
4256
  const source = readArg("--source") || "source-or-type";
4131
4257
  const client = readArg("--client") || "json";
4132
4258
  const outPath = readArg("--write");
4133
- const rendered = scopeConfigJson(project, source, client);
4259
+ const rendered = renderScopedMcpConfig(project, source, client);
4134
4260
  if (outPath) {
4135
4261
  const backup = existsSync(outPath) ? `${outPath}.bak-${Date.now()}` : void 0;
4136
4262
  if (backup) writeFileSync(backup, readFileSync(outPath, "utf-8"), "utf-8");
@@ -4151,4 +4277,11 @@ async function main() {
4151
4277
  await server.connect(transport);
4152
4278
  console.error("Whisper Context MCP server running on stdio");
4153
4279
  }
4154
- main().catch(console.error);
4280
+ if (process.argv[1] && /server\.(mjs|cjs|js|ts)$/.test(process.argv[1])) {
4281
+ main().catch(console.error);
4282
+ }
4283
+ export {
4284
+ createMcpServer,
4285
+ createWhisperMcpClient,
4286
+ renderScopedMcpConfig
4287
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usewhisper/mcp-server",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "whisperContractVersion": "2026.03.09",
5
5
  "scripts": {
6
6
  "build": "tsup ../src/mcp/server.ts --format esm --out-dir dist",