@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
JavaScript
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 = ``;
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 = ``;
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`);
});