knip-mcp-server
Version:
MCP server for knip.dev integration to help AI agents identify and clean up unused code
137 lines • 5.12 kB
JavaScript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema, McpError, ErrorCode, } from '@modelcontextprotocol/sdk/types.js';
import { createKnipTools } from './tools/index.js';
import { logger } from './lib/logger.js';
class KnipMcpServer {
server;
config;
constructor(config = {}) {
this.config = {
projectRoot: process.cwd(),
logLevel: 'info',
enableSafeMode: true,
maxFileSize: 1024 * 1024, // 1MB
maxFilesPerOperation: 100,
...config,
};
this.server = new Server({
name: '@genar/knip-mcp-server',
version: '0.1.0',
}, {
capabilities: {
tools: {},
},
});
this.setupHandlers();
}
setupHandlers() {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools = createKnipTools(this.config);
return {
tools: Object.values(tools).map(tool => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
})),
};
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
const tools = createKnipTools(this.config);
const tool = tools[name];
if (!tool) {
throw new McpError(ErrorCode.MethodNotFound, `Tool '${name}' not found`);
}
logger.info(`Executing tool: ${name}`, { args });
const result = await tool.execute(args || {});
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
catch (error) {
logger.error(`Error executing tool ${name}:`, error);
if (error instanceof McpError) {
throw error;
}
throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`);
}
});
// Error handler
this.server.onerror = (error) => {
logger.error('Server error:', error);
};
// Process error handlers
process.on('SIGINT', async () => {
logger.info('Received SIGINT, shutting down gracefully...');
await this.close();
process.exit(0);
});
process.on('SIGTERM', async () => {
logger.info('Received SIGTERM, shutting down gracefully...');
await this.close();
process.exit(0);
});
process.on('uncaughtException', (error) => {
logger.error('Uncaught exception:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled rejection', { promise, reason });
process.exit(1);
});
}
async start() {
const transport = new StdioServerTransport();
logger.info('Starting Knip MCP Server...', {
projectRoot: this.config.projectRoot,
logLevel: this.config.logLevel,
safeMode: this.config.enableSafeMode,
});
await this.server.connect(transport);
logger.info('Knip MCP Server started successfully');
}
async close() {
logger.info('Shutting down Knip MCP Server...');
await this.server.close();
logger.info('Knip MCP Server shut down complete');
}
}
// CLI entry point
if (import.meta.url === `file://${process.argv[1]}`) {
const config = {
projectRoot: process.env.KNIP_PROJECT_ROOT || process.cwd(),
logLevel: process.env.KNIP_LOG_LEVEL || 'info',
enableSafeMode: process.env.KNIP_SAFE_MODE !== 'false',
};
if (process.env.KNIP_CONFIG_PATH) {
config.knipConfigPath = process.env.KNIP_CONFIG_PATH;
}
if (process.env.KNIP_BACKUP_DIR) {
config.backupDir = process.env.KNIP_BACKUP_DIR;
}
if (process.env.KNIP_MAX_FILE_SIZE) {
config.maxFileSize = parseInt(process.env.KNIP_MAX_FILE_SIZE);
}
if (process.env.KNIP_MAX_FILES_PER_OP) {
config.maxFilesPerOperation = parseInt(process.env.KNIP_MAX_FILES_PER_OP);
}
const server = new KnipMcpServer(config);
server.start().catch((error) => {
logger.error('Failed to start server:', error);
process.exit(1);
});
}
export { KnipMcpServer };
export * from './types/index.js';
export * from './lib/knip-client.js';
//# sourceMappingURL=index.js.map