exovault-mcp-server 1.3.0 → 1.4.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.
@@ -69,7 +69,7 @@ export declare class GatewayClient {
69
69
  graphSeeds?: number;
70
70
  graphMaxHops?: number;
71
71
  }): Promise<string>;
72
- readMemories(memoryIds: string[]): Promise<string>;
72
+ readMemories(memoryIds: string[], includeMediaContent?: boolean): Promise<string>;
73
73
  updateMemory(params: {
74
74
  memoryId: string;
75
75
  content?: string;
@@ -136,8 +136,8 @@ export declare class GatewayClient {
136
136
  limit?: number;
137
137
  folderId?: string;
138
138
  }): Promise<string>;
139
- readNote(noteId: string): Promise<string>;
140
- readNotes(noteIds: string[]): Promise<string>;
139
+ readNote(noteId: string, includeMediaContent?: boolean): Promise<string>;
140
+ readNotes(noteIds: string[], includeMediaContent?: boolean): Promise<string>;
141
141
  searchNotes(params: {
142
142
  query: string;
143
143
  topK?: number;
@@ -344,5 +344,21 @@ export declare class GatewayClient {
344
344
  embeddingStatus: string;
345
345
  base64Content: string;
346
346
  }>;
347
+ viewMedia(params: {
348
+ attachmentId: string;
349
+ }): Promise<{
350
+ attachmentId: string;
351
+ memoryId: string | null;
352
+ noteId: string | null;
353
+ modality: string;
354
+ mimeType: string;
355
+ fileName: string | null;
356
+ fileSizeBytes: number;
357
+ embeddingStatus: string;
358
+ extractionStatus: string;
359
+ extractedText: string | null;
360
+ base64Content: string | null;
361
+ contentNote: string | null;
362
+ }>;
347
363
  deleteMedia(attachmentId: string, vaultId?: string): Promise<string>;
348
364
  }
@@ -102,8 +102,8 @@ export class GatewayClient {
102
102
  const result = await this.request("POST", "/api/agent/search-memories", params);
103
103
  return JSON.stringify(result);
104
104
  }
105
- async readMemories(memoryIds) {
106
- const result = await this.request("POST", "/api/agent/read-memories", { memoryIds });
105
+ async readMemories(memoryIds, includeMediaContent) {
106
+ const result = await this.request("POST", "/api/agent/read-memories", { memoryIds, includeMediaContent });
107
107
  return JSON.stringify(result);
108
108
  }
109
109
  async updateMemory(params) {
@@ -145,12 +145,12 @@ export class GatewayClient {
145
145
  const result = await this.request("POST", "/api/agent/list-notes", params);
146
146
  return JSON.stringify(result);
147
147
  }
148
- async readNote(noteId) {
149
- const result = await this.request("POST", "/api/agent/read-note", { noteId });
148
+ async readNote(noteId, includeMediaContent) {
149
+ const result = await this.request("POST", "/api/agent/read-note", { noteId, includeMediaContent });
150
150
  return JSON.stringify(result);
151
151
  }
152
- async readNotes(noteIds) {
153
- const result = await this.request("POST", "/api/agent/read-notes", { noteIds });
152
+ async readNotes(noteIds, includeMediaContent) {
153
+ const result = await this.request("POST", "/api/agent/read-notes", { noteIds, includeMediaContent });
154
154
  return JSON.stringify(result);
155
155
  }
156
156
  async searchNotes(params) {
@@ -323,6 +323,9 @@ export class GatewayClient {
323
323
  async downloadMedia(params) {
324
324
  return this.request("POST", "/api/agent/download-media", params);
325
325
  }
326
+ async viewMedia(params) {
327
+ return this.request("POST", "/api/agent/view-media", params);
328
+ }
326
329
  async deleteMedia(attachmentId, vaultId) {
327
330
  const body = { attachmentId };
328
331
  if (vaultId)
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ import { exploreGraph } from "./tools/explore-graph.js";
30
30
  import { recall } from "./tools/recall.js";
31
31
  import { search } from "./tools/search.js";
32
32
  import { sendMessage, ackMessage, readMessages } from "./tools/agent-messages.js";
33
+ import { viewMedia } from "./tools/view-media.js";
33
34
  // Task tools are thin wrappers around memory tools — no separate agent-tasks import needed
34
35
  import { resolveVaultId } from "./tools/resolve-vault-id.js";
35
36
  import { GatewayClient } from "./gateway-client.js";
@@ -430,10 +431,11 @@ async function main() {
430
431
  description: "Read the full decrypted content of a note, including title, tags, and vault name",
431
432
  inputSchema: {
432
433
  noteId: s(z.string().uuid().describe("The note ID to read")),
434
+ includeMediaContent: s(z.boolean().optional().describe("When true, includes base64 image content for images under 500KB. Gateway mode only. Default: false.")),
433
435
  },
434
436
  }, auto.wrap(wrapToolHandler(async (args) => {
435
- const { noteId } = args;
436
- return gw ? await gw.readNote(noteId) : await readNote(ctx, noteId);
437
+ const { noteId, includeMediaContent } = args;
438
+ return gw ? await gw.readNote(noteId, includeMediaContent) : await readNote(ctx, noteId, includeMediaContent);
437
439
  })));
438
440
  // ─── search_notes ─────────────────────────────────────────────────────────
439
441
  server.registerTool("search_notes", {
@@ -548,10 +550,11 @@ async function main() {
548
550
  description: "Read the full decrypted content of multiple notes at once. Returns all notes with their titles, content, tags, and vault names. Reports any IDs that were not found.",
549
551
  inputSchema: {
550
552
  noteIds: s(z.array(z.string().uuid()).min(1).max(20).describe("Array of note IDs to read (max 20)")),
553
+ includeMediaContent: s(z.boolean().optional().describe("When true, includes base64 image content for images under 500KB. Gateway mode only. Default: false.")),
551
554
  },
552
555
  }, auto.wrap(wrapToolHandler(async (args) => {
553
- const { noteIds } = args;
554
- return gw ? await gw.readNotes(noteIds) : await readNotes(ctx, noteIds);
556
+ const { noteIds, includeMediaContent } = args;
557
+ return gw ? await gw.readNotes(noteIds, includeMediaContent) : await readNotes(ctx, noteIds, includeMediaContent);
555
558
  })));
556
559
  // ─── write_memory ───────────────────────────────────────────────────────────
557
560
  server.registerTool("write_memory", {
@@ -646,10 +649,11 @@ async function main() {
646
649
  description: "Read and decrypt full memory entries by IDs.",
647
650
  inputSchema: {
648
651
  memoryIds: s(z.array(z.string().uuid()).min(1).max(50).describe("Array of memory IDs to read")),
652
+ includeMediaContent: s(z.boolean().optional().describe("When true, includes base64 image content for images under 500KB. Gateway mode only. Default: false.")),
649
653
  },
650
654
  }, auto.wrap(wrapToolHandler(async (args) => {
651
- const { memoryIds } = args;
652
- return gw ? await gw.readMemories(memoryIds) : await readMemories(ctx, memoryIds);
655
+ const { memoryIds, includeMediaContent } = args;
656
+ return gw ? await gw.readMemories(memoryIds, includeMediaContent) : await readMemories(ctx, memoryIds, includeMediaContent);
653
657
  })));
654
658
  // ─── archive_memory ─────────────────────────────────────────────────────────
655
659
  server.registerTool("archive_memory", {
@@ -1260,6 +1264,23 @@ async function main() {
1260
1264
  const result = await gw.downloadMedia({ attachmentId });
1261
1265
  return JSON.stringify(result);
1262
1266
  })));
1267
+ // ─── view_media ─────────────────────────────────────────────────────────
1268
+ server.registerTool("view_media", {
1269
+ description: "View a media attachment intelligently. Returns extracted text for all modalities, " +
1270
+ "plus base64 image content for images under 500KB. For non-image files (PDFs, audio, " +
1271
+ "video), returns the extracted text/transcription instead of raw bytes. " +
1272
+ "Use download_media if you need the actual file bytes regardless of size.",
1273
+ inputSchema: {
1274
+ attachmentId: s(z.string().uuid().describe("Media attachment ID to view")),
1275
+ },
1276
+ }, auto.wrap(wrapToolHandler(async (args) => {
1277
+ const { attachmentId } = args;
1278
+ if (gw) {
1279
+ const result = await gw.viewMedia({ attachmentId });
1280
+ return JSON.stringify(result);
1281
+ }
1282
+ return viewMedia(ctx, attachmentId);
1283
+ })));
1263
1284
  // ─── delete_media ───────────────────────────────────────────────────────
1264
1285
  server.registerTool("delete_media", {
1265
1286
  description: "Delete a media attachment and its embedding. Permanently removes the file from storage.",
@@ -1,2 +1,2 @@
1
1
  import type { McpContext } from "../auth.js";
2
- export declare function readMemories(ctx: McpContext, memoryIds: string[]): Promise<string>;
2
+ export declare function readMemories(ctx: McpContext, memoryIds: string[], _includeMediaContent?: boolean): Promise<string>;
@@ -2,7 +2,7 @@ import { getMemoriesByIds, touchMemories, getAttachmentsForMemories, formatAttac
2
2
  import { decrypt } from "../crypto.js";
3
3
  import { logMcpUsageEvent } from "../usage.js";
4
4
  import { decryptMemoryFields } from "./decrypt-helpers.js";
5
- export async function readMemories(ctx, memoryIds) {
5
+ export async function readMemories(ctx, memoryIds, _includeMediaContent) {
6
6
  const memories = await getMemoriesByIds(ctx.supabase, ctx.userId, memoryIds);
7
7
  const foundIds = new Set(memories.map((m) => m.id));
8
8
  const notFound = memoryIds.filter((id) => !foundIds.has(id));
@@ -1,2 +1,2 @@
1
1
  import type { McpContext } from "../auth.js";
2
- export declare function readNote(ctx: McpContext, noteId: string): Promise<string>;
2
+ export declare function readNote(ctx: McpContext, noteId: string, _includeMediaContent?: boolean): Promise<string>;
@@ -2,7 +2,7 @@ import { getNote, getVault, getAttachmentsForNotes, formatAttachmentWithExtracti
2
2
  import { decrypt } from "../crypto.js";
3
3
  import { logMcpUsageEvent } from "../usage.js";
4
4
  import { decryptNoteFields } from "./decrypt-helpers.js";
5
- export async function readNote(ctx, noteId) {
5
+ export async function readNote(ctx, noteId, _includeMediaContent) {
6
6
  const note = await getNote(ctx.supabase, ctx.userId, noteId);
7
7
  if (!note) {
8
8
  return JSON.stringify({ error: "Note not found" });
@@ -3,4 +3,4 @@ import type { McpContext } from "../auth.js";
3
3
  * Batch-reads multiple notes by ID. Returns full decrypted content for each,
4
4
  * plus a list of any IDs that were not found.
5
5
  */
6
- export declare function readNotes(ctx: McpContext, noteIds: string[]): Promise<string>;
6
+ export declare function readNotes(ctx: McpContext, noteIds: string[], _includeMediaContent?: boolean): Promise<string>;
@@ -5,7 +5,7 @@ import { decryptNoteFields } from "./decrypt-helpers.js";
5
5
  * Batch-reads multiple notes by ID. Returns full decrypted content for each,
6
6
  * plus a list of any IDs that were not found.
7
7
  */
8
- export async function readNotes(ctx, noteIds) {
8
+ export async function readNotes(ctx, noteIds, _includeMediaContent) {
9
9
  const notes = await getNotesByIds(ctx.supabase, ctx.userId, noteIds);
10
10
  // Build a set of found IDs to report missing ones
11
11
  const foundIds = new Set(notes.map((n) => n.id));
@@ -0,0 +1,6 @@
1
+ import type { McpContext } from "../auth.js";
2
+ /**
3
+ * View a media attachment intelligently.
4
+ * Direct mode: returns extracted text only (no storage access).
5
+ */
6
+ export declare function viewMedia(ctx: McpContext, attachmentId: string): Promise<string>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * View a media attachment intelligently.
3
+ * Direct mode: returns extracted text only (no storage access).
4
+ */
5
+ export async function viewMedia(ctx, attachmentId) {
6
+ const { data, error } = await ctx.supabase
7
+ .from("media_attachments")
8
+ .select("id, memory_id, note_id, vault_id, modality, mime_type, file_name, file_size_bytes, embedding_status, extraction_status, extracted_text, extracted_text_iv")
9
+ .eq("id", attachmentId)
10
+ .eq("user_id", ctx.userId)
11
+ .maybeSingle();
12
+ if (error)
13
+ return JSON.stringify({ error: "Failed to fetch attachment" });
14
+ if (!data)
15
+ return JSON.stringify({ error: "Attachment not found" });
16
+ let extractedText = null;
17
+ if (data.extracted_text && data.extracted_text_iv && ctx.masterKey) {
18
+ const { decrypt } = await import("../crypto.js");
19
+ try {
20
+ extractedText = await decrypt(data.extracted_text, data.extracted_text_iv, ctx.masterKey);
21
+ }
22
+ catch {
23
+ extractedText = null;
24
+ }
25
+ }
26
+ let contentNote = null;
27
+ if (data.modality === "image") {
28
+ contentNote = "base64 unavailable in direct mode — use gateway mode or download_media";
29
+ }
30
+ else {
31
+ const label = data.modality.charAt(0).toUpperCase() + data.modality.slice(1);
32
+ const verb = (data.modality === "audio" || data.modality === "video") ? "transcription" : "extracted text";
33
+ contentNote = `${label} — returning ${verb}. Use download_media for raw bytes.`;
34
+ }
35
+ if (!extractedText && data.extraction_status !== "completed") {
36
+ const note = `Extraction ${data.extraction_status} — no text content available yet.`;
37
+ contentNote = contentNote ? `${contentNote} ${note}` : note;
38
+ }
39
+ return JSON.stringify({
40
+ attachmentId: data.id,
41
+ memoryId: data.memory_id,
42
+ noteId: data.note_id,
43
+ modality: data.modality,
44
+ mimeType: data.mime_type,
45
+ fileName: data.file_name,
46
+ fileSizeBytes: data.file_size_bytes,
47
+ embeddingStatus: data.embedding_status,
48
+ extractionStatus: data.extraction_status,
49
+ extractedText,
50
+ base64Content: null,
51
+ contentNote,
52
+ });
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exovault-mcp-server",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "description": "MCP server for ExoVault — read, search, and manage encrypted notes from Claude Code",
6
6
  "main": "dist/index.js",