Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 05723f8

Browse files
committed
feat: add Gitee Pull Request operation tools, including create, list, get, update, and merge
Signed-off-by: 诺墨 <[email protected]>
1 parent 0b7a9fa commit 05723f8

File tree

3 files changed

+297
-1
lines changed

3 files changed

+297
-1
lines changed

common/types.ts

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,86 @@ export const GiteeIssueCommentSchema = z.object({
301301
source: z.any().nullable(), // source 可能为 null
302302
});
303303

304+
export const GiteePullRequestSchema = z.object({
305+
id: z.number(),
306+
url: z.string().url(),
307+
html_url: z.string().url(),
308+
diff_url: z.string().url().optional(),
309+
patch_url: z.string().url().optional(),
310+
issue_url: z.string().url().optional(),
311+
commits_url: z.string().url().optional(),
312+
review_comments_url: z.string().url().optional(),
313+
review_comment_url: z.string().url().optional(),
314+
comments_url: z.string().url().optional(),
315+
statuses_url: z.string().url().optional(),
316+
number: z.number(),
317+
state: z.string(),
318+
title: z.string(),
319+
body: z.string().nullable(),
320+
assignees: z.array(GiteeUserSchema).optional(),
321+
milestone: z.object({
322+
id: z.number(),
323+
number: z.number(),
324+
state: z.string(),
325+
title: z.string(),
326+
description: z.string().nullable(),
327+
creator: GiteeUserSchema,
328+
open_issues: z.number(),
329+
closed_issues: z.number(),
330+
created_at: z.string(),
331+
updated_at: z.string(),
332+
due_on: z.string().nullable(),
333+
}).nullable().optional(),
334+
locked: z.boolean().optional(),
335+
created_at: z.string(),
336+
updated_at: z.string(),
337+
closed_at: z.string().nullable().optional(),
338+
merged_at: z.string().nullable().optional(),
339+
head: z.object({
340+
label: z.string(),
341+
ref: z.string(),
342+
sha: z.string(),
343+
user: GiteeUserSchema,
344+
repo: z.lazy(() => GiteeRepositorySchema).optional(),
345+
}),
346+
base: z.object({
347+
label: z.string(),
348+
ref: z.string(),
349+
sha: z.string(),
350+
user: GiteeUserSchema,
351+
repo: z.lazy(() => GiteeRepositorySchema).optional(),
352+
}),
353+
_links: z.object({
354+
self: z.object({
355+
href: z.string().url(),
356+
}).optional(),
357+
html: z.object({
358+
href: z.string().url(),
359+
}).optional(),
360+
issue: z.object({
361+
href: z.string().url(),
362+
}).optional(),
363+
comments: z.object({
364+
href: z.string().url(),
365+
}).optional(),
366+
review_comments: z.object({
367+
href: z.string().url(),
368+
}).optional(),
369+
review_comment: z.object({
370+
href: z.string().url(),
371+
}).optional(),
372+
commits: z.object({
373+
href: z.string().url(),
374+
}).optional(),
375+
statuses: z.object({
376+
href: z.string().url(),
377+
}).optional(),
378+
}).optional(),
379+
user: GiteeUserSchema,
380+
merge_commit_sha: z.string().nullable().optional(),
381+
mergeable: z.boolean().nullable().optional(),
382+
});
383+
304384
// Type Exports
305385
export type GiteeUser = z.infer<typeof GiteeUserSchema>;
306386
export type GiteeRepository = z.infer<typeof GiteeRepositorySchema>;
@@ -311,4 +391,5 @@ export type GiteeFileContent = z.infer<typeof GiteeFileContentSchema>;
311391
export type GiteeDirectoryContent = z.infer<typeof GiteeDirectoryContentSchema>;
312392
export type GiteeFileOperationResult = z.infer<typeof GiteeFileOperationResultSchema>;
313393
export type GiteeIssue = z.infer<typeof GiteeIssueSchema>;
314-
export type GiteeIssueComment = z.infer<typeof GiteeIssueCommentSchema>;
394+
export type GiteeIssueComment = z.infer<typeof GiteeIssueCommentSchema>;
395+
export type GiteePullRequest = z.infer<typeof GiteePullRequestSchema>;

index.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { VERSION } from "./common/version.js";
66
import * as branchOperations from "./operations/branches.js";
77
import * as fileOperations from "./operations/files.js";
88
import * as issueOperations from "./operations/issues.js";
9+
import * as pullOperations from "./operations/pulls.js";
910
import * as repoOperations from "./operations/repos.js";
1011
import * as userOperations from "./operations/users.js";
1112
import { z } from 'zod';
@@ -195,6 +196,69 @@ export function createGiteeMCPServer() {
195196
},
196197
});
197198

