@arii/boomtick-mcp 0.6.2 → 0.7.1
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.
- package/.mcp/schemas/boomtick.health.json +22 -3
- package/.mcp/schemas/boomtick_health.json +22 -3
- package/.mcp/schemas/github.checkout_branch.json +16 -3
- package/.mcp/schemas/github.comment_triage_summary.json +16 -3
- package/.mcp/schemas/github.create_pull_request.json +16 -3
- package/.mcp/schemas/github.get_merge_conflict_files.json +55 -5
- package/.mcp/schemas/github.get_pr.json +12 -3
- package/.mcp/schemas/github.get_pr_commits.json +12 -3
- package/.mcp/schemas/github.get_pr_reviews.json +24 -3
- package/.mcp/schemas/github.issue_comment.json +19 -3
- package/.mcp/schemas/github.issue_view.json +17 -3
- package/.mcp/schemas/github.open_replacement_pr.json +16 -3
- package/.mcp/schemas/github_checkout_branch.json +16 -3
- package/.mcp/schemas/github_comment_triage_summary.json +16 -3
- package/.mcp/schemas/github_create_pull_request.json +16 -3
- package/.mcp/schemas/github_get_merge_conflict_files.json +55 -5
- package/.mcp/schemas/github_get_pr.json +12 -3
- package/.mcp/schemas/github_get_pr_commits.json +12 -3
- package/.mcp/schemas/github_get_pr_reviews.json +24 -3
- package/.mcp/schemas/github_issue_comment.json +19 -3
- package/.mcp/schemas/github_issue_view.json +17 -3
- package/.mcp/schemas/github_open_replacement_pr.json +16 -3
- package/.mcp/schemas/jules.cancel_session.json +38 -3
- package/.mcp/schemas/jules.create_session.json +52 -3
- package/.mcp/schemas/jules.get_messages.json +19 -3
- package/.mcp/schemas/jules.get_pr.json +23 -3
- package/.mcp/schemas/jules.get_session.json +38 -3
- package/.mcp/schemas/jules.list_sessions.json +53 -3
- package/.mcp/schemas/jules.send_message.json +31 -3
- package/.mcp/schemas/jules.trigger_feedback.json +28 -3
- package/.mcp/schemas/jules_cancel_session.json +38 -3
- package/.mcp/schemas/jules_create_session.json +52 -3
- package/.mcp/schemas/jules_get_messages.json +19 -3
- package/.mcp/schemas/jules_get_pr.json +23 -3
- package/.mcp/schemas/jules_get_session.json +38 -3
- package/.mcp/schemas/jules_list_sessions.json +53 -3
- package/.mcp/schemas/jules_send_message.json +31 -3
- package/.mcp/schemas/jules_trigger_feedback.json +28 -3
- package/.mcp/schemas/repo.check_jules_status.json +93 -3
- package/.mcp/schemas/repo.commit_patch.json +23 -3
- package/.mcp/schemas/repo.create_branch.json +91 -3
- package/.mcp/schemas/repo.create_repair_branch.json +94 -3
- package/.mcp/schemas/repo.get_changed_files.json +50 -3
- package/.mcp/schemas/repo.get_command_schema.json +29 -3
- package/.mcp/schemas/repo.get_package_scripts.json +11 -3
- package/.mcp/schemas/repo.get_workflow_runs.json +12 -3
- package/.mcp/schemas/repo.logs.json +14 -3
- package/.mcp/schemas/repo.multi_exec.json +89 -0
- package/.mcp/schemas/repo.read_agent_context.json +117 -5
- package/.mcp/schemas/repo.read_ci_logs.json +46 -3
- package/.mcp/schemas/repo_check_jules_status.json +93 -3
- package/.mcp/schemas/repo_commit_patch.json +23 -3
- package/.mcp/schemas/repo_create_branch.json +91 -3
- package/.mcp/schemas/repo_create_repair_branch.json +94 -3
- package/.mcp/schemas/repo_get_changed_files.json +50 -3
- package/.mcp/schemas/repo_get_command_schema.json +29 -3
- package/.mcp/schemas/repo_get_package_scripts.json +11 -3
- package/.mcp/schemas/repo_get_workflow_runs.json +12 -3
- package/.mcp/schemas/repo_logs.json +14 -3
- package/.mcp/schemas/repo_multi_exec.json +89 -0
- package/.mcp/schemas/repo_read_agent_context.json +117 -5
- package/.mcp/schemas/repo_read_ci_logs.json +46 -3
- package/CHANGELOG.md +22 -0
- package/README.md +7 -5
- package/dist/config/dynamic.js +3 -2
- package/dist/config/repo.js +5 -5
- package/dist/config.test.js +10 -3
- package/dist/lib/git.js +78 -6
- package/dist/lib/git.test.js +34 -1
- package/dist/mcp/definitions.js +49 -36
- package/dist/mcp/http.js +15 -0
- package/dist/mcp/http.test.js +8 -0
- package/dist/mcp/server.js +34 -0
- package/dist/mcp/server.test.js +31 -0
- package/dist/tools/contract.js +1264 -92
- package/dist/tools/github.get_merge_conflict_files.js +10 -2
- package/dist/tools/jules/create-session.js +12 -2
- package/dist/tools/jules/create-session.test.js +7 -0
- package/dist/tools/repo.create_branch.js +18 -4
- package/dist/tools/repo.create_branch.test.js +41 -5
- package/dist/tools/repo.create_repair_branch.js +26 -9
- package/dist/tools/repo.get_changed_files.js +10 -2
- package/dist/tools/repo.multi_exec.js +21 -0
- package/dist/tools/repo.multi_exec.test.js +13 -0
- package/dist/tools/repo.read_agent_context.js +65 -3
- package/dist/tools/repo.read_agent_context.test.js +30 -1
- package/package.json +1 -1
- package/src/config/dynamic.ts +3 -2
- package/src/config/repo.ts +6 -6
- package/src/config.test.ts +11 -3
- package/src/lib/git.test.ts +45 -1
- package/src/lib/git.ts +94 -6
- package/src/mcp/definitions.ts +77 -36
- package/src/mcp/http.test.ts +10 -0
- package/src/mcp/http.ts +20 -0
- package/src/mcp/server.test.ts +38 -0
- package/src/mcp/server.ts +36 -0
- package/src/tools/contract.ts +1355 -67
- package/src/tools/github.get_merge_conflict_files.ts +11 -2
- package/src/tools/jules/create-session.test.ts +9 -0
- package/src/tools/jules/create-session.ts +13 -2
- package/src/tools/repo.create_branch.test.ts +73 -5
- package/src/tools/repo.create_branch.ts +30 -4
- package/src/tools/repo.create_repair_branch.ts +31 -9
- package/src/tools/repo.get_changed_files.ts +10 -2
- package/src/tools/repo.multi_exec.test.ts +15 -0
- package/src/tools/repo.multi_exec.ts +23 -0
- package/src/tools/repo.read_agent_context.test.ts +35 -1
- package/src/tools/repo.read_agent_context.ts +68 -3
package/src/lib/git.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { runCommand } from "./shell.js";
|
|
|
2
2
|
import { config } from "../config.js";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import fs from "fs/promises";
|
|
5
|
+
import * as syncFs from "fs";
|
|
5
6
|
import { execSync } from "child_process";
|
|
6
7
|
|
|
7
8
|
const repoCache = new Map<string, string>();
|
|
@@ -37,6 +38,87 @@ export function resolveRepo(explicitRepo?: string | null, cwd?: string): string
|
|
|
37
38
|
return fallback;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
export interface RepoPathOptions {
|
|
42
|
+
path?: string | null;
|
|
43
|
+
repoPath?: string | null;
|
|
44
|
+
repo?: string | null;
|
|
45
|
+
cwd?: string | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resolveRepoPath(options: RepoPathOptions = {}): string {
|
|
49
|
+
// 1. Explicit path parameter has highest precedence
|
|
50
|
+
if (options.path && options.path.trim().length > 0) {
|
|
51
|
+
return path.resolve(options.path.trim());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 2. repoPath alias parameter
|
|
55
|
+
if (options.repoPath && options.repoPath.trim().length > 0) {
|
|
56
|
+
return path.resolve(options.repoPath.trim());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 3. Repository name override (e.g. "arii/tech-dancer" or "tech-dancer")
|
|
60
|
+
if (options.repo && options.repo.trim().length > 0) {
|
|
61
|
+
const rawRepo = options.repo.trim();
|
|
62
|
+
const repoName = rawRepo.includes("/") ? rawRepo.split("/")[1] : rawRepo;
|
|
63
|
+
|
|
64
|
+
// First check project_config.json ecosystem_repos if available
|
|
65
|
+
let rootPath: string = process.cwd();
|
|
66
|
+
try {
|
|
67
|
+
if (options.cwd && options.cwd.trim().length > 0) {
|
|
68
|
+
rootPath = options.cwd.trim();
|
|
69
|
+
} else if (config.repoPath) {
|
|
70
|
+
rootPath = config.repoPath;
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
rootPath = process.cwd();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const projectConfigPath = path.join(rootPath, "project_config.json");
|
|
77
|
+
if (syncFs.existsSync(projectConfigPath)) {
|
|
78
|
+
try {
|
|
79
|
+
const parsed = JSON.parse(syncFs.readFileSync(projectConfigPath, "utf-8"));
|
|
80
|
+
if (parsed?.ecosystem_repos && typeof parsed.ecosystem_repos === "object") {
|
|
81
|
+
for (const [key, relPath] of Object.entries(parsed.ecosystem_repos)) {
|
|
82
|
+
const keyRepoName = key.includes("/") ? key.split("/")[1] : key;
|
|
83
|
+
if (key === rawRepo || keyRepoName === repoName) {
|
|
84
|
+
return path.resolve(rootPath, relPath as string);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
// Fallback to directory inspection
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check sibling directory relative to cwd or rootPath
|
|
94
|
+
const candidateDirs = [
|
|
95
|
+
path.resolve(process.cwd(), "..", repoName),
|
|
96
|
+
path.resolve(rootPath, "..", repoName),
|
|
97
|
+
];
|
|
98
|
+
for (const cand of candidateDirs) {
|
|
99
|
+
if (syncFs.existsSync(cand)) {
|
|
100
|
+
return cand;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 4. Explicit cwd parameter
|
|
106
|
+
if (options.cwd && options.cwd.trim().length > 0) {
|
|
107
|
+
return path.resolve(options.cwd.trim());
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 5. Configured repoPath or process.cwd()
|
|
111
|
+
try {
|
|
112
|
+
if (config.repoPath && syncFs.existsSync(config.repoPath)) {
|
|
113
|
+
return config.repoPath;
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
// Fall back to process.cwd()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return process.cwd();
|
|
120
|
+
}
|
|
121
|
+
|
|
40
122
|
function validatePrNumber(value: unknown): number {
|
|
41
123
|
const prNumber = Number(value);
|
|
42
124
|
|
|
@@ -47,20 +129,25 @@ function validatePrNumber(value: unknown): number {
|
|
|
47
129
|
return prNumber;
|
|
48
130
|
}
|
|
49
131
|
|
|
50
|
-
export async function createWorktree(branch: string, prNumber: number): Promise<string> {
|
|
132
|
+
export async function createWorktree(branch: string, prNumber: number, baseRepoPath?: string): Promise<string> {
|
|
51
133
|
const safePrNumber = validatePrNumber(prNumber);
|
|
52
134
|
|
|
53
135
|
const workspaceRoot = "/tmp/boomtick-worktrees";
|
|
54
136
|
// nosemgrep
|
|
55
137
|
const worktreePath = path.join(workspaceRoot, `boomtick-mcp-rescue-${safePrNumber}`);
|
|
138
|
+
const cwdOption = baseRepoPath ? { cwd: baseRepoPath } : undefined;
|
|
56
139
|
|
|
57
140
|
// Clean up if exists
|
|
58
141
|
try {
|
|
59
142
|
await fs.rm(worktreePath, { recursive: true, force: true });
|
|
60
|
-
await runCommand("git", ["worktree", "prune"]);
|
|
143
|
+
await runCommand("git", ["worktree", "prune"], cwdOption);
|
|
61
144
|
} catch (e) { /* ignore if no previous worktree */ }
|
|
62
145
|
|
|
63
|
-
const result = await runCommand(
|
|
146
|
+
const result = await runCommand(
|
|
147
|
+
"git",
|
|
148
|
+
["worktree", "add", "-b", `repair-pr-${prNumber}-${Date.now()}`, worktreePath, branch],
|
|
149
|
+
cwdOption
|
|
150
|
+
);
|
|
64
151
|
|
|
65
152
|
if (result.exitCode !== 0) {
|
|
66
153
|
throw new Error(`Failed to create worktree: ${result.stderr}`);
|
|
@@ -69,9 +156,10 @@ export async function createWorktree(branch: string, prNumber: number): Promise<
|
|
|
69
156
|
return worktreePath;
|
|
70
157
|
}
|
|
71
158
|
|
|
72
|
-
export async function removeWorktree(worktreePath: string): Promise<void> {
|
|
73
|
-
|
|
74
|
-
await runCommand("git", ["worktree", "
|
|
159
|
+
export async function removeWorktree(worktreePath: string, baseRepoPath?: string): Promise<void> {
|
|
160
|
+
const cwdOption = baseRepoPath ? { cwd: baseRepoPath } : undefined;
|
|
161
|
+
await runCommand("git", ["worktree", "remove", "--force", worktreePath], cwdOption);
|
|
162
|
+
await runCommand("git", ["worktree", "prune"], cwdOption);
|
|
75
163
|
}
|
|
76
164
|
|
|
77
165
|
export interface ChangedFile {
|
package/src/mcp/definitions.ts
CHANGED
|
@@ -1,41 +1,70 @@
|
|
|
1
1
|
import { Tool, Prompt, Resource, ResourceTemplate } from "@modelcontextprotocol/sdk/types.js";
|
|
2
2
|
import {
|
|
3
3
|
HealthCheckInputJsonSchema,
|
|
4
|
+
HealthCheckResponseJsonSchema,
|
|
4
5
|
SearchPRsInputJsonSchema,
|
|
5
6
|
SearchPRsResponseJsonSchema,
|
|
6
7
|
GetPrDiffInputJsonSchema,
|
|
7
8
|
PrDiffResponseJsonSchema,
|
|
8
9
|
CheckoutBranchInputJsonSchema,
|
|
10
|
+
CheckoutBranchResponseJsonSchema,
|
|
9
11
|
GetMergeConflictFilesInputJsonSchema,
|
|
10
12
|
GetMergeConflictFilesResponseJsonSchema,
|
|
11
13
|
GetChangedFilesInputJsonSchema,
|
|
14
|
+
GetChangedFilesResponseJsonSchema,
|
|
12
15
|
GetPackageScriptsInputJsonSchema,
|
|
16
|
+
GetPackageScriptsResponseJsonSchema,
|
|
13
17
|
ReadCiLogsInputJsonSchema,
|
|
18
|
+
ReadCiLogsResponseJsonSchema,
|
|
14
19
|
RepoLogsInputJsonSchema,
|
|
20
|
+
RepoLogsResponseJsonSchema,
|
|
15
21
|
CreateBranchInputJsonSchema,
|
|
22
|
+
CreateBranchResponseJsonSchema,
|
|
16
23
|
CreateRepairBranchInputJsonSchema,
|
|
24
|
+
CreateRepairBranchResponseJsonSchema,
|
|
25
|
+
CheckJulesStatusResponseJsonSchema,
|
|
17
26
|
RunTestsInputJsonSchema,
|
|
18
27
|
RunTestsResponseJsonSchema,
|
|
19
28
|
CommitPatchInputJsonSchema,
|
|
29
|
+
CommitPatchResponseJsonSchema,
|
|
20
30
|
OpenReplacementPrInputJsonSchema,
|
|
31
|
+
OpenReplacementPrResponseJsonSchema,
|
|
21
32
|
CreatePullRequestInputJsonSchema,
|
|
33
|
+
CreatePullRequestResponseJsonSchema,
|
|
22
34
|
CommentTriageSummaryInputJsonSchema,
|
|
35
|
+
CommentTriageSummaryResponseJsonSchema,
|
|
23
36
|
GetPrInputJsonSchema,
|
|
37
|
+
GetPrResponseJsonSchema,
|
|
24
38
|
IssueViewInputJsonSchema,
|
|
39
|
+
IssueViewResponseJsonSchema,
|
|
25
40
|
IssueUpdateInputJsonSchema,
|
|
26
41
|
IssueUpdateResponseJsonSchema,
|
|
27
42
|
IssueCommentInputJsonSchema,
|
|
43
|
+
IssueCommentResponseJsonSchema,
|
|
28
44
|
CreateIssueInputJsonSchema,
|
|
29
45
|
CreateIssueResponseJsonSchema,
|
|
30
46
|
GetCommandSchemaInputJsonSchema,
|
|
47
|
+
GetCommandSchemaResponseJsonSchema,
|
|
31
48
|
ReadAgentContextInputJsonSchema as ReadAgentContextSchema,
|
|
49
|
+
ReadAgentContextResponseJsonSchema,
|
|
50
|
+
MultiExecInputJsonSchema,
|
|
51
|
+
MultiExecResponseJsonSchema,
|
|
32
52
|
CreateJulesSessionInputJsonSchema,
|
|
33
53
|
JulesSessionIdInputJsonSchema,
|
|
34
54
|
JulesSendMessageInputJsonSchema,
|
|
55
|
+
JulesSendMessageResponseJsonSchema,
|
|
56
|
+
JulesGetMessagesResponseJsonSchema,
|
|
35
57
|
JulesListSessionsInputJsonSchema,
|
|
58
|
+
JulesListSessionsResponseJsonSchema,
|
|
59
|
+
JulesSessionResponseJsonSchema,
|
|
60
|
+
JulesGetPrResponseJsonSchema,
|
|
61
|
+
JulesTriggerFeedbackResponseJsonSchema,
|
|
36
62
|
GetPrReviewsInputJsonSchema,
|
|
63
|
+
GetPrReviewsResponseJsonSchema,
|
|
37
64
|
GetPrCommitsInputJsonSchema,
|
|
65
|
+
GetPrCommitsResponseJsonSchema,
|
|
38
66
|
GetWorkflowRunsInputJsonSchema,
|
|
67
|
+
GetWorkflowRunsResponseJsonSchema,
|
|
39
68
|
} from "../tools/contract.js";
|
|
40
69
|
|
|
41
70
|
export const MCP_PROMPTS: Prompt[] = [
|
|
@@ -78,12 +107,24 @@ export const MCP_RESOURCES: Resource[] = [
|
|
|
78
107
|
mimeType: "text/markdown",
|
|
79
108
|
description: "The agent contract specification file (.agents/AGENT_CONTRACT.md).",
|
|
80
109
|
},
|
|
110
|
+
{
|
|
111
|
+
uri: "mcp://workspace/project-config",
|
|
112
|
+
name: "Project Configuration",
|
|
113
|
+
mimeType: "application/json",
|
|
114
|
+
description: "The project configuration (project_config.json).",
|
|
115
|
+
},
|
|
81
116
|
{
|
|
82
117
|
uri: "mcp://git/changed-files",
|
|
83
118
|
name: "Git Changed Files",
|
|
84
119
|
mimeType: "application/json",
|
|
85
120
|
description: "List of changed files in the repository.",
|
|
86
121
|
},
|
|
122
|
+
{
|
|
123
|
+
uri: "mcp://git/status",
|
|
124
|
+
name: "Git Status",
|
|
125
|
+
mimeType: "application/json",
|
|
126
|
+
description: "Current working directory git status.",
|
|
127
|
+
},
|
|
87
128
|
{
|
|
88
129
|
uri: "mcp://ci/latest-run",
|
|
89
130
|
name: "Latest CI Run",
|
|
@@ -125,18 +166,12 @@ export const MCP_RESOURCE_TEMPLATES: ResourceTemplate[] = [
|
|
|
125
166
|
},
|
|
126
167
|
];
|
|
127
168
|
|
|
128
|
-
const DEFAULT_OUTPUT_SCHEMA = {
|
|
129
|
-
type: "object",
|
|
130
|
-
properties: {},
|
|
131
|
-
additionalProperties: true,
|
|
132
|
-
} as const;
|
|
133
|
-
|
|
134
169
|
const BASE_MCP_TOOLS: Tool[] = [
|
|
135
170
|
{
|
|
136
171
|
name: "boomtick.health",
|
|
137
172
|
description: "Check the health and configuration of the MCP server.",
|
|
138
173
|
inputSchema: HealthCheckInputJsonSchema as any,
|
|
139
|
-
outputSchema:
|
|
174
|
+
outputSchema: HealthCheckResponseJsonSchema as any,
|
|
140
175
|
},
|
|
141
176
|
{
|
|
142
177
|
name: "github.search_open_prs",
|
|
@@ -146,9 +181,15 @@ const BASE_MCP_TOOLS: Tool[] = [
|
|
|
146
181
|
},
|
|
147
182
|
{
|
|
148
183
|
name: "repo.read_agent_context",
|
|
149
|
-
description: "Read the
|
|
184
|
+
description: "Read the repository index including metadata, model review chains, or multi-repo ecosystem status.",
|
|
150
185
|
inputSchema: ReadAgentContextSchema as any,
|
|
151
|
-
outputSchema:
|
|
186
|
+
outputSchema: ReadAgentContextResponseJsonSchema as any,
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "repo.multi_exec",
|
|
190
|
+
description: "Execute a shell command sequentially across ecosystem repositories (or a filtered subset).",
|
|
191
|
+
inputSchema: MultiExecInputJsonSchema as any,
|
|
192
|
+
outputSchema: MultiExecResponseJsonSchema as any,
|
|
152
193
|
},
|
|
153
194
|
{
|
|
154
195
|
name: "github.get_pr_diff",
|
|
@@ -160,7 +201,7 @@ const BASE_MCP_TOOLS: Tool[] = [
|
|
|
160
201
|
name: "github.checkout_branch",
|
|
161
202
|
description: "Checkout a specific branch in the repository or worktree.",
|
|
162
203
|
inputSchema: CheckoutBranchInputJsonSchema as any,
|
|
163
|
-
outputSchema:
|
|
204
|
+
outputSchema: CheckoutBranchResponseJsonSchema as any,
|
|
164
205
|
},
|
|
165
206
|
{
|
|
166
207
|
name: "github.get_merge_conflict_files",
|
|
@@ -172,43 +213,43 @@ const BASE_MCP_TOOLS: Tool[] = [
|
|
|
172
213
|
name: "repo.get_changed_files",
|
|
173
214
|
description: "Get the list of changed files between two refs.",
|
|
174
215
|
inputSchema: GetChangedFilesInputJsonSchema as any,
|
|
175
|
-
outputSchema:
|
|
216
|
+
outputSchema: GetChangedFilesResponseJsonSchema as any,
|
|
176
217
|
},
|
|
177
218
|
{
|
|
178
219
|
name: "repo.get_command_schema",
|
|
179
220
|
description: "Retrieve a lightweight, targeted schema for a specific td subcommand. Use this instead of reading the entire cli-schema.json to save tokens.",
|
|
180
221
|
inputSchema: GetCommandSchemaInputJsonSchema as any,
|
|
181
|
-
outputSchema:
|
|
222
|
+
outputSchema: GetCommandSchemaResponseJsonSchema as any,
|
|
182
223
|
},
|
|
183
224
|
{
|
|
184
225
|
name: "repo.get_package_scripts",
|
|
185
226
|
description: "Get the scripts defined in package.json.",
|
|
186
227
|
inputSchema: GetPackageScriptsInputJsonSchema as any,
|
|
187
|
-
outputSchema:
|
|
228
|
+
outputSchema: GetPackageScriptsResponseJsonSchema as any,
|
|
188
229
|
},
|
|
189
230
|
{
|
|
190
231
|
name: "repo.read_ci_logs",
|
|
191
232
|
description: "Read CI logs for a given pull request.",
|
|
192
233
|
inputSchema: ReadCiLogsInputJsonSchema as any,
|
|
193
|
-
outputSchema:
|
|
234
|
+
outputSchema: ReadCiLogsResponseJsonSchema as any,
|
|
194
235
|
},
|
|
195
236
|
{
|
|
196
237
|
name: "repo.logs",
|
|
197
238
|
description: "Stream or grep combined CI logs for all jobs in a pull request.",
|
|
198
239
|
inputSchema: RepoLogsInputJsonSchema as any,
|
|
199
|
-
outputSchema:
|
|
240
|
+
outputSchema: RepoLogsResponseJsonSchema as any,
|
|
200
241
|
},
|
|
201
242
|
{
|
|
202
243
|
name: "repo.create_branch",
|
|
203
244
|
description: "Creates a new clean git branch from a target base branch.",
|
|
204
245
|
inputSchema: CreateBranchInputJsonSchema as any,
|
|
205
|
-
outputSchema:
|
|
246
|
+
outputSchema: CreateBranchResponseJsonSchema as any,
|
|
206
247
|
},
|
|
207
248
|
{
|
|
208
249
|
name: "repo.create_repair_branch",
|
|
209
250
|
description: "Creates a fresh worktree and a new repair branch for an existing PR. This isolates the repair work from the main codebase.",
|
|
210
251
|
inputSchema: CreateRepairBranchInputJsonSchema as any,
|
|
211
|
-
outputSchema:
|
|
252
|
+
outputSchema: CreateRepairBranchResponseJsonSchema as any,
|
|
212
253
|
},
|
|
213
254
|
{
|
|
214
255
|
name: "repo.check_jules_status",
|
|
@@ -224,7 +265,7 @@ const BASE_MCP_TOOLS: Tool[] = [
|
|
|
224
265
|
intervalMs: { type: "integer", description: "Polling interval in milliseconds." },
|
|
225
266
|
},
|
|
226
267
|
} as any,
|
|
227
|
-
outputSchema:
|
|
268
|
+
outputSchema: CheckJulesStatusResponseJsonSchema as any,
|
|
228
269
|
},
|
|
229
270
|
{
|
|
230
271
|
name: "repo.run_tests",
|
|
@@ -236,55 +277,55 @@ const BASE_MCP_TOOLS: Tool[] = [
|
|
|
236
277
|
name: "repo.commit_patch",
|
|
237
278
|
description: "Commit verified repair changes in a worktree.",
|
|
238
279
|
inputSchema: CommitPatchInputJsonSchema as any,
|
|
239
|
-
outputSchema:
|
|
280
|
+
outputSchema: CommitPatchResponseJsonSchema as any,
|
|
240
281
|
},
|
|
241
282
|
{
|
|
242
283
|
name: "github.open_replacement_pr",
|
|
243
284
|
description: "Open a new PR that replaces or repairs the original PR.",
|
|
244
285
|
inputSchema: OpenReplacementPrInputJsonSchema as any,
|
|
245
|
-
outputSchema:
|
|
286
|
+
outputSchema: OpenReplacementPrResponseJsonSchema as any,
|
|
246
287
|
},
|
|
247
288
|
{
|
|
248
289
|
name: "github.create_pull_request",
|
|
249
290
|
description: "Creates a pull request on GitHub using the MCP server's integrated credentials. Bypasses terminal CLI constraints.",
|
|
250
291
|
inputSchema: CreatePullRequestInputJsonSchema as any,
|
|
251
|
-
outputSchema:
|
|
292
|
+
outputSchema: CreatePullRequestResponseJsonSchema as any,
|
|
252
293
|
},
|
|
253
294
|
{
|
|
254
295
|
name: "github.comment_triage_summary",
|
|
255
296
|
description: "Comment on the original PR with a diagnosis and replacement link.",
|
|
256
297
|
inputSchema: CommentTriageSummaryInputJsonSchema as any,
|
|
257
|
-
outputSchema:
|
|
298
|
+
outputSchema: CommentTriageSummaryResponseJsonSchema as any,
|
|
258
299
|
},
|
|
259
300
|
{
|
|
260
301
|
name: "github.get_pr",
|
|
261
302
|
description: "Get details of a GitHub PR including title, state, and URLs.",
|
|
262
303
|
inputSchema: GetPrInputJsonSchema as any,
|
|
263
|
-
outputSchema:
|
|
304
|
+
outputSchema: GetPrResponseJsonSchema as any,
|
|
264
305
|
},
|
|
265
306
|
{
|
|
266
307
|
name: "github.get_pr_reviews",
|
|
267
308
|
description: "Get top-level reviews, verdicts, states, and bodies for a GitHub PR.",
|
|
268
309
|
inputSchema: GetPrReviewsInputJsonSchema as any,
|
|
269
|
-
outputSchema:
|
|
310
|
+
outputSchema: GetPrReviewsResponseJsonSchema as any,
|
|
270
311
|
},
|
|
271
312
|
{
|
|
272
313
|
name: "github.get_pr_commits",
|
|
273
314
|
description: "List commits and commit metadata (SHAs, authors, messages) for a GitHub PR.",
|
|
274
315
|
inputSchema: GetPrCommitsInputJsonSchema as any,
|
|
275
|
-
outputSchema:
|
|
316
|
+
outputSchema: GetPrCommitsResponseJsonSchema as any,
|
|
276
317
|
},
|
|
277
318
|
{
|
|
278
319
|
name: "repo.get_workflow_runs",
|
|
279
320
|
description: "List recent GitHub Actions workflow runs for the repository.",
|
|
280
321
|
inputSchema: GetWorkflowRunsInputJsonSchema as any,
|
|
281
|
-
outputSchema:
|
|
322
|
+
outputSchema: GetWorkflowRunsResponseJsonSchema as any,
|
|
282
323
|
},
|
|
283
324
|
{
|
|
284
325
|
name: "github.issue_view",
|
|
285
326
|
description: "View details of a GitHub issue including title, body, and state.",
|
|
286
327
|
inputSchema: IssueViewInputJsonSchema as any,
|
|
287
|
-
outputSchema:
|
|
328
|
+
outputSchema: IssueViewResponseJsonSchema as any,
|
|
288
329
|
},
|
|
289
330
|
{
|
|
290
331
|
name: "github.issue_update",
|
|
@@ -296,7 +337,7 @@ const BASE_MCP_TOOLS: Tool[] = [
|
|
|
296
337
|
name: "github.issue_comment",
|
|
297
338
|
description: "Add a new comment to a GitHub issue.",
|
|
298
339
|
inputSchema: IssueCommentInputJsonSchema as any,
|
|
299
|
-
outputSchema:
|
|
340
|
+
outputSchema: IssueCommentResponseJsonSchema as any,
|
|
300
341
|
},
|
|
301
342
|
{
|
|
302
343
|
name: "github.create_issue",
|
|
@@ -308,49 +349,49 @@ const BASE_MCP_TOOLS: Tool[] = [
|
|
|
308
349
|
name: "jules.create_session",
|
|
309
350
|
description: "Create a Jules session that performs work externally and may generate a GitHub pull request.",
|
|
310
351
|
inputSchema: CreateJulesSessionInputJsonSchema as any,
|
|
311
|
-
outputSchema:
|
|
352
|
+
outputSchema: JulesSessionResponseJsonSchema as any,
|
|
312
353
|
},
|
|
313
354
|
{
|
|
314
355
|
name: "jules.get_session",
|
|
315
356
|
description: "Get the status and details of a Jules session.",
|
|
316
357
|
inputSchema: JulesSessionIdInputJsonSchema as any,
|
|
317
|
-
outputSchema:
|
|
358
|
+
outputSchema: JulesSessionResponseJsonSchema as any,
|
|
318
359
|
},
|
|
319
360
|
{
|
|
320
361
|
name: "jules.send_message",
|
|
321
362
|
description: "Send a message to an active Jules session.",
|
|
322
363
|
inputSchema: JulesSendMessageInputJsonSchema as any,
|
|
323
|
-
outputSchema:
|
|
364
|
+
outputSchema: JulesSendMessageResponseJsonSchema as any,
|
|
324
365
|
},
|
|
325
366
|
{
|
|
326
367
|
name: "jules.get_messages",
|
|
327
368
|
description: "Get the message history of a Jules session.",
|
|
328
369
|
inputSchema: JulesSessionIdInputJsonSchema as any,
|
|
329
|
-
outputSchema:
|
|
370
|
+
outputSchema: JulesGetMessagesResponseJsonSchema as any,
|
|
330
371
|
},
|
|
331
372
|
{
|
|
332
373
|
name: "jules.list_sessions",
|
|
333
374
|
description: "List all Jules sessions.",
|
|
334
375
|
inputSchema: JulesListSessionsInputJsonSchema as any,
|
|
335
|
-
outputSchema:
|
|
376
|
+
outputSchema: JulesListSessionsResponseJsonSchema as any,
|
|
336
377
|
},
|
|
337
378
|
{
|
|
338
379
|
name: "jules.cancel_session",
|
|
339
380
|
description: "Cancel an ongoing Jules session.",
|
|
340
381
|
inputSchema: JulesSessionIdInputJsonSchema as any,
|
|
341
|
-
outputSchema:
|
|
382
|
+
outputSchema: JulesSessionResponseJsonSchema as any,
|
|
342
383
|
},
|
|
343
384
|
{
|
|
344
385
|
name: "jules.get_pr",
|
|
345
386
|
description: "Get the generated pull request url associated with an active Jules agent session.",
|
|
346
387
|
inputSchema: JulesSessionIdInputJsonSchema as any,
|
|
347
|
-
outputSchema:
|
|
388
|
+
outputSchema: JulesGetPrResponseJsonSchema as any,
|
|
348
389
|
},
|
|
349
390
|
{
|
|
350
391
|
name: "jules.trigger_feedback",
|
|
351
392
|
description: "Automatically collect CI status/logs for the PR associated with a Jules session and send them back as feedback.",
|
|
352
393
|
inputSchema: JulesSessionIdInputJsonSchema as any,
|
|
353
|
-
outputSchema:
|
|
394
|
+
outputSchema: JulesTriggerFeedbackResponseJsonSchema as any,
|
|
354
395
|
},
|
|
355
396
|
];
|
|
356
397
|
|
package/src/mcp/http.test.ts
CHANGED
|
@@ -18,6 +18,16 @@ describe("createMcpHttpServer", () => {
|
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
it("should respond with 200 on GET /health endpoint", async () => {
|
|
22
|
+
httpServerObj = createMcpHttpServer({ port: 0, mcpServer });
|
|
23
|
+
port = await httpServerObj.listen(0);
|
|
24
|
+
|
|
25
|
+
const res = await fetch(`http://localhost:${port}/health`);
|
|
26
|
+
expect(res.status).toBe(200);
|
|
27
|
+
const body = await res.json();
|
|
28
|
+
expect(body).toEqual({ status: "ok", transport: "http" });
|
|
29
|
+
});
|
|
30
|
+
|
|
21
31
|
it("should respond with RFC 9728 OAuth protected resource metadata on /.well-known/oauth-protected-resource", async () => {
|
|
22
32
|
httpServerObj = createMcpHttpServer({ port: 0, mcpServer });
|
|
23
33
|
port = await httpServerObj.listen(0);
|
package/src/mcp/http.ts
CHANGED
|
@@ -32,6 +32,13 @@ export function createMcpHttpServer(options: HttpServerOptions) {
|
|
|
32
32
|
|
|
33
33
|
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
34
34
|
|
|
35
|
+
// Endpoint 0: Health check endpoint for readiness probes / load balancers
|
|
36
|
+
if (url.pathname === "/health" && req.method === "GET") {
|
|
37
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
38
|
+
res.end(JSON.stringify({ status: "ok", transport: "http" }));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
35
42
|
// Endpoint 1: RFC 9728 OAuth Protected Resource Metadata Discovery
|
|
36
43
|
if (url.pathname === "/.well-known/oauth-protected-resource" && (req.method === "GET" || req.method === "HEAD")) {
|
|
37
44
|
const metadata = {
|
|
@@ -82,11 +89,24 @@ export function createMcpHttpServer(options: HttpServerOptions) {
|
|
|
82
89
|
|
|
83
90
|
// Endpoint 2: SSE stream setup
|
|
84
91
|
if (url.pathname === "/sse" && req.method === "GET") {
|
|
92
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
93
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
94
|
+
res.setHeader("Connection", "keep-alive");
|
|
95
|
+
|
|
85
96
|
const transport = new SSEServerTransport("/messages", res);
|
|
86
97
|
transports.set(transport.sessionId, transport);
|
|
98
|
+
|
|
99
|
+
const keepAliveInterval = setInterval(() => {
|
|
100
|
+
if (!res.writableEnded) {
|
|
101
|
+
res.write(": keep-alive\n\n");
|
|
102
|
+
}
|
|
103
|
+
}, 15000);
|
|
104
|
+
|
|
87
105
|
transport.onclose = () => {
|
|
106
|
+
clearInterval(keepAliveInterval);
|
|
88
107
|
transports.delete(transport.sessionId);
|
|
89
108
|
};
|
|
109
|
+
|
|
90
110
|
await mcpServer.connect(transport);
|
|
91
111
|
return;
|
|
92
112
|
}
|
package/src/mcp/server.test.ts
CHANGED
|
@@ -207,6 +207,33 @@ describe("BoomtickMCPServer", () => {
|
|
|
207
207
|
expect(JSON.parse(result.contents[0].text)).toEqual({ files: ["src/index.ts"] });
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
+
it("should handle mcp://workspace/project-config", async () => {
|
|
211
|
+
const handler = requestHandlers.get("resources/read");
|
|
212
|
+
vi.mocked(fs.readFile).mockResolvedValue('{"packageName": "boomtick"}');
|
|
213
|
+
|
|
214
|
+
const result = await handler!({
|
|
215
|
+
method: "resources/read",
|
|
216
|
+
params: { uri: "mcp://workspace/project-config" },
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
expect(result.contents[0].uri).toBe("mcp://workspace/project-config");
|
|
220
|
+
expect(result.contents[0].mimeType).toBe("application/json");
|
|
221
|
+
expect(JSON.parse(result.contents[0].text)).toEqual({ packageName: "boomtick" });
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should handle mcp://git/status", async () => {
|
|
225
|
+
const handler = requestHandlers.get("resources/read");
|
|
226
|
+
|
|
227
|
+
const result = await handler!({
|
|
228
|
+
method: "resources/read",
|
|
229
|
+
params: { uri: "mcp://git/status" },
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
expect(result.contents[0].uri).toBe("mcp://git/status");
|
|
233
|
+
expect(result.contents[0].mimeType).toBe("application/json");
|
|
234
|
+
expect(JSON.parse(result.contents[0].text)).toHaveProperty("status");
|
|
235
|
+
});
|
|
236
|
+
|
|
210
237
|
it("should handle mcp://ci/latest-run", async () => {
|
|
211
238
|
const handler = requestHandlers.get("resources/read");
|
|
212
239
|
vi.spyOn(workflowRunsModule, "getWorkflowRunsHandler").mockResolvedValue({ workflowRuns: [{ id: 1, status: "completed", conclusion: "success" }] } as any);
|
|
@@ -241,6 +268,17 @@ describe("BoomtickMCPServer", () => {
|
|
|
241
268
|
expect(result.tools).toBeDefined();
|
|
242
269
|
expect(Array.isArray(result.tools)).toBe(true);
|
|
243
270
|
});
|
|
271
|
+
|
|
272
|
+
it("should ensure all tools have typed outputSchema and none are empty or untyped DEFAULT_OUTPUT_SCHEMA", async () => {
|
|
273
|
+
const handler = requestHandlers.get("tools/list");
|
|
274
|
+
const result = await handler!({ method: "tools/list" });
|
|
275
|
+
for (const tool of result.tools) {
|
|
276
|
+
expect(tool.outputSchema).toBeDefined();
|
|
277
|
+
expect(tool.outputSchema.type).toBe("object");
|
|
278
|
+
expect(tool.outputSchema.properties).toBeDefined();
|
|
279
|
+
expect(Object.keys(tool.outputSchema.properties).length).toBeGreaterThan(0);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
244
282
|
});
|
|
245
283
|
|
|
246
284
|
describe("CallTool", () => {
|
package/src/mcp/server.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { getMergeConflictFilesHandler, GetMergeConflictFilesInputSchema } from "
|
|
|
20
20
|
import { checkoutBranchHandler, CheckoutBranchInputSchema } from "../tools/github.checkout_branch.js";
|
|
21
21
|
import { getChangedFilesHandler, GetChangedFilesInputSchema } from "../tools/repo.get_changed_files.js";
|
|
22
22
|
import { readAgentContextHandler, ReadAgentContextInputSchema } from "../tools/repo.read_agent_context.js";
|
|
23
|
+
import { multiExecHandler, MultiExecInputSchema } from "../tools/repo.multi_exec.js";
|
|
23
24
|
import { getCommandSchemaHandler, GetCommandSchemaInputSchema } from "../tools/repo.get_command_schema.js";
|
|
24
25
|
import { getPackageScriptsHandler, GetPackageScriptsInputSchema } from "../tools/repo.get_package_scripts.js";
|
|
25
26
|
import { readCiLogsHandler, ReadCiLogsInputSchema } from "../tools/repo.read_ci_logs.js";
|
|
@@ -142,6 +143,22 @@ export class BoomtickMCPServer {
|
|
|
142
143
|
contents: [{ uri, mimeType: "text/markdown", text: content }],
|
|
143
144
|
};
|
|
144
145
|
}
|
|
146
|
+
if (
|
|
147
|
+
uri === "mcp://workspace/project-config" ||
|
|
148
|
+
uri === "mcp://workspace/project_config" ||
|
|
149
|
+
uri === "repo://project-config"
|
|
150
|
+
) {
|
|
151
|
+
const configPath = path.join(config.repoPath, "project_config.json");
|
|
152
|
+
let content = "{}";
|
|
153
|
+
try {
|
|
154
|
+
content = await fs.readFile(configPath, "utf-8");
|
|
155
|
+
} catch {
|
|
156
|
+
// Fallback if file doesn't exist
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
contents: [{ uri, mimeType: "application/json", text: content }],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
145
162
|
if (
|
|
146
163
|
uri === "mcp://git/changed-files" ||
|
|
147
164
|
uri === "repo://changed-files"
|
|
@@ -151,6 +168,22 @@ export class BoomtickMCPServer {
|
|
|
151
168
|
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(changedFiles, null, 2) }],
|
|
152
169
|
};
|
|
153
170
|
}
|
|
171
|
+
if (
|
|
172
|
+
uri === "mcp://git/status" ||
|
|
173
|
+
uri === "repo://git-status" ||
|
|
174
|
+
uri === "repo://status"
|
|
175
|
+
) {
|
|
176
|
+
let statusText = "";
|
|
177
|
+
try {
|
|
178
|
+
const res = spawnSync("git", ["status", "--porcelain"], { cwd: config.repoPath, encoding: "utf-8" });
|
|
179
|
+
statusText = res.stdout || "";
|
|
180
|
+
} catch {
|
|
181
|
+
// Fallback if git fails
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
contents: [{ uri, mimeType: "application/json", text: JSON.stringify({ status: statusText.trim() }, null, 2) }],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
154
187
|
if (
|
|
155
188
|
uri === "mcp://ci/latest-run" ||
|
|
156
189
|
uri === "repo://ci/latest-run" ||
|
|
@@ -213,6 +246,9 @@ export class BoomtickMCPServer {
|
|
|
213
246
|
case "repo.read.agent.context":
|
|
214
247
|
case "repo.read_agent_context":
|
|
215
248
|
return createSuccessResult(await readAgentContextHandler(ReadAgentContextInputSchema.parse(normArgs)));
|
|
249
|
+
case "repo.multi.exec":
|
|
250
|
+
case "repo.multi_exec":
|
|
251
|
+
return createSuccessResult(await multiExecHandler(MultiExecInputSchema.parse(normArgs)));
|
|
216
252
|
case "github.get.pr.diff":
|
|
217
253
|
case "github.get_pr_diff":
|
|
218
254
|
return createSuccessResult(await getPrDiffHandler(GetPrDiffInputSchema.parse(normArgs)));
|