#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import { GitAnalytics } from './git-analytics.js';
import chalk from 'chalk';
import path from 'path';
// Server configuration
const server = new Server(
{
name: 'git-analytics-mcp-server',
version: '1.0.0',
}
);
// Helper function to format analytics data
function formatAnalyticsData(data: any, title: string): string {
let output = `\n${chalk.bold.blue('='.repeat(60))}\n`;
output += `${chalk.bold.yellow(title.toUpperCase())}\n`;
output += `${chalk.bold.blue('='.repeat(60))}\n`;
if (typeof data === 'object' && data !== null) {
output += JSON.stringify(data, null, 2);
} else {
output += String(data);
}
return output;
}
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
console.log(chalk.blue('\n[Server] Received request to list tools'));
const tools = {
tools: [
{
name: 'get_repository_overview',
description: 'Get a comprehensive overview of the Git repository including stats, top contributors, and recent activity',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Path to the Git repository (defaults to current directory)',
default: '.'
}
}
}
},
{
name: 'get_repository_stats',
description: 'Get detailed repository statistics including commits, authors, branches, and code changes',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Path to the Git repository (defaults to current directory)',
default: '.'
}
}
}
},
{
name: 'get_author_stats',
description: 'Get detailed statistics for all contributors including commits, code changes, and activity periods',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Path to the Git repository (defaults to current directory)',
default: '.'
}
}
}
}
]
};
console.log(chalk.green('[Server] Sending available tools:', JSON.stringify(tools, null, 2)));
return tools;
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const repoPath = (args?.path as string) || '.';
console.log(chalk.blue('\n[Server] Received tool call:'));
console.log(chalk.yellow(' Tool:', name));
console.log(chalk.yellow(' Arguments:', JSON.stringify(args, null, 2)));
console.log(chalk.yellow(' Repository Path:', path.resolve(repoPath)));
try {
const analytics = new GitAnalytics(repoPath);
// Validate repository
console.log(chalk.blue('[Server] Validating repository...'));
const isValidRepo = await analytics.validateRepository();
if (!isValidRepo) {
console.log(chalk.red('[Server] Invalid repository'));
throw new McpError(
ErrorCode.InvalidRequest,
`Invalid Git repository at path: ${path.resolve(repoPath)}`
);
}
console.log(chalk.green('[Server] Repository is valid'));
switch (name) {
case 'get_repository_overview': {
console.log(chalk.blue('[Server] Getting repository overview...'));
const repoStats = await analytics.getRepositoryStats();
const authorStats = await analytics.getAuthorStats();
const report = formatAnalyticsData({
repository: repoStats,
authors: authorStats
}, 'Repository Overview');
console.log(chalk.green('[Server] Sending repository overview'));
return {
content: [
{
type: 'text',
text: report
}
]
};
}
case 'get_repository_stats': {
console.log(chalk.blue('[Server] Getting repository stats...'));
const stats = await analytics.getRepositoryStats();
console.log(chalk.green('[Server] Sending repository stats'));
return {
content: [
{
type: 'text',
text: formatAnalyticsData(stats, 'Repository Statistics')
}
]
};
}
case 'get_author_stats': {
console.log(chalk.blue('[Server] Getting author stats...'));
const stats = await analytics.getAuthorStats();
console.log(chalk.green('[Server] Sending author stats'));
return {
content: [
{
type: 'text',
text: formatAnalyticsData(stats, 'Author Statistics')
}
]
};
}
default:
console.log(chalk.red(`[Server] Unknown tool: ${name}`));
throw new McpError(
ErrorCode.InvalidRequest,
`Unknown tool: ${name}`
);
}
} catch (error: unknown) {
if (error instanceof McpError) {
console.log(chalk.red(`[Server] MCP Error: ${error.message}`));
throw error;
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
console.log(chalk.red(`[Server] Error: ${errorMessage}`));
throw new McpError(
ErrorCode.InternalError,
`Error executing tool ${name}: ${errorMessage}`
);
}
});
// Start server
async function main() {
try {
console.log(chalk.blue('\n[Server] Starting MCP server...'));
const transport = new StdioServerTransport();
await server.connect(transport);
console.log(chalk.green('[Server] Git Analytics MCP Server running on stdio'));
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
console.error(chalk.red('[Server] Error starting server:'), error);
console.error(chalk.red('[Server] Error details:'), {
message: errorMessage,
stack: error instanceof Error ? error.stack : 'No stack trace available',
type: error ? typeof error : 'unknown'
});
process.exit(1);
}
}
main();