UNPKG

@cloudwerxlab/all-your-base64-mcp

Version:

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

175 lines (156 loc) 6.58 kB
#!/usr/bin/env node import path from 'path'; import fs from 'fs'; import { fileURLToPath, URL } from 'url'; import { randomUUID } from 'crypto'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; import express from 'express'; // Get directory info for ES modules const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Configuration: parse BASE_URL or use default const DEFAULT_PORT = 6644; const BASE_URL_ENV = process.env.BASE_URL; let BASE_URL; let PORT; if (BASE_URL_ENV) { BASE_URL = BASE_URL_ENV; try { const urlObj = new URL(BASE_URL_ENV); PORT = parseInt(urlObj.port) || DEFAULT_PORT; } catch { PORT = parseInt(process.env.PORT) || DEFAULT_PORT; } } else { PORT = parseInt(process.env.PORT) || DEFAULT_PORT; BASE_URL = `http://localhost:${PORT}`; } const IMAGE_DIR = path.join(__dirname, 'public', 'images'); // Ensure the images directory exists if (!fs.existsSync(IMAGE_DIR)) { fs.mkdirSync(IMAGE_DIR, { recursive: true }); console.error(`Created images directory: ${IMAGE_DIR}`); } // Start HTTP server to serve static images const app = express(); app.use('/images', express.static(IMAGE_DIR)); app.listen(PORT, () => { console.error(`🖼 Image server listening at ${BASE_URL}/images`); }); // Create a shared MCP server with image display tools function createMcpServer() { const server = new McpServer({ name: 'AllYourBase64MCP', version: '2.2.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,...)') }, { title: "Convert base64 image to URL for display in chat", description: "Use this tool when you need to display an image that's provided as base64-encoded data. Ideal for cases where an image is generated by code, extracted from PDFs, or taken from API responses that include base64 images. The tool saves the image to a server and returns a markdown link. To display the image, embed the returned markdown directly in your response: ![Alt text]({url}). Do NOT wrap in code blocks. Always include a description of what the image shows in your response. Supports PNG, JPEG, and other common formats, with or without the data URI prefix." }, async ({ data }) => { try { // Handle data URI prefix if present let mime = 'image/png'; let b64 = data; const match = data.match(/^data:(image\/[^;]+);base64,(.+)$/); if (match) { mime = match[1]; b64 = match[2]; } // Determine file extension based on MIME type const ext = mime.split('/')[1]; const filename = `${randomUUID()}.${ext}`; const filePath = path.join(IMAGE_DIR, filename); // Ensure the images directory exists await fs.promises.mkdir(IMAGE_DIR, { recursive: true }).catch(console.error); // Save the image file const buffer = Buffer.from(b64, 'base64'); await fs.promises.writeFile(filePath, buffer); // Generate URL and markdown const url = `${BASE_URL}/images/${filename}`; const markdown = `![Image](${url})`; console.error(`Converted base64 image to URL: ${url}`); return { content: [{ type: 'text', text: markdown }] }; } catch (error) { console.error('Error processing base64 image:', error); return { content: [{ type: 'text', text: `Error converting base64 image: ${error.message}` }], isError: true }; } } ); // Tool to display an existing image URL as markdown server.tool( 'display-url', { url: z.string().url().describe('URL of an image to display') }, { title: "Display image URL in chat", description: "Use this tool when you already have a valid image URL and need to display that image in the chat. Perfect for showing images from websites, documentation, or public image repositories. The tool formats the URL as markdown for proper display. To show the image, embed the returned markdown directly in your response: ![Alt text]({url}). Do NOT wrap in code blocks. Always reference the image content in your response text and provide context about what the image shows. This tool does not download or modify the original image." }, async ({ url }) => { try { const markdown = `![Image](${url})`; console.error(`Formatted image URL for display: ${url}`); return { content: [{ type: 'text', text: markdown }] }; } catch (error) { console.error('Error formatting image URL:', error); return { content: [{ type: 'text', text: `Error displaying image URL: ${error.message}` }], isError: true }; } } ); return server; } // stdio-only startup const server = createMcpServer(); const transport = new StdioServerTransport(); server.connect(transport).then(() => { console.error('✅ AllYourBase64MCP MCP server running on stdio'); console.error('🔧 Available tools:'); console.error(' - base64-to-url: Use this tool when you need to display an image that\'s provided as base64-encoded data. Embed the returned markdown directly in your response.'); console.error(' - display-url: Use this tool when you already have a valid image URL and need to display that image in the chat. Embed the returned markdown directly in your response.'); }).catch(err => { console.error('❌ Error connecting MCP server:', err); process.exit(1); }); // Handle graceful shutdown process.on('SIGINT', async () => { console.error('🛑 Shutting down AllYourBase64MCP...'); await server.close(); console.error('👋 Server shutdown complete. Goodbye!'); process.exit(0); }); process.on('SIGTERM', async () => { console.error('🛑 Shutting down AllYourBase64MCP...'); await server.close(); console.error('👋 Server shutdown complete. Goodbye!'); process.exit(0); }); // Export for programmatic use export default createMcpServer;