@usewhisper/mcp-server 2.8.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 +279 -230
  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,125 @@ 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
+ }));
3071
3150
  }
3072
3151
  async listMemories(params) {
3073
3152
  const projectRef = this.getRequiredProject(params.project);
@@ -4262,79 +4341,40 @@ async function ingestLocalPath(params) {
4262
4341
  return { ingested, scanned: files.length, queued: docs.length, skipped, workspace_id: workspaceId };
4263
4342
  }
4264
4343
  async function createSourceByType(params) {
4265
- 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";
4266
- const config = {};
4267
- if (params.type === "github") {
4268
- if (!params.owner || !params.repo) throw new Error("github source requires owner and repo");
4269
- config.owner = params.owner;
4270
- config.repo = params.repo;
4271
- if (params.branch) config.branch = params.branch;
4272
- if (params.paths) config.paths = params.paths;
4273
- } else if (params.type === "web") {
4274
- if (!params.url) throw new Error("web source requires url");
4275
- config.url = params.url;
4276
- if (params.crawl_depth !== void 0) config.crawl_depth = params.crawl_depth;
4277
- if (params.include_paths) config.include_paths = params.include_paths;
4278
- if (params.exclude_paths) config.exclude_paths = params.exclude_paths;
4279
- } else if (params.type === "pdf") {
4280
- if (!params.url && !params.file_path) throw new Error("pdf source requires url or file_path");
4281
- if (params.url) config.url = params.url;
4282
- if (params.file_path) config.file_path = params.file_path;
4283
- } else if (params.type === "local") {
4284
- if (!params.path) throw new Error("local source requires path");
4285
- const ingest = await ingestLocalPath({
4286
- project: params.project,
4287
- path: params.path,
4288
- glob: params.glob,
4289
- max_files: params.max_files
4290
- });
4291
- return {
4292
- source_id: `local_${ingest.workspace_id}`,
4293
- status: "ready",
4294
- job_id: `local_ingest_${Date.now()}`,
4295
- index_started: true,
4296
- warnings: ingest.skipped.slice(0, 20),
4297
- details: ingest
4298
- };
4299
- } else if (params.type === "slack") {
4300
- config.channel_ids = params.channel_ids || [];
4301
- if (params.since) config.since = params.since;
4302
- if (params.workspace_id) config.workspace_id = params.workspace_id;
4303
- if (params.token) config.token = params.token;
4304
- if (params.auth_ref) config.auth_ref = params.auth_ref;
4305
- } else if (params.type === "video") {
4306
- if (!params.url) throw new Error("video source requires url");
4307
- config.url = params.url;
4308
- if (params.platform) config.platform = params.platform;
4309
- if (params.language) config.language = params.language;
4310
- if (params.allow_stt_fallback !== void 0) config.allow_stt_fallback = params.allow_stt_fallback;
4311
- if (params.max_duration_minutes !== void 0) config.max_duration_minutes = params.max_duration_minutes;
4312
- if (params.max_chunks !== void 0) config.max_chunks = params.max_chunks;
4313
- }
4314
- if (params.metadata) config.metadata = params.metadata;
4315
- if (params.ingestion_profile) config.ingestion_profile = params.ingestion_profile;
4316
- if (params.strategy_override) config.strategy_override = params.strategy_override;
4317
- if (params.profile_config) config.profile_config = params.profile_config;
4318
- config.auto_index = params.auto_index ?? true;
4319
- const created = await whisper.addSource(params.project, {
4320
- name: params.name || `${params.type}-source-${Date.now()}`,
4321
- connector_type,
4322
- 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
4323
4376
  });
4324
- let jobId;
4325
- let status = "queued";
4326
- if (params.auto_index ?? true) {
4327
- const syncRes = await whisper.syncSource(created.id);
4328
- jobId = String(syncRes?.id || syncRes?.job_id || "");
4329
- status = "indexing";
4330
- }
4331
- return {
4332
- source_id: created.id,
4333
- status,
4334
- job_id: jobId || null,
4335
- index_started: params.auto_index ?? true,
4336
- warnings: []
4337
- };
4377
+ return result;
4338
4378
  }
4339
4379
  function normalizeRecordMessages(input) {
4340
4380
  if (Array.isArray(input.messages) && input.messages.length > 0) {
@@ -4358,85 +4398,60 @@ async function learnFromInput(input) {
4358
4398
  if (!resolvedProject) {
4359
4399
  throw new Error("No project resolved. Set WHISPER_PROJECT or provide project.");
4360
4400
  }
4361
- if (input.content) {
4362
- const mergedMetadata = {
4363
- ...input.metadata || {},
4364
- ...input.ingestion_profile ? { ingestion_profile: input.ingestion_profile } : {},
4365
- ...input.strategy_override ? { strategy_override: input.strategy_override } : {},
4366
- ...input.profile_config ? { profile_config: input.profile_config } : {}
4367
- };
4368
- const result = await whisper.addContext({
4369
- project: resolvedProject,
4370
- content: input.content,
4371
- title: input.title || "Learned Context",
4372
- metadata: mergedMetadata
4373
- });
4374
- return {
4375
- mode: "text",
4376
- project: resolvedProject,
4377
- ingested: result.ingested
4378
- };
4379
- }
4380
- if (input.owner && input.repo) {
4381
- return createSourceByType({
4382
- project: resolvedProject,
4383
- type: "github",
4384
- owner: input.owner,
4385
- repo: input.repo,
4386
- branch: input.branch,
4387
- name: input.name,
4388
- auto_index: true,
4389
- metadata: input.metadata,
4390
- ingestion_profile: input.ingestion_profile,
4391
- strategy_override: input.strategy_override,
4392
- profile_config: input.profile_config
4393
- });
4394
- }
4395
- if (input.path) {
4396
- return createSourceByType({
4397
- project: resolvedProject,
4398
- type: "local",
4399
- path: input.path,
4400
- glob: input.glob,
4401
- max_files: input.max_files,
4402
- name: input.name,
4403
- metadata: input.metadata,
4404
- ingestion_profile: input.ingestion_profile,
4405
- strategy_override: input.strategy_override,
4406
- profile_config: input.profile_config
4407
- });
4408
- }
4409
- if (input.file_path) {
4410
- 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",
4411
4407
  project: resolvedProject,
4412
- type: "pdf",
4413
- file_path: input.file_path,
4414
- name: input.name,
4415
- metadata: input.metadata,
4416
- ingestion_profile: input.ingestion_profile,
4417
- strategy_override: input.strategy_override,
4418
- 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
+ }))
4419
4414
  });
