// Copyright 2025 Chris Bunting
// Brief: Main entry point for Static Analysis MCP Server
// Scope: Multi-language static code analysis with ESLint, Pylint, SpotBugs, and more
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { StaticAnalysisService } from './services/StaticAnalysisService.js';
import { LanguageDetector } from './services/LanguageDetector.js';
import { ConfigParser } from './services/ConfigParser.js';
import { Logger } from './utils/Logger.js';
import {
AnalysisResult,
} from '@mcp-code-analysis/shared-types';
class StaticAnalysisServer {
private server: Server;
private analysisService: StaticAnalysisService;
private languageDetector: LanguageDetector;
private configParser: ConfigParser;
private logger: Logger;
constructor() {
this.server = new Server(
{
name: 'static-analysis-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
this.logger = new Logger();
this.languageDetector = new LanguageDetector();
this.configParser = new ConfigParser();
this.analysisService = new StaticAnalysisService(
this.languageDetector,
this.configParser,
this.logger
);
this.setupHandlers();
}
private setupHandlers(): void {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'analyze_file',
description: 'Analyze a single file for static analysis issues',
inputSchema: {
type: 'object',
properties: {
filePath: {
type: 'string',
description: 'Path to the file to analyze',
},
language: {
type: 'string',
description: 'Programming language (optional, auto-detected if not provided)',
enum: ['javascript', 'typescript', 'python', 'java', 'c', 'cpp', 'go', 'rust'],
},
options: {
type: 'object',
description: 'Analysis options',
properties: {
rules: {
type: 'array',
items: { type: 'string' },
description: 'Specific rules to enable',
},
exclude: {
type: 'array',
items: { type: 'string' },
description: 'Rules to exclude',
},
fixable: {
type: 'boolean',
description: 'Only show fixable issues',
},
configFile: {
type: 'string',
description: 'Path to custom configuration file',
},
},
},
},
required: ['filePath'],
},
},
{
name: 'analyze_project',
description: 'Analyze an entire project for static analysis issues',
inputSchema: {
type: 'object',
properties: {
projectPath: {
type: 'string',
description: 'Path to the project root directory',
},
filePatterns: {
type: 'array',
items: { type: 'string' },
description: 'File patterns to include (glob patterns)',
},
excludePatterns: {
type: 'array',
items: { type: 'string' },
description: 'File patterns to exclude',
},
options: {
type: 'object',
description: 'Analysis options',
properties: {
rules: {
type: 'array',
items: { type: 'string' },
description: 'Specific rules to enable',
},
exclude: {
type: 'array',
items: { type: 'string' },
description: 'Rules to exclude',
},
maxWarnings: {
type: 'number',
description: 'Maximum number of warnings to report',
},
configFile: {
type: 'string',
description: 'Path to custom configuration file',
},
},
},
},
required: ['projectPath'],
},
},
{
name: 'get_rules',
description: 'Get available rules for a specific language',
inputSchema: {
type: 'object',
properties: {
language: {
type: 'string',
description: 'Programming language',
enum: ['javascript', 'typescript', 'python', 'java', 'c', 'cpp', 'go', 'rust'],
},
configFile: {
type: 'string',
description: 'Path to configuration file (optional)',
},
},
required: ['language'],
},
},
{
name: 'configure_analyzer',
description: 'Configure the analyzer with custom settings',
inputSchema: {
type: 'object',
properties: {
language: {
type: 'string',
description: 'Programming language',
enum: ['javascript', 'typescript', 'python', 'java', 'c', 'cpp', 'go', 'rust'],
},
config: {
type: 'object',
description: 'Configuration object',
},
},
required: ['language', 'config'],
},
},
{
name: 'batch_analyze',
description: 'Analyze multiple files in batch',
inputSchema: {
type: 'object',
properties: {
filePaths: {
type: 'array',
items: { type: 'string' },
description: 'List of file paths to analyze',
},
options: {
type: 'object',
description: 'Analysis options',
properties: {
rules: {
type: 'array',
items: { type: 'string' },
description: 'Specific rules to enable',
},
exclude: {
type: 'array',
items: { type: 'string' },
description: 'Rules to exclude',
},
maxWarnings: {
type: 'number',
description: 'Maximum number of warnings to report',
},
},
},
},
required: ['filePaths'],
},
},
],
};
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
let result: any;
switch (name) {
case 'analyze_file':
result = await this.analyzeFile(args);
break;
case 'analyze_project':
result = await this.analyzeProject(args);
break;
case 'get_rules':
result = await this.getRules(args);
break;
case 'configure_analyzer':
result = await this.configureAnalyzer(args);
break;
case 'batch_analyze':
result = await this.batchAnalyze(args);
break;
default:
throw new Error(`Unknown tool: ${name}`);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
this.logger.error(`Error executing tool ${name}:`, error);
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: error instanceof Error ? error.message : 'Unknown error',
}),
},
],
isError: true,
};
}
});
}
private async analyzeFile(args: any): Promise<AnalysisResult> {
const { filePath, language, options = {} } = args;
this.logger.info(`Analyzing file: ${filePath}`);
return await this.analysisService.analyzeFile(filePath, language, options);
}
private async analyzeProject(args: any): Promise<AnalysisResult[]> {
const { projectPath, filePatterns, excludePatterns, options = {} } = args;
this.logger.info(`Analyzing project: ${projectPath}`);
return await this.analysisService.analyzeProject(
projectPath,
filePatterns,
excludePatterns,
options
);
}
private async getRules(args: any): Promise<any> {
const { language, configFile } = args;
this.logger.info(`Getting rules for language: ${language}`);
return await this.analysisService.getRules(language, configFile);
}
private async configureAnalyzer(args: any): Promise<any> {
const { language, config } = args;
this.logger.info(`Configuring analyzer for language: ${language}`);
return await this.analysisService.configureAnalyzer(language, config);
}
private async batchAnalyze(args: any): Promise<AnalysisResult[]> {
const { filePaths, options = {} } = args;
this.logger.info(`Batch analyzing ${filePaths.length} files`);
return await this.analysisService.batchAnalyze(filePaths, options);
}
async run(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
this.logger.info('Static Analysis MCP Server started');
}
}
async function main(): Promise<void> {
const server = new StaticAnalysisServer();
await server.run();
}
main().catch((error) => {
console.error('Failed to start Static Analysis MCP Server:', error);
process.exit(1);
});