Skip to main content
Glama
status.ts5.87 kB
import type {McpServer, ToolCallback} from '@modelcontextprotocol/sdk/server/mcp.js'; import type {Options} from 'simple-git'; import {simpleGit} from 'simple-git'; import {z} from 'zod'; import type {ToolConfig} from '../types.js'; // Git status input schema constant export const GIT_STATUS_INPUT_SCHEMA = { repoPath: z.string().describe('Absolute path to the git repository'), short: z.boolean().optional().describe('Give the output in the short-format (-s, --short)'), branch: z.boolean().optional().describe('Show the branch and tracking info even in short-format (-b, --branch)'), showStash: z.boolean().optional().describe('Show the number of entries currently stashed away (--show-stash)'), long: z.boolean().optional().describe('Give the output in the long-format (--long, default)'), verbose: z .union([z.boolean(), z.number().int().min(0).max(2)]) .optional() .describe('Show textual changes that are staged to be committed (-v, --verbose, can be specified twice)'), untrackedFiles: z .union([z.boolean(), z.enum(['no', 'normal', 'all'])]) .optional() .describe('Show untracked files (-u[<mode>], --untracked-files[=<mode>])'), ignoreSubmodules: z .enum(['none', 'untracked', 'dirty', 'all']) .optional() .describe('Ignore changes to submodules (--ignore-submodules[=<when>])'), ignored: z .union([z.boolean(), z.enum(['traditional', 'no', 'matching'])]) .optional() .describe('Show ignored files as well (--ignored[=<mode>])'), aheadBehind: z .boolean() .optional() .describe('Display detailed ahead/behind counts relative to upstream branch (--ahead-behind, --no-ahead-behind)'), renames: z.boolean().optional().describe('Turn on/off rename detection (--renames, --no-renames)'), findRenames: z .union([z.boolean(), z.number().int().min(0).max(100)]) .optional() .describe('Turn on rename detection, optionally set similarity threshold (--find-renames[=<n>])'), column: z .union([z.boolean(), z.string()]) .optional() .describe('Display untracked files in columns (--column[=<options>], --no-column)'), pathspec: z.array(z.string()).optional().describe('Limit the output to the given paths'), }; /** * Git Status Tool * Provides git status functionality for MCP */ export class GitStatusTool { readonly config: ToolConfig<typeof GIT_STATUS_INPUT_SCHEMA, never> = { description: 'Get the current git repository status.', inputSchema: GIT_STATUS_INPUT_SCHEMA, annotations: { title: 'Status', readOnlyHint: true, }, }; get name() { return 'status'; } register(srv: McpServer) { srv.registerTool(this.name, this.config, this.#handle); } /** * Transform status input parameters to git command options */ // eslint-disable-next-line complexity public inputToOptions(input: z.infer<z.ZodObject<typeof GIT_STATUS_INPUT_SCHEMA>>) { const options: Options = {}; if (input.short) { options['--short'] = null; } if (input.branch) { options['--branch'] = null; } if (input.showStash) { options['--show-stash'] = null; } if (input.long) { options['--long'] = null; } if (input.verbose !== undefined) { if (typeof input.verbose === 'boolean' && input.verbose) { options['--verbose'] = null; } else if (typeof input.verbose === 'number') { // For numeric verbose levels, we can use -v multiple times // Create array of -v flags for the specified level const verboseFlags = Array.from({length: input.verbose}, () => '-v'); options['-v'] = verboseFlags; } } if (input.untrackedFiles !== undefined) { if (typeof input.untrackedFiles === 'boolean') { options['--untracked-files'] = input.untrackedFiles ? null : 'no'; } else { options['--untracked-files'] = input.untrackedFiles; } } if (input.ignoreSubmodules) { options['--ignore-submodules'] = input.ignoreSubmodules; } if (input.ignored !== undefined) { if (typeof input.ignored === 'boolean') { if (input.ignored) { options['--ignored'] = null; } else { options['--no-ignored'] = null; } } else { options['--ignored'] = input.ignored; } } if (input.aheadBehind !== undefined) { if (input.aheadBehind) { options['--ahead-behind'] = null; } else { options['--no-ahead-behind'] = null; } } if (input.renames !== undefined) { if (input.renames) { options['--renames'] = null; } else { options['--no-renames'] = null; } } if (input.findRenames !== undefined) { if (typeof input.findRenames === 'boolean') { if (input.findRenames) { options['--find-renames'] = null; } else { options['--no-find-renames'] = null; } } else { options['--find-renames'] = input.findRenames; } } if (input.column !== undefined) { if (typeof input.column === 'boolean') { if (input.column) { options['--column'] = null; } else { options['--no-column'] = null; } } else { options['--column'] = input.column; } } // Add pathspec - for status, pathspec can be added as additional arguments if (input.pathspec && input.pathspec.length > 0) { // Simple-git handles pathspec as additional arguments // We'll add them as an array to be spread into the command options['--'] = input.pathspec; } return options; } readonly #handle: ToolCallback<typeof GIT_STATUS_INPUT_SCHEMA> = async (input) => { const sg = simpleGit(input.repoPath); const isRepo = await sg.checkIsRepo(); if (!isRepo) { return { isError: true, content: [ { type: 'text', text: 'Not a git repository', }, ], }; } const status = await sg.status(this.inputToOptions(input)); return { content: [ { type: 'text', text: 'Git status retrieved successfully', }, { type: 'text', text: JSON.stringify(status), }, ], }; }; }

Implementation Reference

Latest Blog Posts

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/ver0-project/mcps'

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