4420
4415
  }
4421
- if (input.url) {
4422
- 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",
4423
4422
  project: resolvedProject,
4424
- type: input.url.endsWith(".pdf") ? "pdf" : "web",
4425
- url: input.url,
4426
- name: input.name,
4423
+ title: input.title,
4424
+ content: input.content,
4427
4425
  metadata: input.metadata,
4428
- ingestion_profile: input.ingestion_profile,
4429
- strategy_override: input.strategy_override,
4430
- profile_config: input.profile_config,
4431
- crawl_depth: input.crawl_depth,
4432
- channel_ids: input.channel_ids,
4433
- token: input.token,
4434
- workspace_id: input.workspace_id,
4435
- since: input.since,
4436
- auth_ref: input.auth_ref
4426
+ tags: input.tags,
4427
+ namespace: input.namespace,
4428
+ options: input.options
4437
4429
  });
4438
4430
  }
4439
- 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
+ });
4440
4455
  }
4441
4456
  function renderScopedMcpConfig(project, source, client) {
4442
4457
  const serverDef = {
@@ -5067,10 +5082,10 @@ server.tool(
5067
5082
  );
5068
5083
  server.tool(
5069
5084
  "context.add_source",
5070
- "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.",
5071
5086
  {
5072
5087
  project: z.string().optional().describe("Project name or slug"),
5073
- type: z.enum(["github", "web", "pdf", "local", "slack", "video"]).default("github"),
5088
+ type: z.enum(["github", "web", "playwright", "pdf", "local", "slack", "video"]).default("github"),
5074
5089
  name: z.string().optional(),
5075
5090
  auto_index: z.boolean().optional().default(true),
5076
5091
  metadata: z.record(z.string()).optional(),
@@ -5089,6 +5104,8 @@ server.tool(
5089
5104
  path: z.string().optional(),
5090
5105
  glob: z.string().optional(),
5091
5106
  max_files: z.number().optional(),
5107
+ max_pages: z.number().optional(),
5108
+ extract_mode: z.enum(["text", "structured", "markdown"]).optional(),
5092
5109
  workspace_id: z.string().optional(),
5093
5110
  channel_ids: z.array(z.string()).optional(),
5094
5111
  since: z.string().optional(),
@@ -5127,6 +5144,8 @@ server.tool(
5127
5144
  path: input.path,
5128
5145
  glob: input.glob,
5129
5146
  max_files: input.max_files,
5147
+ max_pages: input.max_pages,
5148
+ extract_mode: input.extract_mode,
5130
5149
  workspace_id: input.workspace_id,
5131
5150
  channel_ids: input.channel_ids,
5132
5151
  since: input.since,
@@ -5165,7 +5184,7 @@ server.tool(
5165
5184
  );
5166
5185
  server.tool(
5167
5186
  "context.add_text",
5168
- "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.",
5169
5188
  {
5170
5189
  project: z.string().optional().describe("Project name or slug"),
5171
5190
  title: z.string().describe("Title for this content"),
@@ -5198,7 +5217,7 @@ server.tool(
5198
5217
  );
5199
5218
  server.tool(
5200
5219
  "context.add_document",
5201
- "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.",
5202
5221
  {
5203
5222
  project: z.string().optional().describe("Project name or slug"),
5204
5223
  source_type: z.enum(["text", "video"]).default("text"),
@@ -5303,7 +5322,7 @@ server.tool(
5303
5322
  );
5304
5323
  server.tool(
5305
5324
  "memory.ingest_conversation",
5306
- "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.",
5307
5326
  {
5308
5327
  project: z.string().optional().describe("Project name or slug"),
5309
5328
  session_id: z.string().describe("Session identifier"),
@@ -6610,7 +6629,7 @@ server.tool(
6610
6629
  {
6611
6630
  action: z.enum(["source", "workspace"]).default("source"),
6612
6631
  project: z.string().optional(),
6613
- type: z.enum(["github", "web", "pdf", "local", "slack", "video"]).optional(),
6632
+ type: z.enum(["github", "web", "playwright", "pdf", "local", "slack", "video"]).optional(),
6614
6633
  name: z.string().optional(),
6615
6634
  owner: z.string().optional(),
6616
6635
  repo: z.string().optional(),
@@ -6766,25 +6785,55 @@ server.tool(
6766
6785
  );
6767
6786
  server.tool(
6768
6787
  "learn",
6769
- "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.",
6770
6789
  {
6790
+ mode: z.enum(["conversation", "text", "source"]).describe("What kind of learning to perform"),
6771
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"),
6772
6800
  content: z.string().optional().describe("Inline text content to ingest"),
6773
- 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"),
6774
6805
  url: z.string().optional().describe("URL to learn from"),
6775
6806
  owner: z.string().optional().describe("GitHub owner"),
6776
6807
  repo: z.string().optional().describe("GitHub repository"),
6777
6808
  branch: z.string().optional(),
6809
+ paths: z.array(z.string()).optional(),
6778
6810
  path: z.string().optional().describe("Local path to learn from"),
6779
6811
  file_path: z.string().optional().describe("Single file path to learn from"),
6780
6812
  name: z.string().optional().describe("Optional source name"),
6781
- metadata: z.record(z.string()).optional(),
6782
- ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
6783
- strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
6784
- profile_config: z.record(z.any()).optional(),
6785
- max_files: z.number().optional(),
6786
- glob: z.string().optional(),
6787
- 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()
6788
6837
  },
6789
6838
  async (input) => {
6790
6839
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usewhisper/mcp-server",
3
- "version": "2.8.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",