@shaohui_jin/git-cockpit-mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,573 @@
1
+ // src/mcpServer.ts
2
+ import { randomUUID } from "crypto";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
6
+
7
+ // src/tools/handlers.ts
8
+ import { z } from "zod";
9
+ import {
10
+ BackupManager,
11
+ GitOperationError,
12
+ PermissionError
13
+ } from "@shaohui_jin/git-cockpit-core";
14
+ async function resolveRepo(runtime, ctx, argsPath) {
15
+ if (ctx.repoId !== void 0) {
16
+ const handle = await runtime.repoManager.getById(ctx.repoId);
17
+ if (handle) return handle;
18
+ throw new GitOperationError(`\u4ED3\u5E93\u4E0D\u5B58\u5728: id=${ctx.repoId}`, "REPO_NOT_FOUND");
19
+ }
20
+ if (ctx.repoPath) {
21
+ return runtime.repoManager.getByPath(ctx.repoPath);
22
+ }
23
+ if (argsPath) {
24
+ return runtime.repoManager.open(argsPath);
25
+ }
26
+ const current = await runtime.repoManager.getCurrent();
27
+ if (!current) {
28
+ throw new GitOperationError("\u5C1A\u672A\u6253\u5F00\u4EFB\u4F55\u4ED3\u5E93\u3002\u8BF7\u5148\u6253\u5F00\u4ED3\u5E93\u6216\u63D0\u4F9B repoPath \u53C2\u6570\u3002", "NO_ACTIVE_REPO");
29
+ }
30
+ return current;
31
+ }
32
+ function extractError(err) {
33
+ if (err instanceof PermissionError) {
34
+ return { code: "PERMISSION_DENIED", message: err.message, requiredApproval: err.requiredApproval };
35
+ }
36
+ if (err instanceof GitOperationError) {
37
+ return { code: err.code, message: err.message, requiredApproval: false };
38
+ }
39
+ if (err instanceof z.ZodError) {
40
+ const summary = err.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).slice(0, 5).join("; ");
41
+ return { code: "INVALID_ARGS", message: `\u53C2\u6570\u6821\u9A8C\u5931\u8D25\uFF1A${summary}`, requiredApproval: false };
42
+ }
43
+ const msg = err instanceof Error ? err.message : String(err);
44
+ return { code: "INTERNAL_ERROR", message: msg.slice(0, 500), requiredApproval: false };
45
+ }
46
+ async function executeTool(def, rawArgs, ctx) {
47
+ const runtime = ctx.runtime;
48
+ const t0 = Date.now();
49
+ const dryRun2 = typeof rawArgs.dryRun === "boolean" ? rawArgs.dryRun : runtime.permissions.getDryRunDefault();
50
+ const record = (result, params, errorMessage, repoPath2) => {
51
+ runtime.auditLogger.log({
52
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
53
+ source: ctx.source,
54
+ tool: def.name,
55
+ repoPath: repoPath2 ?? "",
56
+ params,
57
+ result,
58
+ error: errorMessage ?? null,
59
+ durationMs: Date.now() - t0,
60
+ dryRun: dryRun2
61
+ });
62
+ runtime.eventBus.emit("log", { tool: def.name, result, at: (/* @__PURE__ */ new Date()).toISOString() });
63
+ };
64
+ try {
65
+ runtime.permissions.assertAllowed(def.name);
66
+ let args;
67
+ try {
68
+ args = def.schema.parse(rawArgs);
69
+ } catch (err) {
70
+ const e = extractError(err);
71
+ record("error", rawArgs, e.message);
72
+ return { tool: def.name, source: ctx.source, dryRun: dryRun2, success: false, error: e, durationMs: Date.now() - t0 };
73
+ }
74
+ const handle = await resolveRepo(runtime, ctx, args.repoPath);
75
+ const git = handle.service;
76
+ const repoPath2 = git.repoPath;
77
+ let backup = null;
78
+ if (!dryRun2 && def.risk === "dangerous" && runtime.config.git.backupOnDangerousOps) {
79
+ backup = await new BackupManager(git).createBackup();
80
+ args = { ...args, __backup: { branch: backup.branch, stashRef: backup.stashRef } };
81
+ }
82
+ const result = await def.handler(args, { ...ctx, git, repoPath: repoPath2 });
83
+ record("success", args, void 0, repoPath2);
84
+ const preview = dryRun2 ? result : void 0;
85
+ return {
86
+ tool: def.name,
87
+ source: ctx.source,
88
+ dryRun: dryRun2,
89
+ success: true,
90
+ result,
91
+ preview,
92
+ backupCreated: backup,
93
+ durationMs: Date.now() - t0
94
+ };
95
+ } catch (err) {
96
+ const e = extractError(err);
97
+ const isDenied = err instanceof PermissionError;
98
+ record(isDenied ? "denied" : "error", rawArgs, e.message);
99
+ return {
100
+ tool: def.name,
101
+ source: ctx.source,
102
+ dryRun: dryRun2,
103
+ success: false,
104
+ error: e,
105
+ durationMs: Date.now() - t0
106
+ };
107
+ }
108
+ }
109
+ function formatResultForMcp(exec) {
110
+ if (!exec.success) {
111
+ const prefix = exec.error?.requiredApproval ? "\u9700\u8981\u5BA1\u6279" : "\u64CD\u4F5C\u5931\u8D25";
112
+ return `${prefix}\uFF1A${exec.error?.message ?? "\u672A\u77E5\u9519\u8BEF"}`;
113
+ }
114
+ const parts = [];
115
+ if (exec.dryRun) {
116
+ parts.push("[dry-run \u9884\u89C8] \u672A\u5B9E\u9645\u6267\u884C\u4EFB\u4F55\u64CD\u4F5C");
117
+ }
118
+ if (exec.backupCreated) {
119
+ parts.push(`[\u81EA\u52A8\u5907\u4EFD] \u5206\u652F ${exec.backupCreated.branch ?? "(\u672A\u521B\u5EFA)"}${exec.backupCreated.stashRef ? `\uFF0Cstash ${exec.backupCreated.stashRef}` : ""}`);
120
+ }
121
+ const payload = typeof exec.result === "string" ? exec.result : JSON.stringify(exec.result, null, 2);
122
+ parts.push(payload);
123
+ return parts.join("\n\n");
124
+ }
125
+
126
+ // src/tools/schemas.ts
127
+ import { z as z2 } from "zod";
128
+ var repoPath = z2.string().optional().describe("\u4ED3\u5E93\u7EDD\u5BF9\u8DEF\u5F84\u3002\u4E0D\u63D0\u4F9B\u65F6\u4F7F\u7528\u5F53\u524D\u6253\u5F00\u7684\u4ED3\u5E93\u3002");
129
+ var dryRun = z2.boolean().optional().describe("\u4E3A true \u65F6\u4EC5\u8FD4\u56DE\u5C06\u6267\u884C\u7684\u547D\u4EE4\u4E0E\u5F71\u54CD\u8303\u56F4\uFF08\u9884\u89C8/\u5E72\u8FD0\u884C\uFF09\uFF0C\u4E0D\u771F\u6B63\u4FEE\u6539\u4ED3\u5E93\u3002");
130
+ var maxCount = z2.number().int().min(1).max(1e4).optional().describe("\u6700\u591A\u8FD4\u56DE\u7684\u63D0\u4EA4\u6570\u91CF\u3002");
131
+ var readonlyBase = { repoPath };
132
+ var GitStatusSchema = z2.object({ ...readonlyBase });
133
+ var GitLogSchema = z2.object({
134
+ ...readonlyBase,
135
+ maxCount,
136
+ from: z2.string().optional().describe("\u8D77\u59CB\u63D0\u4EA4\uFF08\u542B\uFF09\uFF1B\u4E0E to \u914D\u5408\u8868\u793A\u8303\u56F4 from..to"),
137
+ to: z2.string().optional().describe("\u7ED3\u675F\u63D0\u4EA4\uFF08\u542B\uFF09"),
138
+ author: z2.string().optional().describe("\u6309\u4F5C\u8005\u8FC7\u6EE4\uFF08\u652F\u6301\u5B50\u4E32\uFF09"),
139
+ path: z2.string().optional().describe("\u4EC5\u67E5\u770B\u8BE5\u8DEF\u5F84\u7684\u5386\u53F2"),
140
+ all: z2.boolean().optional().describe("\u5305\u542B\u6240\u6709\u5206\u652F\uFF08--all\uFF09")
141
+ });
142
+ var GitDiffSchema = z2.object({
143
+ ...readonlyBase,
144
+ from: z2.string().optional().describe("\u8D77\u70B9\u63D0\u4EA4"),
145
+ to: z2.string().optional().describe("\u7EC8\u70B9\u63D0\u4EA4"),
146
+ path: z2.string().optional().describe("\u9650\u5B9A\u7684\u6587\u4EF6\u8DEF\u5F84"),
147
+ staged: z2.boolean().optional().describe("\u67E5\u770B\u5DF2\u6682\u5B58\uFF08\u7D22\u5F15\uFF09\u5DEE\u5F02"),
148
+ maxPatchBytes: z2.number().int().positive().optional().describe("diff \u6B63\u6587\u6700\u5927\u5B57\u8282\u6570\uFF0C\u8D85\u51FA\u622A\u65AD")
149
+ });
150
+ var GitShowSchema = z2.object({
151
+ ...readonlyBase,
152
+ commit: z2.string().describe("\u8981\u67E5\u770B\u7684\u63D0\u4EA4 hash"),
153
+ path: z2.string().optional().describe("\u9650\u5B9A\u6587\u4EF6"),
154
+ maxPatchBytes: z2.number().int().positive().optional()
155
+ });
156
+ var GitBranchListSchema = z2.object({ ...readonlyBase });
157
+ var GitTagListSchema = z2.object({ ...readonlyBase });
158
+ var GitRemoteListSchema = z2.object({ ...readonlyBase });
159
+ var GitFileContentSchema = z2.object({
160
+ ...readonlyBase,
161
+ commit: z2.string().describe("\u63D0\u4EA4 hash\uFF08\u5982 HEAD\u3001main\uFF09"),
162
+ path: z2.string().describe("\u4ED3\u5E93\u5185\u6587\u4EF6\u76F8\u5BF9\u8DEF\u5F84")
163
+ });
164
+ var GitGraphSchema = z2.object({
165
+ ...readonlyBase,
166
+ maxCount: z2.number().int().min(1).max(5e3).optional().describe("\u6700\u5927\u63D0\u4EA4\u6570")
167
+ });
168
+ var writeBase = { repoPath, dryRun };
169
+ var pathsField = z2.array(z2.string()).default([]).describe("\u6587\u4EF6\u8DEF\u5F84\u5217\u8868\uFF1B\u7F3A\u7701\u4E3A\u7A7A\u5373\u5168\u90E8\u6587\u4EF6");
170
+ var GitAddSchema = z2.object({ ...writeBase, paths: pathsField });
171
+ var GitUnstageSchema = z2.object({ ...writeBase, paths: pathsField });
172
+ var GitCommitSchema = z2.object({
173
+ ...writeBase,
174
+ message: z2.string().min(1).describe("\u63D0\u4EA4\u4FE1\u606F"),
175
+ paths: z2.array(z2.string()).optional().describe("\u4EC5\u63D0\u4EA4\u8FD9\u4E9B\u8DEF\u5F84\uFF08\u53EF\u7A7A\uFF09"),
176
+ allowEmpty: z2.boolean().optional().describe("\u5141\u8BB8\u7A7A\u63D0\u4EA4\uFF08--allow-empty\uFF09")
177
+ });
178
+ var GitCheckoutSchema = z2.object({
179
+ ...writeBase,
180
+ branch: z2.string().describe("\u8981\u5207\u6362\u5230\u7684\u5206\u652F\u540D")
181
+ });
182
+ var GitBranchCreateSchema = z2.object({
183
+ ...writeBase,
184
+ name: z2.string().describe("\u65B0\u5206\u652F\u540D"),
185
+ startPoint: z2.string().optional().describe("\u521B\u5EFA\u8D77\u70B9\uFF08\u63D0\u4EA4/\u5206\u652F\uFF09\uFF0C\u7F3A\u7701\u4E3A\u5F53\u524D HEAD")
186
+ });
187
+ var GitBranchDeleteSchema = z2.object({
188
+ ...writeBase,
189
+ name: z2.string().describe("\u8981\u5220\u9664\u7684\u5206\u652F\u540D\uFF08\u672A\u5408\u5E76\u5206\u652F\u4F1A\u88AB\u62D2\u7EDD\uFF09")
190
+ });
191
+ var GitMergeSchema = z2.object({
192
+ ...writeBase,
193
+ branch: z2.string().describe("\u8981\u5408\u5E76\u8FDB\u6765\u7684\u5206\u652F\u540D")
194
+ });
195
+ var GitPullSchema = z2.object({
196
+ ...writeBase,
197
+ remote: z2.string().optional().describe("\u8FDC\u7A0B\u540D\uFF08\u7F3A\u7701 origin\uFF09"),
198
+ branch: z2.string().optional().describe("\u8FDC\u7AEF\u5206\u652F\u540D")
199
+ });
200
+ var GitPushSchema = z2.object({
201
+ ...writeBase,
202
+ remote: z2.string().optional().describe("\u8FDC\u7A0B\u540D\uFF08\u7F3A\u7701 origin\uFF09"),
203
+ branch: z2.string().optional().describe("\u5206\u652F\u540D")
204
+ });
205
+ var GitTagCreateSchema = z2.object({
206
+ ...writeBase,
207
+ name: z2.string().describe("\u6807\u7B7E\u540D"),
208
+ message: z2.string().optional().describe("\u9644\u6CE8\u6807\u7B7E\u6D88\u606F\uFF08\u5E26\u5219\u521B\u5EFA annotated tag\uFF09"),
209
+ commit: z2.string().optional().describe("\u6253\u6807\u7B7E\u7684\u63D0\u4EA4\uFF0C\u7F3A\u7701\u4E3A HEAD")
210
+ });
211
+ var GitStashSchema = z2.object({
212
+ ...writeBase,
213
+ message: z2.string().optional().describe("stash \u6D88\u606F"),
214
+ includeUntracked: z2.boolean().optional().describe("\u5305\u542B\u672A\u8DDF\u8E2A\u6587\u4EF6\uFF08-u\uFF09")
215
+ });
216
+ var GitStashPopSchema = z2.object({
217
+ ...writeBase,
218
+ index: z2.number().int().min(0).optional().describe("stash \u5E8F\u53F7\uFF0C\u7F3A\u7701\u4E3A\u6700\u65B0\u7684 stash@{0}")
219
+ });
220
+ var GitResetHardSchema = z2.object({ ...writeBase, target: z2.string().describe("\u8981\u91CD\u7F6E\u5230\u7684\u63D0\u4EA4") });
221
+ var GitCleanSchema = z2.object({ ...writeBase });
222
+ var GitPushForceSchema = z2.object({
223
+ ...writeBase,
224
+ remote: z2.string().optional(),
225
+ branch: z2.string().optional()
226
+ });
227
+ var GitBranchDeleteForceSchema = z2.object({
228
+ ...writeBase,
229
+ name: z2.string().describe("\u8981\u5F3A\u5236\u5220\u9664\u7684\u5206\u652F\u540D")
230
+ });
231
+ var GitRebaseSchema = z2.object({
232
+ ...writeBase,
233
+ branch: z2.string().describe("\u8981\u53D8\u57FA\u5230\u7684\u5206\u652F/\u63D0\u4EA4")
234
+ });
235
+
236
+ // src/tools/index.ts
237
+ var TOOL_DEFS = [
238
+ // ---------------------------------------------------------------------------
239
+ // 只读工具(默认开放)
240
+ // ---------------------------------------------------------------------------
241
+ {
242
+ name: "git_status",
243
+ description: "\u83B7\u53D6\u5F53\u524D\u4ED3\u5E93\u7684\u5DE5\u4F5C\u533A\u72B6\u6001\uFF1A\u5F53\u524D\u5206\u652F\u3001\u5DF2\u6682\u5B58/\u672A\u6682\u5B58/\u672A\u8DDF\u8E2A\u6587\u4EF6\u3001\u51B2\u7A81\u6587\u4EF6\u3001\u9886\u5148/\u843D\u540E\u4E8E\u4E0A\u6E38\u7B49\u3002",
244
+ risk: "readonly",
245
+ schema: GitStatusSchema,
246
+ handler: async (_args, ctx) => ctx.git.getStatus()
247
+ },
248
+ {
249
+ name: "git_log",
250
+ description: "\u83B7\u53D6\u63D0\u4EA4\u5386\u53F2\uFF08\u9ED8\u8BA4 HEAD \u8D77\uFF0C\u6700\u591A\u8FD4\u56DE maxCount \u6761\uFF09\u3002\u652F\u6301\u6309\u4F5C\u8005\u3001\u8DEF\u5F84\u3001\u63D0\u4EA4\u8303\u56F4\u8FC7\u6EE4\u3002",
251
+ risk: "readonly",
252
+ schema: GitLogSchema,
253
+ handler: async (args, ctx) => ctx.git.getLog({
254
+ maxCount: args.maxCount,
255
+ from: args.from,
256
+ to: args.to,
257
+ author: args.author,
258
+ path: args.path,
259
+ all: args.all
260
+ })
261
+ },
262
+ {
263
+ name: "git_diff",
264
+ description: "\u83B7\u53D6\u5DE5\u4F5C\u533A/\u6682\u5B58\u533A/\u63D0\u4EA4\u8303\u56F4\u7684\u5DEE\u5F02\u3002\u652F\u6301\u6307\u5B9A\u6587\u4EF6\u3001staged\u3001\u5927 diff \u622A\u65AD\u3002\u8FD4\u56DE\u6587\u4EF6\u7EA7\u7EDF\u8BA1\u4E0E\u5B8C\u6574 unified diff \u6587\u672C\u3002",
265
+ risk: "readonly",
266
+ schema: GitDiffSchema,
267
+ handler: async (args, ctx) => ctx.git.getDiff({
268
+ from: args.from,
269
+ to: args.to,
270
+ path: args.path,
271
+ staged: args.staged,
272
+ maxPatchBytes: args.maxPatchBytes
273
+ })
274
+ },
275
+ {
276
+ name: "git_show",
277
+ description: "\u663E\u793A\u67D0\u4E2A\u63D0\u4EA4\u7684\u8BE6\u7EC6\u4FE1\u606F\uFF08\u5143\u4FE1\u606F + \u76F8\u5BF9\u7236\u63D0\u4EA4\u7684 diff\uFF09\u3002",
278
+ risk: "readonly",
279
+ schema: GitShowSchema,
280
+ handler: async (args, ctx) => ctx.git.getShow(args.commit, {
281
+ path: args.path,
282
+ maxPatchBytes: args.maxPatchBytes
283
+ })
284
+ },
285
+ {
286
+ name: "git_branch_list",
287
+ description: "\u5217\u51FA\u6240\u6709\u672C\u5730\u4E0E\u8FDC\u7A0B\u5206\u652F\uFF0C\u6807\u6CE8\u5F53\u524D\u5206\u652F\u53CA\u5176\u6307\u5411\u7684\u63D0\u4EA4\u3002",
288
+ risk: "readonly",
289
+ schema: GitBranchListSchema,
290
+ handler: async (_args, ctx) => ctx.git.listBranches()
291
+ },
292
+ {
293
+ name: "git_tag_list",
294
+ description: "\u5217\u51FA\u6240\u6709\u6807\u7B7E\uFF08\u6309\u521B\u5EFA\u65F6\u95F4\u5012\u5E8F\uFF09\u3002",
295
+ risk: "readonly",
296
+ schema: GitTagListSchema,
297
+ handler: async (_args, ctx) => ctx.git.listTags()
298
+ },
299
+ {
300
+ name: "git_remote_list",
301
+ description: "\u5217\u51FA\u8FDC\u7A0B\u4ED3\u5E93\u4FE1\u606F\uFF08\u540D\u79F0\u3001fetch/push URL\uFF09\u3002",
302
+ risk: "readonly",
303
+ schema: GitRemoteListSchema,
304
+ handler: async (_args, ctx) => ctx.git.listRemotes()
305
+ },
306
+ {
307
+ name: "git_file_content",
308
+ description: "\u8BFB\u53D6\u6307\u5B9A\u63D0\u4EA4\u4E2D\u67D0\u4E2A\u6587\u4EF6\u7684\u5185\u5BB9\uFF08\u5927\u6587\u4EF6\u81EA\u52A8\u622A\u65AD 2MB\uFF09\u3002",
309
+ risk: "readonly",
310
+ schema: GitFileContentSchema,
311
+ handler: async (args, ctx) => ctx.git.getFileContent(args.commit, args.path)
312
+ },
313
+ {
314
+ name: "git_graph",
315
+ description: "\u83B7\u53D6\u5206\u652F\u62D3\u6251\u56FE\u6570\u636E\uFF08\u5168\u90E8\u5206\u652F\u63D0\u4EA4\u3001\u7236\u63D0\u4EA4\u3001\u5F15\u7528\u88C5\u9970\u3001HEAD \u4F4D\u7F6E\uFF09\uFF0C\u4F9B\u53EF\u89C6\u5316\u6E32\u67D3\u3002",
316
+ risk: "readonly",
317
+ schema: GitGraphSchema,
318
+ handler: async (args, ctx) => ctx.git.getGraph(args.maxCount)
319
+ },
320
+ // ---------------------------------------------------------------------------
321
+ // 写操作工具(默认开放,需预览/确认)
322
+ // ---------------------------------------------------------------------------
323
+ {
324
+ name: "git_add",
325
+ description: "\u6682\u5B58\u6307\u5B9A\u6587\u4EF6\uFF1Bpaths \u4E3A\u7A7A\u8868\u793A\u6682\u5B58\u5168\u90E8\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
326
+ risk: "write",
327
+ schema: GitAddSchema,
328
+ handler: async (args, ctx) => ctx.git.add(args.paths ?? [], { dryRun: args.dryRun })
329
+ },
330
+ {
331
+ name: "git_unstage",
332
+ description: "\u53D6\u6D88\u6682\u5B58\u6307\u5B9A\u6587\u4EF6\uFF08git reset HEAD -- <paths>\uFF09\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
333
+ risk: "write",
334
+ schema: GitUnstageSchema,
335
+ handler: async (args, ctx) => ctx.git.unstage(args.paths ?? [], { dryRun: args.dryRun })
336
+ },
337
+ {
338
+ name: "git_commit",
339
+ description: "\u521B\u5EFA\u63D0\u4EA4\u3002\u9700\u8981\u6307\u5B9A message\uFF1Bpaths \u53EF\u9650\u5B9A\u4EC5\u63D0\u4EA4\u90E8\u5206\u6587\u4EF6\uFF1BallowEmpty \u5141\u8BB8\u7A7A\u63D0\u4EA4\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
340
+ risk: "write",
341
+ schema: GitCommitSchema,
342
+ handler: async (args, ctx) => ctx.git.commit(args.message, {
343
+ dryRun: args.dryRun,
344
+ allowEmpty: args.allowEmpty,
345
+ paths: args.paths
346
+ })
347
+ },
348
+ {
349
+ name: "git_checkout",
350
+ description: "\u5207\u6362\u5230\u6307\u5B9A\u5206\u652F\u3002\u82E5\u672C\u5730\u66F4\u6539\u4F1A\u88AB\u8986\u76D6\u5C06\u62D2\u7EDD\u5E76\u7ED9\u51FA\u63D0\u793A\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
351
+ risk: "write",
352
+ schema: GitCheckoutSchema,
353
+ handler: async (args, ctx) => ctx.git.checkoutBranch(args.branch, { dryRun: args.dryRun })
354
+ },
355
+ {
356
+ name: "git_branch_create",
357
+ description: "\u521B\u5EFA\u65B0\u5206\u652F\uFF08\u9ED8\u8BA4\u4ECE\u5F53\u524D HEAD\uFF1BstartPoint \u53EF\u6307\u5B9A\u8D77\u70B9\uFF09\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
358
+ risk: "write",
359
+ schema: GitBranchCreateSchema,
360
+ handler: async (args, ctx) => ctx.git.createBranch(args.name, {
361
+ dryRun: args.dryRun,
362
+ startPoint: args.startPoint
363
+ })
364
+ },
365
+ {
366
+ name: "git_branch_delete",
367
+ description: "\u5B89\u5168\u5220\u9664\u5206\u652F\uFF08-d\uFF0C\u672A\u5408\u5E76\u5206\u652F\u4F1A\u62D2\u7EDD\uFF09\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
368
+ risk: "write",
369
+ schema: GitBranchDeleteSchema,
370
+ handler: async (args, ctx) => ctx.git.deleteBranch(args.name, { dryRun: args.dryRun })
371
+ },
372
+ {
373
+ name: "git_merge",
374
+ description: "\u628A\u6307\u5B9A\u5206\u652F\u5408\u5E76\u5230\u5F53\u524D\u5206\u652F\uFF08\u666E\u901A\u5408\u5E76\uFF09\u3002\u4EA7\u751F\u51B2\u7A81\u65F6\u4F1A\u63D0\u793A\u89E3\u51B3\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
375
+ risk: "write",
376
+ schema: GitMergeSchema,
377
+ handler: async (args, ctx) => ctx.git.merge(args.branch, { dryRun: args.dryRun })
378
+ },
379
+ {
380
+ name: "git_pull",
381
+ description: "\u4ECE\u8FDC\u7A0B\u62C9\u53D6\u5E76\u5408\u5E76\uFF08\u975E\u5F3A\u5236\uFF09\u3002\u8FDC\u7A0B\u6709\u51B2\u7A81\u5C06\u63D0\u793A\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
382
+ risk: "write",
383
+ schema: GitPullSchema,
384
+ handler: async (args, ctx) => ctx.git.pull({
385
+ dryRun: args.dryRun,
386
+ remote: args.remote,
387
+ branch: args.branch
388
+ })
389
+ },
390
+ {
391
+ name: "git_push",
392
+ description: "\u63A8\u9001\u5230\u8FDC\u7A0B\uFF08\u975E\u5F3A\u5236\uFF09\u3002\u63A8\u9001\u88AB\u62D2\u7EDD\uFF08\u8FDC\u7A0B\u6709\u65B0\u63D0\u4EA4\uFF09\u4F1A\u7ED9\u51FA\u63D0\u793A\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
393
+ risk: "write",
394
+ schema: GitPushSchema,
395
+ handler: async (args, ctx) => ctx.git.push({
396
+ dryRun: args.dryRun,
397
+ remote: args.remote,
398
+ branch: args.branch
399
+ })
400
+ },
401
+ {
402
+ name: "git_tag_create",
403
+ description: "\u521B\u5EFA\u6807\u7B7E\uFF1B\u63D0\u4F9B message \u5219\u521B\u5EFA\u9644\u6CE8\u6807\u7B7E\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
404
+ risk: "write",
405
+ schema: GitTagCreateSchema,
406
+ handler: async (args, ctx) => ctx.git.createTag(args.name, {
407
+ dryRun: args.dryRun,
408
+ message: args.message,
409
+ commit: args.commit
410
+ })
411
+ },
412
+ {
413
+ name: "git_stash",
414
+ description: "\u6682\u5B58\uFF08stash\uFF09\u5F53\u524D\u5DE5\u4F5C\u533A\u66F4\u6539\uFF0C\u53EF\u9009\u5305\u542B\u672A\u8DDF\u8E2A\u6587\u4EF6\uFF08-u\uFF09\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
415
+ risk: "write",
416
+ schema: GitStashSchema,
417
+ handler: async (args, ctx) => ctx.git.stash(args.message, {
418
+ dryRun: args.dryRun,
419
+ includeUntracked: args.includeUntracked
420
+ })
421
+ },
422
+ {
423
+ name: "git_stash_pop",
424
+ description: "\u6062\u590D\u6700\u65B0\uFF08\u6216\u6307\u5B9A index\uFF09\u7684 stash\u3002\u6062\u590D\u4EA7\u751F\u51B2\u7A81\u4F1A\u63D0\u793A\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
425
+ risk: "write",
426
+ schema: GitStashPopSchema,
427
+ handler: async (args, ctx) => ctx.git.stashPop({ dryRun: args.dryRun, index: args.index })
428
+ },
429
+ // ---------------------------------------------------------------------------
430
+ // 高风险工具(默认禁用,需开启/审批;自动备份)
431
+ // ---------------------------------------------------------------------------
432
+ {
433
+ name: "git_reset_hard",
434
+ description: "\u786C\u91CD\u7F6E\u5230\u6307\u5B9A\u63D0\u4EA4\uFF08\u4E22\u5F03\u7D22\u5F15\u4E0E\u5DE5\u4F5C\u533A\u66F4\u6539\uFF0C\u9AD8\u98CE\u9669\u4E0D\u53EF\u9006\uFF0C\u6267\u884C\u524D\u81EA\u52A8\u5907\u4EFD\uFF09\u3002",
435
+ risk: "dangerous",
436
+ schema: GitResetHardSchema,
437
+ handler: async (args, ctx) => ctx.git.resetHard(args.target, { dryRun: args.dryRun })
438
+ },
439
+ {
440
+ name: "git_clean",
441
+ description: "\u6E05\u7406\uFF08\u5220\u9664\uFF09\u6240\u6709\u672A\u8DDF\u8E2A\u6587\u4EF6\u4E0E\u76EE\u5F55\uFF08\u9AD8\u98CE\u9669\u4E0D\u53EF\u9006\uFF0C\u6267\u884C\u524D\u81EA\u52A8\u5907\u4EFD\uFF09\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
442
+ risk: "dangerous",
443
+ schema: GitCleanSchema,
444
+ handler: async (args, ctx) => ctx.git.clean({ dryRun: args.dryRun })
445
+ },
446
+ {
447
+ name: "git_push_force",
448
+ description: "\u4EE5 --force-with-lease \u5F3A\u5236\u63A8\u9001\uFF08\u8986\u76D6\u8FDC\u7A0B\u5386\u53F2\uFF0C\u9AD8\u98CE\u9669\uFF0C\u6267\u884C\u524D\u81EA\u52A8\u5907\u4EFD\uFF09\u3002",
449
+ risk: "dangerous",
450
+ schema: GitPushForceSchema,
451
+ handler: async (args, ctx) => ctx.git.pushForce({
452
+ dryRun: args.dryRun,
453
+ remote: args.remote,
454
+ branch: args.branch
455
+ })
456
+ },
457
+ {
458
+ name: "git_branch_delete_force",
459
+ description: "\u5F3A\u5236\u5220\u9664\u5206\u652F\uFF08-D\uFF0C\u4E0D\u68C0\u67E5\u5408\u5E76\u72B6\u6001\uFF0C\u9AD8\u98CE\u9669\uFF09\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
460
+ risk: "dangerous",
461
+ schema: GitBranchDeleteForceSchema,
462
+ handler: async (args, ctx) => ctx.git.deleteBranchForce(args.name, { dryRun: args.dryRun })
463
+ },
464
+ {
465
+ name: "git_rebase",
466
+ description: "\u628A\u5F53\u524D\u5206\u652F\u53D8\u57FA\u5230\u6307\u5B9A\u5206\u652F\uFF08\u91CD\u5199\u63D0\u4EA4\u5386\u53F2\uFF0C\u9AD8\u98CE\u9669\uFF0C\u4EA7\u751F\u51B2\u7A81\u9700\u624B\u52A8\u89E3\u51B3\u540E git rebase --continue\uFF09\u3002",
467
+ risk: "dangerous",
468
+ schema: GitRebaseSchema,
469
+ handler: async (args, ctx) => ctx.git.rebase(args.branch, { dryRun: args.dryRun })
470
+ }
471
+ ];
472
+ var TOOL_DEF_MAP = new Map(TOOL_DEFS.map((d) => [d.name, d]));
473
+ function toolSummaries() {
474
+ return TOOL_DEFS.map((d) => ({ name: d.name, description: d.description }));
475
+ }
476
+
477
+ // src/mcpServer.ts
478
+ var MCP_SERVER_INFO = { name: "git-cockpit-mcp-server", version: "0.1.0" };
479
+ function createMcpServer(runtime) {
480
+ const server = new McpServer(
481
+ { name: MCP_SERVER_INFO.name, version: MCP_SERVER_INFO.version },
482
+ {
483
+ capabilities: { tools: {} },
484
+ instructions: [
485
+ "Git Cockpit MCP Server\uFF1A\u53EF\u89C6\u5316\u7684 Git \u64CD\u4F5C\u5DE5\u5177\u3002",
486
+ "\u4F7F\u7528\u89C4\u5219\uFF1A",
487
+ "1. \u5148\u8C03\u7528 git_status \u67E5\u770B\u5DE5\u4F5C\u533A\u72B6\u6001\uFF1B",
488
+ "2. \u5199\u64CD\u4F5C\uFF08git_add/git_commit/git_push \u7B49\uFF09\u9ED8\u8BA4\u53EF\u6267\u884C\uFF0C\u4F46\u8BF7\u5148\u4F7F\u7528 dry_run=true \u9884\u89C8\u5F71\u54CD\u8303\u56F4\uFF1B",
489
+ "3. \u9AD8\u98CE\u9669\u5DE5\u5177\uFF08git_reset_hard/git_clean/git_push_force/git_branch_delete_force/git_rebase\uFF09\u9ED8\u8BA4\u7981\u7528\uFF0C",
490
+ " \u9700\u7BA1\u7406\u5458\u5728\u914D\u7F6E\u4E2D\u5F00\u542F\uFF1B\u6267\u884C\u65F6\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u5907\u4EFD\u5F53\u524D\u72B6\u6001\uFF1B",
491
+ "4. \u672A\u6307\u5B9A repoPath \u65F6\u4F7F\u7528\u6700\u8FD1\u6253\u5F00\u7684\u4ED3\u5E93\u3002"
492
+ ].join("\n")
493
+ }
494
+ );
495
+ for (const def of TOOL_DEFS) {
496
+ const register = server.registerTool;
497
+ register(
498
+ def.name,
499
+ { description: def.description, inputSchema: def.schema },
500
+ async (args) => {
501
+ const exec = await executeTool(def, args ?? {}, {
502
+ runtime,
503
+ source: "mcp"
504
+ });
505
+ return {
506
+ content: [{ type: "text", text: formatResultForMcp(exec) }],
507
+ isError: !exec.success
508
+ };
509
+ }
510
+ );
511
+ }
512
+ return server;
513
+ }
514
+ async function startMcpStdio(runtime) {
515
+ const server = createMcpServer(runtime);
516
+ const transport = new StdioServerTransport();
517
+ await server.connect(transport);
518
+ }
519
+ var McpHttpHandler = class {
520
+ constructor(runtime) {
521
+ this.runtime = runtime;
522
+ }
523
+ runtime;
524
+ sessions = /* @__PURE__ */ new Map();
525
+ async handle(req, res) {
526
+ const headerId = req.headers["mcp-session-id"];
527
+ if (typeof headerId === "string" && headerId) {
528
+ const session2 = this.sessions.get(headerId);
529
+ if (!session2) {
530
+ res.statusCode = 404;
531
+ res.end("MCP session not found");
532
+ return;
533
+ }
534
+ await session2.transport.handleRequest(req, res, void 0);
535
+ return;
536
+ }
537
+ const transport = new StreamableHTTPServerTransport({
538
+ sessionIdGenerator: () => randomUUID()
539
+ });
540
+ const server = createMcpServer(this.runtime);
541
+ await server.connect(transport);
542
+ const session = { id: "", server, transport };
543
+ transport.onclose = () => {
544
+ if (session.id) this.sessions.delete(session.id);
545
+ void server.close().catch(() => void 0);
546
+ };
547
+ await transport.handleRequest(req, res, void 0);
548
+ const sid = transport.sessionId;
549
+ if (sid) {
550
+ session.id = sid;
551
+ this.sessions.set(sid, session);
552
+ }
553
+ }
554
+ dispose() {
555
+ for (const session of this.sessions.values()) {
556
+ session.transport.close().catch(() => void 0);
557
+ }
558
+ this.sessions.clear();
559
+ }
560
+ };
561
+
562
+ export {
563
+ executeTool,
564
+ formatResultForMcp,
565
+ TOOL_DEFS,
566
+ TOOL_DEF_MAP,
567
+ toolSummaries,
568
+ MCP_SERVER_INFO,
569
+ createMcpServer,
570
+ startMcpStdio,
571
+ McpHttpHandler
572
+ };
573
+ //# sourceMappingURL=chunk-HAXZI36Y.js.map