UNPKG

@cloudwerxlab/base64-mcp

Version:

MCP server that converts base64 images and image URLs into markdown for display in chat

95 lines (83 loc) 3.25 kB
#!/usr/bin/env node import express from 'express'; import path from 'path'; import fs from 'fs'; import { fileURLToPath } from 'url'; import { randomUUID } from 'crypto'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { z } from 'zod'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); app.use(express.json()); const PORT = process.env.PORT || 4000; const BASE_URL = process.env.BASE_URL || `http://localhost:${PORT}`; const IMAGE_DIR = path.join(__dirname, 'public', 'images'); // Ensure the image directory exists fs.promises.mkdir(IMAGE_DIR, { recursive: true }).catch(console.error); // Helper to create a new MCP server with image display tools function createServer() { const server = new McpServer({ name: 'Base64MCP', version: '1.0.0', description: 'Converts base64 images and image URLs into markdown for display in chat' }); // Tool to convert base64 image data to a URL and return markdown server.tool( 'base64-to-url', { data: z.string().describe('Base64-encoded image data, optionally prefixed with data URI scheme (e.g., data:image/png;base64,...)') }, async ({ data }) => { let mime = 'image/png'; let b64 = data; const match = data.match(/^data:(image\/[^;]+);base64,(.+)$/); if (match) { mime = match[1]; b64 = match[2]; } const ext = mime.split('/')[1]; const filename = `${randomUUID()}.${ext}`; const filePath = path.join(IMAGE_DIR, filename); const buffer = Buffer.from(b64, 'base64'); await fs.promises.writeFile(filePath, buffer); const url = `${BASE_URL}/images/${filename}`; const markdown = `![Image](${url})`; return { content: [{ type: 'text', text: markdown }] }; } ); // Tool to display an existing image URL as markdown server.tool( 'display-url', { url: z.string().url().describe('URL of an image to display') }, async ({ url }) => { const markdown = `![Image](${url})`; return { content: [{ type: 'text', text: markdown }] }; } ); return server; } // Serve static image files app.use('/images', express.static(path.join(__dirname, 'public', 'images'))); // Stateless MCP HTTP endpoint app.post('/mcp', async (req, res) => { try { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); res.on('close', () => { transport.close(); server.close(); }); await server.connect(transport); await transport.handleRequest(req, res, req.body); } catch (err) { console.error('MCP handler error:', err); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error' }, id: null }); } } }); // Start the Express server app.listen(PORT, () => { console.log(`Base64MCP listening at http://localhost:${PORT}/mcp`); console.log(`Images served at http://localhost:${PORT}/images`); });