Codebase MCP
- src
- tools
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { execSync } from "child_process";
import path from "path";
import fs from "fs";
// Create MCP server
const server = new McpServer({
name: "codebase-mcp",
version: "1.0.0",
});
// Define the 'getCodebase' tool
server.tool(
"getCodebase",
"Retrieve the entire codebase as a single text output using RepoMix",
{
cwd: z.string().describe("Current working directory of the codebase (defaults to current dir)").optional(),
format: z.enum(["xml", "markdown", "plain"]).describe("Output format (xml, markdown, or plain)").default("xml").optional(),
includeFileSummary: z.boolean().describe("Include summary of each file").default(true).optional(),
includeDirectoryStructure: z.boolean().describe("Include directory structure").default(true).optional(),
removeComments: z.boolean().describe("Remove comments from the code").default(false).optional(),
removeEmptyLines: z.boolean().describe("Remove empty lines from the code").default(false).optional(),
showLineNumbers: z.boolean().describe("Show line numbers").default(true).optional(),
includePatterns: z.string().describe("Include patterns (using glob patterns, comma-separated)").optional(),
ignorePatterns: z.string().describe("Ignore patterns (using glob patterns, comma-separated)").optional(),
},
async ({ cwd, format, includeFileSummary, includeDirectoryStructure, removeComments, removeEmptyLines, showLineNumbers, includePatterns, ignorePatterns }) => {
try {
// Prepare options for Repomix
const workingDir = cwd || process.cwd();
let command = "npx repomix --output stdout";
// Add formatting options
if (format) {
command += ` --style ${format}`;
}
if (includeFileSummary === true) {
command += ` --include-file-summary`;
} else if (includeFileSummary === false) {
command += ` --no-include-file-summary`;
}
if (includeDirectoryStructure === true) {
command += ` --include-directory-structure`;
} else if (includeDirectoryStructure === false) {
command += ` --no-include-directory-structure`;
}
if (removeComments === true) {
command += ` --remove-comments`;
} else if (removeComments === false) {
command += ` --no-remove-comments`;
}
if (removeEmptyLines === true) {
command += ` --remove-empty-lines`;
} else if (removeEmptyLines === false) {
command += ` --no-remove-empty-lines`;
}
if (showLineNumbers === true) {
command += ` --show-line-numbers`;
} else if (showLineNumbers === false) {
command += ` --no-show-line-numbers`;
}
if (includePatterns) {
command += ` --include "${includePatterns}"`;
}
if (ignorePatterns) {
command += ` --ignore "${ignorePatterns}"`;
}
console.error(`Running command: ${command}`);
// Run Repomix to dump the codebase
const output = execSync(command, {
cwd: workingDir,
maxBuffer: 1024 * 1024 * 50, // 50MB buffer for large codebases
}).toString();
return {
content: [
{
type: "text",
text: output,
},
],
};
} catch (error: unknown) {
console.error("Error running Repomix:", error);
return {
content: [
{
type: "text",
text: `Error retrieving codebase: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Define the 'getRemoteCodebase' tool for processing remote repositories
server.tool(
"getRemoteCodebase",
"Retrieve a remote repository's codebase as a single text output using RepoMix",
{
repo: z.string().describe("GitHub repository URL or shorthand format (e.g., 'username/repo')"),
format: z.enum(["xml", "markdown", "plain"]).describe("Output format (xml, markdown, or plain)").default("xml").optional(),
includeFileSummary: z.boolean().describe("Include summary of each file").default(true).optional(),
includeDirectoryStructure: z.boolean().describe("Include directory structure").default(true).optional(),
removeComments: z.boolean().describe("Remove comments from the code").default(false).optional(),
removeEmptyLines: z.boolean().describe("Remove empty lines from the code").default(false).optional(),
showLineNumbers: z.boolean().describe("Show line numbers").default(true).optional(),
includePatterns: z.string().describe("Include patterns (using glob patterns, comma-separated)").optional(),
ignorePatterns: z.string().describe("Ignore patterns (using glob patterns, comma-separated)").optional(),
},
async ({ repo, format, includeFileSummary, includeDirectoryStructure, removeComments, removeEmptyLines, showLineNumbers, includePatterns, ignorePatterns }) => {
try {
let command = `npx repomix --remote ${repo} --output stdout`;
// Add formatting options
if (format) {
command += ` --style ${format}`;
}
if (includeFileSummary === true) {
command += ` --include-file-summary`;
} else if (includeFileSummary === false) {
command += ` --no-include-file-summary`;
}
if (includeDirectoryStructure === true) {
command += ` --include-directory-structure`;
} else if (includeDirectoryStructure === false) {
command += ` --no-include-directory-structure`;
}
if (removeComments === true) {
command += ` --remove-comments`;
} else if (removeComments === false) {
command += ` --no-remove-comments`;
}
if (removeEmptyLines === true) {
command += ` --remove-empty-lines`;
} else if (removeEmptyLines === false) {
command += ` --no-remove-empty-lines`;
}
if (showLineNumbers === true) {
command += ` --show-line-numbers`;
} else if (showLineNumbers === false) {
command += ` --no-show-line-numbers`;
}
if (includePatterns) {
command += ` --include "${includePatterns}"`;
}
if (ignorePatterns) {
command += ` --ignore "${ignorePatterns}"`;
}
console.error(`Running command: ${command}`);
// Run Repomix to dump the codebase
const output = execSync(command, {
maxBuffer: 1024 * 1024 * 50, // 50MB buffer for large codebases
}).toString();
return {
content: [
{
type: "text",
text: output,
},
],
};
} catch (error: unknown) {
console.error("Error running Repomix on remote repository:", error);
return {
content: [
{
type: "text",
text: `Error retrieving remote codebase: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Define the 'saveCodebase' tool for saving the codebase to a file
server.tool(
"saveCodebase",
"Save the codebase to a file using RepoMix",
{
cwd: z.string().describe("Current working directory of the codebase (defaults to current dir)").optional(),
outputFile: z.string().describe("Output file path").default("repomix-output.txt"),
format: z.enum(["xml", "markdown", "plain"]).describe("Output format (xml, markdown, or plain)").default("xml").optional(),
includeFileSummary: z.boolean().describe("Include summary of each file").default(true).optional(),
includeDirectoryStructure: z.boolean().describe("Include directory structure").default(true).optional(),
removeComments: z.boolean().describe("Remove comments from the code").default(false).optional(),
removeEmptyLines: z.boolean().describe("Remove empty lines from the code").default(false).optional(),
showLineNumbers: z.boolean().describe("Show line numbers").default(true).optional(),
includePatterns: z.string().describe("Include patterns (using glob patterns, comma-separated)").optional(),
ignorePatterns: z.string().describe("Ignore patterns (using glob patterns, comma-separated)").optional(),
},
async ({ cwd, outputFile, format, includeFileSummary, includeDirectoryStructure, removeComments, removeEmptyLines, showLineNumbers, includePatterns, ignorePatterns }) => {
try {
// Prepare options for Repomix
const workingDir = cwd || process.cwd();
const outputPath = path.isAbsolute(outputFile) ? outputFile : path.join(workingDir, outputFile);
let command = `npx repomix --output "${outputPath}"`;
// Add formatting options
if (format) {
command += ` --style ${format}`;
}
if (includeFileSummary === true) {
command += ` --include-file-summary`;
} else if (includeFileSummary === false) {
command += ` --no-include-file-summary`;
}
if (includeDirectoryStructure === true) {
command += ` --include-directory-structure`;
} else if (includeDirectoryStructure === false) {
command += ` --no-include-directory-structure`;
}
if (removeComments === true) {
command += ` --remove-comments`;
} else if (removeComments === false) {
command += ` --no-remove-comments`;
}
if (removeEmptyLines === true) {
command += ` --remove-empty-lines`;
} else if (removeEmptyLines === false) {
command += ` --no-remove-empty-lines`;
}
if (showLineNumbers === true) {
command += ` --show-line-numbers`;
} else if (showLineNumbers === false) {
command += ` --no-show-line-numbers`;
}
if (includePatterns) {
command += ` --include "${includePatterns}"`;
}
if (ignorePatterns) {
command += ` --ignore "${ignorePatterns}"`;
}
console.error(`Running command: ${command}`);
// Run Repomix to save the codebase to a file
execSync(command, {
cwd: workingDir,
maxBuffer: 1024 * 1024 * 50, // 50MB buffer for large codebases
});
// Check if the file was created successfully
if (fs.existsSync(outputPath)) {
const stats = fs.statSync(outputPath);
const fileSizeInMB = stats.size / (1024 * 1024);
return {
content: [
{
type: "text",
text: `Codebase saved successfully to ${outputPath} (${fileSizeInMB.toFixed(2)} MB)`,
},
],
};
} else {
return {
content: [
{
type: "text",
text: `Failed to save codebase to ${outputPath}. File was not created.`,
},
],
};
}
} catch (error: unknown) {
console.error("Error saving codebase:", error);
return {
content: [
{
type: "text",
text: `Error saving codebase: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Codebase MCP Server running on stdio");
}
main().catch(console.error);