199+
// 注册 Pull Request 操作工具
200+
server.registerTool({
201+
name: "create_pull_request",
202+
description: "在 Gitee 仓库中创建 Pull Request",
203+
schema: pullOperations.CreatePullRequestSchema,
204+
handler: async (params: any) => {
205+
const { owner, repo, ...rest } = params;
206+
// 确保 owner 和 repo 参数存在
207+
if (!owner || !repo) {
208+
throw new Error("owner 和 repo 参数是必需的");
209+
}
210+
return await pullOperations.createPullRequest({ owner, repo, ...rest });
211+
},
212+
});
213+
214+
server.registerTool({
215+
name: "list_pull_requests",
216+
description: "列出 Gitee 仓库中的 Pull Requests",
217+
schema: pullOperations.ListPullRequestsSchema,
218+
handler: async (params: any) => {
219+
const { owner, repo, ...options } = params;
220+
return await pullOperations.listPullRequests(owner, repo, options);
221+
},
222+
});
223+
224+
server.registerTool({
225+
name: "get_pull_request",
226+
description: "获取 Gitee 仓库中的特定 Pull Request",
227+
schema: pullOperations.GetPullRequestSchema,
228+
handler: async (params: any) => {
229+
const { owner, repo, pull_number } = params;
230+
return await pullOperations.getPullRequest(owner, repo, pull_number);
231+
},
232+
});
233+
234+
server.registerTool({
235+
name: "update_pull_request",
236+
description: "更新 Gitee 仓库中的 Pull Request",
237+
schema: pullOperations.UpdatePullRequestSchema,
238+
handler: async (params: any) => {
239+
const { owner, repo, pull_number, ...options } = params;
240+
// 确保必需参数存在
241+
if (!owner || !repo || pull_number === undefined) {
242+
throw new Error("owner, repo 和 pull_number 参数是必需的");
243+
}
244+
return await pullOperations.updatePullRequest(owner, repo, pull_number, options);
245+
},
246+
});
247+
248+
server.registerTool({
249+
name: "merge_pull_request",
250+
description: "合并 Gitee 仓库中的 Pull Request",
251+
schema: pullOperations.MergePullRequestSchema,
252+
handler: async (params: any) => {
253+
const { owner, repo, pull_number, ...options } = params;
254+
// 确保必需参数存在
255+
if (!owner || !repo || pull_number === undefined) {
256+
throw new Error("owner, repo 和 pull_number 参数是必需的");
257+
}
258+
return await pullOperations.mergePullRequest(owner, repo, pull_number, options);
259+
},
260+
});
261+
198262
// 注册用户操作工具
199263
server.registerTool({
200264
name: "get_user",

operations/pulls.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { z } from "zod";
2+
import { giteeRequest, validateOwnerName, validateRepositoryName, validateBranchName } from "../common/utils.js";
3+
import { GiteePullRequestSchema } from "../common/types.js";
4+
5+
// Schema 定义
6+
export const CreatePullRequestSchema = z.object({
7+
owner: z.string().describe("仓库所属空间地址 (企业、组织或个人的地址 path)"),
8+
repo: z.string().describe("仓库路径 (path)"),
9+
title: z.string().describe("Pull Request 标题"),
10+
head: z.string().describe("源分支的名称"),
11+
base: z.string().describe("目标分支的名称"),
12+
body: z.string().optional().describe("Pull Request 内容"),
13+
milestone_number: z.number().optional().describe("里程碑序号"),
14+
labels: z.array(z.string()).optional().describe("标签"),
15+
issue: z.string().optional().describe("相关的 Issue,格式为 #xxx"),
16+
assignees: z.array(z.string()).optional().describe("审查人员"),
17+
testers: z.array(z.string()).optional().describe("测试人员"),
18+
prune_source_branch: z.boolean().optional().describe("合并后是否删除源分支"),
19+
});
20+
21+
export const ListPullRequestsSchema = z.object({
22+
owner: z.string().describe("仓库所属空间地址 (企业、组织或个人的地址 path)"),
23+
repo: z.string().describe("仓库路径 (path)"),
24+
state: z.enum(["open", "closed", "merged", "all"]).default("open").optional().describe("Pull Request 状态"),
25+
sort: z.enum(["created", "updated", "popularity", "long-running"]).default("created").optional().describe("排序字段"),
26+
direction: z.enum(["asc", "desc"]).default("desc").optional().describe("排序方向"),
27+
milestone: z.number().optional().describe("里程碑 ID"),
28+
labels: z.string().optional().describe("标签,多个标签以逗号分隔"),
29+
page: z.number().int().default(1).optional().describe("当前的页码"),
30+
per_page: z.number().int().min(1).max(100).optional().describe("每页的数量,最大为 100"),
31+
});
32+
33+
export const GetPullRequestSchema = z.object({
34+
owner: z.string().describe("仓库所属空间地址 (企业、组织或个人的地址 path)"),
35+
repo: z.string().describe("仓库路径 (path)"),
36+
pull_number: z.number().describe("Pull Request 编号"),
37+
});
38+
39+
export const UpdatePullRequestSchema = z.object({
40+
owner: z.string().describe("仓库所属空间地址 (企业、组织或个人的地址 path)"),
41+
repo: z.string().describe("仓库路径 (path)"),
42+
pull_number: z.number().describe("Pull Request 编号"),
43+
title: z.string().optional().describe("Pull Request 标题"),
44+
body: z.string().optional().describe("Pull Request 内容"),
45+
state: z.enum(["open", "closed"]).optional().describe("Pull Request 状态"),
46+
milestone_number: z.number().optional().describe("里程碑序号"),
47+
labels: z.array(z.string()).optional().describe("标签"),
48+
assignees: z.array(z.string()).optional().describe("审查人员"),
49+
testers: z.array(z.string()).optional().describe("测试人员"),
50+
});
51+
52+
export const MergePullRequestSchema = z.object({
53+
owner: z.string().describe("仓库所属空间地址 (企业、组织或个人的地址 path)"),
54+
repo: z.string().describe("仓库路径 (path)"),
55+
pull_number: z.number().describe("Pull Request 编号"),
56+
merge_method: z.enum(["merge", "squash", "rebase"]).default("merge").optional().describe("合并方式"),
57+
prune_source_branch: z.boolean().optional().describe("合并后是否删除源分支"),
58+
});
59+
60+
// 类型导出
61+
export type CreatePullRequestOptions = z.infer<typeof CreatePullRequestSchema>;
62+
export type ListPullRequestsOptions = z.infer<typeof ListPullRequestsSchema>;
63+
export type GetPullRequestOptions = z.infer<typeof GetPullRequestSchema>;
64+
export type UpdatePullRequestOptions = z.infer<typeof UpdatePullRequestSchema>;
65+
export type MergePullRequestOptions = z.infer<typeof MergePullRequestSchema>;
66+
67+
// 函数实现
68+
export async function createPullRequest(options: CreatePullRequestOptions) {
69+
const { owner, repo, ...rest } = options;
70+
const validatedOwner = validateOwnerName(owner);
71+
const validatedRepo = validateRepositoryName(repo);
72+
const validatedHead = validateBranchName(rest.head);
73+
const validatedBase = validateBranchName(rest.base);
74+
75+
const url = `https://gitee.com/api/v5/repos/${validatedOwner}/${validatedRepo}/pulls`;
76+
const body = {
77+
...rest,
78+
head: validatedHead,
79+
base: validatedBase,
80+
};
81+
82+
const response = await giteeRequest(url, "POST", body);
83+
84+
return GiteePullRequestSchema.parse(response);
85+
}
86+
87+
export async function listPullRequests(
88+
owner: string,
89+
repo: string,
90+
options: Omit<ListPullRequestsOptions, "owner" | "repo">
91+
) {
92+
owner = validateOwnerName(owner);
93+
repo = validateRepositoryName(repo);
94+
95+
const url = new URL(`https://gitee.com/api/v5/repos/${owner}/${repo}/pulls`);
96+
97+
// 添加查询参数
98+
Object.entries(options).forEach(([key, value]) => {
99+
if (value !== undefined) {
100+
url.searchParams.append(key, value.toString());
101+
}
102+
});
103+
104+
const response = await giteeRequest(url.toString(), "GET");
105+
106+
return z.array(GiteePullRequestSchema).parse(response);
107+
}
108+
109+
export async function getPullRequest(
110+
owner: string,
111+
repo: string,
112+
pullNumber: number
113+
) {
114+
owner = validateOwnerName(owner);
115+
repo = validateRepositoryName(repo);
116+
117+
const url = `https://gitee.com/api/v5/repos/${owner}/${repo}/pulls/${pullNumber}`;
118+
const response = await giteeRequest(url, "GET");
119+
120+
return GiteePullRequestSchema.parse(response);
121+
}
122+
123+
export async function updatePullRequest(
124+
owner: string,
125+
repo: string,
126+
pullNumber: number,
127+
options: Omit<UpdatePullRequestOptions, "owner" | "repo" | "pull_number">
128+
) {
129+
owner = validateOwnerName(owner);
130+
repo = validateRepositoryName(repo);
131+
132+
const url = `https://gitee.com/api/v5/repos/${owner}/${repo}/pulls/${pullNumber}`;
133+
const response = await giteeRequest(url, "PATCH", options);
134+
135+
return GiteePullRequestSchema.parse(response);
136+
}
137+
138+
export async function mergePullRequest(
139+
owner: string,
140+
repo: string,
141+
pullNumber: number,
142+
options: Omit<MergePullRequestOptions, "owner" | "repo" | "pull_number">
143+
) {
144+
owner = validateOwnerName(owner);
145+
repo = validateRepositoryName(repo);
146+
147+
const url = `https://gitee.com/api/v5/repos/${owner}/${repo}/pulls/${pullNumber}/merge`;
148+
const response = await giteeRequest(url, "PUT", options);
149+
150+
return response;
151+
}

0 commit comments

Comments
 (0)