@usewhisper/mcp-server 2.2.0 → 2.3.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 +260 -40
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -524,9 +524,71 @@ var WhisperContext = class _WhisperContext {
524
524
  this.projectRefToId.set(p.slug, p.id);
525
525
  this.projectRefToId.set(p.name, p.id);
526
526
  }
527
+ if (this.defaultProject) {
528
+ const hasDefaultProject = this.projectCache.some(
529
+ (project) => project.id === this.defaultProject || project.slug === this.defaultProject || project.name === this.defaultProject
530
+ );
531
+ if (!hasDefaultProject) {
532
+ const resolvedDefault = await this.fetchResolvedProject(this.defaultProject);
533
+ if (resolvedDefault) {
534
+ this.projectCache = [...this.projectCache, resolvedDefault];
535
+ this.projectRefToId.set(resolvedDefault.id, resolvedDefault.id);
536
+ this.projectRefToId.set(resolvedDefault.slug, resolvedDefault.id);
537
+ this.projectRefToId.set(resolvedDefault.name, resolvedDefault.id);
538
+ }
539
+ }
540
+ }
527
541
  this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
528
542
  return this.projectCache;
529
543
  }
544
+ async fetchResolvedProject(projectRef) {
545
+ if (!projectRef) return null;
546
+ try {
547
+ const response = await this.request(`/v1/projects/resolve?project=${encodeURIComponent(projectRef)}`, { method: "GET" });
548
+ return response?.resolved || null;
549
+ } catch (error) {
550
+ if (error instanceof WhisperError && error.code === "PROJECT_NOT_FOUND") {
551
+ return null;
552
+ }
553
+ throw error;
554
+ }
555
+ }
556
+ async resolveProject(projectRef) {
557
+ const resolvedRef = this.getRequiredProject(projectRef);
558
+ const cachedProjects = await this.refreshProjectCache(false);
559
+ const cachedProject = cachedProjects.find(
560
+ (project) => project.id === resolvedRef || project.slug === resolvedRef || project.name === resolvedRef
561
+ );
562
+ if (cachedProject) {
563
+ return cachedProject;
564
+ }
565
+ const resolvedProject = await this.fetchResolvedProject(resolvedRef);
566
+ if (resolvedProject) {
567
+ this.projectRefToId.set(resolvedProject.id, resolvedProject.id);
568
+ this.projectRefToId.set(resolvedProject.slug, resolvedProject.id);
569
+ this.projectRefToId.set(resolvedProject.name, resolvedProject.id);
570
+ this.projectCache = [
571
+ ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
572
+ resolvedProject
573
+ ];
574
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
575
+ return resolvedProject;
576
+ }
577
+ if (isLikelyProjectId(resolvedRef)) {
578
+ return {
579
+ id: resolvedRef,
580
+ orgId: "",
581
+ name: resolvedRef,
582
+ slug: resolvedRef,
583
+ createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
584
+ updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
585
+ };
586
+ }
587
+ throw new WhisperError({
588
+ code: "PROJECT_NOT_FOUND",
589
+ message: `Project '${resolvedRef}' not found`
590
+ });
591
+ }
530
592
  async resolveProjectId(projectRef) {
531
593
  if (this.projectRefToId.has(projectRef)) {
532
594
  return this.projectRefToId.get(projectRef);
@@ -547,6 +609,18 @@ var WhisperContext = class _WhisperContext {
547
609
  if (isLikelyProjectId(projectRef)) {
548
610
  return projectRef;
549
611
  }
612
+ const resolvedProject = await this.fetchResolvedProject(projectRef);
613
+ if (resolvedProject) {
614
+ this.projectRefToId.set(resolvedProject.id, resolvedProject.id);
615
+ this.projectRefToId.set(resolvedProject.slug, resolvedProject.id);
616
+ this.projectRefToId.set(resolvedProject.name, resolvedProject.id);
617
+ this.projectCache = [
618
+ ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
619
+ resolvedProject
620
+ ];
621
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
622
+ return resolvedProject.id;
623
+ }
550
624
  throw new WhisperError({
551
625
  code: "PROJECT_NOT_FOUND",
552
626
  message: `Project '${projectRef}' not found`
@@ -602,6 +676,26 @@ var WhisperContext = class _WhisperContext {
602
676
  message: `Project '${projectRef}' not found`
603
677
  });
604
678
  }
679
+ shouldRetryWithResolvedProjectId(error) {
680
+ return error instanceof WhisperError && error.status === 404 && !this.isEndpointNotFoundError(error);
681
+ }
682
+ async withProjectPathFallback(projectRef, execute) {
683
+ try {
684
+ return await execute(projectRef);
685
+ } catch (error) {
686
+ if (!this.shouldRetryWithResolvedProjectId(error)) {
687
+ throw error;
688
+ }
689
+ }
690
+ const resolvedProjectId = await this.resolveProjectId(projectRef);
691
+ if (resolvedProjectId === projectRef) {
692
+ throw new WhisperError({
693
+ code: "PROJECT_NOT_FOUND",
694
+ message: `Project '${projectRef}' not found`
695
+ });
696
+ }
697
+ return execute(resolvedProjectId);
698
+ }
605
699
  classifyError(status, message) {
606
700
  if (status === 401 || /api key|unauthorized|forbidden/i.test(message)) {
607
701
  return { code: "INVALID_API_KEY", retryable: false };
@@ -717,29 +811,42 @@ var WhisperContext = class _WhisperContext {
717
811
  return projects;
718
812
  }
719
813
  async getProject(id) {
720
- const projectId = await this.resolveProjectId(id);
721
- return this.request(`/v1/projects/${projectId}`);
814
+ return this.withProjectPathFallback(
815
+ this.getRequiredProject(id),
816
+ (projectPathRef) => this.request(`/v1/projects/${encodeURIComponent(projectPathRef)}`)
817
+ );
818
+ }
819
+ async listSources(project) {
820
+ const projectRef = this.getRequiredProject(project);
821
+ return this.withProjectRefFallback(
822
+ projectRef,
823
+ (resolvedProject) => this.request(`/v1/sources?project=${encodeURIComponent(resolvedProject)}`, { method: "GET" })
824
+ );
722
825
  }
723
826
  async deleteProject(id) {
724
827
  const projectId = await this.resolveProjectId(id);
725
828
  return this.request(`/v1/projects/${projectId}`, { method: "DELETE" });
726
829
  }
727
830
  async addSource(projectId, params) {
728
- const resolvedProjectId = await this.resolveProjectId(projectId);
729
- return this.request(`/v1/projects/${resolvedProjectId}/sources`, {
730
- method: "POST",
731
- body: JSON.stringify(params)
732
- });
831
+ return this.withProjectPathFallback(
832
+ this.getRequiredProject(projectId),
833
+ (projectPathRef) => this.request(`/v1/projects/${encodeURIComponent(projectPathRef)}/sources`, {
834
+ method: "POST",
835
+ body: JSON.stringify(params)
836
+ })
837
+ );
733
838
  }
734
839
  async syncSource(sourceId) {
735
840
  return this.request(`/v1/sources/${sourceId}/sync`, { method: "POST" });
736
841
  }
737
842
  async addSourceByType(projectId, params) {
738
- const resolvedProjectId = await this.resolveProjectId(projectId);
739
- return this.request(`/v1/projects/${resolvedProjectId}/add_source`, {
740
- method: "POST",
741
- body: JSON.stringify(params)
742
- });
843
+ return this.withProjectPathFallback(
844
+ this.getRequiredProject(projectId),
845
+ (projectPathRef) => this.request(`/v1/projects/${encodeURIComponent(projectPathRef)}/add_source`, {
846
+ method: "POST",
847
+ body: JSON.stringify(params)
848
+ })
849
+ );
743
850
  }
744
851
  async getSourceStatus(sourceId) {
745
852
  return this.request(`/v1/sources/${sourceId}/status`, { method: "GET" });
@@ -801,14 +908,16 @@ var WhisperContext = class _WhisperContext {
801
908
  };
802
909
  }
803
910
  async ingest(projectId, documents) {
804
- const resolvedProjectId = await this.resolveProjectId(projectId);
805
- return this.request(`/v1/projects/${resolvedProjectId}/ingest`, {
806
- method: "POST",
807
- body: JSON.stringify({ documents })
808
- });
911
+ return this.withProjectPathFallback(
912
+ this.getRequiredProject(projectId),
913
+ (projectPathRef) => this.request(`/v1/projects/${encodeURIComponent(projectPathRef)}/ingest`, {
914
+ method: "POST",
915
+ body: JSON.stringify({ documents })
916
+ })
917
+ );
809
918
  }
810
919
  async addContext(params) {
811
- const projectId = await this.resolveProjectId(this.getRequiredProject(params.project));
920
+ const projectId = (await this.resolveProject(this.getRequiredProject(params.project))).id;
812
921
  return this.ingest(projectId, [
813
922
  {
814
923
  title: params.title || "Context",
@@ -1218,28 +1327,37 @@ var WhisperContext = class _WhisperContext {
1218
1327
  });
1219
1328
  }
1220
1329
  async createSharedContext(params) {
1221
- const project = await this.resolveProjectId(this.getRequiredProject(params.project));
1222
- return this.request("/v1/context/share", {
1223
- method: "POST",
1224
- body: JSON.stringify({ ...params, project })
1225
- });
1330
+ const projectRef = this.getRequiredProject(params.project);
1331
+ return this.withProjectRefFallback(
1332
+ projectRef,
1333
+ (project) => this.request("/v1/context/share", {
1334
+ method: "POST",
1335
+ body: JSON.stringify({ ...params, project })
1336
+ })
1337
+ );
1226
1338
  }
1227
1339
  async loadSharedContext(shareId) {
1228
1340
  return this.request(`/v1/context/shared/${shareId}`);
1229
1341
  }
1230
1342
  async resumeFromSharedContext(params) {
1231
- const project = await this.resolveProjectId(this.getRequiredProject(params.project));
1232
- return this.request("/v1/context/resume", {
1233
- method: "POST",
1234
- body: JSON.stringify({ ...params, project })
1235
- });
1343
+ const projectRef = this.getRequiredProject(params.project);
1344
+ return this.withProjectRefFallback(
1345
+ projectRef,
1346
+ (project) => this.request("/v1/context/resume", {
1347
+ method: "POST",
1348
+ body: JSON.stringify({ ...params, project })
1349
+ })
1350
+ );
1236
1351
  }
1237
1352
  async consolidateMemories(params) {
1238
- const project = await this.resolveProjectId(this.getRequiredProject(params.project));
1239
- return this.request("/v1/memory/consolidate", {
1240
- method: "POST",
1241
- body: JSON.stringify({ ...params, project })
1242
- });
1353
+ const projectRef = this.getRequiredProject(params.project);
1354
+ return this.withProjectRefFallback(
1355
+ projectRef,
1356
+ (project) => this.request("/v1/memory/consolidate", {
1357
+ method: "POST",
1358
+ body: JSON.stringify({ ...params, project })
1359
+ })
1360
+ );
1243
1361
  }
1244
1362
  async updateImportanceDecay(params) {
1245
1363
  const project = await this.resolveProjectId(this.getRequiredProject(params.project));
@@ -1622,7 +1740,17 @@ function computeChecksum(value) {
1622
1740
  }
1623
1741
  var cachedProjectRef = DEFAULT_PROJECT || void 0;
1624
1742
  async function resolveProjectRef(explicit) {
1625
- if (explicit?.trim()) return explicit.trim();
1743
+ if (explicit?.trim()) {
1744
+ const requestedRef = explicit.trim();
1745
+ try {
1746
+ const resolved = await whisper.resolveProject(requestedRef);
1747
+ cachedProjectRef = resolved.slug || resolved.name || resolved.id;
1748
+ return cachedProjectRef;
1749
+ } catch {
1750
+ cachedProjectRef = requestedRef;
1751
+ return requestedRef;
1752
+ }
1753
+ }
1626
1754
  if (cachedProjectRef) return cachedProjectRef;
1627
1755
  try {
1628
1756
  const { projects } = await whisper.listProjects();
@@ -2568,7 +2696,19 @@ server.tool(
2568
2696
  async () => {
2569
2697
  try {
2570
2698
  const { projects } = await whisper.listProjects();
2571
- const text = projects.length === 0 ? "No projects found." : projects.map((p) => `- ${p.name} (${p.slug})${p.description ? `: ${p.description}` : ""}`).join("\n");
2699
+ const visibleProjects = [...projects];
2700
+ if (cachedProjectRef) {
2701
+ const hasCachedProject = visibleProjects.some(
2702
+ (project) => project.id === cachedProjectRef || project.slug === cachedProjectRef || project.name === cachedProjectRef
2703
+ );
2704
+ if (!hasCachedProject) {
2705
+ try {
2706
+ visibleProjects.push(await whisper.resolveProject(cachedProjectRef));
2707
+ } catch {
2708
+ }
2709
+ }
2710
+ }
2711
+ const text = visibleProjects.length === 0 ? "No projects found." : visibleProjects.map((p) => `- ${p.name} (${p.slug})${p.description ? `: ${p.description}` : ""}`).join("\n");
2572
2712
  return { content: [{ type: "text", text }] };
2573
2713
  } catch (error) {
2574
2714
  return { content: [{ type: "text", text: `Error: ${error.message}` }] };
@@ -2581,8 +2721,8 @@ server.tool(
2581
2721
  { project: z.string().optional().describe("Project name or slug") },
2582
2722
  async ({ project }) => {
2583
2723
  try {
2584
- const projectData = await whisper.getProject(project || DEFAULT_PROJECT);
2585
- const srcs = projectData.sources || [];
2724
+ const sourceData = await whisper.listSources(project || DEFAULT_PROJECT);
2725
+ const srcs = sourceData.sources || [];
2586
2726
  const text = srcs.length === 0 ? "No sources connected." : srcs.map((s) => `- ${s.name} (${s.connectorType}) \u2014 ${s.status}`).join("\n");
2587
2727
  return { content: [{ type: "text", text }] };
2588
2728
  } catch (error) {
@@ -3134,7 +3274,25 @@ server.tool(
3134
3274
  top_k: 25,
3135
3275
  include_relations: false
3136
3276
  });
3137
- const memoryIds = (search.results || []).map((r) => String(r?.memory?.id || "")).filter(Boolean);
3277
+ const normalizedQuery = String(target.query || "").trim().toLowerCase();
3278
+ const exactMatches = (search.results || []).filter((r) => {
3279
+ const memory = r?.memory || r;
3280
+ const content = String(memory?.content || "").trim().toLowerCase();
3281
+ const memoryId = String(memory?.id || "").trim().toLowerCase();
3282
+ const metadata = memory?.metadata || {};
3283
+ const normalizedContent = String(metadata?.normalized_content || "").trim().toLowerCase();
3284
+ const canonicalContent = String(metadata?.canonical_content || "").trim().toLowerCase();
3285
+ return memoryId === normalizedQuery || content === normalizedQuery || normalizedContent === normalizedQuery || canonicalContent === normalizedQuery;
3286
+ });
3287
+ const memoryIds = exactMatches.map((r) => String(r?.memory?.id || "")).filter(Boolean);
3288
+ if (memoryIds.length === 0) {
3289
+ const payload2 = {
3290
+ status: "completed",
3291
+ affected_ids: affectedIds,
3292
+ warning: "Query did not resolve to an exact memory match. No memories were changed."
3293
+ };
3294
+ return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
3295
+ }
3138
3296
  for (const id of memoryIds) {
3139
3297
  await applyToMemory(id);
3140
3298
  }
@@ -3208,6 +3366,7 @@ server.tool(
3208
3366
  const bundleWithoutChecksum = {
3209
3367
  bundle_version: "1.0",
3210
3368
  workspace_id: workspaceId,
3369
+ ...resolvedProject ? { project: resolvedProject } : {},
3211
3370
  exported_at: now,
3212
3371
  contents
3213
3372
  };
@@ -3227,6 +3386,7 @@ server.tool(
3227
3386
  bundle: z.object({
3228
3387
  bundle_version: z.string(),
3229
3388
  workspace_id: z.string(),
3389
+ project: z.string().optional(),
3230
3390
  exported_at: z.string(),
3231
3391
  contents: z.object({
3232
3392
  memories: z.array(z.any()).default([]),
@@ -3250,8 +3410,10 @@ server.tool(
3250
3410
  if (dedupe_strategy === "id") return existing.some((e) => e.id === incoming.id);
3251
3411
  const norm = incoming.content.toLowerCase().trim();
3252
3412
  return existing.some((e) => e.content.toLowerCase().trim() === norm);
3413
+ }, normalizeString2 = function(value) {
3414
+ return String(value || "").trim().toLowerCase();
3253
3415
  };
3254
- var dedupe = dedupe2;
3416
+ var dedupe = dedupe2, normalizeString = normalizeString2;
3255
3417
  const workspaceId = getWorkspaceId(workspace_id || bundle.workspace_id);
3256
3418
  const state = loadState();
3257
3419
  const workspace = getWorkspaceState(state, workspaceId);
@@ -3277,6 +3439,30 @@ server.tool(
3277
3439
  session_summaries: 0,
3278
3440
  documents: 0
3279
3441
  };
3442
+ const resolvedProject = await resolveProjectRef(bundle.project);
3443
+ async function shouldSkipImportedMemory(memory) {
3444
+ if (dedupe_strategy === "none" || !resolvedProject) return false;
3445
+ const exactId = normalizeString2(memory?.id);
3446
+ const exactContent = normalizeString2(memory?.content);
3447
+ if (!exactId && !exactContent) return false;
3448
+ const existing = await whisper.searchMemoriesSOTA({
3449
+ project: resolvedProject,
3450
+ query: exactId || exactContent,
3451
+ top_k: 10,
3452
+ include_relations: false
3453
+ });
3454
+ return (existing.results || []).some((result) => {
3455
+ const candidate = result?.memory || result;
3456
+ const candidateId = normalizeString2(candidate?.id);
3457
+ const candidateContent = normalizeString2(candidate?.content);
3458
+ const candidateMetadata = candidate?.metadata || {};
3459
+ const importedOriginalId = normalizeString2(candidateMetadata?.bundle_original_id);
3460
+ if (dedupe_strategy === "id") {
3461
+ return Boolean(exactId) && (candidateId === exactId || importedOriginalId === exactId);
3462
+ }
3463
+ return Boolean(exactContent) && candidateContent === exactContent;
3464
+ });
3465
+ }
3280
3466
  if (mode === "replace") {
3281
3467
  workspace.entities = [];
3282
3468
  workspace.decisions = [];
@@ -3286,6 +3472,40 @@ server.tool(
3286
3472
  workspace.documents = [];
3287
3473
  workspace.events = [];
3288
3474
  }
3475
+ if (!checksumVerified) {
3476
+ const payload2 = {
3477
+ imported_counts: importedCounts,
3478
+ skipped_counts: skippedCounts,
3479
+ conflicts,
3480
+ checksum_verified: checksumVerified
3481
+ };
3482
+ return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
3483
+ }
3484
+ for (const memory of bundle.contents.memories || []) {
3485
+ if (!resolvedProject) {
3486
+ skippedCounts.memories += 1;
3487
+ continue;
3488
+ }
3489
+ if (await shouldSkipImportedMemory(memory)) {
3490
+ skippedCounts.memories += 1;
3491
+ continue;
3492
+ }
3493
+ await whisper.addMemory({
3494
+ project: resolvedProject,
3495
+ content: String(memory?.content || ""),
3496
+ memory_type: String(memory?.type || memory?.memoryType || "factual"),
3497
+ user_id: memory?.user_id || memory?.userId,
3498
+ session_id: memory?.session_id || memory?.sessionId,
3499
+ importance: typeof memory?.importance === "number" ? memory.importance : void 0,
3500
+ metadata: {
3501
+ ...memory?.metadata && typeof memory.metadata === "object" ? memory.metadata : {},
3502
+ bundle_original_id: memory?.id || void 0,
3503
+ imported_from_bundle: true,
3504
+ bundle_exported_at: bundle.exported_at
3505
+ }
3506
+ });
3507
+ importedCounts.memories += 1;
3508
+ }
3289
3509
  const keys = ["entities", "decisions", "failures", "annotations", "session_summaries", "documents"];
3290
3510
  for (const key of keys) {
3291
3511
  const sourceItems = bundle.contents[key];
@@ -3312,7 +3532,7 @@ server.tool(
3312
3532
  const payload = {
3313
3533
  imported_counts: importedCounts,
3314
3534
  skipped_counts: skippedCounts,
3315
- conflicts,
3535
+ conflicts: resolvedProject ? conflicts : [...conflicts, "project_unresolved"],
3316
3536
  checksum_verified: checksumVerified
3317
3537
  };
3318
3538
  return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usewhisper/mcp-server",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "whisperContractVersion": "2026.03.09",
5
5
  "scripts": {
6
6
  "build": "tsup ../src/mcp/server.ts --format esm --out-dir dist",