From 30b8bba3b8975882447fe1a064082bfbd922faac Mon Sep 17 00:00:00 2001 From: Lubrsy706 Date: Fri, 15 May 2026 01:36:24 +0800 Subject: [PATCH] fix(filesystem): reject non-media read_media_file inputs --- .../__tests__/structured-content.test.ts | 31 +++++++++++++++++++ src/filesystem/index.ts | 17 +++++----- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/filesystem/__tests__/structured-content.test.ts b/src/filesystem/__tests__/structured-content.test.ts index 4b8f92b0a3..465c0fdcd2 100644 --- a/src/filesystem/__tests__/structured-content.test.ts +++ b/src/filesystem/__tests__/structured-content.test.ts @@ -25,6 +25,13 @@ describe('structuredContent schema compliance', () => { // Create test files await fs.writeFile(path.join(testDir, 'test.txt'), 'test content'); + await fs.writeFile( + path.join(testDir, 'pixel.png'), + Buffer.from( + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=', + 'base64' + ) + ); await fs.mkdir(path.join(testDir, 'subdir')); await fs.writeFile(path.join(testDir, 'subdir', 'nested.txt'), 'nested content'); @@ -155,4 +162,28 @@ describe('structuredContent schema compliance', () => { expect(Array.isArray(structuredContent.content)).toBe(false); }); }); + + describe('read_media_file', () => { + it('returns a valid MCP image content type for images', async () => { + const result = await client.callTool({ + name: 'read_media_file', + arguments: { path: path.join(testDir, 'pixel.png') } + }); + + const [content] = result.content as Array<{ type: string; mimeType?: string; data?: string }>; + expect(content.type).toBe('image'); + expect(content.mimeType).toBe('image/png'); + expect(content.data).toBeTruthy(); + }); + + it('rejects 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, 'test.txt') } + }); + + expect(result.isError).toBe(true); + expect(JSON.stringify(result.content)).toContain('Unsupported media type'); + }); + }); }); diff --git a/src/filesystem/index.ts b/src/filesystem/index.ts index 7b67e63e58..e60387a9b5 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,19 @@ server.registerTool( ".ogg": "audio/ogg", ".flac": "audio/flac", }; - const mimeType = mimeTypes[extension] || "application/octet-stream"; + const mimeType = mimeTypes[extension]; + if (!mimeType) { + throw new Error( + `Unsupported media type for ${args.path}. read_media_file only supports image and audio files.` + ); + } + const data = await readFileAsBase64Stream(validPath); 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 }; + : "audio"; + const contentItem = { type: type as 'image' | 'audio', data, mimeType }; return { content: [contentItem], structuredContent: { content: [contentItem] }