@usewhisper/mcp-server 2.7.0 → 2.9.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 +334 -241
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -2089,9 +2089,33 @@ ${lines.join("\n")}`;
2089
2089
  }
2090
2090
  });
2091
2091
  }
2092
+ async resolveLearningScope(overrides) {
2093
+ const merged = {
2094
+ ...this.baseContext,
2095
+ ...overrides
2096
+ };
2097
+ const { scope } = await this.resolveScope(overrides);
2098
+ return {
2099
+ project: scope.project,
2100
+ sessionId: merged.sessionId || scope.sessionId,
2101
+ userId: merged.userId
2102
+ };
2103
+ }
2092
2104
  async afterTurn(input, context = {}) {
2093
2105
  this.pushTouchedFiles(input.touchedFiles);
2094
- const { scope } = await this.resolveScope(context);
2106
+ if (input.auto_learn === false) {
2107
+ return {
2108
+ success: true,
2109
+ sessionIngested: false,
2110
+ memoriesCreated: 0,
2111
+ relationsCreated: 0,
2112
+ invalidatedCount: 0,
2113
+ mergedCount: 0,
2114
+ droppedCount: 0,
2115
+ warnings: []
2116
+ };
2117
+ }
2118
+ const scope = await this.resolveLearningScope(context);
2095
2119
  const result = await this.args.adapter.ingestSession({
2096
2120
  project: scope.project,
2097
2121
  session_id: scope.sessionId,
@@ -2474,6 +2498,19 @@ var WhisperClient = class _WhisperClient {
2474
2498
  });
2475
2499
  return response.data;
2476
2500
  }
2501
+ async learn(params) {
2502
+ const project = (await this.resolveProject(params.project)).id;
2503
+ const response = await this.runtimeClient.request({
2504
+ endpoint: "/v1/learn",
2505
+ method: "POST",
2506
+ operation: params.mode === "conversation" ? "session" : "bulk",
2507
+ body: {
2508
+ ...params,
2509
+ project
2510
+ }
2511
+ });
2512
+ return response.data;
2513
+ }
2477
2514
  createAgentRuntime(options = {}) {
2478
2515
  const baseContext = {
2479
2516
  workspacePath: options.workspacePath,
@@ -2522,6 +2559,14 @@ var WhisperClient = class _WhisperClient {
2522
2559
  sessionId: params.sessionId || context.sessionId || ""
2523
2560
  })
2524
2561
  },
2562
+ learn: (params) => base.learn({
2563
+ ...params,
2564
+ project: params.project || context.project || base.config.project,
2565
+ ...params.mode === "conversation" ? {
2566
+ user_id: params.user_id ?? context.userId,
2567
+ session_id: params.session_id || context.sessionId || ""
2568
+ } : {}
2569
+ }),
2525
2570
  queue: base.queue,
2526
2571
  diagnostics: base.diagnostics
2527
2572
  };
@@ -2983,91 +3028,138 @@ var WhisperContext = class _WhisperContext {
2983
3028
  return this.request(`/v1/sources/${sourceId}/sync`, { method: "POST" });
2984
3029
  }
2985
3030
  async addSourceByType(projectId, params) {
2986
- return this.withProjectPathFallback(
2987
- this.getRequiredProject(projectId),
2988
- (projectPathRef) => this.request(`/v1/projects/${encodeURIComponent(projectPathRef)}/add_source`, {
2989
- method: "POST",
2990
- body: JSON.stringify(params)
2991
- })
2992
- );
3031
+ const result = await this.learn({
3032
+ mode: "source",
3033
+ project: projectId,
3034
+ type: "video",
3035
+ name: params.name,
3036
+ url: params.url,
3037
+ platform: params.platform,
3038
+ language: params.language,
3039
+ options: {
3040
+ async: true,
3041
+ auto_index: params.auto_sync ?? true,
3042
+ ingestion_profile: params.ingestion_profile,
3043
+ strategy_override: params.strategy_override,
3044
+ profile_config: params.profile_config,
3045
+ allow_stt_fallback: params.allow_stt_fallback,
3046
+ max_duration_minutes: params.max_duration_minutes
3047
+ }
3048
+ });
3049
+ return {
3050
+ source_id: result.source_id,
3051
+ sync_job_id: result.job_id ?? null,
3052
+ status: result.status === "processing" || result.status === "queued" ? result.status : "created"
3053
+ };
2993
3054
  }
2994
3055
  async getSourceStatus(sourceId) {
2995
3056
  return this.request(`/v1/sources/${sourceId}/status`, { method: "GET" });
2996
3057
  }
2997
3058
  async createCanonicalSource(project, params) {
2998
- const connector_type = params.type === "github" ? "github" : params.type === "web" ? "website" : params.type === "pdf" ? "pdf" : params.type === "local" ? "local-folder" : "slack";
2999
- const config = {};
3000
- if (params.type === "github") {
3001
- if (!params.owner || !params.repo) throw new WhisperError({ code: "REQUEST_FAILED", message: "github source requires owner and repo" });
3002
- config.owner = params.owner;
3003
- config.repo = params.repo;
3004
- if (params.branch) config.branch = params.branch;
3005
- if (params.paths) config.paths = params.paths;
3006
- } else if (params.type === "web") {
3007
- if (!params.url) throw new WhisperError({ code: "REQUEST_FAILED", message: "web source requires url" });
3008
- config.url = params.url;
3009
- if (params.crawl_depth !== void 0) config.crawl_depth = params.crawl_depth;
3010
- if (params.include_paths) config.include_paths = params.include_paths;
3011
- if (params.exclude_paths) config.exclude_paths = params.exclude_paths;
3012
- } else if (params.type === "pdf") {
3013
- if (!params.url && !params.file_path) throw new WhisperError({ code: "REQUEST_FAILED", message: "pdf source requires url or file_path" });
3014
- if (params.url) config.url = params.url;
3015
- if (params.file_path) config.file_path = params.file_path;
3016
- } else if (params.type === "local") {
3017
- if (!params.path) throw new WhisperError({ code: "REQUEST_FAILED", message: "local source requires path" });
3018
- config.path = params.path;
3019
- if (params.glob) config.glob = params.glob;
3020
- if (params.max_files !== void 0) config.max_files = params.max_files;
3021
- } else {
3022
- config.channel_ids = params.channel_ids || [];
3023
- if (params.since) config.since = params.since;
3024
- if (params.workspace_id) config.workspace_id = params.workspace_id;
3025
- if (params.token) config.token = params.token;
3026
- if (params.auth_ref) config.auth_ref = params.auth_ref;
3027
- }
3028
- if (params.metadata) config.metadata = params.metadata;
3029
- if (params.ingestion_profile) config.ingestion_profile = params.ingestion_profile;
3030
- if (params.strategy_override) config.strategy_override = params.strategy_override;
3031
- if (params.profile_config) config.profile_config = params.profile_config;
3032
- config.auto_index = params.auto_index ?? true;
3033
- const created = await this.addSource(project, {
3034
- name: params.name || `${params.type}-source-${Date.now()}`,
3035
- connector_type,
3036
- config
3059
+ const result = await this.learn({
3060
+ mode: "source",
3061
+ project,
3062
+ type: params.type,
3063
+ name: params.name,
3064
+ metadata: params.metadata,
3065
+ owner: params.owner,
3066
+ repo: params.repo,
3067
+ branch: params.branch,
3068
+ paths: params.paths,
3069
+ url: params.url,
3070
+ file_path: params.file_path,
3071
+ path: params.path,
3072
+ channel_ids: params.channel_ids,
3073
+ since: params.since,
3074
+ token: params.token,
3075
+ auth_ref: params.auth_ref,
3076
+ platform: params.platform,
3077
+ language: params.language,
3078
+ options: {
3079
+ async: true,
3080
+ auto_index: params.auto_index ?? true,
3081
+ ingestion_profile: params.ingestion_profile,
3082
+ strategy_override: params.strategy_override,
3083
+ profile_config: params.profile_config,
3084
+ crawl_depth: params.crawl_depth,
3085
+ include_paths: params.include_paths,
3086
+ exclude_paths: params.exclude_paths,
3087
+ glob: params.glob,
3088
+ max_files: params.max_files,
3089
+ max_pages: params.max_pages,
3090
+ extract_mode: params.extract_mode,
3091
+ workspace_id: params.workspace_id,
3092
+ allow_stt_fallback: params.allow_stt_fallback,
3093
+ max_duration_minutes: params.max_duration_minutes,
3094
+ max_chunks: params.max_chunks
3095
+ }
3037
3096
  });
3038
- let status = "queued";
3039
- let jobId = null;
3040
- if (params.auto_index ?? true) {
3041
- const syncRes = await this.syncSource(created.id);
3042
- status = "indexing";
3043
- jobId = String(syncRes?.id || syncRes?.job_id || "");
3044
- }
3045
3097
  return {
3046
- source_id: created.id,
3047
- status,
3048
- job_id: jobId,
3049
- index_started: params.auto_index ?? true,
3098
+ source_id: result.source_id,
3099
+ status: result.status === "processing" ? "indexing" : result.status === "created" ? "queued" : result.status,
3100
+ job_id: result.job_id ?? null,
3101
+ index_started: result.index_started,
3050
3102
  warnings: []
3051
3103
  };
3052
3104
  }
3053
3105
  async ingest(projectId, documents) {
3054
- return this.withProjectPathFallback(
3055
- this.getRequiredProject(projectId),
3056
- (projectPathRef) => this.request(`/v1/projects/${encodeURIComponent(projectPathRef)}/ingest`, {
3057
- method: "POST",
3058
- body: JSON.stringify({ documents })
3059
- })
3106
+ await Promise.all(
3107
+ documents.map(
3108
+ (doc) => this.learn({
3109
+ mode: "text",
3110
+ project: projectId,
3111
+ title: doc.title,
3112
+ content: doc.content,
3113
+ metadata: {
3114
+ ...doc.metadata || {},
3115
+ ...doc.file_path ? { file_path: doc.file_path } : {}
3116
+ },
3117
+ options: {
3118
+ async: true,
3119
+ ingestion_profile: doc.ingestion_profile,
3120
+ strategy_override: doc.strategy_override,
3121
+ profile_config: doc.profile_config
3122
+ }
3123
+ })
3124
+ )
3060
3125
  );
3126
+ return { ingested: documents.length };
3061
3127
  }
3062
3128
  async addContext(params) {
3063
- const projectId = (await this.resolveProject(this.getRequiredProject(params.project))).id;
3064
- return this.ingest(projectId, [
3065
- {
3066
- title: params.title || "Context",
3067
- content: params.content,
3068
- metadata: params.metadata || { source: "addContext" }
3129
+ await this.learn({
3130
+ mode: "text",
3131
+ project: this.getRequiredProject(params.project),
3132
+ title: params.title || "Context",
3133
+ content: params.content,
3134
+ metadata: params.metadata || { source: "addContext" },
3135
+ options: {
3136
+ async: true
3069
3137
  }
3070
- ]);
3138
+ });
3139
+ return { ingested: 1 };
3140
+ }
3141
+ async learn(params) {
3142
+ const projectRef = this.getRequiredProject(params.project);
3143
+ return this.withProjectRefFallback(projectRef, (project) => this.request("/v1/learn", {
3144
+ method: "POST",
3145
+ body: JSON.stringify({
3146
+ ...params,
3147
+ project
3148
+ })
3149
+ }));
3150
+ }
3151
+ async listMemories(params) {
3152
+ const projectRef = this.getRequiredProject(params.project);
3153
+ return this.withProjectRefFallback(projectRef, async (project) => {
3154
+ const query = new URLSearchParams({
3155
+ project,
3156
+ ...params.user_id ? { user_id: params.user_id } : {},
3157
+ ...params.session_id ? { session_id: params.session_id } : {},
3158
+ ...params.agent_id ? { agent_id: params.agent_id } : {},
3159
+ limit: String(Math.min(Math.max(params.limit ?? 200, 1), 200))
3160
+ });
3161
+ return this.request(`/v1/memories?${query.toString()}`, { method: "GET" });
3162
+ });
3071
3163
  }
3072
3164
  async addMemory(params) {
3073
3165
  const projectRef = this.getRequiredProject(params.project);
@@ -3919,6 +4011,21 @@ async function resolveProjectRef(explicit) {
3919
4011
  return void 0;
3920
4012
  }
3921
4013
  }
4014
+ async function ingestSessionWithSyncFallback(params) {
4015
+ try {
4016
+ return await whisper.ingestSession({
4017
+ ...params,
4018
+ async: false,
4019
+ write_mode: "sync"
4020
+ });
4021
+ } catch (error) {
4022
+ const message = String(error?.message || error || "").toLowerCase();
4023
+ if (!message.includes("sync_write_restricted")) {
4024
+ throw error;
4025
+ }
4026
+ return whisper.ingestSession(params);
4027
+ }
4028
+ }
3922
4029
  function defaultMcpUserId() {
3923
4030
  const explicit = process.env.WHISPER_USER_ID?.trim();
3924
4031
  if (explicit) return explicit;
@@ -4234,79 +4341,40 @@ async function ingestLocalPath(params) {
4234
4341
  return { ingested, scanned: files.length, queued: docs.length, skipped, workspace_id: workspaceId };
4235
4342
  }
4236
4343
  async function createSourceByType(params) {
4237
- const connector_type = params.type === "github" ? "github" : params.type === "web" ? "website" : params.type === "pdf" ? "pdf" : params.type === "local" ? "local-folder" : params.type === "slack" ? "slack" : "video";
4238
- const config = {};
4239
- if (params.type === "github") {
4240
- if (!params.owner || !params.repo) throw new Error("github source requires owner and repo");
4241
- config.owner = params.owner;
4242
- config.repo = params.repo;
4243
- if (params.branch) config.branch = params.branch;
4244
- if (params.paths) config.paths = params.paths;
4245
- } else if (params.type === "web") {
4246
- if (!params.url) throw new Error("web source requires url");
4247
- config.url = params.url;
4248
- if (params.crawl_depth !== void 0) config.crawl_depth = params.crawl_depth;
4249
- if (params.include_paths) config.include_paths = params.include_paths;
4250
- if (params.exclude_paths) config.exclude_paths = params.exclude_paths;
4251
- } else if (params.type === "pdf") {
4252
- if (!params.url && !params.file_path) throw new Error("pdf source requires url or file_path");
4253
- if (params.url) config.url = params.url;
4254
- if (params.file_path) config.file_path = params.file_path;
4255
- } else if (params.type === "local") {
4256
- if (!params.path) throw new Error("local source requires path");
4257
- const ingest = await ingestLocalPath({
4258
- project: params.project,
4259
- path: params.path,
4260
- glob: params.glob,
4261
- max_files: params.max_files
4262
- });
4263
- return {
4264
- source_id: `local_${ingest.workspace_id}`,
4265
- status: "ready",
4266
- job_id: `local_ingest_${Date.now()}`,
4267
- index_started: true,
4268
- warnings: ingest.skipped.slice(0, 20),
4269
- details: ingest
4270
- };
4271
- } else if (params.type === "slack") {
4272
- config.channel_ids = params.channel_ids || [];
4273
- if (params.since) config.since = params.since;
4274
- if (params.workspace_id) config.workspace_id = params.workspace_id;
4275
- if (params.token) config.token = params.token;
4276
- if (params.auth_ref) config.auth_ref = params.auth_ref;
4277
- } else if (params.type === "video") {
4278
- if (!params.url) throw new Error("video source requires url");
4279
- config.url = params.url;
4280
- if (params.platform) config.platform = params.platform;
4281
- if (params.language) config.language = params.language;
4282
- if (params.allow_stt_fallback !== void 0) config.allow_stt_fallback = params.allow_stt_fallback;
4283
- if (params.max_duration_minutes !== void 0) config.max_duration_minutes = params.max_duration_minutes;
4284
- if (params.max_chunks !== void 0) config.max_chunks = params.max_chunks;
4285
- }
4286
- if (params.metadata) config.metadata = params.metadata;
4287
- if (params.ingestion_profile) config.ingestion_profile = params.ingestion_profile;
4288
- if (params.strategy_override) config.strategy_override = params.strategy_override;
4289
- if (params.profile_config) config.profile_config = params.profile_config;
4290
- config.auto_index = params.auto_index ?? true;
4291
- const created = await whisper.addSource(params.project, {
4292
- name: params.name || `${params.type}-source-${Date.now()}`,
4293
- connector_type,
4294
- config
4344
+ const result = await whisper.createCanonicalSource(params.project, {
4345
+ type: params.type,
4346
+ name: params.name,
4347
+ auto_index: params.auto_index,
4348
+ metadata: params.metadata,
4349
+ ingestion_profile: params.ingestion_profile,
4350
+ strategy_override: params.strategy_override,
4351
+ profile_config: params.profile_config,
4352
+ owner: params.owner,
4353
+ repo: params.repo,
4354
+ branch: params.branch,
4355
+ paths: params.paths,
4356
+ url: params.url,
4357
+ crawl_depth: params.crawl_depth,
4358
+ include_paths: params.include_paths,
4359
+ exclude_paths: params.exclude_paths,
4360
+ file_path: params.file_path,
4361
+ path: params.path,
4362
+ glob: params.glob,
4363
+ max_files: params.max_files,
4364
+ max_pages: params.max_pages,
4365
+ extract_mode: params.extract_mode,
4366
+ workspace_id: params.workspace_id,
4367
+ channel_ids: params.channel_ids,
4368
+ since: params.since,
4369
+ token: params.token,
4370
+ auth_ref: params.auth_ref,
4371
+ platform: params.platform,
4372
+ language: params.language,
4373
+ allow_stt_fallback: params.allow_stt_fallback,
4374
+ max_duration_minutes: params.max_duration_minutes,
4375
+ max_chunks: params.max_chunks
4295
4376
  });
4296
- let jobId;
4297
- let status = "queued";
4298
- if (params.auto_index ?? true) {
4299
- const syncRes = await whisper.syncSource(created.id);
4300
- jobId = String(syncRes?.id || syncRes?.job_id || "");
4301
- status = "indexing";
4302
- }
4303
- return {
4304
- source_id: created.id,
4305
- status,
4306
- job_id: jobId || null,
4307
- index_started: params.auto_index ?? true,
4308
- warnings: []
4309
- };
4377
+ return result;
4310
4378
  }
4311
4379
  function normalizeRecordMessages(input) {
4312
4380
  if (Array.isArray(input.messages) && input.messages.length > 0) {
@@ -4330,85 +4398,60 @@ async function learnFromInput(input) {
4330
4398
  if (!resolvedProject) {
4331
4399
  throw new Error("No project resolved. Set WHISPER_PROJECT or provide project.");
4332
4400
  }
4333
- if (input.content) {
4334
- const mergedMetadata = {
4335
- ...input.metadata || {},
4336
- ...input.ingestion_profile ? { ingestion_profile: input.ingestion_profile } : {},
4337
- ...input.strategy_override ? { strategy_override: input.strategy_override } : {},
4338
- ...input.profile_config ? { profile_config: input.profile_config } : {}
4339
- };
4340
- const result = await whisper.addContext({
4341
- project: resolvedProject,
4342
- content: input.content,
4343
- title: input.title || "Learned Context",
4344
- metadata: mergedMetadata
4345
- });
4346
- return {
4347
- mode: "text",
4348
- project: resolvedProject,
4349
- ingested: result.ingested
4350
- };
4351
- }
4352
- if (input.owner && input.repo) {
4353
- return createSourceByType({
4354
- project: resolvedProject,
4355
- type: "github",
4356
- owner: input.owner,
4357
- repo: input.repo,
4358
- branch: input.branch,
4359
- name: input.name,
4360
- auto_index: true,
4361
- metadata: input.metadata,
4362
- ingestion_profile: input.ingestion_profile,
4363
- strategy_override: input.strategy_override,
4364
- profile_config: input.profile_config
4365
- });
4366
- }
4367
- if (input.path) {
4368
- return createSourceByType({
4369
- project: resolvedProject,
4370
- type: "local",
4371
- path: input.path,
4372
- glob: input.glob,
4373
- max_files: input.max_files,
4374
- name: input.name,
4375
- metadata: input.metadata,
4376
- ingestion_profile: input.ingestion_profile,
4377
- strategy_override: input.strategy_override,
4378
- profile_config: input.profile_config
4379
- });
4380
- }
4381
- if (input.file_path) {
4382
- return createSourceByType({
4401
+ if (input.mode === "conversation") {
4402
+ if (!input.session_id || !input.messages || input.messages.length === 0) {
4403
+ throw new Error("conversation learn requires session_id and messages[]");
4404
+ }
4405
+ return whisper.learn({
4406
+ mode: "conversation",
4383
4407
  project: resolvedProject,
4384
- type: "pdf",
4385
- file_path: input.file_path,
4386
- name: input.name,
4387
- metadata: input.metadata,
4388
- ingestion_profile: input.ingestion_profile,
4389
- strategy_override: input.strategy_override,
4390
- profile_config: input.profile_config
4408
+ user_id: input.user_id,
4409
+ session_id: input.session_id,
4410
+ messages: input.messages.map((message) => ({
4411
+ ...message,
4412
+ timestamp: message.timestamp || (/* @__PURE__ */ new Date()).toISOString()
4413
+ }))
4391
4414
  });
4392
4415
  }
4393
- if (input.url) {
4394
- return createSourceByType({
4416
+ if (input.mode === "text") {
4417
+ if (!input.title || !input.content) {
4418
+ throw new Error("text learn requires title and content");
4419
+ }
4420
+ return whisper.learn({
4421
+ mode: "text",
4395
4422
  project: resolvedProject,
4396
- type: input.url.endsWith(".pdf") ? "pdf" : "web",
4397
- url: input.url,
4398
- name: input.name,
4423
+ title: input.title,
4424
+ content: input.content,
4399
4425
  metadata: input.metadata,
4400
- ingestion_profile: input.ingestion_profile,
4401
- strategy_override: input.strategy_override,
4402
- profile_config: input.profile_config,
4403
- crawl_depth: input.crawl_depth,
4404
- channel_ids: input.channel_ids,
4405
- token: input.token,
4406
- workspace_id: input.workspace_id,
4407
- since: input.since,
4408
- auth_ref: input.auth_ref
4426
+ tags: input.tags,
4427
+ namespace: input.namespace,
4428
+ options: input.options
4409
4429
  });
4410
4430
  }
4411
- throw new Error("Provide content, owner+repo, path, file_path, or url.");
4431
+ if (!input.type) {
4432
+ throw new Error("source learn requires type");
4433
+ }
4434
+ return whisper.learn({
4435
+ mode: "source",
4436
+ project: resolvedProject,
4437
+ type: input.type,
4438
+ name: input.name,
4439
+ metadata: input.metadata,
4440
+ owner: input.owner,
4441
+ repo: input.repo,
4442
+ branch: input.branch,
4443
+ paths: input.paths,
4444
+ url: input.url,
4445
+ file_path: input.file_path,
4446
+ path: input.path,
4447
+ channel_ids: input.channel_ids,
4448
+ since: input.since,
4449
+ token: input.token,
4450
+ auth_ref: input.auth_ref,
4451
+ platform: input.platform,
4452
+ language: input.language,
4453
+ options: input.options
4454
+ });
4412
4455
  }
4413
4456
  function renderScopedMcpConfig(project, source, client) {
4414
4457
  const serverDef = {
@@ -5039,10 +5082,10 @@ server.tool(
5039
5082
  );
5040
5083
  server.tool(
5041
5084
  "context.add_source",
5042
- "Add a source to a project with normalized source contract and auto-index by default.",
5085
+ "Compatibility learning tool. Add a source to a project with normalized source contract and auto-index by default. Prefer `learn` for new integrations.",
5043
5086
  {
5044
5087
  project: z.string().optional().describe("Project name or slug"),
5045
- type: z.enum(["github", "web", "pdf", "local", "slack", "video"]).default("github"),
5088
+ type: z.enum(["github", "web", "playwright", "pdf", "local", "slack", "video"]).default("github"),
5046
5089
  name: z.string().optional(),
5047
5090
  auto_index: z.boolean().optional().default(true),
5048
5091
  metadata: z.record(z.string()).optional(),
@@ -5061,6 +5104,8 @@ server.tool(
5061
5104
  path: z.string().optional(),
5062
5105
  glob: z.string().optional(),
5063
5106
  max_files: z.number().optional(),
5107
+ max_pages: z.number().optional(),
5108
+ extract_mode: z.enum(["text", "structured", "markdown"]).optional(),
5064
5109
  workspace_id: z.string().optional(),
5065
5110
  channel_ids: z.array(z.string()).optional(),
5066
5111
  since: z.string().optional(),
@@ -5099,6 +5144,8 @@ server.tool(
5099
5144
  path: input.path,
5100
5145
  glob: input.glob,
5101
5146
  max_files: input.max_files,
5147
+ max_pages: input.max_pages,
5148
+ extract_mode: input.extract_mode,
5102
5149
  workspace_id: input.workspace_id,
5103
5150
  channel_ids: input.channel_ids,
5104
5151
  since: input.since,
@@ -5137,7 +5184,7 @@ server.tool(
5137
5184
  );
5138
5185
  server.tool(
5139
5186
  "context.add_text",
5140
- "Add text content to a project's knowledge base.",
5187
+ "Compatibility learning tool. Add text content to a project's knowledge base. Prefer `learn` for new integrations.",
5141
5188
  {
5142
5189
  project: z.string().optional().describe("Project name or slug"),
5143
5190
  title: z.string().describe("Title for this content"),
@@ -5148,8 +5195,12 @@ server.tool(
5148
5195
  },
5149
5196
  async ({ project, title, content, ingestion_profile, strategy_override, profile_config }) => {
5150
5197
  try {
5198
+ const resolvedProject = await resolveProjectRef(project);
5199
+ if (!resolvedProject) {
5200
+ return { content: [{ type: "text", text: "Error: No project resolved. Set WHISPER_PROJECT or provide project." }] };
5201
+ }
5151
5202
  await whisper.addContext({
5152
- project,
5203
+ project: resolvedProject,
5153
5204
  title,
5154
5205
  content,
5155
5206
  metadata: {
@@ -5166,7 +5217,7 @@ server.tool(
5166
5217
  );
5167
5218
  server.tool(
5168
5219
  "context.add_document",
5169
- "Ingest a document into project knowledge. Supports plain text and video URLs.",
5220
+ "Compatibility learning tool. Ingest a document into project knowledge. Supports plain text and video URLs. Prefer `learn` for new integrations.",
5170
5221
  {
5171
5222
  project: z.string().optional().describe("Project name or slug"),
5172
5223
  source_type: z.enum(["text", "video"]).default("text"),
@@ -5271,7 +5322,7 @@ server.tool(
5271
5322
  );
5272
5323
  server.tool(
5273
5324
  "memory.ingest_conversation",
5274
- "Extract memories from a conversation session. Automatically handles disambiguation, temporal grounding, and relation detection.",
5325
+ "Compatibility learning tool. Extract memories from a conversation session. Prefer `learn` for new integrations.",
5275
5326
  {
5276
5327
  project: z.string().optional().describe("Project name or slug"),
5277
5328
  session_id: z.string().describe("Session identifier"),
@@ -5290,7 +5341,7 @@ server.tool(
5290
5341
  content: message.content,
5291
5342
  timestamp: message.timestamp
5292
5343
  }));
5293
- const result = await whisper.ingestSession({
5344
+ const result = await ingestSessionWithSyncFallback({
5294
5345
  project: scope.project,
5295
5346
  session_id: scope.sessionId,
5296
5347
  user_id: scope.userId,
@@ -5404,8 +5455,12 @@ server.tool(
5404
5455
  },
5405
5456
  async ({ project, session_id, title, expiry_days }) => {
5406
5457
  try {
5458
+ const resolvedProject = await resolveProjectRef(project);
5459
+ if (!resolvedProject) {
5460
+ return { content: [{ type: "text", text: "Error: No project resolved. Set WHISPER_PROJECT or provide project." }] };
5461
+ }
5407
5462
  const result = await whisper.createSharedContext({
5408
- project,
5463
+ project: resolvedProject,
5409
5464
  session_id,
5410
5465
  title,
5411
5466
  expiry_days
@@ -5660,13 +5715,11 @@ server.tool(
5660
5715
  let memories = [];
5661
5716
  if (resolvedProject) {
5662
5717
  try {
5663
- const m = await whisper.searchMemoriesSOTA({
5718
+ const listed = await whisper.listMemories({
5664
5719
  project: resolvedProject,
5665
- query: "memory",
5666
- top_k: 100,
5667
- include_relations: true
5720
+ limit: 200
5668
5721
  });
5669
- memories = (m.results || []).map((r) => r.memory || r);
5722
+ memories = Array.isArray(listed?.memories) ? listed.memories : [];
5670
5723
  } catch {
5671
5724
  memories = [];
5672
5725
  }
@@ -5757,7 +5810,7 @@ server.tool(
5757
5810
  session_summaries: 0,
5758
5811
  documents: 0
5759
5812
  };
5760
- const resolvedProject = await resolveProjectRef(bundle.project);
5813
+ const resolvedProject = await resolveProjectRef(bundle.project || cachedProjectRef || DEFAULT_PROJECT || void 0);
5761
5814
  async function shouldSkipImportedMemory(memory) {
5762
5815
  if (dedupe_strategy === "none" || !resolvedProject) return false;
5763
5816
  const exactId = normalizeString2(memory?.id);
@@ -6576,7 +6629,7 @@ server.tool(
6576
6629
  {
6577
6630
  action: z.enum(["source", "workspace"]).default("source"),
6578
6631
  project: z.string().optional(),
6579
- type: z.enum(["github", "web", "pdf", "local", "slack", "video"]).optional(),
6632
+ type: z.enum(["github", "web", "playwright", "pdf", "local", "slack", "video"]).optional(),
6580
6633
  name: z.string().optional(),
6581
6634
  owner: z.string().optional(),
6582
6635
  repo: z.string().optional(),
@@ -6711,7 +6764,13 @@ server.tool(
6711
6764
  content,
6712
6765
  timestamp
6713
6766
  });
6714
- const result = await whisper.ingestSession({ project, session_id, user_id, messages: normalizedMessages });
6767
+ const resolvedProject = await resolveProjectRef(project);
6768
+ const result = await ingestSessionWithSyncFallback({
6769
+ project: resolvedProject,
6770
+ session_id,
6771
+ user_id,
6772
+ messages: normalizedMessages
6773
+ });
6715
6774
  return primaryToolSuccess({
6716
6775
  tool: "record",
6717
6776
  session_id,
@@ -6726,25 +6785,55 @@ server.tool(
6726
6785
  );
6727
6786
  server.tool(
6728
6787
  "learn",
6729
- "Learn content or connect a source so it becomes retrievable later. Use this for docs, URLs, repos, files, or local paths.",
6788
+ "Unified learning tool for conversation memory, text ingestion, and source indexing. Prefer this over the older learning-adjacent compatibility tools.",
6730
6789
  {
6790
+ mode: z.enum(["conversation", "text", "source"]).describe("What kind of learning to perform"),
6731
6791
  project: z.string().optional(),
6792
+ user_id: z.string().optional().describe("Optional end-user identity for conversation learning"),
6793
+ session_id: z.string().optional().describe("Session identifier for conversation learning"),
6794
+ messages: z.array(z.object({
6795
+ role: z.string(),
6796
+ content: z.string(),
6797
+ timestamp: z.string().optional()
6798
+ })).optional().describe("Conversation messages to learn from"),
6799
+ title: z.string().optional().describe("Title for text learning"),
6732
6800
  content: z.string().optional().describe("Inline text content to ingest"),
6733
- title: z.string().optional().describe("Title for inline content"),
6801
+ metadata: z.record(z.any()).optional(),
6802
+ tags: z.array(z.string()).optional(),
6803
+ namespace: z.string().optional(),
6804
+ type: z.enum(["github", "web", "playwright", "pdf", "local", "slack", "video"]).optional().describe("Source type when mode=source"),
6734
6805
  url: z.string().optional().describe("URL to learn from"),
6735
6806
  owner: z.string().optional().describe("GitHub owner"),
6736
6807
  repo: z.string().optional().describe("GitHub repository"),
6737
6808
  branch: z.string().optional(),
6809
+ paths: z.array(z.string()).optional(),
6738
6810
  path: z.string().optional().describe("Local path to learn from"),
6739
6811
  file_path: z.string().optional().describe("Single file path to learn from"),
6740
6812
  name: z.string().optional().describe("Optional source name"),
6741
- metadata: z.record(z.string()).optional(),
6742
- ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
6743
- strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
6744
- profile_config: z.record(z.any()).optional(),
6745
- max_files: z.number().optional(),
6746
- glob: z.string().optional(),
6747
- crawl_depth: z.number().optional()
6813
+ channel_ids: z.array(z.string()).optional(),
6814
+ since: z.string().optional(),
6815
+ token: z.string().optional(),
6816
+ auth_ref: z.string().optional(),
6817
+ platform: z.enum(["youtube", "loom", "generic"]).optional(),
6818
+ language: z.string().optional(),
6819
+ options: z.object({
6820
+ async: z.boolean().optional(),
6821
+ auto_index: z.boolean().optional(),
6822
+ ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
6823
+ strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
6824
+ profile_config: z.record(z.any()).optional(),
6825
+ crawl_depth: z.number().optional(),
6826
+ include_paths: z.array(z.string()).optional(),
6827
+ exclude_paths: z.array(z.string()).optional(),
6828
+ glob: z.string().optional(),
6829
+ max_files: z.number().optional(),
6830
+ max_pages: z.number().optional(),
6831
+ extract_mode: z.enum(["text", "structured", "markdown"]).optional(),
6832
+ workspace_id: z.string().optional(),
6833
+ allow_stt_fallback: z.boolean().optional(),
6834
+ max_duration_minutes: z.number().optional(),
6835
+ max_chunks: z.number().optional()
6836
+ }).optional()
6748
6837
  },
6749
6838
  async (input) => {
6750
6839
  try {
@@ -6769,7 +6858,11 @@ server.tool(
6769
6858
  },
6770
6859
  async ({ project, session_id, title, expiry_days }) => {
6771
6860
  try {
6772
- const result = await whisper.createSharedContext({ project, session_id, title, expiry_days });
6861
+ const resolvedProject = await resolveProjectRef(project);
6862
+ if (!resolvedProject) {
6863
+ return primaryToolError("No project resolved. Set WHISPER_PROJECT or provide project.");
6864
+ }
6865
+ const result = await whisper.createSharedContext({ project: resolvedProject, session_id, title, expiry_days });
6773
6866
  return primaryToolSuccess({
6774
6867
  tool: "share_context",
6775
6868
  share_id: result.share_id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usewhisper/mcp-server",
3
- "version": "2.7.0",
3
+ "version": "2.9.0",
4
4
  "whisperContractVersion": "2026.03.10",
5
5
  "scripts": {
6
6
  "build": "tsup ../src/mcp/server.ts --format esm --out-dir dist",