@usewhisper/mcp-server 2.0.0 → 2.1.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 +159 -30
  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()}`,
@@ -858,12 +863,37 @@ var WhisperContext = class _WhisperContext {
858
863
  write_mode: params.write_mode
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
+ };
864
884
  }
865
885
  if (direct?.success === true) {
866
- return { id: "", success: true, path: "sota", fallback_used: false };
886
+ return {
887
+ id: "",
888
+ success: true,
889
+ path: "sota",
890
+ fallback_used: false,
891
+ mode,
892
+ ...direct?.status_url ? { status_url: direct.status_url } : {},
893
+ ...direct?.accepted_at ? { accepted_at: direct.accepted_at } : {},
894
+ ...direct?.visibility_sla_ms ? { visibility_sla_ms: direct.visibility_sla_ms } : {},
895
+ ...direct?.pending_visibility !== void 0 ? { pending_visibility: Boolean(direct.pending_visibility) } : {}
896
+ };
867
897
  }
868
898
  } catch (error) {
869
899
  if (params.allow_legacy_fallback === false) {
@@ -891,7 +921,14 @@ var WhisperContext = class _WhisperContext {
891
921
  message: "Memory create succeeded but no memory id was returned by the API"
892
922
  });
893
923
  }
894
- return { id, success: true, path: "legacy", fallback_used: true };
924
+ return {
925
+ id,
926
+ success: true,
927
+ path: "legacy",
928
+ fallback_used: true,
929
+ mode: "sync",
930
+ memory_id: id
931
+ };
895
932
  });
896
933
  }
897
934
  async addMemoriesBulk(params) {
@@ -1408,15 +1445,25 @@ var BASE_URL = process.env.WHISPER_BASE_URL;
1408
1445
  var RUNTIME_MODE = (process.env.WHISPER_MCP_MODE || "remote").toLowerCase();
1409
1446
  var CLI_ARGS = process.argv.slice(2);
1410
1447
  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;
1448
+ function createWhisperMcpClient(options) {
1449
+ const apiKey = options?.apiKey ?? API_KEY;
1450
+ if (!apiKey || IS_MANAGEMENT_ONLY) {
1451
+ return null;
1452
+ }
1453
+ return new WhisperContext({
1454
+ apiKey,
1455
+ project: options?.project ?? DEFAULT_PROJECT,
1456
+ ...(options?.baseUrl ?? BASE_URL) && { baseUrl: options?.baseUrl ?? BASE_URL }
1457
+ });
1458
+ }
1459
+ var whisper = createWhisperMcpClient();
1416
1460
  var server = new McpServer({
1417
1461
  name: "whisper-context",
1418
1462
  version: "0.2.8"
1419
1463
  });
1464
+ function createMcpServer() {
1465
+ return server;
1466
+ }
1420
1467
  var STATE_DIR = join(homedir(), ".whisper-mcp");
1421
1468
  var STATE_PATH = join(STATE_DIR, "state.json");
1422
1469
  var AUDIT_LOG_PATH = join(STATE_DIR, "forget-audit.log");
@@ -1877,6 +1924,9 @@ async function createSourceByType(params) {
1877
1924
  if (params.max_chunks !== void 0) config.max_chunks = params.max_chunks;
1878
1925
  }
1879
1926
  if (params.metadata) config.metadata = params.metadata;
1927
+ if (params.ingestion_profile) config.ingestion_profile = params.ingestion_profile;
1928
+ if (params.strategy_override) config.strategy_override = params.strategy_override;
1929
+ if (params.profile_config) config.profile_config = params.profile_config;
1880
1930
  config.auto_index = params.auto_index ?? true;
1881
1931
  const created = await whisper.addSource(params.project, {
1882
1932
  name: params.name || `${params.type}-source-${Date.now()}`,
@@ -1921,11 +1971,17 @@ async function learnFromInput(input) {
1921
1971
  throw new Error("No project resolved. Set WHISPER_PROJECT or provide project.");
1922
1972
  }
1923
1973
  if (input.content) {
1974
+ const mergedMetadata = {
1975
+ ...input.metadata || {},
1976
+ ...input.ingestion_profile ? { ingestion_profile: input.ingestion_profile } : {},
1977
+ ...input.strategy_override ? { strategy_override: input.strategy_override } : {},
1978
+ ...input.profile_config ? { profile_config: input.profile_config } : {}
1979
+ };
1924
1980
  const result = await whisper.addContext({
1925
1981
  project: resolvedProject,
1926
1982
  content: input.content,
1927
1983
  title: input.title || "Learned Context",
1928
- metadata: input.metadata
1984
+ metadata: mergedMetadata
1929
1985
  });
1930
1986
  return {
1931
1987
  mode: "text",
@@ -1942,7 +1998,10 @@ async function learnFromInput(input) {
1942
1998
  branch: input.branch,
1943
1999
  name: input.name,
1944
2000
  auto_index: true,
1945
- metadata: input.metadata
2001
+ metadata: input.metadata,
2002
+ ingestion_profile: input.ingestion_profile,
2003
+ strategy_override: input.strategy_override,
2004
+ profile_config: input.profile_config
1946
2005
  });
1947
2006
  }
1948
2007
  if (input.path) {
@@ -1953,7 +2012,10 @@ async function learnFromInput(input) {
1953
2012
  glob: input.glob,
1954
2013
  max_files: input.max_files,
1955
2014
  name: input.name,
1956
- metadata: input.metadata
2015
+ metadata: input.metadata,
2016
+ ingestion_profile: input.ingestion_profile,
2017
+ strategy_override: input.strategy_override,
2018
+ profile_config: input.profile_config
1957
2019
  });
1958
2020
  }
1959
2021
  if (input.file_path) {
@@ -1962,7 +2024,10 @@ async function learnFromInput(input) {
1962
2024
  type: "pdf",
1963
2025
  file_path: input.file_path,
1964
2026
  name: input.name,
1965
- metadata: input.metadata
2027
+ metadata: input.metadata,
2028
+ ingestion_profile: input.ingestion_profile,
2029
+ strategy_override: input.strategy_override,
2030
+ profile_config: input.profile_config
1966
2031
  });
1967
2032
  }
1968
2033
  if (input.url) {
@@ -1972,6 +2037,9 @@ async function learnFromInput(input) {
1972
2037
  url: input.url,
1973
2038
  name: input.name,
1974
2039
  metadata: input.metadata,
2040
+ ingestion_profile: input.ingestion_profile,
2041
+ strategy_override: input.strategy_override,
2042
+ profile_config: input.profile_config,
1975
2043
  crawl_depth: input.crawl_depth,
1976
2044
  channel_ids: input.channel_ids,
1977
2045
  token: input.token,
@@ -1982,7 +2050,7 @@ async function learnFromInput(input) {
1982
2050
  }
1983
2051
  throw new Error("Provide content, owner+repo, path, file_path, or url.");
1984
2052
  }
1985
- function scopeConfigJson(project, source, client) {
2053
+ function renderScopedMcpConfig(project, source, client) {
1986
2054
  const serverDef = {
1987
2055
  command: "npx",
1988
2056
  args: ["-y", "@usewhisper/mcp-server"],
@@ -2445,7 +2513,12 @@ server.tool(
2445
2513
  agent_id,
2446
2514
  importance
2447
2515
  });
2448
- return { content: [{ type: "text", text: `Memory stored (id: ${result.id}, type: ${memory_type}).` }] };
2516
+ const memoryId = result?.memory_id || result.id;
2517
+ const jobId = result?.job_id;
2518
+ const mode = result?.mode;
2519
+ const typeLabel = memory_type || "factual";
2520
+ const text = mode === "async" || jobId ? `Memory queued (job_id: ${jobId || result.id}, type: ${typeLabel}).` : `Memory stored (id: ${memoryId}, type: ${typeLabel}).`;
2521
+ return { content: [{ type: "text", text }] };
2449
2522
  } catch (error) {
2450
2523
  return { content: [{ type: "text", text: `Error: ${error.message}` }] };
2451
2524
  }
@@ -2522,6 +2595,9 @@ server.tool(
2522
2595
  name: z.string().optional(),
2523
2596
  auto_index: z.boolean().optional().default(true),
2524
2597
  metadata: z.record(z.string()).optional(),
2598
+ ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
2599
+ strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
2600
+ profile_config: z.record(z.any()).optional(),
2525
2601
  owner: z.string().optional(),
2526
2602
  repo: z.string().optional(),
2527
2603
  branch: z.string().optional(),
@@ -2557,6 +2633,9 @@ server.tool(
2557
2633
  name: input.name,
2558
2634
  auto_index: input.auto_index,
2559
2635
  metadata: input.metadata,
2636
+ ingestion_profile: input.ingestion_profile,
2637
+ strategy_override: input.strategy_override,
2638
+ profile_config: input.profile_config,
2560
2639
  owner: input.owner,
2561
2640
  repo: input.repo,
2562
2641
  branch: input.branch,
@@ -2607,14 +2686,22 @@ server.tool(
2607
2686
  {
2608
2687
  project: z.string().optional().describe("Project name or slug"),
2609
2688
  title: z.string().describe("Title for this content"),
2610
- content: z.string().describe("The text content to index")
2689
+ content: z.string().describe("The text content to index"),
2690
+ ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
2691
+ strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
2692
+ profile_config: z.record(z.any()).optional()
2611
2693
  },
2612
- async ({ project, title, content }) => {
2694
+ async ({ project, title, content, ingestion_profile, strategy_override, profile_config }) => {
2613
2695
  try {
2614
2696
  await whisper.addContext({
2615
2697
  project,
2616
2698
  title,
2617
- content
2699
+ content,
2700
+ metadata: {
2701
+ ...ingestion_profile ? { ingestion_profile } : {},
2702
+ ...strategy_override ? { strategy_override } : {},
2703
+ ...profile_config ? { profile_config } : {}
2704
+ }
2618
2705
  });
2619
2706
  return { content: [{ type: "text", text: `Indexed "${title}" (${content.length} chars).` }] };
2620
2707
  } catch (error) {
@@ -2634,9 +2721,12 @@ server.tool(
2634
2721
  auto_sync: z.boolean().optional().default(true),
2635
2722
  tags: z.array(z.string()).optional(),
2636
2723
  platform: z.enum(["youtube", "loom", "generic"]).optional(),
2637
- language: z.string().optional()
2724
+ language: z.string().optional(),
2725
+ ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
2726
+ strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
2727
+ profile_config: z.record(z.any()).optional()
2638
2728
  },
2639
- async ({ project, source_type, title, content, url, auto_sync, tags, platform, language }) => {
2729
+ async ({ project, source_type, title, content, url, auto_sync, tags, platform, language, ingestion_profile, strategy_override, profile_config }) => {
2640
2730
  try {
2641
2731
  const resolvedProject = await resolveProjectRef(project);
2642
2732
  if (!resolvedProject) {
@@ -2652,7 +2742,10 @@ server.tool(
2652
2742
  auto_sync,
2653
2743
  tags,
2654
2744
  platform,
2655
- language
2745
+ language,
2746
+ ingestion_profile,
2747
+ strategy_override,
2748
+ profile_config
2656
2749
  });
2657
2750
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2658
2751
  }
@@ -2663,7 +2756,13 @@ server.tool(
2663
2756
  project: resolvedProject,
2664
2757
  title: title || "Document",
2665
2758
  content,
2666
- metadata: { source: "mcp:add_document", tags: tags || [] }
2759
+ metadata: {
2760
+ source: "mcp:add_document",
2761
+ tags: tags || [],
2762
+ ...ingestion_profile ? { ingestion_profile } : {},
2763
+ ...strategy_override ? { strategy_override } : {},
2764
+ ...profile_config ? { profile_config } : {}
2765
+ }
2667
2766
  });
2668
2767
  return { content: [{ type: "text", text: `Indexed "${title || "Document"}" (${content.length} chars).` }] };
2669
2768
  } catch (error) {
@@ -2725,16 +2824,21 @@ server.tool(
2725
2824
  },
2726
2825
  async ({ project, session_id, user_id, messages }) => {
2727
2826
  try {
2827
+ const normalizedMessages = messages.map((message) => ({
2828
+ role: message.role,
2829
+ content: message.content,
2830
+ timestamp: message.timestamp
2831
+ }));
2728
2832
  const result = await whisper.ingestSession({
2729
2833
  project,
2730
2834
  session_id,
2731
2835
  user_id,
2732
- messages
2836
+ messages: normalizedMessages
2733
2837
  });
2734
2838
  return {
2735
2839
  content: [{
2736
2840
  type: "text",
2737
- text: `Processed ${messages.length} messages:
2841
+ text: `Processed ${normalizedMessages.length} messages:
2738
2842
  - Created ${result.memories_created} memories
2739
2843
  - Detected ${result.relations_created} relations
2740
2844
  - Updated ${result.memories_invalidated} outdated memories` + (result.errors && result.errors.length > 0 ? `
@@ -4016,11 +4120,17 @@ server.tool(
4016
4120
  async ({ project, content, memory_type, user_id, session_id, agent_id, importance }) => {
4017
4121
  try {
4018
4122
  const result = await whisper.addMemory({ project, content, memory_type, user_id, session_id, agent_id, importance });
4123
+ const memoryId = result?.memory_id || (result.mode === "sync" ? result.id : null);
4124
+ const jobId = result?.job_id || (result.mode === "async" ? result.id : null);
4019
4125
  return primaryToolSuccess({
4020
4126
  tool: "remember",
4021
- id: result.id || null,
4127
+ id: memoryId || jobId || null,
4128
+ memory_id: memoryId,
4129
+ job_id: jobId,
4130
+ mode: result?.mode || null,
4022
4131
  memory_type,
4023
- stored: result.success === true
4132
+ stored: result.success === true,
4133
+ queued: result?.mode === "async" || Boolean(jobId)
4024
4134
  });
4025
4135
  } catch (error) {
4026
4136
  return primaryToolError(error.message);
@@ -4045,7 +4155,16 @@ server.tool(
4045
4155
  },
4046
4156
  async ({ project, session_id, user_id, messages, role, content, timestamp }) => {
4047
4157
  try {
4048
- const normalizedMessages = normalizeRecordMessages({ messages, role, content, timestamp });
4158
+ const normalizedMessages = normalizeRecordMessages({
4159
+ messages: messages?.map((message) => ({
4160
+ role: message.role,
4161
+ content: message.content,
4162
+ timestamp: message.timestamp
4163
+ })),
4164
+ role,
4165
+ content,
4166
+ timestamp
4167
+ });
4049
4168
  const result = await whisper.ingestSession({ project, session_id, user_id, messages: normalizedMessages });
4050
4169
  return primaryToolSuccess({
4051
4170
  tool: "record",
@@ -4074,6 +4193,9 @@ server.tool(
4074
4193
  file_path: z.string().optional().describe("Single file path to learn from"),
4075
4194
  name: z.string().optional().describe("Optional source name"),
4076
4195
  metadata: z.record(z.string()).optional(),
4196
+ ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
4197
+ strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
4198
+ profile_config: z.record(z.any()).optional(),
4077
4199
  max_files: z.number().optional(),
4078
4200
  glob: z.string().optional(),
4079
4201
  crawl_depth: z.number().optional()
@@ -4130,7 +4252,7 @@ async function main() {
4130
4252
  const source = readArg("--source") || "source-or-type";
4131
4253
  const client = readArg("--client") || "json";
4132
4254
  const outPath = readArg("--write");
4133
- const rendered = scopeConfigJson(project, source, client);
4255
+ const rendered = renderScopedMcpConfig(project, source, client);
4134
4256
  if (outPath) {
4135
4257
  const backup = existsSync(outPath) ? `${outPath}.bak-${Date.now()}` : void 0;
4136
4258
  if (backup) writeFileSync(backup, readFileSync(outPath, "utf-8"), "utf-8");
@@ -4151,4 +4273,11 @@ async function main() {
4151
4273
  await server.connect(transport);
4152
4274
  console.error("Whisper Context MCP server running on stdio");
4153
4275
  }
4154
- main().catch(console.error);
4276
+ if (process.argv[1] && /server\.(mjs|cjs|js|ts)$/.test(process.argv[1])) {
4277
+ main().catch(console.error);
4278
+ }
4279
+ export {
4280
+ createMcpServer,
4281
+ createWhisperMcpClient,
4282
+ renderScopedMcpConfig
4283
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usewhisper/mcp-server",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "whisperContractVersion": "2026.03.09",
5
5
  "scripts": {
6
6
  "build": "tsup ../src/mcp/server.ts --format esm --out-dir dist",