diff --git a/src/tools/file-operations.ts b/src/tools/file-operations.ts index c24c45e..e780482 100644 --- a/src/tools/file-operations.ts +++ b/src/tools/file-operations.ts @@ -64,6 +64,10 @@ function getMimeType(filePath: string): string { return MIME_TYPES[ext] ?? 'application/octet-stream'; } +function isImageMimeType(mimeType: string): boolean { + return mimeType.startsWith('image/'); +} + /** * Register all file operation tools */ @@ -175,7 +179,7 @@ export async function registerFileOperationTools(server: ObsidianMCPServer): Pro // T031: Read note tool server.registerTool( 'obsidian_read_note', - 'Read the content of a note from the Obsidian vault. Specify either the note name (file) or full path (path). For large files (e.g. PDFs), use max_chars and offset to read in chunks and avoid exceeding context limits. Binary files (ZIP, images, PDFs, etc.) are automatically detected and returned as an MCP embedded resource with a uri, mimeType, and base64-encoded blob field instead of plain text.', + 'Read the content of a note from the Obsidian vault. Specify either the note name (file) or full path (path). For large files (e.g. PDFs), use max_chars and offset to read in chunks and avoid exceeding context limits. Binary files are automatically detected: images (PNG, JPG, GIF, WEBP, SVG) are returned as MCP image content (type: "image") with base64-encoded data and mimeType; all other binary files (ZIP, PDF, DOCX, etc.) are returned as MCP embedded resource content (type: "resource") with uri, mimeType, and base64-encoded blob.', { type: 'object', properties: { @@ -198,7 +202,7 @@ export async function registerFileOperationTools(server: ObsidianMCPServer): Pro }, }, createToolHandler( - 'Read the content of a note. Binary files are returned as an MCP embedded resource (type: "resource") with uri, mimeType, and a base64-encoded blob field.', + 'Read the content of a note. Images (PNG, JPG, GIF, WEBP, SVG) are returned as MCP image content (type: "image") with base64-encoded data. Other binary files are returned as MCP embedded resource content (type: "resource") with uri, mimeType, and base64-encoded blob.', { type: 'object', properties: { @@ -233,12 +237,28 @@ export async function registerFileOperationTools(server: ObsidianMCPServer): Pro const result = await executeObsidianCommandBinary('read', cmdArgs); handleCLIResult(result, { operation: 'read_note', identifier: sanitized.file || sanitized.path }); - // Detect binary content from the raw buffer and return as MCP resource + // Detect binary content from the raw buffer and return as spec-appropriate MCP content if (result.stdoutBuffer && isBinaryContent(result.stdoutBuffer)) { const identifier = sanitized.file || sanitized.path as string; + const mimeType = getMimeType(identifier); + const base64 = result.stdoutBuffer.toString('base64'); + + // Images use the MCP image content type per spec + if (isImageMimeType(mimeType)) { + return { + content: [ + { + type: 'image' as const, + data: base64, + mimeType, + }, + ], + }; + } + + // All other binary files use the embedded resource content type per spec const vaultName = process.env.OBSIDIAN_VAULT ?? 'vault'; const uri = `obsidian://${encodeURIComponent(vaultName)}/${encodeURIComponent(identifier)}`; - const mimeType = getMimeType(identifier); return { content: [ { @@ -246,7 +266,7 @@ export async function registerFileOperationTools(server: ObsidianMCPServer): Pro resource: { uri, mimeType, - blob: result.stdoutBuffer.toString('base64'), + blob: base64, }, }, ], diff --git a/src/utils/types.ts b/src/utils/types.ts index 4258b3a..46bfa15 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -18,6 +18,8 @@ export interface ToolInput { export interface ToolOutput { content: Array< | { type: 'text'; text: string } + | { type: 'image'; data: string; mimeType: string } + | { type: 'audio'; data: string; mimeType: string } | { type: 'resource'; resource: { uri: string; mimeType?: string; blob: string } } >; isError?: false;