import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import fs from 'node:fs';
import path from 'node:path';
import os from 'os';
import { spawn } from 'child_process';
// Type definitions for MCP responses
type McpResponse = {
content: Array<{
type: string;
text: string;
}>;
};
// Helper function to create a temporary directory
function createTempDir(): string {
const tempDir = path.join(os.tmpdir(), `mcp-git-test-${Date.now()}`);
fs.mkdirSync(tempDir);
return tempDir;
}
// Helper function to clean up temporary directory
function cleanupTempDir(dir: string): void {
if (fs.existsSync(dir)) {
fs.rmSync(dir, { recursive: true, force: true });
}
}
describe('MCP Git Server Tests', () => {
let tempDir: string;
let testRepoPath: string;
let client: Client;
let serverProcess: any;
const TEST_REPO_URL = 'https://github.com/octocat/Hello-World.git';
const TEST_BRANCH = 'test-branch';
beforeAll(async () => {
// Create test directory
tempDir = createTempDir();
testRepoPath = path.join(tempDir, 'test-repo');
// Create MCP client with transport
const transport = new StdioClientTransport({
command: "node",
args: ["dist/index.js"]
});
// Create MCP server with transport
client = new Client({
name: "git-server-test",
version: "0.1.0"
});
// Connect to MCP server
await client.connect(transport);
});
afterAll(async () => {
// Cleanup
cleanupTempDir(tempDir);
await client.close();
});
describe('Git Operations Sequence', () => {
it('should clone a repository', async () => {
// Clone repository using MCP tool
const result = await client.callTool({
name: 'git_clone',
arguments: {
repository: TEST_REPO_URL,
directory: testRepoPath
}
}) as McpResponse;
// check if the directory exists
expect(fs.existsSync(testRepoPath)).toBe(true);
expect(fs.existsSync(path.join(testRepoPath, '.git'))).toBe(true);
expect(fs.existsSync(path.join(testRepoPath, 'README'))).toBe(true);
});
it('should create a new branch', async () => {
// Create and checkout new branch using MCP tool
const result = await client.callTool({
name: 'git_checkout',
arguments: {
repository_path: testRepoPath,
branch: TEST_BRANCH,
create: true
}
}) as McpResponse;
expect(result.content[0].text).toContain('Switched to a new branch');
// Verify branch exists using MCP tool
const branchResult = await client.callTool({
name: 'git_branch',
arguments: {
repository_path: testRepoPath
}
}) as McpResponse;
expect(branchResult.content[0].text).toContain(TEST_BRANCH);
});
it('should be able to make changes and commit', async () => {
// Create a test file
const testFile = path.join(testRepoPath, 'test.txt');
fs.writeFileSync(testFile, 'Hello, World!');
// Add file using MCP tool
await client.callTool({
name: 'git_add',
arguments: {
repository_path: testRepoPath,
files: ['test.txt']
}
});
// Commit changes using MCP tool
const commitResult = await client.callTool({
name: 'git_commit',
arguments: {
repository_path: testRepoPath,
message: 'Add test file'
}
}) as McpResponse;
expect(commitResult.content[0].text).toContain('Add test file');
// Verify file exists
expect(fs.existsSync(testFile)).toBe(true);
// Check commit history using MCP tool
const logResult = await client.callTool({
name: 'git_log',
arguments: {
repository_path: testRepoPath,
count: 1
}
}) as McpResponse;
expect(logResult.content[0].text).toContain('Add test file');
});
it('should be able to list remotes', async () => {
const result = await client.callTool({
name: 'git_remote',
arguments: {
repository_path: testRepoPath,
action: 'list'
}
}) as McpResponse;
expect(result.content[0].text).toContain('origin');
});
it('should handle errors gracefully', async () => {
// Test invalid repository path
const result = await client.callTool({
name: 'git_clone',
arguments: {
repository: 'invalid-url',
directory: testRepoPath
}
}) as McpResponse;
expect(result.content[0].text).toContain('Error:');
expect(result.content[0].text).toContain('repository \'invalid-url\' does not exist');
// Test invalid branch name
const checkoutResult = await client.callTool({
name: 'git_checkout',
arguments: {
repository_path: testRepoPath,
branch: 'invalid-branch'
}
}) as McpResponse;
expect(checkoutResult.content[0].text).toContain('Error:');
expect(checkoutResult.content[0].text).toContain('pathspec \'invalid-branch\' did not match any file(s) known to git');
});
});
});