feat: git功能开发
This commit is contained in:
@@ -127,6 +127,14 @@ Example configuration for Claude Desktop (`claude_desktop_config.json`):
|
||||
- `docs_bun` - Get Bun documentation
|
||||
- `code_review` - Review code
|
||||
- `code_optimize` - Get optimization suggestions
|
||||
- `git_status` - Get git repository status
|
||||
- `git_add` - Stage files for commit
|
||||
- `git_commit` - Commit staged changes
|
||||
- `git_push` - Push commits to remote
|
||||
- `git_pull` - Pull latest changes from remote
|
||||
- `git_log` - Show commit history
|
||||
- `git_branch` - List, create, or delete branches
|
||||
- `git_diff` - Show changes between commits or working directory
|
||||
|
||||
### DevOps
|
||||
- `nas_list_files` - List NAS files
|
||||
|
||||
@@ -11,6 +11,7 @@ import { registerCodeSnippetTools } from "./tools/programming/codeSnippet.js";
|
||||
import { registerProjectTemplateTools } from "./tools/programming/projectTemplate.js";
|
||||
import { registerDocsTools } from "./tools/programming/docs.js";
|
||||
import { registerCodeReviewTools } from "./tools/programming/codeReview.js";
|
||||
import { registerGitTools } from "./tools/programming/git.js";
|
||||
|
||||
import { registerNASTools } from "./tools/devops/nas.js";
|
||||
import { registerServerTools } from "./tools/devops/server.js";
|
||||
@@ -35,6 +36,7 @@ registerCodeSnippetTools();
|
||||
registerProjectTemplateTools();
|
||||
registerDocsTools();
|
||||
registerCodeReviewTools();
|
||||
registerGitTools();
|
||||
|
||||
// DevOps tools
|
||||
registerNASTools();
|
||||
|
||||
672
src/tools/programming/git.ts
Normal file
672
src/tools/programming/git.ts
Normal file
@@ -0,0 +1,672 @@
|
||||
/**
|
||||
* Git version control tools
|
||||
*/
|
||||
|
||||
import { mcpServer } from "../../server.js";
|
||||
import { logger } from "../../utils/logger.js";
|
||||
import { execSync } from "child_process";
|
||||
import { existsSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
export function registerGitTools(): void {
|
||||
// Git status
|
||||
mcpServer.registerTool(
|
||||
{
|
||||
name: "git_status",
|
||||
description: "Get git repository status (working directory, staged files, branch)",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
path: {
|
||||
type: "string",
|
||||
description: "Path to git repository (default: current directory)",
|
||||
default: ".",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const repoPath = (args.path as string) || process.cwd();
|
||||
const gitDir = join(repoPath, ".git");
|
||||
|
||||
if (!existsSync(gitDir)) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: Not a git repository: ${repoPath}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const status = execSync("git status --short", {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
|
||||
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
|
||||
const lastCommit = execSync("git log -1 --oneline", {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
|
||||
let output = `Git Status (${repoPath})\n\n`;
|
||||
output += `Branch: ${branch}\n`;
|
||||
output += `Last commit: ${lastCommit}\n\n`;
|
||||
|
||||
if (status) {
|
||||
output += `Working directory changes:\n${status}`;
|
||||
} else {
|
||||
output += "Working directory clean";
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: output,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Git add
|
||||
mcpServer.registerTool(
|
||||
{
|
||||
name: "git_add",
|
||||
description: "Stage files for commit",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
files: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "Files to stage (use '.' for all files)",
|
||||
},
|
||||
path: {
|
||||
type: "string",
|
||||
description: "Path to git repository (default: current directory)",
|
||||
default: ".",
|
||||
},
|
||||
},
|
||||
required: ["files"],
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const repoPath = (args.path as string) || process.cwd();
|
||||
const files = args.files as string[];
|
||||
|
||||
const gitDir = join(repoPath, ".git");
|
||||
if (!existsSync(gitDir)) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: Not a git repository: ${repoPath}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const filesToAdd = files.length === 0 ? ["."] : files;
|
||||
execSync(`git add ${filesToAdd.join(" ")}`, {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
});
|
||||
|
||||
const staged = execSync("git diff --cached --name-only", {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Files staged successfully!\n\nStaged files:\n${staged || "No files staged"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Git commit
|
||||
mcpServer.registerTool(
|
||||
{
|
||||
name: "git_commit",
|
||||
description: "Commit staged changes",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
message: {
|
||||
type: "string",
|
||||
description: "Commit message",
|
||||
},
|
||||
path: {
|
||||
type: "string",
|
||||
description: "Path to git repository (default: current directory)",
|
||||
default: ".",
|
||||
},
|
||||
},
|
||||
required: ["message"],
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const repoPath = (args.path as string) || process.cwd();
|
||||
const message = args.message as string;
|
||||
|
||||
const gitDir = join(repoPath, ".git");
|
||||
if (!existsSync(gitDir)) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: Not a git repository: ${repoPath}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
execSync(`git commit -m "${message}"`, {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
});
|
||||
|
||||
const commitHash = execSync("git rev-parse HEAD", {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
|
||||
const commitInfo = execSync("git log -1 --oneline", {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Commit created successfully!\n\n${commitInfo}\nHash: ${commitHash}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: ${error instanceof Error ? error.message : String(error)}\n\nNote: Make sure you have staged files before committing.`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Git push
|
||||
mcpServer.registerTool(
|
||||
{
|
||||
name: "git_push",
|
||||
description: "Push commits to remote repository",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
remote: {
|
||||
type: "string",
|
||||
description: "Remote name (default: origin)",
|
||||
default: "origin",
|
||||
},
|
||||
branch: {
|
||||
type: "string",
|
||||
description: "Branch to push (default: current branch)",
|
||||
},
|
||||
path: {
|
||||
type: "string",
|
||||
description: "Path to git repository (default: current directory)",
|
||||
default: ".",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const repoPath = (args.path as string) || process.cwd();
|
||||
const remote = (args.remote as string) || "origin";
|
||||
const branch = args.branch as string | undefined;
|
||||
|
||||
const gitDir = join(repoPath, ".git");
|
||||
if (!existsSync(gitDir)) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: Not a git repository: ${repoPath}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const currentBranch =
|
||||
branch ||
|
||||
execSync("git rev-parse --abbrev-ref HEAD", {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
|
||||
execSync(`git push ${remote} ${currentBranch}`, {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Pushed to ${remote}/${currentBranch} successfully!`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: ${error instanceof Error ? error.message : String(error)}\n\nNote: Make sure you have commits to push and remote is configured.`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Git pull
|
||||
mcpServer.registerTool(
|
||||
{
|
||||
name: "git_pull",
|
||||
description: "Pull latest changes from remote repository",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
remote: {
|
||||
type: "string",
|
||||
description: "Remote name (default: origin)",
|
||||
default: "origin",
|
||||
},
|
||||
branch: {
|
||||
type: "string",
|
||||
description: "Branch to pull (default: current branch)",
|
||||
},
|
||||
path: {
|
||||
type: "string",
|
||||
description: "Path to git repository (default: current directory)",
|
||||
default: ".",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const repoPath = (args.path as string) || process.cwd();
|
||||
const remote = (args.remote as string) || "origin";
|
||||
const branch = args.branch as string | undefined;
|
||||
|
||||
const gitDir = join(repoPath, ".git");
|
||||
if (!existsSync(gitDir)) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: Not a git repository: ${repoPath}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const currentBranch =
|
||||
branch ||
|
||||
execSync("git rev-parse --abbrev-ref HEAD", {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
|
||||
const output = execSync(`git pull ${remote} ${currentBranch}`, {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Pulled from ${remote}/${currentBranch} successfully!\n\n${output}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Git log
|
||||
mcpServer.registerTool(
|
||||
{
|
||||
name: "git_log",
|
||||
description: "Show commit history",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
limit: {
|
||||
type: "number",
|
||||
description: "Number of commits to show (default: 10)",
|
||||
default: 10,
|
||||
},
|
||||
path: {
|
||||
type: "string",
|
||||
description: "Path to git repository (default: current directory)",
|
||||
default: ".",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const repoPath = (args.path as string) || process.cwd();
|
||||
const limit = (args.limit as number) || 10;
|
||||
|
||||
const gitDir = join(repoPath, ".git");
|
||||
if (!existsSync(gitDir)) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: Not a git repository: ${repoPath}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const log = execSync(`git log -${limit} --oneline --decorate`, {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Recent commits (${limit}):\n\n${log || "No commits found"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Git branch
|
||||
mcpServer.registerTool(
|
||||
{
|
||||
name: "git_branch",
|
||||
description: "List, create, or delete branches",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
action: {
|
||||
type: "string",
|
||||
description: "Action: 'list', 'create', or 'delete'",
|
||||
enum: ["list", "create", "delete"],
|
||||
default: "list",
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
description: "Branch name (required for create/delete)",
|
||||
},
|
||||
path: {
|
||||
type: "string",
|
||||
description: "Path to git repository (default: current directory)",
|
||||
default: ".",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const repoPath = (args.path as string) || process.cwd();
|
||||
const action = (args.action as string) || "list";
|
||||
const name = args.name as string | undefined;
|
||||
|
||||
const gitDir = join(repoPath, ".git");
|
||||
if (!existsSync(gitDir)) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: Not a git repository: ${repoPath}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (action === "list") {
|
||||
const branches = execSync("git branch -a", {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
|
||||
const currentBranch = execSync("git rev-parse --abbrev-ref HEAD", {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Branches:\n\n${branches}\n\nCurrent branch: ${currentBranch}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} else if (action === "create") {
|
||||
if (!name) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Error: Branch name is required for create action",
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
execSync(`git branch ${name}`, {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Branch "${name}" created successfully!`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} else if (action === "delete") {
|
||||
if (!name) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Error: Branch name is required for delete action",
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
execSync(`git branch -d ${name}`, {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Branch "${name}" deleted successfully!`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Invalid action",
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Git diff
|
||||
mcpServer.registerTool(
|
||||
{
|
||||
name: "git_diff",
|
||||
description: "Show changes between commits, branches, or working directory",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
path: {
|
||||
type: "string",
|
||||
description: "Path to git repository (default: current directory)",
|
||||
default: ".",
|
||||
},
|
||||
file: {
|
||||
type: "string",
|
||||
description: "Specific file to show diff (optional)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const repoPath = (args.path as string) || process.cwd();
|
||||
const file = args.file as string | undefined;
|
||||
|
||||
const gitDir = join(repoPath, ".git");
|
||||
if (!existsSync(gitDir)) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: Not a git repository: ${repoPath}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const diffCommand = file ? `git diff ${file}` : "git diff";
|
||||
const diff = execSync(diffCommand, {
|
||||
cwd: repoPath,
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: diff || "No changes found",
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user