-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpr-create-tool.ts
More file actions
122 lines (110 loc) · 3.65 KB
/
pr-create-tool.ts
File metadata and controls
122 lines (110 loc) · 3.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import type { FastMCP } from "fastmcp";
import { z } from "zod";
import { gateAuth } from "./github-auth.js";
import { classifyError, getOctokit } from "./github-client.js";
import { errorRespond, jsonRespond, truncateText } from "./json.js";
import { RepoRefSchema } from "./schemas.js";
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface PrCreateResult {
number: number;
url: string;
state: string;
draft: boolean;
}
export interface PrCreateDryRunResult {
dryRun: true;
plan: {
owner: string;
repo: string;
head: string;
base: string;
title: string;
draft: boolean;
bodyPreview: string;
};
}
// ---------------------------------------------------------------------------
// Tool registration
// ---------------------------------------------------------------------------
export function registerPrCreateTool(server: FastMCP): void {
server.addTool({
name: "pr_create",
description:
"Create a pull request. Requires a head branch (source) and base branch (target). Returns PR number, URL, state, and draft status.",
annotations: { readOnlyHint: false },
parameters: RepoRefSchema.extend({
title: z.string().describe("Pull request title."),
body: z.string().optional().describe("Pull request body (markdown)."),
head: z
.string()
.describe(
"Source branch name (e.g. 'feature/my-branch'). Required; the branch must exist.",
),
base: z.string().describe("Target branch name (e.g. 'main')."),
draft: z
.boolean()
.optional()
.default(false)
.describe("Mark the PR as a draft. Defaults to false."),
maintainerCanModify: z
.boolean()
.optional()
.default(true)
.describe("Allow repository maintainers to modify the PR. Defaults to true."),
dryRun: z
.boolean()
.optional()
.default(false)
.describe(
"If true, compute and return the planned PR creation (owner/repo/head/base/title/draft/bodyPreview) WITHOUT executing any mutation.",
),
}),
execute: async (args) => {
const auth = gateAuth();
if (!auth.ok) return errorRespond(auth.envelope);
const { owner, repo, title, body, head, base, draft, maintainerCanModify, dryRun } = args;
try {
const octokit = getOctokit();
if (dryRun) {
const plan: PrCreateDryRunResult["plan"] = {
owner,
repo,
head,
base: base ?? "main",
title,
draft: draft ?? false,
bodyPreview: body ? truncateText(body, 200) : "",
};
return jsonRespond({ dryRun: true, plan });
}
// Build PR creation request
const requestParams: Parameters<typeof octokit.pulls.create>[0] = {
owner,
repo,
title,
head,
base,
draft,
maintainer_can_modify: maintainerCanModify,
...(body?.trim() ? { body } : {}),
};
const pr = await octokit.pulls.create(requestParams);
const result: PrCreateResult = {
number: pr.data.number,
url: pr.data.html_url,
state: pr.data.state,
draft: pr.data.draft || false,
};
return jsonRespond(result);
} catch (err) {
console.error(
`[pr_create] Failed to create PR for ${owner}/${repo}:`,
err instanceof Error ? err.message : String(err),
);
return errorRespond(classifyError(err));
}
},
});
}