DEV Community

Cover image for Building an MCP Server for Git-Based AI Memory: Lessons from ShadowGit
Alessandro A.
Alessandro A.

Posted on

Building an MCP Server for Git-Based AI Memory: Lessons from ShadowGit

Building an MCP Server for Git-Based AI Memory: Lessons from ShadowGit

The Problem

Every time you start a new Claude conversation, you paste your entire codebase. Again. The AI has no memory of what you worked on yesterday, what solutions failed, or what actually fixed that bug last week.

I built ShadowGit to solve this: automatic code snapshots with an MCP server that gives AI assistants searchable history. Here's what I learned.

The Technical Approach

Auto-Commit System

The polling orchestrator checks for changes every 5 seconds (configurable):

// From polling-orchestrator.ts
private pollingInterval: number = 5000; // Default 5 seconds

const scheduleNextPoll = async () => {
  if (!this.isPollingInProgress) {
    await this.pollAllRepositories();
  }
  this.pollingTimer = setTimeout(scheduleNextPoll, this.pollingInterval);
};
Enter fullscreen mode Exit fullscreen mode

This creates a .shadowgit.git repository parallel to your main repo - a proper git repository, not just a folder:

// Commit messages are intelligent, not just timestamps
generateMessage(repoPath, shadowgitDir, changedFiles, totalLines, isAISession) {
  const timestamp = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '');

  if (isAISession) {
    return `๐Ÿค– AI-Assisted Changes @ ${timestamp}
    ๐Ÿ”ง AI Tool Session Detected
    ๐Ÿ“ Types: ${fileTypes.join(', ')}
    ๐Ÿ“ Impact: ${totalLines} lines changed`;
  }
  // Regular commits have different format
}
Enter fullscreen mode Exit fullscreen mode

MCP Server Implementation

The MCP server exposes git operations through a clean protocol:

{
  name: 'git_command',
  description: 'Execute a read-only git command on a ShadowGit repository',
  inputSchema: {
    type: 'object',
    properties: {
      repo: { type: 'string', description: 'Repository name' },
      command: { type: 'string', description: 'Git command to execute' }
    },
    required: ['repo', 'command']
  }
}
Enter fullscreen mode Exit fullscreen mode

Now Claude can run:

  • git_command({repo: "my-app", command: "diff HEAD~5"}) - See recent changes
  • git_command({repo: "my-app", command: "log --grep='auth'"}) - Search commits
  • git_command({repo: "my-app", command: "show HEAD~10:src/file.ts"}) - Retrieve old versions

The GitExecutor validates every command for safety:

// Security: Only allow safe read-only commands
private readonly ALLOWED_COMMANDS = [
  'log', 'diff', 'show', 'status', 'ls-files', 'blame', 'shortlog'
];

private readonly BLOCKED_ARGS = [
  '--git-dir', '--work-tree', '-C', // Prevent directory traversal
  'push', 'pull', 'fetch', 'merge', 'rebase' // No modifications
];
Enter fullscreen mode Exit fullscreen mode

Session Management

When AI actively works on code, you don't want fragmented commits every 5 seconds. The session system is elegant:

// MCP tools for session management
{
  name: 'start_session',
  description: 'Start a work session. MUST be called BEFORE making changes',
  inputSchema: {
    properties: {
      repo: { type: 'string' },
      description: { type: 'string' }
    }
  }
}

// Session handler communicates with the ShadowGit app
async startSession(args) {
  const sessionId = await this.sessionClient.startSession({
    repoPath,
    aiTool: 'MCP Client',
    description: args.description
  });

  // ShadowGit pauses auto-commits for this repo
  // Returns session ID for later use
  return `Session started: ${sessionId}`;
}
Enter fullscreen mode Exit fullscreen mode

The SessionManager tracks active sessions with SQLite:

// Session storage with automatic expiration
class SessionManager {
  private readonly SESSION_EXPIRY_MS = 60 * 60 * 1000; // 1 hour

  async createSession(repoPath, tool, description) {
    const session = {
      id: uuidv4(),
      repoPath,
      tool,
      startTime: Date.now(),
      expiresAt: Date.now() + this.SESSION_EXPIRY_MS
    };
    // Store in SQLite for persistence
  }
}
Enter fullscreen mode Exit fullscreen mode

Architecture Deep Dive

The Two-Component Design

  1. ShadowGit App (Electron) - Handles file watching, auto-commits, UI
  2. MCP Server (Node.js) - Provides AI interface to git history

They communicate through a local HTTP API on port 45682:

// Session API runs locally
const SESSION_API_PORT = 45682;

// MCP server checks if session API is running
const response = await fetch(`http://localhost:${SESSION_API_PORT}/api/health`);
Enter fullscreen mode Exit fullscreen mode

Smart Commit Decisions

Not every file change needs a commit. The CommitDecisionEngine is sophisticated:

shouldCommit(repoPath: string, stats: ChangeStats): boolean {
  const timeSinceLastCommit = Date.now() - this.lastCommitTime;

  // During AI sessions, different rules apply
  if (this.hasActiveSession(repoPath)) {
    return false; // Let AI control commits via checkpoint()
  }

  // Smart batching for regular work
  if (stats.totalLines > 100 || timeSinceLastCommit > 30000) {
    return true;
  }

  // File-type specific rules...
}
Enter fullscreen mode Exit fullscreen mode

Results

The technical implementation works beautifully. Real metrics from usage:

  • Token reduction: fewer tokens when AI uses git diff vs reading files
  • Debug speed: Finding breaking changes takes seconds, not multiple prompts
  • History depth: Typical repos accumulate 200-500 commits per day

What I Learned

The Hard Truth About Developer Tools

  1. Simple defaults matter - 5-second polling feels instant, but 15 seconds feels laggy. User perception is everything.

  2. Open source strategy - Released the MCP server open source. This killed direct monetization but created goodwill and adoption.

  3. AI integration is the future - MCP protocol is powerful. Every developer tool should consider AI-first interfaces.

Technical Insights

Building this taught me:

  • Git as a database - Using git for temporal queries is incredibly powerful
  • TypeScript everywhere - Full type safety from Electron to MCP made refactoring painless
  • Session complexity - Managing state between two processes (Electron + MCP) requires careful design

What Actually Matters

After building this, the key lessons:

  • Performance > Features - 5-second commits beat 15-second commits with better messages
  • Security first - Blocking git write operations was non-negotiable
  • Local-only - Developers won't trust tools that upload code

Open Source

The MCP server is available at: https://github.com/blade47/shadowgit-mcp

Real implementation is ~2000 lines of TypeScript across multiple modules. The architecture supports:

  • Multiple repositories
  • Concurrent sessions
  • Safe git operations
  • Automatic cleanup

Would I Build It Again?

Yes, but I'd:

  1. Start with MCP only - The Electron app added complexity
  2. Focus on metrics - Track token savings, debug time reduction

Key Takeaways

  • MCP protocol is production-ready - Robust enough for real applications
  • Git + AI is powerful - Historical context dramatically improves AI assistance
  • Complexity kills adoption - Simpler architecture would have been better
  • Local-first wins - Developers value privacy over features

The code is MIT licensed. Sometimes the best outcome isn't a business, but a tool that makes a few developers' lives easier.


Technical Stack: TypeScript, Electron, MCP SDK, SQLite, Node.js

Tags: #mcp #claude #git #ai #typescript #electron

Top comments (0)