#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from '@modelcontextprotocol/sdk/types.js';
import { execSync } from 'child_process';
import { readFileSync, writeFileSync, existsSync } from 'fs';
import { join } from 'path';
class BuildMCPServer {
private server: Server;
constructor() {
this.server = new Server(
{
name: 'build-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
this.setupErrorHandling();
}
private setupErrorHandling(): void {
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private setupToolHandlers(): void {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'run_build',
description: 'Run build command in the current directory',
inputSchema: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'Build command to run (e.g., "npm run build", "yarn build")',
default: 'npm run build'
},
directory: {
type: 'string',
description: 'Directory to run the build in (default: current directory)'
}
}
}
},
{
name: 'run_test',
description: 'Run tests in the current directory',
inputSchema: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'Test command to run (e.g., "npm test", "yarn test")',
default: 'npm test'
},
directory: {
type: 'string',
description: 'Directory to run tests in (default: current directory)'
}
}
}
},
{
name: 'check_package_json',
description: 'Read and analyze package.json file',
inputSchema: {
type: 'object',
properties: {
directory: {
type: 'string',
description: 'Directory containing package.json (default: current directory)'
}
}
}
},
{
name: 'install_dependencies',
description: 'Install project dependencies',
inputSchema: {
type: 'object',
properties: {
manager: {
type: 'string',
description: 'Package manager to use',
enum: ['npm', 'yarn', 'pnpm'],
default: 'npm'
},
directory: {
type: 'string',
description: 'Directory to install dependencies in (default: current directory)'
}
}
}
},
{
name: 'lint_code',
description: 'Run linting on the codebase',
inputSchema: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'Lint command to run (e.g., "npm run lint", "eslint .")',
default: 'npm run lint'
},
directory: {
type: 'string',
description: 'Directory to run linting in (default: current directory)'
}
}
}
}
] as Tool[]
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'run_build':
return await this.runBuild(args);
case 'run_test':
return await this.runTest(args);
case 'check_package_json':
return await this.checkPackageJson(args);
case 'install_dependencies':
return await this.installDependencies(args);
case 'lint_code':
return await this.lintCode(args);
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : String(error)}`
}
],
isError: true
};
}
});
}
private async runBuild(args: any) {
const command = args?.command || 'npm run build';
const directory = args?.directory || process.cwd();
try {
const output = execSync(command, {
cwd: directory,
encoding: 'utf8',
timeout: 300000 // 5 minutes
});
return {
content: [
{
type: 'text',
text: `Build successful!\nCommand: ${command}\nDirectory: ${directory}\nOutput:\n${output}`
}
]
};
} catch (error: any) {
throw new Error(`Build failed: ${error.message}\nStderr: ${error.stderr || 'N/A'}`);
}
}
private async runTest(args: any) {
const command = args?.command || 'npm test';
const directory = args?.directory || process.cwd();
try {
const output = execSync(command, {
cwd: directory,
encoding: 'utf8',
timeout: 300000 // 5 minutes
});
return {
content: [
{
type: 'text',
text: `Tests passed!\nCommand: ${command}\nDirectory: ${directory}\nOutput:\n${output}`
}
]
};
} catch (error: any) {
throw new Error(`Tests failed: ${error.message}\nStderr: ${error.stderr || 'N/A'}`);
}
}
private async checkPackageJson(args: any) {
const directory = args?.directory || process.cwd();
const packagePath = join(directory, 'package.json');
if (!existsSync(packagePath)) {
throw new Error(`package.json not found in ${directory}`);
}
try {
const packageContent = readFileSync(packagePath, 'utf8');
const packageJson = JSON.parse(packageContent);
const analysis = {
name: packageJson.name || 'N/A',
version: packageJson.version || 'N/A',
dependencies: Object.keys(packageJson.dependencies || {}),
devDependencies: Object.keys(packageJson.devDependencies || {}),
scripts: Object.keys(packageJson.scripts || {}),
main: packageJson.main || 'N/A',
type: packageJson.type || 'commonjs'
};
return {
content: [
{
type: 'text',
text: `Package.json analysis:\n${JSON.stringify(analysis, null, 2)}`
}
]
};
} catch (error: any) {
throw new Error(`Failed to parse package.json: ${error.message}`);
}
}
private async installDependencies(args: any) {
const manager = args?.manager || 'npm';
const directory = args?.directory || process.cwd();
const command = `${manager} install`;
try {
const output = execSync(command, {
cwd: directory,
encoding: 'utf8',
timeout: 600000 // 10 minutes
});
return {
content: [
{
type: 'text',
text: `Dependencies installed successfully!\nCommand: ${command}\nDirectory: ${directory}\nOutput:\n${output}`
}
]
};
} catch (error: any) {
throw new Error(`Dependency installation failed: ${error.message}\nStderr: ${error.stderr || 'N/A'}`);
}
}
private async lintCode(args: any) {
const command = args?.command || 'npm run lint';
const directory = args?.directory || process.cwd();
try {
const output = execSync(command, {
cwd: directory,
encoding: 'utf8',
timeout: 300000 // 5 minutes
});
return {
content: [
{
type: 'text',
text: `Linting completed!\nCommand: ${command}\nDirectory: ${directory}\nOutput:\n${output}`
}
]
};
} catch (error: any) {
// Linting might fail but still provide useful output
const output = error.stdout || '';
const stderr = error.stderr || '';
return {
content: [
{
type: 'text',
text: `Linting completed with issues:\nCommand: ${command}\nDirectory: ${directory}\nOutput:\n${output}\nErrors:\n${stderr}`
}
]
};
}
}
async run(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Build MCP Server running on stdio');
}
}
const server = new BuildMCPServer();
server.run().catch(console.error);