diff --git a/src/filesystem/__tests__/structured-content.test.ts b/src/filesystem/__tests__/structured-content.test.ts index 4b8f92b0a3..7d281775a1 100644 --- a/src/filesystem/__tests__/structured-content.test.ts +++ b/src/filesystem/__tests__/structured-content.test.ts @@ -25,6 +25,8 @@ describe('structuredContent schema compliance', () => { // Create test files await fs.writeFile(path.join(testDir, 'test.txt'), 'test content'); + await fs.writeFile(path.join(testDir, 'image.png'), Buffer.from('test image data')); + await fs.writeFile(path.join(testDir, 'archive.bin'), Buffer.from('test binary data')); await fs.mkdir(path.join(testDir, 'subdir')); await fs.writeFile(path.join(testDir, 'subdir', 'nested.txt'), 'nested content'); @@ -155,4 +157,41 @@ describe('structuredContent schema compliance', () => { expect(Array.isArray(structuredContent.content)).toBe(false); }); }); + + describe('read_media_file', () => { + it('should return spec-compliant image content for image files', async () => { + const result = await client.callTool({ + name: 'read_media_file', + arguments: { path: path.join(testDir, 'image.png') } + }); + + expect(result.isError).not.toBe(true); + expect(result.content).toHaveLength(1); + expect(result.content[0]).toMatchObject({ + type: 'image', + data: Buffer.from('test image data').toString('base64'), + mimeType: 'image/png' + }); + + const structuredContent = result.structuredContent as { content: unknown[] }; + expect(structuredContent.content[0]).toMatchObject({ + type: 'image', + mimeType: 'image/png' + }); + }); + + it('should reject non-media files instead of returning an invalid blob content type', async () => { + const result = await client.callTool({ + name: 'read_media_file', + arguments: { path: path.join(testDir, 'archive.bin') } + }); + + expect(result.isError).toBe(true); + expect(result.content).toHaveLength(1); + expect(result.content[0]).toMatchObject({ + type: 'text' + }); + expect(JSON.stringify(result)).not.toContain('"blob"'); + }); + }); }); diff --git a/src/filesystem/index.ts b/src/filesystem/index.ts index 7b67e63e58..22083b3fba 100644 --- a/src/filesystem/index.ts +++ b/src/filesystem/index.ts @@ -257,7 +257,7 @@ server.registerTool( }, outputSchema: { content: z.array(z.object({ - type: z.enum(["image", "audio", "blob"]), + type: z.enum(["image", "audio"]), data: z.string(), mimeType: z.string() })) @@ -280,16 +280,29 @@ server.registerTool( ".ogg": "audio/ogg", ".flac": "audio/flac", }; - const mimeType = mimeTypes[extension] || "application/octet-stream"; - const data = await readFileAsBase64Stream(validPath); + const mimeType = mimeTypes[extension]; + if (!mimeType) { + return { + isError: true, + content: [{ type: "text" as const, text: `Unsupported media type: ${extension || "unknown extension"}. read_media_file only supports image and audio files.` }] + }; + } const type = mimeType.startsWith("image/") ? "image" : mimeType.startsWith("audio/") ? "audio" - // Fallback for other binary types, not officially supported by the spec but has been used for some time - : "blob"; - const contentItem = { type: type as 'image' | 'audio' | 'blob', data, mimeType }; + : undefined; + + if (!type) { + return { + isError: true, + content: [{ type: "text" as const, text: `Unsupported media MIME type: ${mimeType}. read_media_file only supports image and audio files.` }] + }; + } + + const data = await readFileAsBase64Stream(validPath); + const contentItem = { type, data, mimeType }; return { content: [contentItem], structuredContent: { content: [contentItem] }