Skip to main content
Glama

vulcan-file-ops

attach-image.test.ts17.3 kB
import { describe, test, expect, beforeAll, afterAll } from "@jest/globals"; import { promises as fs } from "fs"; import path from "path"; import os from "os"; import { handleReadTool } from "../tools/read-tools.js"; import { setAllowedDirectories, getAllowedDirectories } from "../utils/lib.js"; const TEST_FIXTURES_DIR = path.join(__dirname, "fixtures"); const TEST_WORKSPACE = path.join(os.tmpdir(), `vulcan-test-attach-img-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`); const FIXTURES_DIR = path.join(TEST_WORKSPACE, "fixtures"); // Test image files - now in src/tests/fixtures const TEST_IMAGE_1 = path.join( TEST_FIXTURES_DIR, "pexels-stephen-alicia-1315397-3063411.jpg" ); const TEST_IMAGE_2 = path.join(TEST_FIXTURES_DIR, "pexels-stywo-1054218.jpg"); async function setupTestEnvironment() { await fs.mkdir(FIXTURES_DIR, { recursive: true }); // Register test directories const currentDirs = getAllowedDirectories(); setAllowedDirectories([...currentDirs, TEST_FIXTURES_DIR, TEST_WORKSPACE]); } async function cleanupTestEnvironment() { try { await fs.rm(TEST_WORKSPACE, { recursive: true, force: true }); } catch (error) { // Ignore cleanup errors } } describe("attach_image tool", () => { beforeAll(async () => { await setupTestEnvironment(); }); afterAll(async () => { await cleanupTestEnvironment(); }); describe("successful image attachment", () => { test("attaches JPEG image in correct MCP format", async () => { const result = await handleReadTool("attach_image", { path: TEST_IMAGE_1, }); // Verify response structure expect(result.content).toBeDefined(); expect(Array.isArray(result.content)).toBe(true); expect(result.content.length).toBe(1); const content = result.content[0] as any; // Verify MCP format (type, data, mimeType) expect(content.type).toBe("image"); expect(content.data).toBeTruthy(); expect(typeof content.data).toBe("string"); expect(content.mimeType).toBe("image/jpeg"); // Old source wrapper should NOT exist expect(content.source).toBeUndefined(); // Verify base64 is valid expect(() => Buffer.from(content.data, "base64")).not.toThrow(); // Verify base64 data is substantial (not empty/corrupted) const decoded = Buffer.from(content.data, "base64"); expect(decoded.length).toBeGreaterThan(1000); // Real images should be >1KB }); test("attaches second JPEG image successfully", async () => { const result = await handleReadTool("attach_image", { path: TEST_IMAGE_2, }); expect(result.content).toBeDefined(); expect((result.content[0] as any).type).toBe("image"); expect((result.content[0] as any).mimeType).toBe("image/jpeg"); expect((result.content[0] as any).data).toBeTruthy(); // Verify different images have different base64 data const result1 = await handleReadTool("attach_image", { path: TEST_IMAGE_1, }); expect((result.content[0] as any).data).not.toBe( (result1.content[0] as any).data ); }); test("handles .jpg extension (alternative JPEG)", async () => { // Create a copy with .jpg extension const jpgPath = path.join(FIXTURES_DIR, "test-image.jpg"); await fs.copyFile(TEST_IMAGE_1, jpgPath); const result = await handleReadTool("attach_image", { path: jpgPath, }); expect((result.content[0] as any).mimeType).toBe("image/jpeg"); expect((result.content[0] as any).data).toBeTruthy(); }); test("returns no 'data' or 'mimeType' at root level (old format)", async () => { const result = await handleReadTool("attach_image", { path: TEST_IMAGE_1, }); const content = result.content[0] as any; // Correct MCP format has data and mimeType at root level expect(content.data).toBeDefined(); expect(content.mimeType).toBeDefined(); expect(content.type).toBe("image"); // Old source wrapper should NOT exist expect(content.source).toBeUndefined(); }); }); describe("supported image formats", () => { test("supports PNG format", async () => { // Create a minimal 1x1 PNG const pngPath = path.join(FIXTURES_DIR, "test.png"); const pngData = Buffer.from( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "base64" ); await fs.writeFile(pngPath, pngData); const result = await handleReadTool("attach_image", { path: pngPath, }); expect((result.content[0] as any).mimeType).toBe("image/png"); }); test("supports GIF format", async () => { // Create a minimal 1x1 GIF const gifPath = path.join(FIXTURES_DIR, "test.gif"); const gifData = Buffer.from( "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", "base64" ); await fs.writeFile(gifPath, gifData); const result = await handleReadTool("attach_image", { path: gifPath, }); expect((result.content[0] as any).mimeType).toBe("image/gif"); }); test("supports WebP format", async () => { const webpPath = path.join(FIXTURES_DIR, "test.webp"); // Minimal WebP file (1x1 pixel) const webpData = Buffer.from( "UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=", "base64" ); await fs.writeFile(webpPath, webpData); const result = await handleReadTool("attach_image", { path: webpPath, }); expect((result.content[0] as any).mimeType).toBe("image/webp"); }); test("supports BMP format", async () => { const bmpPath = path.join(FIXTURES_DIR, "test.bmp"); // Minimal BMP file header + 1x1 pixel const bmpData = Buffer.from( "Qk1+AAAAAAAAAHoAAAAMAAAAAAEAAQABACAAACQA", "base64" ); await fs.writeFile(bmpPath, bmpData); const result = await handleReadTool("attach_image", { path: bmpPath, }); expect((result.content[0] as any).mimeType).toBe("image/bmp"); }); test("supports SVG format", async () => { const svgPath = path.join(FIXTURES_DIR, "test.svg"); const svgData = '<svg width="1" height="1"><rect width="1" height="1" fill="red"/></svg>'; await fs.writeFile(svgPath, svgData); const result = await handleReadTool("attach_image", { path: svgPath, }); expect((result.content[0] as any).mimeType).toBe("image/svg+xml"); }); }); describe("error handling", () => { test("rejects unsupported audio formats (MP3)", async () => { const mp3Path = path.join(FIXTURES_DIR, "test.mp3"); await fs.writeFile(mp3Path, Buffer.from("fake audio data")); await expect( handleReadTool("attach_image", { path: mp3Path }) ).rejects.toThrow("Unsupported image format: .mp3"); await expect( handleReadTool("attach_image", { path: mp3Path }) ).rejects.toThrow("Supported formats: PNG, JPEG, GIF, WebP, BMP, SVG"); }); test("rejects unsupported audio formats (WAV)", async () => { const wavPath = path.join(FIXTURES_DIR, "test.wav"); await fs.writeFile(wavPath, Buffer.from("fake audio data")); await expect( handleReadTool("attach_image", { path: wavPath }) ).rejects.toThrow("Unsupported image format: .wav"); }); test("rejects unsupported audio formats (OGG)", async () => { const oggPath = path.join(FIXTURES_DIR, "test.ogg"); await fs.writeFile(oggPath, Buffer.from("fake audio data")); await expect( handleReadTool("attach_image", { path: oggPath }) ).rejects.toThrow("Unsupported image format: .ogg"); }); test("rejects unsupported audio formats (FLAC)", async () => { const flacPath = path.join(FIXTURES_DIR, "test.flac"); await fs.writeFile(flacPath, Buffer.from("fake audio data")); await expect( handleReadTool("attach_image", { path: flacPath }) ).rejects.toThrow("Unsupported image format: .flac"); }); test("rejects unsupported file formats (PDF)", async () => { const pdfPath = path.join(TEST_FIXTURES_DIR, "sample.pdf"); await expect( handleReadTool("attach_image", { path: pdfPath }) ).rejects.toThrow("Unsupported image format: .pdf"); }); test("rejects unsupported file formats (TXT)", async () => { const txtPath = path.join(FIXTURES_DIR, "test.txt"); await fs.writeFile(txtPath, "plain text"); await expect( handleReadTool("attach_image", { path: txtPath }) ).rejects.toThrow("Unsupported image format: .txt"); }); test("rejects paths outside allowed directories", async () => { const outsidePath = path.join("/", "tmp", "image.png"); await expect( handleReadTool("attach_image", { path: outsidePath }) ).rejects.toThrow(); }); test("handles missing files gracefully", async () => { const nonExistentPath = path.join(FIXTURES_DIR, "nonexistent.png"); await expect( handleReadTool("attach_image", { path: nonExistentPath }) ).rejects.toThrow(); }); test("validates arguments schema", async () => { await expect(handleReadTool("attach_image", {})).rejects.toThrow( "Invalid arguments for attach_image" ); await expect( handleReadTool("attach_image", { path: 123 }) ).rejects.toThrow("Invalid arguments for attach_image"); await expect( handleReadTool("attach_image", { wrong_field: "test.png" }) ).rejects.toThrow("Invalid arguments for attach_image"); }); test("handles corrupted image files", async () => { const corruptedPath = path.join(FIXTURES_DIR, "corrupted.png"); await fs.writeFile(corruptedPath, "not a real PNG file"); // Should still return the data (MCP client will handle validation) const result = await handleReadTool("attach_image", { path: corruptedPath, }); expect((result.content[0] as any).mimeType).toBe("image/png"); expect((result.content[0] as any).data).toBeTruthy(); }); }); describe("case sensitivity", () => { test("handles uppercase extensions (PNG)", async () => { const upperPath = path.join(FIXTURES_DIR, "test.PNG"); await fs.copyFile(TEST_IMAGE_1, upperPath); const result = await handleReadTool("attach_image", { path: upperPath, }); expect((result.content[0] as any).mimeType).toBe("image/png"); }); test("handles uppercase extensions (JPG)", async () => { const upperPath = path.join(FIXTURES_DIR, "test.JPG"); await fs.copyFile(TEST_IMAGE_1, upperPath); const result = await handleReadTool("attach_image", { path: upperPath, }); expect((result.content[0] as any).mimeType).toBe("image/jpeg"); }); test("handles mixed case extensions (JpEg)", async () => { const mixedPath = path.join(FIXTURES_DIR, "test.JpEg"); await fs.copyFile(TEST_IMAGE_1, mixedPath); const result = await handleReadTool("attach_image", { path: mixedPath, }); expect((result.content[0] as any).mimeType).toBe("image/jpeg"); }); }); describe("base64 encoding validation", () => { test("base64 data can be decoded back to binary", async () => { const result = await handleReadTool("attach_image", { path: TEST_IMAGE_1, }); const base64Data = (result.content[0] as any).data; const decoded = Buffer.from(base64Data, "base64"); // Verify decoded buffer is valid expect(decoded.length).toBeGreaterThan(0); // JPEG files start with FF D8 FF expect(decoded[0]).toBe(0xff); expect(decoded[1]).toBe(0xd8); expect(decoded[2]).toBe(0xff); }); test("base64 encoding preserves file content", async () => { const originalFile = await fs.readFile(TEST_IMAGE_1); const result = await handleReadTool("attach_image", { path: TEST_IMAGE_1, }); const base64Data = (result.content[0] as any).data; const decoded = Buffer.from(base64Data, "base64"); // Decoded data should match original file exactly expect(decoded.equals(originalFile)).toBe(true); }); test("base64 contains no whitespace or newlines", async () => { const result = await handleReadTool("attach_image", { path: TEST_IMAGE_1, }); const base64Data = (result.content[0] as any).data; // Base64 should be continuous without whitespace expect(base64Data).not.toMatch(/\s/); expect(base64Data).not.toMatch(/\n/); expect(base64Data).not.toMatch(/\r/); }); }); describe("MCP specification compliance", () => { test("returns exact MCP format structure", async () => { const result = await handleReadTool("attach_image", { path: TEST_IMAGE_1, }); // Verify top-level structure expect(result).toHaveProperty("content"); expect(Array.isArray(result.content)).toBe(true); // Verify content item structure - new correct format const content = result.content[0] as any; expect(content).toHaveProperty("type"); expect(content).toHaveProperty("data"); expect(content).toHaveProperty("mimeType"); expect(content.type).toBe("image"); expect(content.mimeType).toBe("image/jpeg"); expect(typeof content.data).toBe("string"); // Old source wrapper should NOT exist expect(content).not.toHaveProperty("source"); }); test("content array contains exactly one item", async () => { const result = await handleReadTool("attach_image", { path: TEST_IMAGE_1, }); expect(result.content.length).toBe(1); }); test("media_type uses correct MIME format", async () => { const formats = [ { ext: "png", mime: "image/png" }, { ext: "jpg", mime: "image/jpeg" }, { ext: "gif", mime: "image/gif" }, { ext: "webp", mime: "image/webp" }, { ext: "bmp", mime: "image/bmp" }, ]; for (const format of formats) { const testPath = path.join(FIXTURES_DIR, `test.${format.ext}`); await fs.copyFile(TEST_IMAGE_1, testPath); const result = await handleReadTool("attach_image", { path: testPath, }); expect((result.content[0] as any).mimeType).toBe(format.mime); expect((result.content[0] as any).mimeType).toMatch(/^image\//); } }); }); describe("Batch Image Attachment", () => { test("attaches multiple images in a single call", async () => { const result = await handleReadTool("attach_image", { path: [TEST_IMAGE_1, TEST_IMAGE_2], }); // Should return array with both images expect(result.content).toBeDefined(); expect(Array.isArray(result.content)).toBe(true); expect(result.content.length).toBe(2); // First image const image1 = result.content[0] as any; expect(image1.type).toBe("image"); expect(image1.data).toBeTruthy(); expect(image1.mimeType).toBe("image/jpeg"); expect(typeof image1.data).toBe("string"); // Second image const image2 = result.content[1] as any; expect(image2.type).toBe("image"); expect(image2.data).toBeTruthy(); expect(image2.mimeType).toBe("image/jpeg"); expect(typeof image2.data).toBe("string"); // Images should have different base64 data expect(image1.data).not.toBe(image2.data); }); test("handles mixed image formats in batch", async () => { // Create a PNG copy for testing const pngPath = path.join(FIXTURES_DIR, "test-batch.png"); await fs.copyFile(TEST_IMAGE_1, pngPath); const result = await handleReadTool("attach_image", { path: [TEST_IMAGE_2, pngPath], }); expect(result.content.length).toBe(2); expect((result.content[0] as any).mimeType).toBe("image/jpeg"); expect((result.content[1] as any).mimeType).toBe("image/png"); }); test("single path string still works (backward compatibility)", async () => { const result = await handleReadTool("attach_image", { path: TEST_IMAGE_1, // Single string, not array }); expect(result.content).toBeDefined(); expect(Array.isArray(result.content)).toBe(true); expect(result.content.length).toBe(1); expect((result.content[0] as any).type).toBe("image"); expect((result.content[0] as any).mimeType).toBe("image/jpeg"); }); test("rejects batch with unsupported format", async () => { const mp3Path = path.join(FIXTURES_DIR, "test.mp3"); await fs.writeFile(mp3Path, "fake mp3 content"); await expect( handleReadTool("attach_image", { path: [TEST_IMAGE_1, mp3Path], }) ).rejects.toThrow(/Unsupported image format/); }); test("batch with nonexistent file fails gracefully", async () => { await expect( handleReadTool("attach_image", { path: [TEST_IMAGE_1, path.join(FIXTURES_DIR, "nonexistent.jpg")], }) ).rejects.toThrow(); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/n0zer0d4y/vulcan-file-ops'

If you have feedback or need assistance with the MCP directory API, please join our Discord server