Skip to main content
Glama
index.ts•5.98 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { exec } from "child_process"; import { promisify } from "util"; import * as fs from 'fs/promises'; import * as path from 'path'; const execAsync = promisify(exec); const CWD_MARKER = "CWD_MARKER_EXEC_FINISH"; const WORKSPACE_DIR = "/workspace"; // All operations will be contained here for security export default function createServer({ config }) { // Configure git on startup to use the GITHUB_PAT for all https://github.com operations. // This allows users to run `git clone/push/pull` in the terminal without manual auth. const configureGit = async () => { const token = process.env.GITHUB_PAT; if (token) { try { // This tells git: whenever you see a URL that starts with "https://github.com/", // replace it with "https://<token>@github.com/" before making the request. await execAsync(`git config --global url."https://${token}@github.com/".insteadOf "https://github.com/"`); // Set a default user for commits made from the terminal await execAsync(`git config --global user.name "PyForge User"`); await execAsync(`git config --global user.email "user@pyforge.dev"`); console.log("Git PAT and user configured globally for terminal use."); } catch (e) { console.error("Failed to configure git globally:", e); } } else { console.warn("GITHUB_PAT not set. Git operations in the terminal requiring auth may fail."); } }; configureGit(); const server = new McpServer({ name: "PyForge Bash MCP Server", version: "1.0.0", }); // Ensure workspace directory exists on startup fs.mkdir(WORKSPACE_DIR, { recursive: true }).catch(console.error); server.registerTool("run_bash_command", { title: "Run Bash Command", description: "Executes a bash command in the workspace and returns the output.", inputSchema: { command: z.string().describe("The bash command to execute."), cwd: z.string().default(WORKSPACE_DIR).describe("The current working directory to execute the command in."), }, }, async ({ command, cwd }) => { // Security: Ensure cwd is within WORKSPACE_DIR and resolve any '..' const resolvedCwd = path.resolve(WORKSPACE_DIR, path.relative(WORKSPACE_DIR, cwd)); if (!resolvedCwd.startsWith(WORKSPACE_DIR)) { return { content: [{ type: "text", text: `Error: Attempted to access directory outside of workspace.` }] }; } // Construct command to run and then print the new CWD const fullCommand = `cd "${resolvedCwd}" && ${command} && echo "${CWD_MARKER}:$(pwd)"`; try { const { stdout, stderr } = await execAsync(fullCommand, { cwd: WORKSPACE_DIR }); const output = stdout + (stderr ? `\n--- STDERR ---\n${stderr}` : ''); return { content: [{ type: "text", text: output }] }; } catch (e: any) { // 'e' from exec contains stdout and stderr which are useful for capturing output from failing commands const output = (e.stdout || '') + (e.stderr ? `\n--- STDERR ---\n${e.stderr}` : ''); const finalOutput = output.trim() ? output : `Execution failed: ${e.message}`; return { content: [{ type: "text", text: finalOutput }] }; } }); server.registerTool("github_get_status", { title: "Get Git Repository Status", description: "Checks if the workspace is a git repository and returns its status, including branch, remote URL, and changed files.", inputSchema: {}, }, async () => { try { // Check if it's a git repo. This will throw if not. await execAsync(`cd ${WORKSPACE_DIR} && git rev-parse --is-inside-work-tree`); const { stdout: branch } = await execAsync(`cd ${WORKSPACE_DIR} && git rev-parse --abbrev-ref HEAD`); const { stdout: remoteUrl } = await execAsync(`cd ${WORKSPACE_DIR} && git remote get-url origin`); const { stdout: status } = await execAsync(`cd ${WORKSPACE_DIR} && git status --porcelain`); const files = status.trim().split('\n').filter(Boolean); const response = { isRepo: true, branch: branch.trim(), remoteUrl: remoteUrl.trim(), files: files, }; return { content: [{ type: "text", text: JSON.stringify(response) }] }; } catch (e) { // If any command fails, assume it's not a git repo or it's in a bad state. return { content: [{ type: "text", text: JSON.stringify({ isRepo: false }) }] }; } }); server.registerTool("github_commit_and_push", { title: "Commit and Push to GitHub", description: "Commits all changes in the workspace and pushes them to a specified branch.", inputSchema: { branch: z.string().describe("The branch to push to."), commitMessage: z.string().describe("The commit message."), authorName: z.string().describe("The author's name for the commit."), authorEmail: z.string().describe("The author's email for the commit."), }, }, async ({ branch, commitMessage, authorName, authorEmail }) => { // The GITHUB_PAT is handled by the global git config set on startup. const commands = [ `cd ${WORKSPACE_DIR}`, `git config user.name "${authorName}"`, `git config user.email "${authorEmail}"`, 'git add .', `git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, `git push origin ${branch}` ].join(' && '); try { const { stdout, stderr } = await execAsync(commands); return { content: [{ type: "text", text: `Successfully pushed to ${branch}.\n${stdout}\n${stderr}` }] }; } catch (e: any) { return { content: [{ type: "text", text: `Commit and push failed: ${e.stderr || e.message}` }] }; } }); return server.server; }

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/pythondev-pro/egw_writings_mcp_server'

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