WebGL-MCP Server
by grokadegames
Verified
// WebGL MCP server for analyzing and optimizing WebGL applications
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { createRequire } from "module";
import path from 'path';
import fs from 'fs';
const require = createRequire(import.meta.url);
// Helper function to check if a path exists
async function pathExists(filePath) {
try {
await fs.promises.access(filePath);
return true;
} catch {
return false;
}
}
// Helper function to analyze HTML file for template features
async function analyzeTemplate(filePath) {
if (!await pathExists(filePath)) {
return {
templateName: 'Unknown',
features: [],
recommendations: ['Could not find HTML file to analyze template.']
};
}
try {
const content = await fs.promises.readFile(filePath, 'utf8');
const features = [];
const recommendations = [];
let templateName = 'Unknown Template';
// Check for Better Minimal WebGL Template
if (content.includes('BetterMinimal') ||
(content.includes('scaleToFit') && content.includes('data-pixel-art'))) {
templateName = 'Better Minimal WebGL Template';
// Detect features
if (content.includes('scaleToFit')) features.push('Canvas Scaling');
if (content.includes('data-pixel-art="true"')) features.push('Pixel Art Optimization');
if (content.includes('progressHandler')) features.push('Loading Progress Bar');
if (content.includes('iPhone|iPad|iPod|Android')) features.push('Mobile Detection');
// Check for potential improvements
if (!content.includes('progressHandler')) {
recommendations.push('Add loading progress indicator for better user experience');
}
if (!content.includes('data-pixel-art')) {
recommendations.push('Consider adding pixel art optimization for pixel art games');
}
if (!content.includes('window.focus()')) {
recommendations.push('Add window.focus() after resize to ensure keyboard input works correctly');
}
}
// Unity Default Template
else if (content.includes('UnityLoader') || content.includes('unityInstance')) {
templateName = 'Unity Default Template';
recommendations.push('Consider using the Better Minimal WebGL Template for improved performance and user experience');
recommendations.push('Better Minimal WebGL Template provides automatic canvas scaling for different screen sizes');
recommendations.push('Better Minimal WebGL Template includes mobile optimizations and loading progress visualization');
}
// Unknown Template
else {
recommendations.push('Using an unknown template. Consider adopting Better Minimal WebGL Template for optimal WebGL performance');
}
return { templateName, features, recommendations };
} catch (error) {
console.error('Error analyzing template:', error);
return {
templateName: 'Error',
features: [],
recommendations: [`Error analyzing template: ${error.message}`]
};
}
}
// Create an MCP server
const server = new McpServer({
name: "Grokade Games WebGL-MCP",
version: "1.0.0"
});
// Add WebGL analysis tool
server.tool(
"analyze-webgl",
{
path: z.string().describe("Path to WebGL build folder or index.html file")
},
async ({ path: buildPath }) => {
console.error(`Analyzing WebGL at path: ${buildPath}`);
try {
// Determine if path is a file or directory
const stats = await fs.promises.stat(buildPath);
const isDirectory = stats.isDirectory();
// Find index.html file
let indexPath;
if (isDirectory) {
indexPath = path.join(buildPath, 'index.html');
if (!await pathExists(indexPath)) {
// Try to find index.html in any subdirectory
const files = await fs.promises.readdir(buildPath, { withFileTypes: true });
for (const file of files) {
if (file.isDirectory()) {
const subIndexPath = path.join(buildPath, file.name, 'index.html');
if (await pathExists(subIndexPath)) {
indexPath = subIndexPath;
break;
}
}
}
}
} else if (path.basename(buildPath).toLowerCase() === 'index.html') {
indexPath = buildPath;
}
// Analyze template if index.html was found
const templateAnalysis = indexPath ? await analyzeTemplate(indexPath) : {
templateName: 'Unknown',
features: [],
recommendations: ['No index.html file found to analyze.']
};
// Count JS and asset files if it's a directory
let fileStats = {
total: 0,
js: 0,
wasm: 0,
textures: 0,
other: 0
};
let largeFiles = [];
if (isDirectory) {
await countFiles(buildPath, fileStats, largeFiles);
}
// WebGL Analysis results
const analysisText = `Analyzed WebGL at path: ${buildPath}
Template Analysis:
- Template: ${templateAnalysis.templateName}
- Features: ${templateAnalysis.features.length ? templateAnalysis.features.join(', ') : 'None detected'}
${isDirectory ? `Build Statistics:
- Total Files: ${fileStats.total}
- JavaScript Files: ${fileStats.js}
- WebAssembly Files: ${fileStats.wasm}
- Texture/Image Files: ${fileStats.textures}
- Other Files: ${fileStats.other}` : ''}
${largeFiles.length ? `Large Files Detected (>2MB):
${largeFiles.map(f => `- ${f.name} (${(f.size / (1024 * 1024)).toFixed(2)} MB)`).join('\n')}` : ''}
Recommendations:
${templateAnalysis.recommendations.map(r => `- ${r}`).join('\n')}
${isDirectory && fileStats.js > 10 ? '- Large number of JavaScript files detected. Consider code splitting or bundling.' : ''}
${largeFiles.length ? '- Consider optimizing large files to improve load times.' : ''}
${fileStats.wasm === 0 ? '- No WebAssembly files detected. Consider using WebAssembly for performance-critical code.' : ''}`;
return {
content: [{
type: "text",
text: analysisText
}]
};
} catch (error) {
console.error('Error analyzing WebGL:', error);
throw new Error(`Failed to analyze WebGL at path: ${buildPath}`);
}
}
);
// Helper function to count files recursively
async function countFiles(dirPath, stats, largeFiles) {
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
await countFiles(fullPath, stats, largeFiles);
} else {
stats.total++;
const ext = path.extname(entry.name).toLowerCase();
if (ext === '.js') {
stats.js++;
} else if (ext === '.wasm') {
stats.wasm++;
} else if (['.jpg', '.jpeg', '.png', '.webp', '.gif', '.svg', '.bmp'].includes(ext)) {
stats.textures++;
} else {
stats.other++;
}
// Check for large files
try {
const fileStat = await fs.promises.stat(fullPath);
if (fileStat.size > 2 * 1024 * 1024) { // Files larger than 2MB
largeFiles.push({
name: path.relative(dirPath, fullPath),
size: fileStat.size
});
}
} catch (error) {
console.error(`Error checking file size: ${fullPath}`, error);
}
}
}
}
// Add WebGL optimization tool
server.tool(
"optimize-webgl",
{
path: z.string().describe("Path to WebGL build folder or index.html file"),
targetFPS: z.number().optional().describe("Target frames per second"),
memoryLimit: z.number().optional().describe("Memory limit in MB"),
optimizationGoals: z.array(z.enum(['performance', 'memory', 'quality', 'mobile'])).optional()
.describe("Optimization goals in order of priority")
},
async ({ path: buildPath, targetFPS = 60, memoryLimit = 512, optimizationGoals = ['performance'] }) => {
console.error(`Optimizing WebGL at path: ${buildPath}`, { targetFPS, memoryLimit, optimizationGoals });
try {
// Determine if path is a file or directory
const stats = await fs.promises.stat(buildPath);
const isDirectory = stats.isDirectory();
// Find index.html file
let indexPath;
if (isDirectory) {
indexPath = path.join(buildPath, 'index.html');
if (!await pathExists(indexPath)) {
// Try to find index.html in any subdirectory
const files = await fs.promises.readdir(buildPath, { withFileTypes: true });
for (const file of files) {
if (file.isDirectory()) {
const subIndexPath = path.join(buildPath, file.name, 'index.html');
if (await pathExists(subIndexPath)) {
indexPath = subIndexPath;
break;
}
}
}
}
} else if (path.basename(buildPath).toLowerCase() === 'index.html') {
indexPath = buildPath;
}
// Analyze template if index.html was found
const templateAnalysis = indexPath ? await analyzeTemplate(indexPath) : {
templateName: 'Unknown',
features: [],
recommendations: ['No index.html file found to analyze.']
};
// Generate optimization recommendations based on goals
const optimizationRecommendations = [];
// Template-specific recommendations
if (templateAnalysis.templateName !== 'Better Minimal WebGL Template') {
optimizationRecommendations.push('Use the Better Minimal WebGL Template for optimized WebGL performance:');
optimizationRecommendations.push(' - Provides automatic canvas scaling');
optimizationRecommendations.push(' - Includes loading progress visualization');
optimizationRecommendations.push(' - Optimizes for mobile devices');
optimizationRecommendations.push(' - Supports pixel art optimization');
}
// Performance recommendations
if (optimizationGoals.includes('performance')) {
optimizationRecommendations.push('\nPerformance Optimization Recommendations:');
optimizationRecommendations.push(' - Enable compression for all assets (gzip/Brotli)');
optimizationRecommendations.push(' - Use WebGL 2.0 for better rendering performance');
optimizationRecommendations.push(' - Implement texture compression (DXT/ASTC)');
optimizationRecommendations.push(' - Use WebAssembly for performance-critical code');
optimizationRecommendations.push(' - Implement shader minification and optimization');
optimizationRecommendations.push(' - Reduce draw calls through batching');
optimizationRecommendations.push(' - Implement occlusion culling for complex scenes');
}
// Memory recommendations
if (optimizationGoals.includes('memory')) {
optimizationRecommendations.push('\nMemory Optimization Recommendations:');
optimizationRecommendations.push(' - Set explicit memory limit in WebGL context');
optimizationRecommendations.push(` - Target memory usage below ${memoryLimit}MB`);
optimizationRecommendations.push(' - Implement asset unloading for unused resources');
optimizationRecommendations.push(' - Use texture atlases to reduce memory fragmentation');
optimizationRecommendations.push(' - Implement level streaming for large worlds');
optimizationRecommendations.push(' - Use lower resolution textures with mipmaps');
}
// Mobile recommendations
if (optimizationGoals.includes('mobile')) {
optimizationRecommendations.push('\nMobile Optimization Recommendations:');
optimizationRecommendations.push(' - Implement responsive design with Better Minimal WebGL Template');
optimizationRecommendations.push(' - Use appropriate touch input handling');
optimizationRecommendations.push(' - Reduce shader complexity for mobile GPUs');
optimizationRecommendations.push(' - Implement progressive loading for mobile networks');
optimizationRecommendations.push(' - Add battery-saving measures (lower FPS when inactive)');
optimizationRecommendations.push(' - Optimize for offline usage with service workers');
}
// Quality recommendations
if (optimizationGoals.includes('quality')) {
optimizationRecommendations.push('\nQuality Optimization Recommendations:');
optimizationRecommendations.push(' - Implement post-processing effects with WebGL 2.0');
optimizationRecommendations.push(' - Use HDR rendering where supported');
optimizationRecommendations.push(' - Enable anisotropic filtering for textures');
optimizationRecommendations.push(' - Implement MSAA or FXAA for anti-aliasing');
optimizationRecommendations.push(' - Use dynamic resolution scaling based on performance');
}
// Implementation guidance
optimizationRecommendations.push('\nImplementation Notes:');
optimizationRecommendations.push(' - For Better Minimal WebGL Template implementation:');
optimizationRecommendations.push(' * Download from: https://seansleblanc.itch.io/better-minimal-webgl-template (external resource)');
optimizationRecommendations.push(' * Extract WebGLTemplates folder to your Unity project\'s Assets folder');
optimizationRecommendations.push(' * Select the template in Player Settings under WebGL > Resolution and Presentation');
optimizationRecommendations.push(' * Configure options for scaling, pixel art optimization, and background color');
// Target FPS recommendations
optimizationRecommendations.push(`\nTarget FPS: ${targetFPS}`);
if (targetFPS > 60) {
optimizationRecommendations.push(' - For high FPS targets:');
optimizationRecommendations.push(' * Reduce draw calls and batch similar materials');
optimizationRecommendations.push(' * Simplify shaders and reduce instruction count');
optimizationRecommendations.push(' * Consider implementing adaptive quality settings');
}
return {
content: [{
type: "text",
text: `Optimization Recommendations for WebGL at path: ${buildPath}\n\n${optimizationRecommendations.join('\n')}`
}]
};
} catch (error) {
console.error('Error optimizing WebGL:', error);
throw new Error(`Failed to optimize WebGL at path: ${buildPath}`);
}
}
);
// Add WebGL performance analysis tool
server.tool(
"analyze-performance",
{
path: z.string().describe("Path to WebGL build folder or index.html file"),
duration: z.number().optional().describe("Duration of performance test in seconds")
},
async ({ path: buildPath, duration = 10 }) => {
console.error(`Analyzing WebGL performance at path: ${buildPath} for ${duration} seconds`);
try {
// Determine if path is a file or directory
const stats = await fs.promises.stat(buildPath);
const isDirectory = stats.isDirectory();
// Find index.html file
let indexPath;
if (isDirectory) {
indexPath = path.join(buildPath, 'index.html');
if (!await pathExists(indexPath)) {
// Try to find index.html in any subdirectory
const files = await fs.promises.readdir(buildPath, { withFileTypes: true });
for (const file of files) {
if (file.isDirectory()) {
const subIndexPath = path.join(buildPath, file.name, 'index.html');
if (await pathExists(subIndexPath)) {
indexPath = subIndexPath;
break;
}
}
}
}
} else if (path.basename(buildPath).toLowerCase() === 'index.html') {
indexPath = buildPath;
}
// Analyze template if index.html was found
const templateAnalysis = indexPath ? await analyzeTemplate(indexPath) : {
templateName: 'Unknown',
features: [],
recommendations: ['No index.html file found to analyze.']
};
// Performance analysis simulation (in a real implementation, this would involve running the WebGL content)
const performanceRecommendations = [];
// Simulate/predict performance metrics
const fps = Math.floor(Math.random() * 20) + 45; // Simulated fps between 45-65
const drawCalls = Math.floor(Math.random() * 200) + 50; // Simulated draw calls between 50-250
const triangles = Math.floor(Math.random() * 100000) + 10000; // Simulated triangle count
const textureMemory = Math.floor(Math.random() * 500) + 50; // Simulated texture memory (MB)
const jsMemory = Math.floor(Math.random() * 200) + 50; // Simulated JS memory (MB)
// Generate performance recommendations
if (fps < 60) {
performanceRecommendations.push(`Low FPS (${fps}). Consider optimizing rendering and CPU usage.`);
}
if (drawCalls > 100) {
performanceRecommendations.push(`High draw call count (${drawCalls}). Implement batching to reduce draw calls.`);
}
if (triangles > 50000) {
performanceRecommendations.push(`High triangle count (${triangles}). Consider using LOD (Level of Detail) for complex models.`);
}
if (textureMemory > 200) {
performanceRecommendations.push(`High texture memory usage (${textureMemory}MB). Optimize texture sizes and compression.`);
}
if (jsMemory > 100) {
performanceRecommendations.push(`High JavaScript memory usage (${jsMemory}MB). Check for memory leaks and optimize object pooling.`);
}
// Template specific performance recommendations
if (templateAnalysis.templateName !== 'Better Minimal WebGL Template') {
performanceRecommendations.push('Using non-optimal WebGL template. The Better Minimal WebGL Template can improve rendering performance.');
}
const performanceText = `Performance Analysis for WebGL at path: ${buildPath}
Note: This is a simulated analysis. Actual performance would require running the WebGL content.
Results (Simulated):
- Average FPS: ${fps}
- Draw calls per frame: ${drawCalls}
- Triangles per frame: ${triangles.toLocaleString()}
- Texture memory usage: ${textureMemory}MB
- JavaScript memory usage: ${jsMemory}MB
Template Information:
- Template: ${templateAnalysis.templateName}
- Features: ${templateAnalysis.features.length ? templateAnalysis.features.join(', ') : 'None detected'}
Performance Recommendations:
${performanceRecommendations.map(r => `- ${r}`).join('\n')}
Better Minimal WebGL Template Benefits:
- Optimized canvas scaling for better performance
- Reduced memory usage through optimized rendering
- Improved mobile performance with proper viewport handling
- More efficient loading progress visualization`;
return {
content: [{
type: "text",
text: performanceText
}]
};
} catch (error) {
console.error('Error analyzing WebGL performance:', error);
throw new Error(`Failed to analyze WebGL performance at path: ${buildPath}`);
}
}
);
// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("WebGL MCP Server started and connected to transport");