Git MCP Server
by cyanheads
Verified
- src
- resources
/**
* Diff Resources
* =============
*
* MCP resources for exposing Git diff information.
*/
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { GitService } from '../services/git-service.js';
import { Schemas, PathValidation } from '../utils/validation.js';
/**
* Helper function to ensure a variable is treated as a string
*
* @param value - The value to convert to string
* @returns A string representation of the value
*/
function ensureString(value: string | string[]): string {
return Array.isArray(value) ? value[0] : value;
}
/**
* Registers diff resources with the MCP server
*
* @param server - MCP server instance
* @param resourceDescriptors - Resource descriptors for metadata
*/
export function setupDiffResources(server: McpServer, resourceDescriptors: any): void {
// Diff between two refs
server.resource(
"diff-refs",
new ResourceTemplate("git://repo/{repoPath}/diff/{fromRef}/{toRef}?path={path}", { list: undefined }),
{
name: "Reference Diff",
description: "Returns a diff between two Git references (commits, branches, tags)",
mimeType: "text/plain"
},
// Returns a diff between two references (branches, tags, or commits) with optional path filter
async (uri, variables) => {
try {
// Handle variables which might be arrays
const repoPathStr = ensureString(variables.repoPath);
const fromRefStr = ensureString(variables.fromRef);
const toRefStr = variables.toRef ? ensureString(variables.toRef) : 'HEAD';
const pathStr = variables.path ? ensureString(variables.path) : undefined;
// Normalize paths
const normalizedRepoPath = PathValidation.normalizePath(decodeURIComponent(repoPathStr));
const gitService = new GitService(normalizedRepoPath);
// Check if the path is a Git repository
const isRepo = await gitService.isGitRepository();
if (!isRepo) {
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Not a Git repository",
repoPath: normalizedRepoPath,
fromRef: fromRefStr,
toRef: toRefStr,
path: pathStr
}, null, 2),
mimeType: "application/json"
}]
};
}
// Get diff
const diffResult = await gitService.getDiff(fromRefStr, toRefStr, pathStr);
if (!diffResult.resultSuccessful) {
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: diffResult.resultError.errorMessage,
repoPath: normalizedRepoPath,
fromRef: fromRefStr,
toRef: toRefStr,
path: pathStr
}, null, 2),
mimeType: "application/json"
}]
};
}
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
repoPath: normalizedRepoPath,
fromRef: fromRefStr,
toRef: toRefStr,
path: pathStr,
diff: diffResult.resultData
}, null, 2),
mimeType: "application/json"
}]
};
} catch (error) {
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
repoPath: ensureString(variables.repoPath),
fromRef: ensureString(variables.fromRef),
toRef: variables.toRef ? ensureString(variables.toRef) : 'HEAD',
path: variables.path ? ensureString(variables.path) : undefined
}, null, 2),
mimeType: "application/json"
}]
};
}
}
);
// Diff in working directory (unstaged changes)
server.resource(
"diff-unstaged",
new ResourceTemplate("git://repo/{repoPath}/diff-unstaged?path={path}", { list: undefined }),
{
name: "Unstaged Changes Diff",
description: "Returns a diff of all unstaged changes in the working directory",
mimeType: "text/plain"
},
// Returns a diff of all unstaged changes (between working directory and index) with optional path filter
async (uri, variables) => {
try {
// Handle variables which might be arrays
const repoPathStr = ensureString(variables.repoPath);
const pathStr = variables.path ? ensureString(variables.path) : undefined;
// Normalize paths
const normalizedRepoPath = PathValidation.normalizePath(decodeURIComponent(repoPathStr));
const gitService = new GitService(normalizedRepoPath);
// Check if the path is a Git repository
const isRepo = await gitService.isGitRepository();
if (!isRepo) {
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Not a Git repository",
repoPath: normalizedRepoPath,
path: pathStr
}, null, 2),
mimeType: "application/json"
}]
};
}
// Get unstaged diff
const diffResult = await gitService.getUnstagedDiff(pathStr);
if (!diffResult.resultSuccessful) {
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: diffResult.resultError.errorMessage,
repoPath: normalizedRepoPath,
path: pathStr
}, null, 2),
mimeType: "application/json"
}]
};
}
return {
contents: [{
uri: uri.href,
text: diffResult.resultData,
mimeType: "text/plain"
}]
};
} catch (error) {
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
repoPath: ensureString(variables.repoPath),
path: variables.path ? ensureString(variables.path) : undefined
}, null, 2),
mimeType: "application/json"
}]
};
}
}
);
// Diff staged changes
server.resource(
"diff-staged",
new ResourceTemplate("git://repo/{repoPath}/diff-staged?path={path}", { list: undefined }),
{
name: "Staged Changes Diff",
description: "Returns a diff of all staged changes in the index",
mimeType: "text/plain"
},
// Returns a diff of all staged changes (between index and HEAD) with optional path filter
async (uri, variables) => {
try {
// Handle variables which might be arrays
const repoPathStr = ensureString(variables.repoPath);
const pathStr = variables.path ? ensureString(variables.path) : undefined;
// Normalize paths
const normalizedRepoPath = PathValidation.normalizePath(decodeURIComponent(repoPathStr));
const gitService = new GitService(normalizedRepoPath);
// Check if the path is a Git repository
const isRepo = await gitService.isGitRepository();
if (!isRepo) {
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Not a Git repository",
repoPath: normalizedRepoPath,
path: pathStr
}, null, 2),
mimeType: "application/json"
}]
};
}
// Get staged diff
const diffResult = await gitService.getStagedDiff(pathStr);
if (!diffResult.resultSuccessful) {
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: diffResult.resultError.errorMessage,
repoPath: normalizedRepoPath,
path: pathStr
}, null, 2),
mimeType: "application/json"
}]
};
}
return {
contents: [{
uri: uri.href,
text: diffResult.resultData,
mimeType: "text/plain"
}]
};
} catch (error) {
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
repoPath: ensureString(variables.repoPath),
path: variables.path ? ensureString(variables.path) : undefined
}, null, 2),
mimeType: "application/json"
}]
};
}
}
);
}