Skip to main content
Glama

MCP File & Git Manager Server

by osamaloup
index.ts16 kB
// src/index.ts - MCP Server with SSE (Server-Sent Events) Transport import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import express from "express"; import fs from "fs/promises"; import { exec } from "child_process"; import { promisify } from "util"; import path from "path"; import cors from "cors"; const execAsync = promisify(exec); const PROJECT_ROOT = process.env.PROJECT_ROOT || "/workspace"; const PORT = process.env.PORT || 3001; const app = express(); app.use(cors()); app.use(express.json()); // Health check endpoint app.get("/health", (req, res) => { res.json({ status: "healthy", timestamp: new Date().toISOString() }); }); // Create MCP Server const createMCPServer = () => { const server = new Server( { name: "file-git-manager", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); // Register tool list handler server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "read_file", description: "Read contents of a file from the project directory. Returns the full text content of the file.", inputSchema: { type: "object", properties: { path: { type: "string", description: "Relative path to the file from project root (e.g., 'src/index.ts' or 'README.md')", }, }, required: ["path"], }, }, { name: "write_file", description: "Write or overwrite a file in the project directory. Creates parent directories if needed.", inputSchema: { type: "object", properties: { path: { type: "string", description: "Relative path to the file from project root", }, content: { type: "string", description: "Content to write to the file", }, }, required: ["path", "content"], }, }, { name: "list_files", description: "List files and directories in a given path. Can list recursively to show entire directory tree.", inputSchema: { type: "object", properties: { path: { type: "string", description: "Relative path from project root (default: current directory)", default: ".", }, recursive: { type: "boolean", description: "List files recursively through subdirectories", default: false, }, }, }, }, { name: "delete_file", description: "Delete a file or directory (including all contents if directory)", inputSchema: { type: "object", properties: { path: { type: "string", description: "Relative path to the file/directory to delete", }, }, required: ["path"], }, }, { name: "create_directory", description: "Create a new directory (creates parent directories if needed)", inputSchema: { type: "object", properties: { path: { type: "string", description: "Relative path for the new directory", }, }, required: ["path"], }, }, { name: "git_status", description: "Get the current git status showing modified, staged, and untracked files", inputSchema: { type: "object", properties: {}, }, }, { name: "git_add", description: "Stage files for the next commit. Use '.' to stage all changes.", inputSchema: { type: "object", properties: { files: { type: "array", items: { type: "string" }, description: "Array of file paths to stage, or ['.'] for all files", }, }, required: ["files"], }, }, { name: "git_commit", description: "Create a git commit with staged changes", inputSchema: { type: "object", properties: { message: { type: "string", description: "Commit message describing the changes", }, }, required: ["message"], }, }, { name: "git_push", description: "Push commits to remote repository", inputSchema: { type: "object", properties: { remote: { type: "string", description: "Remote name (default: origin)", default: "origin", }, branch: { type: "string", description: "Branch name (leave empty for current branch)", }, }, }, }, { name: "git_pull", description: "Pull and merge changes from remote repository", inputSchema: { type: "object", properties: { remote: { type: "string", description: "Remote name (default: origin)", default: "origin", }, branch: { type: "string", description: "Branch name (leave empty for current branch)", }, }, }, }, { name: "git_log", description: "Show recent commit history with graph visualization", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Number of commits to show (default: 10)", default: 10, }, }, }, }, { name: "git_diff", description: "Show differences in files (unstaged or staged changes)", inputSchema: { type: "object", properties: { file: { type: "string", description: "Specific file to show diff for (optional, shows all if omitted)", }, staged: { type: "boolean", description: "Show staged changes instead of unstaged", default: false, }, }, }, }, { name: "search_in_files", description: "Search for text pattern in files using grep", inputSchema: { type: "object", properties: { pattern: { type: "string", description: "Text pattern to search for", }, path: { type: "string", description: "Directory to search in (default: current directory)", default: ".", }, filePattern: { type: "string", description: "File pattern to match (e.g., '*.js', '*.md')", }, }, required: ["pattern"], }, }, ], }; }); // Register tool execution handler server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (!args) { throw new Error("Arguments are required"); } try { switch (name) { case "read_file": { const filePath = path.join(PROJECT_ROOT, args.path as string); const content = await fs.readFile(filePath, "utf-8"); return { content: [ { type: "text", text: content, }, ], }; } case "write_file": { const filePath = path.join(PROJECT_ROOT, args.path as string); await fs.mkdir(path.dirname(filePath), { recursive: true }); await fs.writeFile(filePath, args.content as string, "utf-8"); return { content: [ { type: "text", text: `✅ File written successfully: ${args.path}`, }, ], }; } case "list_files": { const dirPath = path.join(PROJECT_ROOT, (args.path as string) || "."); const recursive = args.recursive as boolean; const listFilesRecursive = async (dir: string, prefix = ""): Promise<string[]> => { const entries = await fs.readdir(dir, { withFileTypes: true }); const files: string[] = []; for (const entry of entries) { const fullPath = path.join(dir, entry.name); const relativePath = path.relative(PROJECT_ROOT, fullPath); if (entry.isDirectory()) { files.push(`${prefix}📁 ${entry.name}/`); if (recursive) { const subFiles = await listFilesRecursive(fullPath, prefix + " "); files.push(...subFiles); } } else { files.push(`${prefix}📄 ${entry.name}`); } } return files; }; const files = await listFilesRecursive(dirPath); return { content: [ { type: "text", text: files.length > 0 ? files.join("\n") : "Directory is empty", }, ], }; } case "delete_file": { const filePath = path.join(PROJECT_ROOT, args.path as string); const stats = await fs.stat(filePath); if (stats.isDirectory()) { await fs.rm(filePath, { recursive: true, force: true }); } else { await fs.unlink(filePath); } return { content: [ { type: "text", text: `✅ Deleted: ${args.path}`, }, ], }; } case "create_directory": { const dirPath = path.join(PROJECT_ROOT, args.path as string); await fs.mkdir(dirPath, { recursive: true }); return { content: [ { type: "text", text: `✅ Directory created: ${args.path}`, }, ], }; } case "git_status": { const { stdout } = await execAsync("git status", { cwd: PROJECT_ROOT }); return { content: [ { type: "text", text: stdout, }, ], }; } case "git_add": { const files = (args.files as string[]).join(" "); const { stdout, stderr } = await execAsync(`git add ${files}`, { cwd: PROJECT_ROOT, }); return { content: [ { type: "text", text: `✅ Files staged: ${files}\n${stdout}${stderr}`, }, ], }; } case "git_commit": { const message = (args.message as string).replace(/"/g, '\\"'); const { stdout } = await execAsync(`git commit -m "${message}"`, { cwd: PROJECT_ROOT, }); return { content: [ { type: "text", text: `✅ Commit created:\n${stdout}`, }, ], }; } case "git_push": { const remote = (args.remote as string) || "origin"; const branch = args.branch ? ` ${args.branch}` : ""; const { stdout, stderr } = await execAsync(`git push ${remote}${branch}`, { cwd: PROJECT_ROOT, }); return { content: [ { type: "text", text: `✅ Pushed to ${remote}:\n${stdout}${stderr}`, }, ], }; } case "git_pull": { const remote = (args.remote as string) || "origin"; const branch = args.branch ? ` ${args.branch}` : ""; const { stdout, stderr } = await execAsync(`git pull ${remote}${branch}`, { cwd: PROJECT_ROOT, }); return { content: [ { type: "text", text: `✅ Pulled from ${remote}:\n${stdout}${stderr}`, }, ], }; } case "git_log": { const limit = (args.limit as number) || 10; const { stdout } = await execAsync( `git log --oneline --graph --decorate -n ${limit}`, { cwd: PROJECT_ROOT } ); return { content: [ { type: "text", text: stdout || "No commits yet", }, ], }; } case "git_diff": { const file = args.file ? ` ${args.file}` : ""; const staged = args.staged ? " --staged" : ""; const { stdout } = await execAsync(`git diff${staged}${file}`, { cwd: PROJECT_ROOT, }); return { content: [ { type: "text", text: stdout || "No changes", }, ], }; } case "search_in_files": { const pattern = args.pattern as string; const searchPath = (args.path as string) || "."; const filePattern = args.filePattern ? `--include="${args.filePattern}"` : ""; try { const { stdout } = await execAsync( `grep -rn ${filePattern} "${pattern}" ${searchPath}`, { cwd: PROJECT_ROOT } ); return { content: [ { type: "text", text: stdout || "No matches found", }, ], }; } catch (error) { return { content: [ { type: "text", text: "No matches found", }, ], }; } } default: throw new Error(`Unknown tool: ${name}`); } } catch (error: any) { return { content: [ { type: "text", text: `❌ Error: ${error.message}`, }, ], isError: true, }; } }); return server; }; // SSE endpoint for MCP app.post("/sse", async (req, res) => { console.log("📡 New SSE connection request"); const server = createMCPServer(); const transport = new SSEServerTransport("/message", res); await server.connect(transport); console.log("✅ MCP Server connected via SSE"); }); // Message endpoint for SSE app.post("/message", async (req, res) => { console.log("📨 Received message:", req.body); res.json({ received: true }); }); // Start server app.listen(PORT, () => { console.log(`🚀 MCP Server running on port ${PORT}`); console.log(`📍 SSE endpoint: http://localhost:${PORT}/sse`); console.log(`📍 Health check: http://localhost:${PORT}/health`); console.log(`📁 Project root: ${PROJECT_ROOT}`); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/osamaloup/mcp-file-git-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server