repomix-mcp
Version:
MCP server for intelligent context gathering using repomix
258 lines (257 loc) • 10.7 kB
JavaScript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
import { execSync } from "child_process";
import { glob } from "glob";
import path from "path";
import fs from "fs";
class RepomixServer {
server;
constructor() {
this.server = new Server({
name: "repomix-mcp",
version: "1.0.3",
}, {
capabilities: {
tools: {},
},
});
this.setupToolHandlers();
this.server.onerror = (error) => console.error("[MCP Error]", error);
process.on("SIGINT", async () => {
await this.server.close();
process.exit(0);
});
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "gather_context",
description: "Gather context from specified directories using repomix",
inputSchema: {
type: "object",
properties: {
baseDir: {
type: "string",
description: "Base directory to search in",
},
targetDirs: {
type: "array",
items: { type: "string" },
description: "Target directories to find (e.g. ['translations', 'src'])",
},
patterns: {
type: "array",
items: { type: "string" },
description: "File patterns to include",
},
depth: {
type: "string",
enum: ["shallow", "dependencies", "full"],
description: "Context gathering depth",
},
includeConfigs: {
type: "boolean",
description: "Include configuration files",
},
excludePatterns: {
type: "array",
items: { type: "string" },
description: "Patterns to exclude",
},
},
required: ["baseDir"],
},
},
{
name: "analyze_project",
description: "Analyze project structure and suggest context gathering strategy",
inputSchema: {
type: "object",
properties: {
projectDir: {
type: "string",
description: "Project directory to analyze",
},
},
required: ["projectDir"],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const args = request.params.arguments;
switch (request.params.name) {
case "gather_context": {
if (!args || typeof args.baseDir !== "string") {
throw new McpError(ErrorCode.InvalidParams, "baseDir is required and must be a string");
}
const config = {
baseDir: args.baseDir,
targetDirs: Array.isArray(args.targetDirs)
? args.targetDirs.map(String)
: undefined,
patterns: Array.isArray(args.patterns)
? args.patterns.map(String)
: undefined,
depth: args.depth,
includeConfigs: typeof args.includeConfigs === "boolean"
? args.includeConfigs
: undefined,
excludePatterns: Array.isArray(args.excludePatterns)
? args.excludePatterns.map(String)
: undefined,
};
return await this.handleGatherContext(config);
}
case "analyze_project": {
if (!args || typeof args.projectDir !== "string") {
throw new McpError(ErrorCode.InvalidParams, "projectDir is required and must be a string");
}
return await this.handleAnalyzeProject({
projectDir: args.projectDir,
});
}
default:
throw new McpError(ErrorCode.MethodNotFound, "Unknown tool");
}
});
}
async findTargetDirs(baseDir, targetDirs) {
if (!targetDirs || targetDirs.length === 0) {
return [baseDir];
}
const results = [];
for (const dir of targetDirs) {
if (dir === ".") {
results.push(baseDir);
}
else {
const matches = await glob(`**/${dir}`, {
cwd: baseDir,
ignore: ["**/node_modules/**"],
dot: true,
});
results.push(...matches.map((match) => path.join(baseDir, match)));
}
}
return results;
}
async handleGatherContext(config) {
try {
const targetDirs = await this.findTargetDirs(config.baseDir, config.targetDirs);
if (targetDirs.length === 0) {
throw new McpError(ErrorCode.InvalidRequest, "No matching directories found");
}
let combinedOutput = "";
// Process each directory separately
for (const dir of targetDirs) {
let repomixCommand = `npx repomix "${dir}"`;
// Add patterns if specified
if (config.patterns && config.patterns.length > 0) {
config.patterns.forEach((pattern) => {
repomixCommand += ` --include "${pattern}"`;
});
}
// Add depth if specified
if (config.depth) {
repomixCommand += ` --depth ${config.depth}`;
}
try {
// Execute repomix for this directory
const output = execSync(repomixCommand, {
encoding: "utf-8",
maxBuffer: 50 * 1024 * 1024, // 50MB buffer
});
combinedOutput += output + "\n";
}
catch (error) {
console.error(`Failed to process directory ${dir}:`, error);
// Continue with other directories even if one fails
}
}
return {
content: [
{
type: "text",
text: combinedOutput,
},
],
};
}
catch (error) {
if (error instanceof McpError)
throw error;
throw new McpError(ErrorCode.InternalError, `Failed to gather context: ${error instanceof Error ? error.message : String(error)}`);
}
}
async handleAnalyzeProject(args) {
try {
const projectDir = args.projectDir;
// Check for package.json to determine project type
const packageJsonPath = path.join(projectDir, "package.json");
let projectType = "unknown";
let suggestedDirs = ["src"];
let suggestedPatterns = [];
if (fs.existsSync(packageJsonPath)) {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
const deps = {
...packageJson.dependencies,
...packageJson.devDependencies,
};
if (deps.react) {
projectType = "react";
suggestedDirs.push("components", "pages", "hooks");
suggestedPatterns.push("*.tsx", "*.jsx");
}
else if (deps.vue) {
projectType = "vue";
suggestedDirs.push("components", "views", "store");
suggestedPatterns.push("*.vue");
}
else if (deps.angular) {
projectType = "angular";
suggestedDirs.push("app", "components", "services");
suggestedPatterns.push("*.ts");
}
}
return {
content: [
{
type: "text",
text: JSON.stringify({
projectType,
suggestedConfig: {
baseDir: projectDir,
targetDirs: suggestedDirs,
patterns: suggestedPatterns,
includeConfigs: true,
depth: "dependencies",
excludePatterns: [
"**/*.test.*",
"**/*.spec.*",
"**/node_modules/**",
],
},
}, null, 2),
},
],
};
}
catch (error) {
throw new McpError(ErrorCode.InternalError, `Failed to analyze project: ${error instanceof Error ? error.message : String(error)}`);
}
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("Repomix MCP server running on stdio");
}
}
const server = new RepomixServer();
server.run().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});