Skip to main content
Glama

Vibe-Eyes

mcp.js9.42 kB
#!/usr/bin/env node import express from 'express'; import { createServer } from 'http'; import { Server } from 'socket.io'; import { vectorizeImage } from './vectorizer.js'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; // Create Express app const app = express(); const httpServer = createServer(app); const io = new Server(httpServer, { cors: { origin: "*", methods: ["GET", "POST"] }, maxHttpBufferSize: 50 * 1024 * 1024 // 50MB max message size }); let mcpRequestCount = 0; let mcpRequestWithoutSvgCount = 0; // Configuration const PORT = process.env.PORT || 8869; // Store only the latest processed capture let latestCapture = null; // Initialize as null and set with real data when available // Create a test capture for initial state (only used if no real capture comes in) const createTestCapture = () => ({ id: "capture_test_" + Date.now(), timestamp: Date.now(), console_logs: [ { timestamp: Date.now() - 1000, data: ["Player position:", {x: 10, y: 20}] }, { timestamp: Date.now(), data: ["Game started"] } ], console_errors: [], unhandled_exception: null, vectorized: { svg: "<svg width='100' height='100'><circle cx='50' cy='50' r='40' fill='blue'/></svg>", imageType: "png", stats: { processingTime: 100 } } }); // Extract and process image from data URL async function processDataUrl(dataUrl) { try { // Extract the base64 part from the data URL const matches = dataUrl.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/); if (!matches) { throw new Error('Invalid data URL format'); } const imageType = matches[1]; const base64Data = matches[2]; const imageBuffer = Buffer.from(base64Data, 'base64'); // Process the image with vectorizer const result = await vectorizeImage(imageBuffer, { // Configure for faster processing since this is for debugging filterSpeckle: 4, maxIterations: 30 }); return { imageType, svg: result.svg, stats: result.stats }; } catch (error) { console.error('Error processing data URL:', error); return { error: error.message }; } } // Middleware for parsing JSON bodies app.use(express.json({ limit: '100mb' })); // Serve the most recent capture app.get('/latest', (req, res) => { if (latestCapture) { res.json(latestCapture); } else { res.status(404).json({ error: 'No captures available yet' }); } }); // Socket.IO connection handling io.on('connection', (socket) => { console.log('Client connected:', socket.id); // Inform client about latest capture status if (latestCapture) { socket.emit('captureStatus', { hasCapture: true, timestamp: latestCapture.timestamp }); } // Handle debug captures socket.on('debugCapture', async (data, callback) => { try { // console.log(`[${new Date().toISOString()}] Processing new debug capture...`); // Create capture object const capture = { id: `capture_${Date.now()}`, timestamp: data.timestamp || Date.now(), console_logs: data.console_logs || [], console_errors: data.console_errors || [], unhandled_exception: data.unhandled_exception || null }; // Process image if provided - this is async and we need to await it if (data.image) { // console.log(`[${new Date().toISOString()}] Processing image...`); const processingStart = Date.now(); try { // Await image processing to complete before sending ack const imageResult = await processDataUrl(data.image); if (!imageResult.error) { // Store the vectorized data capture.vectorized = { svg: imageResult.svg, imageType: imageResult.imageType, stats: imageResult.stats }; // console.log(`[${new Date().toISOString()}] Image processed in ${Date.now() - processingStart}ms`); } else { console.error(`[${new Date().toISOString()}] Image processing failed:`, imageResult.error); } } catch (imageError) { console.error(`[${new Date().toISOString()}] Image processing exception:`, imageError); } } // Store as latest capture (replacing previous) latestCapture = capture; // Log receipt // console.log(`[${new Date().toISOString()}] Capture complete: ${capture.id}`); // Only send callback AFTER all processing is done if (callback) { callback({ success: true, id: capture.id, processedAt: Date.now(), // Include the SVG in the response to client svg: capture.vectorized ? capture.vectorized.svg : null, stats: capture.vectorized ? capture.vectorized.stats : null, mcpRequestCount, mcpRequestWithoutSvgCount, }); } } catch (error) { console.error(`[${new Date().toISOString()}] Error processing debug capture:`, error); if (callback) { callback({ error: error.message, success: false }); } } }); // Handle request for latest capture socket.on('getLatestCapture', (callback) => { if (latestCapture) { callback({ success: true, capture: latestCapture, // Include the SVG directly for easy access svg: latestCapture.vectorized ? latestCapture.vectorized.svg : null, stats: latestCapture.vectorized ? latestCapture.vectorized.stats : null }); } else { callback({ success: false, error: 'No captures available yet' }); } }); // Handle disconnection socket.on('disconnect', () => { console.log('Client disconnected:', socket.id); }); }); // Create the MCP server with description from README const mcpServer = new McpServer({ name: "Vibe-Eyes", version: "1.0.0", description: `An MCP server that enables LLMs to 'see' what's happening in browser-based games and applications through vectorized canvas approximation and debug information. Note: Debug visualization uses SVG vectorization as approximation which may smooth sharp edges and simplify geometric shapes. The actual canvas rendering may have more precise angles and edges than shown here. Also, the vectorization process is optimized for speed and may not be suitable for high-fidelity graphics. This server provides a way to visualize game states and debug information in a structured format, but it is a single frame at a time and not particularly useful for viewing animations.`, }); // MCP tool with includeSvg parameter mcpServer.tool("getGameDebugInfoWithLogsAndVisualization", { includeSvg: z.boolean().optional().default(true) }, async ({ includeSvg }) => { mcpRequestCount++; if (!includeSvg) { mcpRequestWithoutSvgCount++; } // Simply use the latest capture if (!latestCapture) { // Return basic response when no data is available return { success: false, content: [{ type: "text", text: "No game data available yet." }] }; } // Use the capture directly const capture = latestCapture; // Remove SVG if not explicitly requested if (!includeSvg && capture.vectorized) { delete capture.vectorized.svg; } // Extract console logs for easier viewing const consoleLogs = capture.console_logs.map(log => ({ time: new Date(log.timestamp).toISOString(), message: log.data.join(' ') })); // Extract errors if any const errors = capture.console_errors.map(err => ({ time: new Date(err.timestamp).toISOString(), message: err.data.join(' ') })); // Format the data for better MCP display // Create a text representation of the game state const textContent = ` Game State: ${capture.id} (${new Date(capture.timestamp).toISOString()}) Console Logs: ${consoleLogs.map(log => `[${log.time}] ${log.message}`).join('\n')} ${errors.length ? 'Errors:\n' + errors.map(err => `[${err.time}] ${err.message}`).join('\n') : 'No errors.'} ${capture.unhandled_exception ? `\nUnhandled Exception:\n${JSON.stringify(capture.unhandled_exception, null, 2)}` : ''} `; // Return in MCP-compatible format // Prepare result object const result = { success: true, content: [ { type: "text", text: textContent } ] }; // Only add SVG if we have it and it was requested if (capture.vectorized?.svg && includeSvg) { try { // Add SVG as text directly in the response result.content[0].text += "\nSVG Approximation:\n```svg\n" + capture.vectorized.svg + "\n```"; } catch (error) { console.error("Error with SVG data:", error); // Add error note to text content result.content[0].text += "\n\nError processing SVG data"; } } return result; }); // Initialize both HTTP and MCP servers (async () => { // Start HTTP server httpServer.listen(PORT, () => { console.log(`MCP Vectorizer debug server running on port ${PORT}`); }); // Start MCP server const transport = new StdioServerTransport(); await mcpServer.connect(transport); console.log('MCP Protocol server started'); })();

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/monteslu/vibe-eyes'

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