Skip to main content
Glama

Git MCP Server

CLAUDE.md58.1 kB
# Agent Protocol & Architectural Mandate **Version:** 2.4.1 **Target Project:** git-mcp-server **Last Updated:** 2025-10-11 This document defines the operational rules for contributing to this codebase. Follow it exactly. > **Note on File Synchronization**: This file (`AGENTS.md`), along with `CLAUDE.md` and `.clinerules/AGENTS.md`, are hard-linked on the filesystem for tool compatibility (e.g., Cline does not work with symlinks). **Edit only the root `AGENTS.md`** – changes will automatically propagate to the other copies. DO NOT TOUCH THE OTHER TWO AGENTS.md & CLAUDE.md FILES. --- ## I. Core Principles (Non‑Negotiable) 1. **The Logic Throws, The Handler Catches** - **Your Task (Logic):** - **Tools:** Implement pure, stateless business logic inside the `logic` function of a `ToolDefinition`. - **Resources:** Implement pure, stateless read logic inside the `logic` function of a `ResourceDefinition`. - **Do not add `try...catch` in these logic functions.** - **On Failure:** You must throw `new McpError(...)` with the appropriate `JsonRpcErrorCode` and context. - **Framework's Job (Handlers):** - **Tools** are wrapped by `createMcpToolHandler`, which creates the `RequestContext`, measures execution via `measureToolExecution`, formats the response, and is the only place that catches errors. - **Resources** are wrapped by `registerResource` (`resourceHandlerFactory`). The handler validates params, invokes logic, applies `responseFormatter` (defaulting to JSON), and catches errors. 2. **Full‑Stack Observability** - **Tracing:** OpenTelemetry is preconfigured. Logs and errors are automatically correlated to traces. - **Performance:** `measureToolExecution` automatically records duration, success, payload sizes, and error codes for every tool call. - **No Manual Instrumentation:** Do not add custom spans in your logic. Use the provided utilities and structured logging. The framework handles the single wrapper span per tool invocation. 3. **Structured, Traceable Operations** - Your logic functions will receive two context objects: `appContext` (for internal logging/tracing) and `sdkContext` (for SDK-level operations like Elicitation, Sampling, and Roots). - The `sdkContext` provides methods (like `elicitInput` and `createMessage`) for client interaction. - Pass the _same_ `appContext` through your internal call stack for continuity. - Use the global `logger` for all logging; include the `appContext` in every log call. 4. **Decoupled Storage** - Never directly access persistence backends (`fs`, `supabase-js`, Worker KV/R2) from tool/resource logic. - **Default: Use the `StorageService`**, injected via DI, for simple key-value persistence. - **Advanced: Create domain-specific providers** when your data has rich structure beyond key-value storage (e.g., relational queries, complex filtering, recursive loading). See **When to Create Custom Providers** below. - The concrete storage provider is configured via environment variables and initialized at startup. - **Note for git-mcp-server:** This server uses `StorageService` primarily for session state (working directory persistence). Git operations execute directly via CLI. 5. **Local ↔ Edge Runtime Parity** - All features must work with both local transports (`bun run dev:stdio`, `bun run dev:http`) and the Worker bundle (`bun run build:worker` + `bunx wrangler dev`/`deploy`). - Guard non-portable dependencies so the bundle stays edge-compatible. - Prefer runtime-agnostic abstractions (Hono + `@hono/mcp`, Fetch APIs) to keep Bun/Node on localhost identical to Cloudflare Workers. - **Note for git-mcp-server:** Git CLI operations are local-only and not compatible with edge deployment. Edge deployment should be considered experimental. 6. **Use Elicitation for Missing Input** - If a tool requires a parameter that was not provided, use the `elicitInput` function from the `sdkContext`. - This allows the tool to interactively request the necessary information from the user instead of failing. - **Note for git-mcp-server:** Elicitation is available but not currently used in git tools. All required parameters are explicitly defined in schemas. 7. **Graceful Degradation in Development** - When context values like `tenantId` are missing, default to permissive behavior instead of throwing errors. - **Pattern:** `const tenantId = appContext.tenantId || 'default-tenant';` - This aligns with the philosophy that auth/scope checks default to allowed when auth is disabled. - Production environments with auth enabled will provide real `tenantId` from JWT claims automatically. --- ## II. Architectural Overview & Directory Structure Separation of concerns maps directly to the filesystem. Always place files in their designated locations. | Directory | Purpose & Guidance | | :------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`src/mcp-server/tools/definitions/`** | **MCP Tool definitions.** Add new capabilities here as `[tool-name].tool.ts`. Follow the **Tool Development Workflow**. | | **`src/mcp-server/resources/definitions/`** | **MCP Resource definitions.** Add data sources or contexts as `[resource-name].resource.ts`. Follow the **Resource Development Workflow**. | | **`src/mcp-server/tools/utils/`** | **Shared tool utilities,** including `ToolDefinition` and tool handler factory. | | **`src/mcp-server/resources/utils/`** | **Shared resource utilities,** including `ResourceDefinition` and resource handler factory. | | **`src/mcp-server/transports/`** | **Transport implementations:**<br>- `http/` (Hono + `@hono/mcp` Streamable HTTP)<br>- `stdio/` (MCP spec stdio transport)<br>- `auth/` (strategies and helpers). HTTP mode can enforce JWT or OAuth. Stdio mode should not implement HTTP-based auth. | | **`src/services/`** | **External service integrations** following a consistent domain-driven pattern:<br>- Each service domain (e.g., `git/`, `llm/`, `speech/`) contains: `core/` (interfaces, orchestrators, factories), `providers/` (implementations), `types.ts`, and `index.ts`<br>- Use DI for all service dependencies. See **Service Development Pattern** below.<br>- **For git-mcp-server:** The `git/` service implements a provider-based architecture with CLI operations organized by domain (see **Git Service Architecture** section). | | **`src/storage/`** | **Abstractions and provider implementations** (in-memory, filesystem, supabase, cloudflare-r2, cloudflare-kv). | | **`src/container/`** | **Dependency Injection (`tsyringe`).** Service registration and tokens. | | **`src/utils/`** | **Global utilities.** Includes logging, performance, parsing, network, security, and telemetry. Note: The error handling module is located at `src/utils/internal/error-handler/`. | | **`tests/`** | **Unit/integration tests.** Mirrors `src/` for easy navigation and includes compliance suites. | --- ## III. Architectural Philosophy: Pragmatic SOLID - **Single Responsibility:** Group code that changes together. - **Open/Closed:** Prefer extension via abstractions (interfaces, plugins/middleware). - **Liskov Substitution:** Subtypes must be substitutable without surprises. - **Interface Segregation:** Keep interfaces small and focused. - **Dependency Inversion:** Depend on abstractions (DI-managed services). **Complementary principles:** - **KISS:** Favor simplicity. - **YAGNI:** Don't build what you don't need yet. - **Composition over Inheritance:** Prefer composable modules. --- ## IV. Tool Development Workflow This is the only approved workflow for authoring or modifying tools. #### Step 1 — File Location - Place new tools in `src/mcp-server/tools/definitions/`. - Name files `[tool-name].tool.ts`. - Use existing template tools as reference (e.g., `template-echo-message.tool.ts`). - **For git-mcp-server:** Git tools follow the naming pattern `git-[operation].tool.ts` (e.g., `git-commit.tool.ts`, `git-clone.tool.ts`). #### Step 2 — Define the ToolDefinition Export a single `const` named `[toolName]Tool` of type `ToolDefinition` with: - `name`: Programmatic tool name (`snake_case` is recommended). - `title` (optional): Human-readable title for UIs. - `description`: Clear, LLM-facing description of what the tool does. - `inputSchema`: A `z.object({ ... })`. **Every field must have a `.describe()`**. - `outputSchema`: A `z.object({ ... })` describing the successful output structure. - `logic`: An `async` function with the signature `(input, appContext, sdkContext) => Promise<Output>`. This function should contain pure business logic. - **No `try/catch` blocks**; throw `McpError` on failure. - **For dependencies, resolve them inside the logic function** using the global `container`. Do not use `@injectable` classes for tool logic. The framework is designed for stateless, function-based logic. - `annotations` (optional): UI/behavior hints such as `readOnlyHint`, `openWorldHint`, and others (flexible dictionary). - `responseFormatter` (optional): Map successful output to `ContentBlock[]` for the LLM to consume. **CRITICAL**: The LLM receives this formatted output, not the raw result. Include all data the LLM needs to answer questions. Balance human-readable summaries with complete structured data. If omitted, a default JSON string is used. **Git Tool Naming Convention:** ```typescript /** * Programmatic tool name (must be unique). * Naming convention for git-mcp-server: git_<operation>_<object> * - Use 'git_' prefix for all git operations * - Use lowercase snake_case * - Examples: 'git_commit', 'git_clone', 'git_status', 'git_branch' */ const TOOL_NAME = 'git_commit'; const TOOL_TITLE = 'Git Commit'; const TOOL_DESCRIPTION = 'Create a new commit with staged changes in the repository.'; ``` #### Step 2.5 — Apply Authorization (Mandatory for most tools) - Wrap `logic` with `withToolAuth`. - **Example:** ```ts import { withToolAuth } from '@/mcp-server/transports/auth/lib/withAuth.js'; // ... logic: withToolAuth(['tool:git:write'], yourToolLogic), ``` #### Step 3 — Register via Barrel Export - Add your tool to `src/mcp-server/tools/definitions/index.ts` in `allToolDefinitions`. - The DI container discovers and registers all tools from that array. No further registration is necessary. --- ### Response Formatter: JSON Output Pattern **As of v2.4.1**, all tools should use the standardized `createJsonFormatter` utility for consistent, machine-readable JSON output. #### Why JSON Formatting? 1. **Consistency & Machine Readability**: Structured JSON ensures LLMs can reliably parse tool output 2. **Verbosity Control**: Users can choose `minimal`, `standard`, or `full` detail levels 3. **Complete Context**: LLMs receive complete data needed to answer follow-up questions #### Basic Usage Pattern ```typescript import { createJsonFormatter, type VerbosityLevel, } from '../utils/json-response-formatter.js'; // 1. Define a filter function for verbosity control function filterGitStatusOutput( result: ToolOutput, level: VerbosityLevel, ): Partial<ToolOutput> { if (level === 'minimal') { return { success: result.success, branch: result.branch, }; } if (level === 'standard') { return { success: result.success, branch: result.branch, staged: result.staged, // Complete array, not truncated unstaged: result.unstaged, }; } return result; // Full - everything } // 2. Create the formatter const responseFormatter = createJsonFormatter<ToolOutput>({ filter: filterGitStatusOutput, }); // 3. Use in tool definition export const gitStatusTool: ToolDefinition<...> = { // ... responseFormatter, }; ``` #### Critical Rules for Filter Functions **✅ DO:** - Include or omit entire fields based on verbosity level - Use `shouldInclude(level, 'standard')` for conditional field inclusion - Return complete arrays when included (LLMs need full context) - Omit fields entirely at lower verbosity levels if not needed **❌ DON'T:** - Truncate arrays (breaks LLM context understanding) - Return summaries without structured data - Assume LLM can access the raw output object **Example - Field Inclusion Pattern:** ```typescript import { shouldInclude } from '../utils/json-response-formatter.js'; function filterOutput( result: ToolOutput, level: VerbosityLevel, ): Partial<ToolOutput> { return { success: result.success, commitHash: result.commitHash, // Include complete files array only at standard or higher ...(shouldInclude(level, 'standard') && { files: result.files }), // Include detailed status only at full verbosity ...(shouldInclude(level, 'full') && { detailedStatus: result.detailedStatus, }), }; } ``` #### Advanced Features The `createJsonFormatter` utility provides several helper functions: - **`filterByVerbosity<T>()`** - Field-based filtering with field lists per level - **`shouldInclude()`** - Conditional field inclusion helper - **`mergeFilters<T>()`** - Compose multiple filter functions - **`createFieldMapper<T, R>()`** - Transform/rename fields during filtering - **`createConditionalFilter<T>()`** - Apply different filters based on data properties See [`docs/updating-tool-responses.md`](docs/updating-tool-responses.md) for complete migration guide and advanced usage patterns. #### When to Skip the Filter Function If your tool doesn't need verbosity control, you can omit the filter entirely: ```typescript // Simple tools - always return full output as JSON const responseFormatter = createJsonFormatter<ToolOutput>(); ``` This is appropriate for: - Simple confirmation operations (init, fetch) - Tools with minimal output data - Operations where all data is always relevant --- #### Example Tool Definition (Git-specific with Dependency Injection): ```ts /** * @fileoverview Git status tool - shows working tree status. * @module */ import { z } from 'zod'; import { logger, type RequestContext } from '@/utils/index.js'; import { resolveWorkingDirectory } from '../utils/git-validators.js'; import type { SdkContext, ToolDefinition } from '../utils/toolDefinition.js'; import { withToolAuth } from '@/mcp-server/transports/auth/lib/withAuth.js'; import { PathSchema } from '../schemas/common.js'; import type { StorageService } from '@/storage/core/StorageService.js'; import type { GitProviderFactory } from '@/services/git/core/GitProviderFactory.js'; import { createJsonFormatter, type VerbosityLevel, } from '../utils/json-response-formatter.js'; const TOOL_NAME = 'git_status'; const TOOL_TITLE = 'Git Status'; const TOOL_DESCRIPTION = 'Show the working tree status including staged, unstaged, and untracked files.'; const TOOL_ANNOTATIONS: ToolAnnotations = { readOnlyHint: true, }; const InputSchema = z.object({ path: PathSchema, // Defaults to '.' (session working directory) }); const OutputSchema = z.object({ branch: z.string().describe('Current branch name.'), staged: z.array(z.string()).describe('Files staged for commit.'), unstaged: z.array(z.string()).describe('Files with unstaged changes.'), untracked: z.array(z.string()).describe('Untracked files.'), }); type ToolInput = z.infer<typeof InputSchema>; type ToolOutput = z.infer<typeof OutputSchema>; // Pure business logic function async function gitStatusLogic( input: ToolInput, appContext: RequestContext, _sdkContext: SdkContext, ): Promise<ToolOutput> { logger.debug('Executing git status', { ...appContext, toolInput: input, }); // Resolve working directory and get git provider via DI const { container } = await import('tsyringe'); const { StorageService: StorageServiceToken, GitProviderFactory: GitProviderFactoryToken, } = await import('@/container/tokens.js'); const storage = container.resolve<StorageService>(StorageServiceToken); const factory = container.resolve<GitProviderFactory>( GitProviderFactoryToken, ); const provider = await factory.getProvider(); // Helper handles both '.' (session) and absolute paths const targetPath = await resolveWorkingDirectory( input.path, appContext, storage, ); // Call provider's status method - it handles execution and parsing const result = await provider.status( { includeUntracked: true }, { workingDirectory: targetPath, requestContext: appContext, tenantId: appContext.tenantId || 'default-tenant', }, ); // Map provider result to tool output return { branch: result.currentBranch || 'detached HEAD', staged: [ ...(result.stagedChanges.added || []), ...(result.stagedChanges.modified || []), ...(result.stagedChanges.deleted || []), ], unstaged: [ ...(result.unstagedChanges.modified || []), ...(result.unstagedChanges.deleted || []), ], untracked: result.untrackedFiles, }; } // Filter function for verbosity control function filterGitStatusOutput( result: ToolOutput, level: VerbosityLevel, ): Partial<ToolOutput> { if (level === 'minimal') { // Essential info only return { branch: result.branch, staged: result.staged, unstaged: result.unstaged, untracked: result.untracked, }; } // Standard and full return everything // (This simple tool doesn't have additional detail levels) return result; } // Formatter using standardized JSON output const responseFormatter = createJsonFormatter<ToolOutput>({ filter: filterGitStatusOutput, }); // The final tool definition export const gitStatusTool: ToolDefinition< typeof InputSchema, typeof OutputSchema > = { name: TOOL_NAME, title: TOOL_TITLE, description: TOOL_DESCRIPTION, inputSchema: InputSchema, outputSchema: OutputSchema, annotations: TOOL_ANNOTATIONS, logic: withToolAuth(['tool:git:read'], gitStatusLogic), responseFormatter, }; ``` --- #### Working Directory Resolution Pattern (Git-Specific) **Location:** [`src/mcp-server/tools/utils/git-validators.ts`](src/mcp-server/tools/utils/git-validators.ts) Git tools need to support both explicit paths and session-based working directories. The `resolveWorkingDirectory()` helper provides this functionality. **Usage Pattern:** ```ts import { resolveWorkingDirectory } from '../utils/git-validators.js'; import type { StorageService } from '@/storage/core/StorageService.js'; import type { GitProviderFactory } from '@/services/git/core/GitProviderFactory.js'; async function myGitToolLogic( input: ToolInput, appContext: RequestContext, _sdkContext: SdkContext, ): Promise<ToolOutput> { // 1. Resolve dependencies via DI const { container } = await import('tsyringe'); const { StorageService: StorageServiceToken, GitProviderFactory: GitProviderFactoryToken, } = await import('@/container/tokens.js'); const storage = container.resolve<StorageService>(StorageServiceToken); const factory = container.resolve<GitProviderFactory>( GitProviderFactoryToken, ); const provider = await factory.getProvider(); // 2. Resolve working directory (handles '.' and absolute paths) const targetPath = await resolveWorkingDirectory( input.path, // '.' or absolute path appContext, // Request context with optional tenantId storage, // StorageService instance ); // 3. Use provider for git operations const result = await provider.status( { includeUntracked: true }, { workingDirectory: targetPath, requestContext: appContext, tenantId: appContext.tenantId || 'default-tenant', }, ); // ... rest of logic } ``` **How it Works:** 1. **Path is `'.'`:** Loads from `StorageService` using key `session:workingDir:{tenantId}` - Uses graceful degradation: `tenantId || 'default-tenant'` - Throws `ValidationError` if no session directory is set 2. **Path is absolute:** Uses the provided path directly 3. **Security:** Always sanitizes paths to prevent directory traversal attacks **Storage Key Pattern:** ``` session:workingDir:{tenantId} ``` **Common Mistakes to Avoid:** ❌ **DON'T** try to create synchronous wrappers: ```ts // BROKEN: Can't await in sync function const getWorkingDirectory = () => { return storage.get(...); // Returns Promise, not string! }; ``` ❌ **DON'T** resolve storage outside tool logic: ```ts // WRONG: StorageService requires RequestContext with tenantId const storage = container.resolve<StorageService>(StorageService); // Can't pass context here - it doesn't exist yet! ``` ✅ **DO** resolve DI inside async tool logic: ```ts // CORRECT: Async resolution inside tool logic function async function toolLogic(input, appContext, sdkContext) { const { container } = await import('tsyringe'); const storage = container.resolve<StorageService>(StorageServiceToken); const path = await resolveWorkingDirectory(input.path, appContext, storage); } ``` --- ## V. Resource Development Workflow Resources mirror the tool pattern with a declarative `ResourceDefinition`. Use existing resources as reference templates. #### Step 1 — File Location - Place new resources in `src/mcp-server/resources/definitions/`. - Name files `[resource-name].resource.ts`. - **For git-mcp-server:** The primary resource is `git-working-directory.resource.ts` which provides session context. #### Step 2 — Define the ResourceDefinition Export a single `const` of type `ResourceDefinition` with: - `name`: Unique programmatic resource name. - `title` (optional): Human-readable title for UIs. - `description`: Clear, LLM-facing description of what the resource returns. - `uriTemplate`: A template like `git://working-directory`. - `paramsSchema`: A `z.object({ ... })` for template/route params. **Every field must have a `.describe()`**. - `outputSchema` (optional): A `z.object({ ... })` describing output. - `mimeType` (optional): Default mime type for the response. - `examples` (optional): Helpful discovery samples. - `annotations` (optional): UI/behavior hints (flexible dictionary). - `list` (optional): Provides `ListResourcesResult` for discovery. - `logic`: `(uri, params, context) => { ... }` pure read logic. No `try/catch` here. Throw `McpError` on failure. - `responseFormatter` (optional): `(result, { uri, mimeType }) => contents` array. If omitted, a default JSON formatter is used. **Important:** - The handler validates params via Zod before invoking `logic`. - The `responseFormatter` must return an array of content blocks (`ReadResourceResult['contents']`). The handler performs a shallow validation (each item must be an object with a `uri`). - Resource logic can be `async`; the handler `await`s it. #### Step 2.5 — Apply Authorization - Wrap `logic` with `withResourceAuth`. - **Example:** ```ts import { withResourceAuth } from '@/mcp-server/transports/auth/lib/withAuth.js'; // ... logic: withResourceAuth(['resource:git:read'], yourResourceLogic), ``` #### Step 3 — Register via Barrel Export - Add your resource to `src/mcp-server/resources/definitions/index.ts` in `allResourceDefinitions`. - The DI container discovers and registers all resources from that array. --- ## VI. Service Development Pattern All external service integrations (LLM providers, speech services, email, etc.) follow a consistent domain-driven architecture pattern. **Note for git-mcp-server:** This server uses the service pattern for git operations through a provider-based architecture. Git operations are implemented via CLI provider with abstraction for future providers (isomorphic-git, API-based, etc.). #### Standard Service Structure Every service domain follows this organization: ``` src/services/<service-name>/ ├── core/ # Interfaces and abstractions │ ├── I<Service>Provider.ts # Provider interface contract │ └── <Service>Service.ts # (Optional) Multi-provider orchestrator ├── providers/ # Concrete implementations │ ├── <provider-name>.provider.ts │ └── ... ├── types.ts # Domain-specific types and DTOs └── index.ts # Barrel export (public API) ``` #### When to Use a Service Orchestrator Create a `<Service>Service.ts` class in `core/` when you need: - **Multi-provider orchestration** (e.g., Speech uses different providers for TTS vs STT) - **Provider routing logic** (e.g., fallback chains, load balancing) - **Capability aggregation** (e.g., combined health checks) - **Cross-provider state management** - **Complex business logic** with multi-step operations, state transformations, or conditional flows - **Stateful operations** like session management, progress tracking, or eligibility evaluation If your service uses a **single provider pattern**, skip the service class and inject the provider directly via DI. **Decision Matrix:** | Scenario | Pattern | Example | | -------------------------------------- | -------------------------------- | ------------------------------------------------ | | Simple CRUD with key-value storage | Use `StorageService` directly | User preferences, session working directory | | Single external API integration | Provider only (no service class) | Not used in git-mcp-server | | Multiple providers for same capability | Service orchestrator + Factory | Git operations (CLI, isomorphic-git planned) | | Domain operations with abstraction | Provider pattern with interface | `IGitProvider` → `CliGitProvider` implementation | #### Provider Implementation Guidelines 1. **Interface compliance**: All providers implement `I<Service>Provider` 2. **DI-injectable**: Mark with `@injectable()` decorator 3. **Health checks**: Implement `healthCheck(): Promise<boolean>` 4. **Error handling**: Throw `McpError` for failures (no try/catch in provider logic) 5. **Naming convention**: `<provider-name>.provider.ts` (lowercase, kebab-case) #### When to Create Custom Providers Create a custom provider (instead of using `StorageService`) when: - **Rich data structure:** Your domain has complex nested objects, relationships, or metadata - **Query capabilities:** You need filtering, searching, or aggregation beyond key-value lookup - **Recursive operations:** Loading hierarchical data structures (e.g., directory trees) - **Format transformation:** Reading/writing specific file formats (JSON, CSV, YAML) - **Domain-specific validation:** Type-safe loading with Zod schemas for your domain - **Cross-entity operations:** Joining data from multiple sources **For git-mcp-server:** The server uses `StorageService` for simple session state (working directory persistence). Git repository data is accessed through a provider-based architecture. ```typescript // ✅ StorageService is sufficient for git-mcp-server session state: const tenantId = appContext.tenantId || 'default-tenant'; const workingDir = await storage.get(`session:workingDir:${tenantId}`); // ✅ Git operations through provider pattern: const factory = GitProviderFactory.getInstance(); const provider = await factory.getProvider(); const status = await provider.status(options, context); ``` **When to stick with StorageService:** - Simple key-value data (session working directory, user preferences) - Flat data structures without complex relationships - Basic CRUD operations without specialized queries --- ### Tool Layer vs Service Layer: Git Operations (git-mcp-server specific) **IMPORTANT:** Tools MUST use the GitProvider interface for all git operations. Direct git command execution is forbidden in the tool layer. #### Architecture Boundary ``` ┌─────────────────────────────────────────────────┐ │ Tool Layer (MCP Tools) │ │ - Input validation (Zod schemas) │ │ - Path resolution (session storage) │ │ - Pure validators (no git execution) │ │ - Output formatting for LLM │ │ - Uses: resolveWorkingDirectory() │ │ Location: src/mcp-server/tools/ │ └─────────────────────┬───────────────────────────┘ │ ▼ GitProvider interface ┌─────────────────────────────────────────────────┐ │ Service Layer (Git Provider) │ │ - Git command execution │ │ - Git-specific validators │ │ - Output parsing │ │ - Error transformation │ │ - Uses: executeGitCommand() │ │ Location: src/services/git/ │ └─────────────────────────────────────────────────┘ ``` #### Validator Location Rules | Validator Type | Location | Reason | | -------------------------------- | --------------------------------------------- | ---------------------------------- | | **Path sanitization** | Tool layer (`git-validators.ts`) | Security, tool-specific | | **Session directory resolution** | Tool layer (`git-validators.ts`) | Uses StorageService, tool-specific | | **Protected branch checks** | Tool layer (`git-validators.ts`) | Pure logic, no git execution | | **File path validation** | Tool layer (`git-validators.ts`) | Security, no git execution | | **Commit message format** | Tool layer (`git-validators.ts`) | Pure validation, no git execution | | **Git repository validation** | Service layer (`cli/utils/git-validators.ts`) | Executes `git rev-parse` | | **Branch existence check** | Service layer (`cli/utils/git-validators.ts`) | Executes `git rev-parse --verify` | | **Clean working dir check** | Service layer (`cli/utils/git-validators.ts`) | Executes `git status --porcelain` | | **Remote existence check** | Service layer (`cli/utils/git-validators.ts`) | Executes `git remote get-url` | #### Execution Layer Consolidation As of version 2.4.1, the tool layer **no longer contains git command execution logic**. The deprecated `git-helpers.ts` file has been removed. **❌ OLD (deprecated):** ```typescript // Tool layer directly executing git commands import { execGitCommand } from '../utils/git-helpers.js'; const result = await execGitCommand('status', ['--porcelain'], { cwd, context, }); ``` **✅ NEW (required):** ```typescript // Tools delegate to service layer via GitProvider const factory = container.resolve<GitProviderFactory>(GitProviderFactoryToken); const provider = await factory.getProvider(); const result = await provider.status(options, context); ``` **Benefits:** - **Single execution path** - Easier to maintain, debug, and secure - **Better abstraction** - Tools don't know if git is CLI, isomorphic, or API-based - **Easier testing** - Mock `IGitProvider` interface instead of git commands - **Consistent error handling** - All git errors mapped to `McpError` in one place --- ### Git Service Architecture (git-mcp-server specific) The git service follows a **provider-based architecture** with clear separation between interface, implementations, and operations. #### Architecture Layers ``` src/services/git/ ├── core/ # Abstractions and coordination │ ├── IGitProvider.ts # Provider interface (contract) │ ├── BaseGitProvider.ts # Shared provider functionality │ └── GitProviderFactory.ts # Provider selection and caching ├── providers/ # Concrete implementations │ ├── cli/ # CLI-based provider (current) │ │ ├── operations/ # Organized git operations │ │ ├── utils/ # CLI-specific utilities │ │ ├── CliGitProvider.ts # Main provider class │ │ └── index.ts │ └── isomorphic/ # Isomorphic-git provider (planned) │ ├── operations/ │ └── ... ├── types.ts # Shared git types and DTOs └── index.ts # Public API barrel export ``` #### CLI Operations Organization The CLI provider organizes git operations by **domain** for better maintainability: ``` src/services/git/providers/cli/operations/ ├── core/ # Repository fundamentals │ ├── init.ts # Initialize repository │ ├── clone.ts # Clone repository │ ├── status.ts # Working tree status │ └── clean.ts # Remove untracked files ├── staging/ # Working tree → Index │ ├── add.ts # Stage changes │ └── reset.ts # Unstage/reset ├── commits/ # Commit history │ ├── commit.ts # Create commits │ ├── log.ts # View history │ ├── show.ts # Show objects │ └── diff.ts # Show changes ├── branches/ # Branch operations │ ├── branch.ts # List/create/delete │ ├── checkout.ts # Switch branches │ ├── merge.ts # Merge branches │ ├── rebase.ts # Rebase branches │ └── cherry-pick.ts # Cherry-pick commits ├── remotes/ # Remote operations │ ├── remote.ts # Manage remotes │ ├── fetch.ts # Download changes │ ├── push.ts # Upload changes │ └── pull.ts # Fetch + integrate ├── tags/ # Tag operations │ └── tag.ts # List/create/delete tags ├── stash/ # Stash operations │ └── stash.ts # Push/pop/apply/drop/clear ├── worktree/ # Worktree operations │ └── worktree.ts # Add/list/remove worktrees ├── history/ # History inspection │ ├── blame.ts # Line-by-line authorship │ └── reflog.ts # Reference logs └── index.ts # Single barrel export (root only) ``` **Key Design Principles:** 1. **Logical Grouping**: Operations grouped by domain (core, staging, commits, remotes, etc.) 2. **Single Responsibility**: Each file handles exactly one operation (one function per file) 3. **Consistent Structure**: All categories use subdirectories, no mixed patterns 4. **Single Import Point**: All exports consolidated in root `index.ts` (no nested barrel files) 5. **Pure Functions**: Each operation is a stateless async function that throws `McpError` on failure **Operation Function Signature:** ```typescript export async function executeOperation( options: GitOperationOptions, context: GitOperationContext, execGit: ( args: string[], cwd: string, ctx: RequestContext, ) => Promise<{ stdout: string; stderr: string }>, ): Promise<GitOperationResult> { // Pure business logic - no try/catch // Throw McpError on failure } ``` #### Provider Selection via Factory The `GitProviderFactory` handles provider instantiation and selection: ```typescript const factory = GitProviderFactory.getInstance(); const provider = await factory.getProvider({ preferredType: GitProviderType.CLI, isServerless: false, requiredCapabilities: ['blame', 'reflog'], }); // Provider is cached - subsequent calls return same instance const status = await provider.status(options, context); ``` **Provider Types:** - **CLI** (`GitProviderType.CLI`): Full feature set, local-only (default) - **Isomorphic** (`GitProviderType.ISOMORPHIC`): Core features, edge-compatible (planned) - **GitHub API** (`GitProviderType.GITHUB_API`): Cloud-based, GitHub-specific (future) - **GitLab API** (`GitProviderType.GITLAB_API`): Cloud-based, GitLab-specific (future) #### IGitProvider Interface All providers must implement the `IGitProvider` interface, which defines: - **Repository operations**: init, clone, status, clean - **Commit operations**: add, commit, log, show, diff - **Branch operations**: branch, checkout, merge, rebase, cherryPick - **Remote operations**: remote, fetch, push, pull - **Tag operations**: tag (list/create/delete) - **Stash operations**: stash (push/pop/apply/drop/clear) - **Worktree operations**: worktree (add/list/remove/move/prune) - **Additional operations**: reset, blame, reflog Each provider declares its **capabilities** through the `GitProviderCapabilities` interface, allowing consumers to check feature support before calling methods. #### BaseGitProvider Utilities The `BaseGitProvider` abstract class provides shared functionality: - **Capability checking**: `checkCapability(capability)` throws if unsupported - **Logging helpers**: `logOperationStart()`, `logOperationSuccess()` - **Validation**: `validateWorkingDirectory()`, `createOperationContext()` - **Error handling**: `extractErrorMessage()`, `isGitNotFoundError()` --- ## VII. Core Services & Utilities #### DI-Managed Services (tokens in `src/container/tokens.ts`) **Services Used in git-mcp-server:** - **`StorageService`** - **Token:** `StorageService` - **Usage:** `@inject(StorageService) private storage: StorageService` - **Purpose:** Session state (working directory persistence) - **Note:** Requires `context.tenantId`; `StorageService` enforces presence and throws if missing. - **`Logger`** (pino-backed singleton) - **Token:** `Logger` - **Usage (in injectable classes):** `@inject(Logger) private logger: typeof logger` - **Purpose:** Structured logging for all operations - **`App Config`** - **Token:** `AppConfig` - **Usage:** `@inject(AppConfig) private config: typeof configModule` - **Purpose:** Access to validated environment configuration - **`RateLimiter`** - **Token:** `RateLimiterService` - **Usage:** `@inject(RateLimiterService) private rateLimiter: RateLimiter` - **Purpose:** Optional rate limiting for HTTP transport - **`CreateMcpServerInstance`** (factory function) - **Token:** `CreateMcpServerInstance` - **Usage:** Resolved by the `TransportManager` to create/configure the `McpServer`. - **`TransportManager`** - **Token:** `TransportManagerToken` - **Usage:** `@inject(TransportManagerToken) private transportManager: TransportManager` - **Purpose:** Manages stdio/HTTP transport lifecycle **Services NOT Used in git-mcp-server:** The following services are available in the template but not used in this project: - ❌ `ILlmProvider` (no LLM integration needed) - ❌ `SupabaseAdminClient` (not used for git operations) - ❌ Speech services (not needed) #### Storage Providers (configured in `src/storage/core/storageFactory.ts`) - Supported values (env `STORAGE_PROVIDER_TYPE`): - `in-memory` (default) - **Recommended for git-mcp-server** - `filesystem` (requires `STORAGE_FILESYSTEM_PATH`, Node only) - `supabase` (requires `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE_KEY`) - `cloudflare-r2` (Worker-only) - `cloudflare-kv` (Worker-only) - In serverless environments (Workers), non-Cloudflare providers are forced to `in-memory`. - **Always use `StorageService` from DI to interact with storage.** #### Directly Imported Utilities (for function-style logic) - `logger` from `src/utils/index.js` - `requestContextService` from `src/utils/index.js` - `ErrorHandler.tryCatch` from `src/utils/index.js` (NOT in tool/resource logic; OK in services or setup code) - `sanitization` from `src/utils/index.js` - **Critical for git-mcp-server to prevent path traversal** - `fetchWithTimeout` from `src/utils/index.js` (for robust network calls with timeouts) - `measureToolExecution` from `src/utils/index.js` (used by handlers) #### Key Utilities (`src/utils/`) The `src/utils/` directory contains a rich set of directly importable utilities for common tasks. | Module | Description & Key Exports (git-mcp-server specific) | | :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **`parsing/`** | Robust parsers for various data formats. <br>- `jsonParser`: For parsing git command JSON output. <br>- `yamlParser`: For Git config files. <br>- Not typically needed: `csvParser`, `xmlParser`, `pdfParser`. | | **`security/`** | Security utilities - **CRITICAL for git operations**. <br>- `sanitization`: **MANDATORY** for validating file paths and preventing directory traversal. <br>- `rateLimiter`: Optional DI-managed service for enforcing rate limits. <br>- `idGenerator`: For creating unique identifiers (request IDs, session IDs). | | **`network/`** | Networking helpers (not typically used in git-mcp-server). <br>- `fetchWithTimeout`: Available if needed for remote git operations. | | **`scheduling/`** | Task scheduling (not typically used in git-mcp-server). <br>- `scheduler`: Available but git operations are on-demand. | | **`internal/`** | Core internal machinery. <br>- `logger`: The global Pino logger instance - **use for all logging**. <br>- `requestContextService`: AsyncLocalStorage-based context propagation. <br>- `ErrorHandler`: Centralized error handling. <br>- `performance`: Utilities for performance measurement, including `measureToolExecution`. | | **`telemetry/`** | OpenTelemetry instrumentation and tracing helpers. | --- ## VIII. Authentication & Authorization #### HTTP Transport (configurable) - **Modes:** `MCP_AUTH_MODE` = `'none' | 'jwt' | 'oauth'` - When not `'none'`, the HTTP `/mcp` endpoint requires a `Bearer` token: - **JWT mode** uses a local secret (`MCP_AUTH_SECRET_KEY`). - In production, the secret is required; startup fails otherwise. - In development without the secret, verification is bypassed for template usability and a dev-mode `AuthInfo` is provided using `DEV_MCP_CLIENT_ID` and `DEV_MCP_SCOPES` (or sane defaults). - **OAuth mode** verifies JSON Web Tokens via a remote JWKS: - Requires `OAUTH_ISSUER_URL` and `OAUTH_AUDIENCE`; optionally `OAUTH_JWKS_URI`. - **Extracted claims:** - `clientId`: token claim `'cid'` or `'client_id'` - `scopes`: array claim `'scp'` or space-delimited string `'scope'` - `subject`: `'sub'` (optional) - `tenantId`: `'tid'` (optional; if present, it becomes `context.tenantId` via `requestContextService`) - **Scope enforcement inside logic:** - **Always wrap tool/resource logic with `withToolAuth` or `withResourceAuth`.** - If no auth context exists (e.g., auth disabled), the scope check defaults to allowed for development usability. **Recommended scopes for git-mcp-server:** - `tool:git:read` - For read-only operations (status, log, diff, show) - `tool:git:write` - For write operations (commit, push, tag, branch create) - `resource:git:read` - For resource access (working directory) #### STDIO Transport - Follows MCP spec guidance: no HTTP-based auth flows over stdio. - Authorization is expected to be handled by the host application controlling the process. #### CORS and Endpoints - CORS is enabled with allowed origins from `MCP_ALLOWED_ORIGINS` or `'*'` as fallback. - `GET /healthz`: unprotected health endpoint. - `GET /mcp`: unprotected endpoint returning server identity and config summary. - `POST`/`OPTIONS` `/mcp`: JSON-RPC transport; protection enforced when auth mode is not `'none'`. --- ## IX. Transports & Server Lifecycle #### `createMcpServerInstance` (`src/mcp-server/server.ts`) - Initializes `RequestContext` global config. - Creates `McpServer` with identity and capabilities (logging, `resources/tools listChanged`, **elicitation**, **sampling**, **prompts**, **roots**). - Registers all capabilities via DI-managed registries. - Returns a configured `McpServer`. #### `TransportManager` (`src/mcp-server/transports/manager.ts`) - Resolves the `CreateMcpServerInstance` factory to get a configured `McpServer`. - Based on `MCP_TRANSPORT_TYPE`, it instantiates and manages the lifecycle of the appropriate transport (`http` or `stdio`). - Handles graceful startup and shutdown of the active transport. #### Worker (Edge) - `worker.ts` adapts the same `McpServer` and Hono app to Cloudflare Workers. - Sets a `serverless` flag to guide storage provider selection. - Uses `requestContextService` and `logger` for structured, traceable startup. - **Note for git-mcp-server:** Edge deployment is experimental. Git CLI operations require local filesystem access. --- ## X. Code Style, Validation, and Security - **JSDoc:** Every file must start with `@fileoverview` and `@module`. Exported APIs must be documented. - **Validation:** All inputs are validated via Zod schemas. Ensure every field in schemas has a `.describe()`. - **Logging:** Always include `RequestContext`; use `logger.debug/info/notice/warning/error/crit/emerg` appropriately. - **Error Handling:** Logic throws `McpError`; handlers catch and standardize. Use `ErrorHandler.tryCatch` in services/infrastructure (not in tool/resource logic). - **Secrets:** Access secrets only through `src/config/index.ts`. Never hard-code credentials. - **Rate Limiting:** Use DI-injected `RateLimiter` where needed. - **Telemetry:** Instrumentation is auto-initialized when enabled. Avoid manual spans. **Git-Specific Security Requirements:** - **Path Sanitization:** ALL file paths and repository paths MUST be validated using `sanitization` utilities to prevent directory traversal attacks. - **Command Injection Prevention:** Git command arguments must be validated and never constructed from unsanitized user input. - **Working Directory Validation:** Verify working directory exists and is a valid git repository before operations. - **Destructive Operation Protection:** Operations like `git reset --hard`, `git clean -fd` must require explicit confirmation flags. --- ## XI. Checks & Workflow Commands Use scripts from `package.json`: - `bun rebuild`: cleans and rebuilds; also clears logs. Run after dependency changes. - `bun devcheck` or `bun run devcheck`: lint, format, typecheck, security. Use flags like `--no-fix`, `--no-lint`, `--no-audit` to tailor. - `bun test`: run unit/integration tests. - `bun run dev:stdio` / `bun run dev:http`: run server in development mode. - `bun run start:stdio` / `bun run start:http`: run after build. - `bun run build:worker`: build Cloudflare Worker bundle. --- ## XII. Configuration & Environment - All configuration is validated via Zod in `src/config/index.ts`. - Derives `serviceName` and `version` from `package.json` if not provided via env. - **Key variables:** - **Transport:** `MCP_TRANSPORT_TYPE` (`'stdio'`|`'http'`), `MCP_HTTP_PORT/HOST/PATH` - **Auth:** `MCP_AUTH_MODE` (`'none'`|`'jwt'`|`'oauth'`), `MCP_AUTH_SECRET_KEY` (jwt), `OAUTH_*` (oauth) - **Storage:** `STORAGE_PROVIDER_TYPE` (`'in-memory'`|`'filesystem'`|`'supabase'`|`'cloudflare-r2'`|`'cloudflare-kv'`) - **Git-Specific:** - `GIT_SIGN_COMMITS` (`'true'`|`'false'`) - Enable GPG/SSH commit signing - `GIT_WRAPUP_INSTRUCTIONS_PATH` - Path to custom workflow instructions markdown file - **Telemetry:** `OTEL_ENABLED`, `OTEL_SERVICE_NAME`, `OTEL_SERVICE_VERSION`, `OTEL_EXPORTER_OTLP_*` --- ## XIII. Local & Edge Targets - **Local parity:** Ensure both stdio and HTTP transports run and behave identically for your feature. - **Worker compatibility:** `bun run build:worker` and `wrangler dev --local` must succeed before merging. - `wrangler.toml` should use a `compatibility_date` of `2025-09-01` or later and `nodejs_compat` enabled. - **Git-specific limitation:** Git CLI operations require local filesystem access and are not compatible with edge deployment in their current form. --- ## XIV. Multi-Tenancy & Storage Context ### Storage Tenancy Requirements **`StorageService` requires `context.tenantId`** and will throw `McpError` with `JsonRpcErrorCode.ConfigurationError` if it's missing. ### Automatic Tenancy (HTTP Transport with Auth) When using HTTP transport with authentication enabled (`MCP_AUTH_MODE='jwt'` or `'oauth'`): - The `tenantId` is automatically extracted from the JWT token claim `'tid'` - It's propagated to `RequestContext` via `requestContextService.withAuthInfo()` - All tool/resource invocations automatically receive the correct `tenantId` ### TenantID Handling in Tools **For tools that use `StorageService` or other tenant-scoped services:** Follow the graceful degradation pattern to support both development and production: ```typescript async function myToolLogic( input: ToolInput, appContext: RequestContext, _sdkContext: SdkContext, ): Promise<ToolOutput> { // ✅ Graceful degradation: default to 'default-tenant' in development const tenantId = appContext.tenantId || 'default-tenant'; const service = container.resolve<MyService>(MyServiceToken); const result = await service.doSomething(input.param, tenantId); return result; } ``` **Why this pattern?** - **Development (STDIO/no auth):** Works out-of-the-box without configuration - **Production (HTTP + auth):** Real `tenantId` from JWT automatically available - **Aligns with template philosophy:** Permissive in development, strict in production **Alternative - Explicit Tenant Check:** ```typescript // ❌ Don't throw errors for missing tenantId - breaks development experience if (!appContext.tenantId) { throw new McpError(JsonRpcErrorCode.InvalidRequest, 'Tenant ID required'); } // ✅ Use default instead const tenantId = appContext.tenantId || 'default-tenant'; ``` **When to use explicit tenant checking:** - Security-critical operations where you must verify tenant isolation - Production-only tools that should never run in development mode - Audit trails where the actual tenant must be logged **Troubleshooting:** - **Error:** `"Storage operation requires a tenantId in the request context"` - **Cause:** Tool passed `undefined` to a service expecting `tenantId` - **Solution:** Apply the graceful degradation pattern: `const tenantId = appContext.tenantId || 'default-tenant';` --- ## XV. Quick Checklist Before completing your task, ensure you have: - [ ] Implemented tool/resource logic in a `*.tool.ts` or `*.resource.ts` file. - [ ] Kept `logic` functions pure (no `try...catch`). - [ ] Thrown `McpError` for failures within logic. - [ ] Applied authorization with `withToolAuth` or `withResourceAuth`. - [ ] Used `logger` with `appContext` for all significant operations. - [ ] Used `StorageService` (DI) for session persistence (working directory). - [ ] **Validated all file paths** using `sanitization` utilities (git-specific). - [ ] **Prevented command injection** by validating git command arguments (git-specific). - [ ] Registered definitions in the corresponding `index.ts` barrel files (Tools, Resources). - [ ] Added or updated tests (`bun test`). - [ ] Ran `bun run devcheck` to ensure code quality. - [ ] Smoke-tested local transports (`bun run dev:stdio`/`http`). - [ ] Validated the Worker bundle (`bun run build:worker`) if applicable. That's it. Follow these guidelines to ensure consistency, security, and maintainability across the git-mcp-server codebase.

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/cyanheads/git-mcp-server'

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