Skip to main content
Glama

Unreal Engine Code Analyzer MCP Server

index.ts19 kB
#!/usr/bin/env node /** * Created by Ayelet Technology Private Limited */ import { Server, StdioServerTransport } from '@modelcontextprotocol/create-server'; import type { CallToolRequest, ListToolsRequest } from '@modelcontextprotocol/create-server'; import { UnrealCodeAnalyzer } from './analyzer.js'; import { GAME_GENRES, GameGenre, GenreFlag } from './types/game-genres.js'; class UnrealAnalyzerServer { private server: Server; private analyzer: UnrealCodeAnalyzer; constructor() { this.server = new Server( { name: 'unreal-analyzer', version: '0.1.0', }, { capabilities: { tools: {}, }, } ); this.analyzer = new UnrealCodeAnalyzer(); this.setupToolHandlers(); this.server.onerror = (error: Error) => console.error('[MCP Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } private setupToolHandlers() { this.server.setRequestHandler<ListToolsRequest>('list_tools', async () => ({ tools: [ { name: 'set_unreal_path', description: 'Set the path to Unreal Engine source code', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'Absolute path to Unreal Engine source directory', }, }, required: ['path'], }, }, { name: 'set_custom_codebase', description: 'Set the path to a custom C++ codebase for analysis', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'Absolute path to custom codebase directory', }, }, required: ['path'], }, }, { name: 'analyze_class', description: 'Get detailed information about a C++ class', inputSchema: { type: 'object', properties: { className: { type: 'string', description: 'Name of the class to analyze', }, }, required: ['className'], }, }, { name: 'find_class_hierarchy', description: 'Get the inheritance hierarchy for a class', inputSchema: { type: 'object', properties: { className: { type: 'string', description: 'Name of the class to analyze', }, includeImplementedInterfaces: { type: 'boolean', description: 'Whether to include implemented interfaces', default: true, }, }, required: ['className'], }, }, { name: 'find_references', description: 'Find all references to a class, function, or variable', inputSchema: { type: 'object', properties: { identifier: { type: 'string', description: 'Name of the symbol to find references for', }, type: { type: 'string', description: 'Type of symbol (class, function, variable)', enum: ['class', 'function', 'variable'], }, }, required: ['identifier'], }, }, { name: 'search_code', description: 'Search through code with context', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query (supports regex)', }, filePattern: { type: 'string', description: 'File pattern to search in (e.g. *.h, *.cpp)', default: '*.{h,cpp}', }, includeComments: { type: 'boolean', description: 'Whether to include comments in search', default: true, }, }, required: ['query'], }, }, { name: 'detect_patterns', description: 'Detect Unreal Engine patterns and suggest improvements', inputSchema: { type: 'object', properties: { filePath: { type: 'string', description: 'Path to the file to analyze', }, }, required: ['filePath'], }, }, { name: 'get_best_practices', description: 'Get Unreal Engine best practices and documentation for a specific concept', inputSchema: { type: 'object', properties: { concept: { type: 'string', description: 'Concept to get best practices for (e.g. UPROPERTY, Components, Events)', enum: ['UPROPERTY', 'UFUNCTION', 'Components', 'Events', 'Replication', 'Blueprints'], }, }, required: ['concept'], }, }, { name: 'analyze_subsystem', description: 'Analyze a specific Unreal Engine subsystem', inputSchema: { type: 'object', properties: { subsystem: { type: 'string', description: 'Name of the subsystem (e.g. Rendering, Physics)', enum: [ 'Rendering', 'Physics', 'Audio', 'Networking', 'Input', 'AI', 'Animation', 'UI', ], }, }, required: ['subsystem'], }, }, { name: 'query_api', description: 'Search and retrieve Unreal Engine API documentation', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query for API documentation', }, category: { type: 'string', description: 'Filter by category (Object, Actor, Structure, Component)', enum: ['Object', 'Actor', 'Structure', 'Component', 'Miscellaneous'], }, module: { type: 'string', description: 'Filter by module (Core, RenderCore, etc.)', }, includeExamples: { type: 'boolean', description: 'Include code examples in results', default: true, }, maxResults: { type: 'number', description: 'Maximum number of results to return', default: 10, }, }, required: ['query'], }, }, ], })); this.server.setRequestHandler<CallToolRequest>('call_tool', async (request: CallToolRequest) => { // Only check for initialization for analysis tools const analysisTools = ['analyze_class', 'find_class_hierarchy', 'find_references', 'search_code', 'analyze_subsystem', 'query_api']; if (analysisTools.includes(request.params.name) && !this.analyzer.isInitialized() && request.params.name !== 'set_unreal_path' && request.params.name !== 'set_custom_codebase') { throw new Error('No codebase initialized. Use set_unreal_path or set_custom_codebase first.'); } switch (request.params.name) { case 'detect_patterns': return this.handleDetectPatterns(request.params.arguments); case 'get_best_practices': return this.handleGetBestPractices(request.params.arguments); case 'set_unreal_path': return this.handleSetUnrealPath(request.params.arguments); case 'set_custom_codebase': return this.handleSetCustomCodebase(request.params.arguments); case 'analyze_class': return this.handleAnalyzeClass(request.params.arguments); case 'find_class_hierarchy': return this.handleFindClassHierarchy(request.params.arguments); case 'find_references': return this.handleFindReferences(request.params.arguments); case 'search_code': return this.handleSearchCode(request.params.arguments); case 'analyze_subsystem': return this.handleAnalyzeSubsystem(request.params.arguments); case 'query_api': return this.handleQueryApi(request.params.arguments); default: throw new Error(`Unknown tool: ${request.params.name}`); } }); } private async handleSetUnrealPath(args: any) { try { await this.analyzer.initialize(args.path); return { content: [ { type: 'text', text: `Successfully set Unreal Engine path to: ${args.path}`, }, ], }; } catch (error) { throw new Error(error instanceof Error ? error.message : 'Failed to set Unreal Engine path'); } } private async handleSetCustomCodebase(args: any) { try { await this.analyzer.initializeCustomCodebase(args.path); return { content: [ { type: 'text', text: `Successfully set custom codebase path to: ${args.path}`, }, ], }; } catch (error) { throw new Error(error instanceof Error ? error.message : 'Failed to set custom codebase path'); } } private async handleAnalyzeClass(args: any) { try { const classInfo = await this.analyzer.analyzeClass(args.className); return { content: [ { type: 'text', text: JSON.stringify(classInfo, null, 2), }, ], }; } catch (error) { throw new Error(error instanceof Error ? error.message : 'Failed to analyze class'); } } private async handleFindClassHierarchy(args: any) { try { const hierarchy = await this.analyzer.findClassHierarchy( args.className, args.includeImplementedInterfaces ); return { content: [ { type: 'text', text: JSON.stringify(hierarchy, null, 2), }, ], }; } catch (error) { throw new Error(error instanceof Error ? error.message : 'Failed to find class hierarchy'); } } private async handleFindReferences(args: any) { try { const references = await this.analyzer.findReferences( args.identifier, args.type ); return { content: [ { type: 'text', text: JSON.stringify(references, null, 2), }, ], }; } catch (error) { throw new Error(error instanceof Error ? error.message : 'Failed to find references'); } } private async handleSearchCode(args: any) { try { const results = await this.analyzer.searchCode( args.query, args.filePattern, args.includeComments ); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2), }, ], }; } catch (error) { throw new Error(error instanceof Error ? error.message : 'Failed to search code'); } } private async handleDetectPatterns(args: any) { try { const fileContent = await require('fs').promises.readFile(args.filePath, 'utf8'); const patterns = await this.analyzer.detectPatterns(fileContent, args.filePath); // Format the output to be more readable in Cline const formattedPatterns = patterns.map(match => { return { pattern: match.pattern.name, description: match.pattern.description, location: `${match.file}:${match.line}`, context: match.context, improvements: match.suggestedImprovements?.join('\n'), documentation: match.pattern.documentation, bestPractices: match.pattern.bestPractices.join('\n'), examples: match.pattern.examples.join('\n'), }; }); return { content: [ { type: 'text', text: JSON.stringify(formattedPatterns, null, 2), }, ], }; } catch (error) { throw new Error(error instanceof Error ? error.message : 'Failed to detect patterns'); } } private async handleGetBestPractices(args: any) { const bestPractices: { [key: string]: any } = { 'UPROPERTY': { description: 'Property declaration for Unreal reflection system', bestPractices: [ 'Use appropriate specifiers (EditAnywhere, BlueprintReadWrite)', 'Consider replication needs (Replicated, ReplicatedUsing)', 'Group related properties with categories', 'Use Meta tags for validation and UI customization', ], examples: [ 'UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")\nfloat Health;', 'UPROPERTY(Replicated, Meta = (ClampMin = "0.0"))\nfloat Speed;', ], documentation: 'https://docs.unrealengine.com/5.0/en-US/unreal-engine-uproperty-specifier-reference/', }, 'UFUNCTION': { description: 'Function declaration for Unreal reflection system', bestPractices: [ 'Use BlueprintCallable for functions that can be called from Blueprints', 'Use BlueprintPure for functions without side effects', 'Consider using BlueprintNativeEvent for overridable functions', 'Add categories and tooltips for better organization', ], examples: [ 'UFUNCTION(BlueprintCallable, Category = "Combat")\nvoid TakeDamage(float DamageAmount);', 'UFUNCTION(BlueprintPure, Category = "Stats")\nfloat GetHealthPercentage() const;', ], documentation: 'https://docs.unrealengine.com/5.0/en-US/ufunctions-in-unreal-engine/', }, 'Components': { description: 'Component setup and management in Unreal Engine', bestPractices: [ 'Create components in constructor using CreateDefaultSubobject', 'Set up component hierarchy properly', 'Initialize component properties in constructor', 'Consider component replication needs', ], examples: [ 'MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));\nRootComponent = MeshComponent;', 'CollisionComponent->SetupAttachment(RootComponent);', ], documentation: 'https://docs.unrealengine.com/5.0/en-US/components-in-unreal-engine/', }, 'Events': { description: 'Event handling and delegation in Unreal Engine', bestPractices: [ 'Bind events in BeginPlay and unbind in EndPlay', 'Use weak pointers for delegate bindings', 'Consider using BlueprintAssignable for Blueprint events', 'Handle edge cases and null checks', ], examples: [ 'DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChanged, float, NewHealth);', 'OnHealthChanged.AddDynamic(this, &AMyActor::HandleHealthChanged);', ], documentation: 'https://docs.unrealengine.com/5.0/en-US/delegates-in-unreal-engine/', }, 'Replication': { description: 'Network replication in Unreal Engine', bestPractices: [ 'Mark properties with Replicated specifier', 'Implement GetLifetimeReplicatedProps', 'Use ReplicatedUsing for property change callbacks', 'Consider replication conditions (COND_*)', ], examples: [ 'void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const;', 'UPROPERTY(ReplicatedUsing = OnRep_Health)\nfloat Health;', ], documentation: 'https://docs.unrealengine.com/5.0/en-US/networking-overview-for-unreal-engine/', }, 'Blueprints': { description: 'Blueprint integration and exposure', bestPractices: [ 'Use appropriate function and property specifiers', 'Organize functions and properties into categories', 'Add tooltips and descriptions', 'Consider Blueprint/C++ interaction patterns', ], examples: [ 'UCLASS(Blueprintable, BlueprintType)', 'UFUNCTION(BlueprintImplementableEvent)', ], documentation: 'https://docs.unrealengine.com/5.0/en-US/blueprints-and-cpp-in-unreal-engine/', }, }; const concept = bestPractices[args.concept]; if (!concept) { throw new Error(`Unknown concept: ${args.concept}`); } return { content: [ { type: 'text', text: JSON.stringify(concept, null, 2), }, ], }; } private async handleAnalyzeSubsystem(args: any) { try { const subsystemInfo = await this.analyzer.analyzeSubsystem(args.subsystem); return { content: [ { type: 'text', text: JSON.stringify(subsystemInfo, null, 2), }, ], }; } catch (error) { throw new Error(error instanceof Error ? error.message : 'Failed to analyze subsystem'); } } private async handleQueryApi(args: any) { try { const results = await this.analyzer.queryApiReference(args.query, { category: args.category, module: args.module, includeExamples: args.includeExamples, maxResults: args.maxResults, }); // Format results for better readability const formattedResults = results.map(result => ({ class: result.reference.className, description: result.reference.description, module: result.reference.module, category: result.reference.category, syntax: result.reference.syntax, examples: result.reference.examples, remarks: result.reference.remarks, documentation: result.learningResources[0]?.url, relevance: result.relevance, })); return { content: [ { type: 'text', text: JSON.stringify(formattedResults, null, 2), }, ], }; } catch (error) { throw new Error(error instanceof Error ? error.message : 'Failed to query API documentation'); } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Unreal Engine Analyzer MCP server running on stdio'); } } const server = new UnrealAnalyzerServer(); server.run().catch(console.error);

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/ayeletstudioindia/unreal-analyzer-mcp'

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