@arii/boomtick-mcp 0.5.0 → 0.6.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 +5 -0
- package/.mcp/schemas/boomtick_health.json +5 -0
- package/.mcp/schemas/github.checkout_branch.json +5 -0
- package/.mcp/schemas/github.comment_triage_summary.json +5 -0
- package/.mcp/schemas/github.create_issue.json +5 -0
- package/.mcp/schemas/github.create_pull_request.json +5 -0
- package/.mcp/schemas/github.get_merge_conflict_files.json +5 -0
- package/.mcp/schemas/github.get_pr.json +5 -0
- package/.mcp/schemas/github.get_pr_commits.json +5 -0
- package/.mcp/schemas/github.get_pr_diff.json +5 -0
- package/.mcp/schemas/github.get_pr_reviews.json +5 -0
- package/.mcp/schemas/github.issue_comment.json +5 -0
- package/.mcp/schemas/github.issue_update.json +5 -0
- package/.mcp/schemas/github.issue_view.json +5 -0
- package/.mcp/schemas/github.open_replacement_pr.json +5 -0
- package/.mcp/schemas/github.search_open_prs.json +5 -0
- package/.mcp/schemas/github_checkout_branch.json +5 -0
- package/.mcp/schemas/github_comment_triage_summary.json +5 -0
- package/.mcp/schemas/github_create_issue.json +5 -0
- package/.mcp/schemas/github_create_pull_request.json +5 -0
- package/.mcp/schemas/github_get_merge_conflict_files.json +5 -0
- package/.mcp/schemas/github_get_pr.json +5 -0
- package/.mcp/schemas/github_get_pr_commits.json +5 -0
- package/.mcp/schemas/github_get_pr_diff.json +5 -0
- package/.mcp/schemas/github_get_pr_reviews.json +5 -0
- package/.mcp/schemas/github_issue_comment.json +5 -0
- package/.mcp/schemas/github_issue_update.json +5 -0
- package/.mcp/schemas/github_issue_view.json +5 -0
- package/.mcp/schemas/github_open_replacement_pr.json +5 -0
- package/.mcp/schemas/github_search_open_prs.json +5 -0
- package/.mcp/schemas/jules.cancel_session.json +5 -0
- package/.mcp/schemas/jules.create_session.json +5 -0
- package/.mcp/schemas/jules.get_messages.json +5 -0
- package/.mcp/schemas/jules.get_pr.json +5 -0
- package/.mcp/schemas/jules.get_session.json +5 -0
- package/.mcp/schemas/jules.list_sessions.json +5 -0
- package/.mcp/schemas/jules.send_message.json +5 -0
- package/.mcp/schemas/jules.trigger_feedback.json +5 -0
- package/.mcp/schemas/jules_cancel_session.json +5 -0
- package/.mcp/schemas/jules_create_session.json +5 -0
- package/.mcp/schemas/jules_get_messages.json +5 -0
- package/.mcp/schemas/jules_get_pr.json +5 -0
- package/.mcp/schemas/jules_get_session.json +5 -0
- package/.mcp/schemas/jules_list_sessions.json +5 -0
- package/.mcp/schemas/jules_send_message.json +5 -0
- package/.mcp/schemas/jules_trigger_feedback.json +5 -0
- package/.mcp/schemas/repo.commit_patch.json +5 -0
- package/.mcp/schemas/repo.create_branch.json +5 -0
- package/.mcp/schemas/repo.create_repair_branch.json +5 -0
- package/.mcp/schemas/repo.get_changed_files.json +5 -0
- package/.mcp/schemas/repo.get_command_schema.json +6 -1
- package/.mcp/schemas/repo.get_package_scripts.json +5 -0
- package/.mcp/schemas/repo.get_workflow_runs.json +5 -0
- package/.mcp/schemas/repo.logs.json +5 -0
- package/.mcp/schemas/repo.read_agent_context.json +5 -0
- package/.mcp/schemas/repo.read_ci_logs.json +5 -0
- package/.mcp/schemas/repo.run_tests.json +5 -0
- package/.mcp/schemas/repo_commit_patch.json +5 -0
- package/.mcp/schemas/repo_create_branch.json +5 -0
- package/.mcp/schemas/repo_create_repair_branch.json +5 -0
- package/.mcp/schemas/repo_get_changed_files.json +5 -0
- package/.mcp/schemas/repo_get_command_schema.json +6 -1
- package/.mcp/schemas/repo_get_package_scripts.json +5 -0
- package/.mcp/schemas/repo_get_workflow_runs.json +5 -0
- package/.mcp/schemas/repo_logs.json +5 -0
- package/.mcp/schemas/repo_read_agent_context.json +5 -0
- package/.mcp/schemas/repo_read_ci_logs.json +5 -0
- package/.mcp/schemas/repo_run_tests.json +5 -0
- package/CHANGELOG.md +14 -0
- package/actions/ai-review/action.yml +2 -2
- package/actions/audit/action.yml +1 -1
- package/actions/ci-validate/action.yml +2 -2
- package/actions/impact-analysis/action.yml +3 -3
- package/actions/run-project-gate/action.yml +5 -5
- package/actions/setup/action.yml +1 -1
- package/actions/test-build/action.yml +1 -1
- package/dist/config.js +1 -1
- package/dist/config.test.js +3 -3
- package/dist/lib/result.js +4 -0
- package/dist/lib/result.test.js +4 -2
- package/dist/lib/shell.js +6 -6
- package/dist/mcp/definitions.js +73 -2
- package/dist/mcp/http.js +91 -0
- package/dist/mcp/http.test.js +58 -0
- package/dist/mcp/server.js +65 -13
- package/dist/mcp/server.test.js +44 -0
- package/dist/tools/contract.js +109 -1
- package/dist/tools/github.checkout_branch.js +1 -1
- package/dist/tools/github.comment_triage_summary.js +1 -1
- package/dist/tools/github.create_issue.js +1 -1
- package/dist/tools/github.create_issue.test.js +6 -6
- package/dist/tools/github.create_pull_request.js +1 -1
- package/dist/tools/github.create_pull_request.test.js +4 -4
- package/dist/tools/github.get_pr.js +1 -1
- package/dist/tools/github.get_pr_commits.js +2 -2
- package/dist/tools/github.get_pr_diff.js +1 -1
- package/dist/tools/github.get_pr_reviews.js +2 -2
- package/dist/tools/github.get_pr_reviews.test.js +9 -9
- package/dist/tools/github.issue_comment.js +1 -1
- package/dist/tools/github.issue_update.js +1 -1
- package/dist/tools/github.issue_view.js +1 -1
- package/dist/tools/github.search_open_prs.js +1 -1
- package/dist/tools/jules/cancel-session.js +1 -1
- package/dist/tools/jules/create-session.test.js +11 -11
- package/dist/tools/jules/get-messages.js +1 -1
- package/dist/tools/jules/get-messages.test.js +3 -3
- package/dist/tools/jules/get-pr.test.js +3 -3
- package/dist/tools/jules/list-sessions.js +1 -1
- package/dist/tools/jules/send-message.js +1 -1
- package/dist/tools/jules/send-message.test.js +5 -5
- package/dist/tools/jules/shared.js +4 -4
- package/dist/tools/jules/trigger-feedback.js +1 -1
- package/dist/tools/jules/trigger-feedback.test.js +5 -5
- package/dist/tools/repo.get_command_schema.js +2 -2
- package/dist/tools/repo.get_workflow_runs.js +2 -2
- package/package.json +1 -1
- package/scripts/sync-mcp-schemas.ts +2 -0
- package/src/config.test.ts +3 -3
- package/src/config.ts +1 -1
- package/src/lib/result.test.ts +4 -2
- package/src/lib/result.ts +6 -0
- package/src/lib/shell.ts +6 -6
- package/src/mcp/definitions.ts +76 -3
- package/src/mcp/http.test.ts +72 -0
- package/src/mcp/http.ts +118 -0
- package/src/mcp/server.test.ts +52 -0
- package/src/mcp/server.ts +75 -12
- package/src/tools/contract.ts +109 -1
- package/src/tools/github.checkout_branch.ts +1 -1
- package/src/tools/github.comment_triage_summary.ts +1 -1
- package/src/tools/github.create_issue.test.ts +6 -6
- package/src/tools/github.create_issue.ts +1 -1
- package/src/tools/github.create_pull_request.test.ts +4 -4
- package/src/tools/github.create_pull_request.ts +1 -1
- package/src/tools/github.get_pr.ts +1 -1
- package/src/tools/github.get_pr_commits.ts +2 -2
- package/src/tools/github.get_pr_diff.ts +1 -1
- package/src/tools/github.get_pr_reviews.test.ts +9 -9
- package/src/tools/github.get_pr_reviews.ts +2 -2
- package/src/tools/github.issue_comment.ts +1 -1
- package/src/tools/github.issue_update.ts +1 -1
- package/src/tools/github.issue_view.ts +1 -1
- package/src/tools/github.search_open_prs.ts +1 -1
- package/src/tools/jules/cancel-session.ts +1 -1
- package/src/tools/jules/create-session.test.ts +11 -11
- package/src/tools/jules/get-messages.test.ts +3 -3
- package/src/tools/jules/get-messages.ts +1 -1
- package/src/tools/jules/get-pr.test.ts +3 -3
- package/src/tools/jules/list-sessions.ts +1 -1
- package/src/tools/jules/send-message.test.ts +5 -5
- package/src/tools/jules/send-message.ts +1 -1
- package/src/tools/jules/shared.ts +4 -4
- package/src/tools/jules/trigger-feedback.test.ts +5 -5
- package/src/tools/jules/trigger-feedback.ts +1 -1
- package/src/tools/repo.get_command_schema.ts +2 -2
- package/src/tools/repo.get_workflow_runs.ts +2 -2
package/dist/mcp/definitions.js
CHANGED
|
@@ -26,6 +26,24 @@ export const MCP_PROMPTS = [
|
|
|
26
26
|
},
|
|
27
27
|
];
|
|
28
28
|
export const MCP_RESOURCES = [
|
|
29
|
+
{
|
|
30
|
+
uri: "mcp://workspace/agent-contract",
|
|
31
|
+
name: "Agent Contract",
|
|
32
|
+
mimeType: "text/markdown",
|
|
33
|
+
description: "The agent contract specification file (.agents/AGENT_CONTRACT.md).",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
uri: "repo://agent-contract",
|
|
37
|
+
name: "Agent Contract (Legacy URI)",
|
|
38
|
+
mimeType: "text/markdown",
|
|
39
|
+
description: "The agent contract specification file (.agents/AGENT_CONTRACT.md).",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
uri: "mcp://git/changed-files",
|
|
43
|
+
name: "Git Changed Files",
|
|
44
|
+
mimeType: "application/json",
|
|
45
|
+
description: "List of changed files in the repository.",
|
|
46
|
+
},
|
|
29
47
|
{
|
|
30
48
|
uri: "repo://package-json",
|
|
31
49
|
name: "package.json",
|
|
@@ -38,183 +56,236 @@ export const MCP_RESOURCES = [
|
|
|
38
56
|
mimeType: "application/json",
|
|
39
57
|
description: "The design tokens used in the repository.",
|
|
40
58
|
},
|
|
59
|
+
];
|
|
60
|
+
export const MCP_RESOURCE_TEMPLATES = [
|
|
61
|
+
{
|
|
62
|
+
uriTemplate: "mcp://git/diff/{prNumber}",
|
|
63
|
+
name: "Git PR Diff",
|
|
64
|
+
mimeType: "text/plain",
|
|
65
|
+
description: "Diff and changed files for a pull request.",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
uriTemplate: "mcp://ci/logs/{prNumber}",
|
|
69
|
+
name: "CI Logs",
|
|
70
|
+
mimeType: "application/json",
|
|
71
|
+
description: "CI logs for a pull request.",
|
|
72
|
+
},
|
|
41
73
|
{
|
|
42
|
-
|
|
74
|
+
uriTemplate: "repo://repair-report/{branch}",
|
|
43
75
|
name: "Repair Report",
|
|
44
76
|
mimeType: "application/json",
|
|
45
77
|
description: "The validation report for a specific repair branch.",
|
|
46
78
|
},
|
|
47
79
|
];
|
|
80
|
+
const DEFAULT_OUTPUT_SCHEMA = {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {},
|
|
83
|
+
additionalProperties: true,
|
|
84
|
+
};
|
|
48
85
|
const BASE_MCP_TOOLS = [
|
|
49
86
|
{
|
|
50
87
|
name: "boomtick.health",
|
|
51
88
|
description: "Check the health and configuration of the MCP server.",
|
|
52
89
|
inputSchema: HealthCheckInputJsonSchema,
|
|
90
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
53
91
|
},
|
|
54
92
|
{
|
|
55
93
|
name: "github.search_open_prs",
|
|
56
94
|
description: "Search for open pull requests in the repository.",
|
|
57
95
|
inputSchema: SearchPRsInputJsonSchema,
|
|
96
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
58
97
|
},
|
|
59
98
|
{
|
|
60
99
|
name: "repo.read_agent_context",
|
|
61
100
|
description: "Read the full repository index including file tree, CLI schema, and MCP tools.",
|
|
62
101
|
inputSchema: ReadAgentContextSchema,
|
|
102
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
63
103
|
},
|
|
64
104
|
{
|
|
65
105
|
name: "github.get_pr_diff",
|
|
66
106
|
description: "Get the diff and changed files for a pull request.",
|
|
67
107
|
inputSchema: GetPrDiffInputJsonSchema,
|
|
108
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
68
109
|
},
|
|
69
110
|
{
|
|
70
111
|
name: "github.checkout_branch",
|
|
71
112
|
description: "Checkout a specific branch in the repository or worktree.",
|
|
72
113
|
inputSchema: CheckoutBranchInputJsonSchema,
|
|
114
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
73
115
|
},
|
|
74
116
|
{
|
|
75
117
|
name: "github.get_merge_conflict_files",
|
|
76
118
|
description: "Detect files that conflict when a PR is merged with the base branch.",
|
|
77
119
|
inputSchema: GetMergeConflictFilesInputJsonSchema,
|
|
120
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
78
121
|
},
|
|
79
122
|
{
|
|
80
123
|
name: "repo.get_changed_files",
|
|
81
124
|
description: "Get the list of changed files between two refs.",
|
|
82
125
|
inputSchema: GetChangedFilesInputJsonSchema,
|
|
126
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
83
127
|
},
|
|
84
128
|
{
|
|
85
129
|
name: "repo.get_command_schema",
|
|
86
|
-
description: "Retrieve a lightweight, targeted schema for a specific td
|
|
130
|
+
description: "Retrieve a lightweight, targeted schema for a specific td subcommand. Use this instead of reading the entire cli-schema.json to save tokens.",
|
|
87
131
|
inputSchema: GetCommandSchemaInputJsonSchema,
|
|
132
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
88
133
|
},
|
|
89
134
|
{
|
|
90
135
|
name: "repo.get_package_scripts",
|
|
91
136
|
description: "Get the scripts defined in package.json.",
|
|
92
137
|
inputSchema: GetPackageScriptsInputJsonSchema,
|
|
138
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
93
139
|
},
|
|
94
140
|
{
|
|
95
141
|
name: "repo.read_ci_logs",
|
|
96
142
|
description: "Read CI logs for a given pull request.",
|
|
97
143
|
inputSchema: ReadCiLogsInputJsonSchema,
|
|
144
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
98
145
|
},
|
|
99
146
|
{
|
|
100
147
|
name: "repo.logs",
|
|
101
148
|
description: "Stream or grep combined CI logs for all jobs in a pull request.",
|
|
102
149
|
inputSchema: RepoLogsInputJsonSchema,
|
|
150
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
103
151
|
},
|
|
104
152
|
{
|
|
105
153
|
name: "repo.create_branch",
|
|
106
154
|
description: "Creates a new clean git branch from a target base branch.",
|
|
107
155
|
inputSchema: CreateBranchInputJsonSchema,
|
|
156
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
108
157
|
},
|
|
109
158
|
{
|
|
110
159
|
name: "repo.create_repair_branch",
|
|
111
160
|
description: "Creates a fresh worktree and a new repair branch for an existing PR. This isolates the repair work from the main codebase.",
|
|
112
161
|
inputSchema: CreateRepairBranchInputJsonSchema,
|
|
162
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
113
163
|
},
|
|
114
164
|
{
|
|
115
165
|
name: "repo.run_tests",
|
|
116
166
|
description: "Run repository tests and checks.",
|
|
117
167
|
inputSchema: RunTestsInputJsonSchema,
|
|
168
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
118
169
|
},
|
|
119
170
|
{
|
|
120
171
|
name: "repo.commit_patch",
|
|
121
172
|
description: "Commit verified repair changes in a worktree.",
|
|
122
173
|
inputSchema: CommitPatchInputJsonSchema,
|
|
174
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
123
175
|
},
|
|
124
176
|
{
|
|
125
177
|
name: "github.open_replacement_pr",
|
|
126
178
|
description: "Open a new PR that replaces or repairs the original PR.",
|
|
127
179
|
inputSchema: OpenReplacementPrInputJsonSchema,
|
|
180
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
128
181
|
},
|
|
129
182
|
{
|
|
130
183
|
name: "github.create_pull_request",
|
|
131
184
|
description: "Creates a pull request on GitHub using the MCP server's integrated credentials. Bypasses terminal CLI constraints.",
|
|
132
185
|
inputSchema: CreatePullRequestInputJsonSchema,
|
|
186
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
133
187
|
},
|
|
134
188
|
{
|
|
135
189
|
name: "github.comment_triage_summary",
|
|
136
190
|
description: "Comment on the original PR with a diagnosis and replacement link.",
|
|
137
191
|
inputSchema: CommentTriageSummaryInputJsonSchema,
|
|
192
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
138
193
|
},
|
|
139
194
|
{
|
|
140
195
|
name: "github.get_pr",
|
|
141
196
|
description: "Get details of a GitHub PR including title, state, and URLs.",
|
|
142
197
|
inputSchema: GetPrInputJsonSchema,
|
|
198
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
143
199
|
},
|
|
144
200
|
{
|
|
145
201
|
name: "github.get_pr_reviews",
|
|
146
202
|
description: "Get top-level reviews, verdicts, states, and bodies for a GitHub PR.",
|
|
147
203
|
inputSchema: GetPrReviewsInputJsonSchema,
|
|
204
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
148
205
|
},
|
|
149
206
|
{
|
|
150
207
|
name: "github.get_pr_commits",
|
|
151
208
|
description: "List commits and commit metadata (SHAs, authors, messages) for a GitHub PR.",
|
|
152
209
|
inputSchema: GetPrCommitsInputJsonSchema,
|
|
210
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
153
211
|
},
|
|
154
212
|
{
|
|
155
213
|
name: "repo.get_workflow_runs",
|
|
156
214
|
description: "List recent GitHub Actions workflow runs for the repository.",
|
|
157
215
|
inputSchema: GetWorkflowRunsInputJsonSchema,
|
|
216
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
158
217
|
},
|
|
159
218
|
{
|
|
160
219
|
name: "github.issue_view",
|
|
161
220
|
description: "View details of a GitHub issue including title, body, and state.",
|
|
162
221
|
inputSchema: IssueViewInputJsonSchema,
|
|
222
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
163
223
|
},
|
|
164
224
|
{
|
|
165
225
|
name: "github.issue_update",
|
|
166
226
|
description: "Update a GitHub issue's body, labels, and/or state.",
|
|
167
227
|
inputSchema: IssueUpdateInputJsonSchema,
|
|
228
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
168
229
|
},
|
|
169
230
|
{
|
|
170
231
|
name: "github.issue_comment",
|
|
171
232
|
description: "Add a new comment to a GitHub issue.",
|
|
172
233
|
inputSchema: IssueCommentInputJsonSchema,
|
|
234
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
173
235
|
},
|
|
174
236
|
{
|
|
175
237
|
name: "github.create_issue",
|
|
176
238
|
description: "Create a new GitHub issue.",
|
|
177
239
|
inputSchema: CreateIssueInputJsonSchema,
|
|
240
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
178
241
|
},
|
|
179
242
|
{
|
|
180
243
|
name: "jules.create_session",
|
|
181
244
|
description: "Create a Jules session that performs work externally and may generate a GitHub pull request.",
|
|
182
245
|
inputSchema: CreateJulesSessionInputJsonSchema,
|
|
246
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
183
247
|
},
|
|
184
248
|
{
|
|
185
249
|
name: "jules.get_session",
|
|
186
250
|
description: "Get the status and details of a Jules session.",
|
|
187
251
|
inputSchema: JulesSessionIdInputJsonSchema,
|
|
252
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
188
253
|
},
|
|
189
254
|
{
|
|
190
255
|
name: "jules.send_message",
|
|
191
256
|
description: "Send a message to an active Jules session.",
|
|
192
257
|
inputSchema: JulesSendMessageInputJsonSchema,
|
|
258
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
193
259
|
},
|
|
194
260
|
{
|
|
195
261
|
name: "jules.get_messages",
|
|
196
262
|
description: "Get the message history of a Jules session.",
|
|
197
263
|
inputSchema: JulesSessionIdInputJsonSchema,
|
|
264
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
198
265
|
},
|
|
199
266
|
{
|
|
200
267
|
name: "jules.list_sessions",
|
|
201
268
|
description: "List all Jules sessions.",
|
|
202
269
|
inputSchema: JulesListSessionsInputJsonSchema,
|
|
270
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
203
271
|
},
|
|
204
272
|
{
|
|
205
273
|
name: "jules.cancel_session",
|
|
206
274
|
description: "Cancel an ongoing Jules session.",
|
|
207
275
|
inputSchema: JulesSessionIdInputJsonSchema,
|
|
276
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
208
277
|
},
|
|
209
278
|
{
|
|
210
279
|
name: "jules.get_pr",
|
|
211
280
|
description: "Get the generated pull request url associated with an active Jules agent session.",
|
|
212
281
|
inputSchema: JulesSessionIdInputJsonSchema,
|
|
282
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
213
283
|
},
|
|
214
284
|
{
|
|
215
285
|
name: "jules.trigger_feedback",
|
|
216
286
|
description: "Automatically collect CI status/logs for the PR associated with a Jules session and send them back as feedback.",
|
|
217
287
|
inputSchema: JulesSessionIdInputJsonSchema,
|
|
288
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
218
289
|
},
|
|
219
290
|
];
|
|
220
291
|
export const MCP_TOOLS = BASE_MCP_TOOLS.flatMap((tool) => {
|
package/dist/mcp/http.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
4
|
+
export function createMcpHttpServer(options) {
|
|
5
|
+
const { port = 3000, bearerToken = process.env.BOOMTICK_MCP_BEARER_TOKEN || process.env.MCP_BEARER_TOKEN || process.env.AUTH_TOKEN, mcpServer, } = options;
|
|
6
|
+
const transports = new Map();
|
|
7
|
+
const server = http.createServer(async (req, res) => {
|
|
8
|
+
// CORS Headers
|
|
9
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
10
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
11
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
12
|
+
if (req.method === "OPTIONS") {
|
|
13
|
+
res.writeHead(204);
|
|
14
|
+
res.end();
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
18
|
+
// Endpoint 1: OAuth Protected Resource metadata
|
|
19
|
+
if (url.pathname === "/.well-known/oauth-protected-resource" && req.method === "GET") {
|
|
20
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
21
|
+
res.end(JSON.stringify({
|
|
22
|
+
resource: "mcp://boomtick-mcp",
|
|
23
|
+
"authorization_servers": [
|
|
24
|
+
process.env.MCP_OAUTH_AUTH_SERVER || "https://auth.boomtick.dev",
|
|
25
|
+
],
|
|
26
|
+
scopes_supported: ["mcp:read", "mcp:write"],
|
|
27
|
+
}));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// Bearer Token Validation for SSE & Messages endpoints
|
|
31
|
+
if (url.pathname === "/sse" || url.pathname === "/messages") {
|
|
32
|
+
if (!bearerToken) {
|
|
33
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
34
|
+
res.end(JSON.stringify({ error: "Server misconfiguration: bearerToken is required for HTTP transport" }));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const authHeader = req.headers.authorization;
|
|
38
|
+
const expectedAuth = `Bearer ${bearerToken}`;
|
|
39
|
+
const authBuffer = Buffer.from(authHeader || "");
|
|
40
|
+
const expectedBuffer = Buffer.from(expectedAuth);
|
|
41
|
+
const isMatch = Boolean(authHeader) &&
|
|
42
|
+
authBuffer.length === expectedBuffer.length &&
|
|
43
|
+
crypto.timingSafeEqual(authBuffer, expectedBuffer);
|
|
44
|
+
if (!isMatch) {
|
|
45
|
+
res.writeHead(401, {
|
|
46
|
+
"Content-Type": "application/json",
|
|
47
|
+
"WWW-Authenticate": 'Bearer realm="boomtick-mcp", error="invalid_token"',
|
|
48
|
+
});
|
|
49
|
+
res.end(JSON.stringify({ error: "unauthorized", message: "Invalid or missing Bearer token" }));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Endpoint 2: SSE stream setup
|
|
54
|
+
if (url.pathname === "/sse" && req.method === "GET") {
|
|
55
|
+
const transport = new SSEServerTransport("/messages", res);
|
|
56
|
+
transports.set(transport.sessionId, transport);
|
|
57
|
+
transport.onclose = () => {
|
|
58
|
+
transports.delete(transport.sessionId);
|
|
59
|
+
};
|
|
60
|
+
await mcpServer.connect(transport);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Endpoint 3: POST messages
|
|
64
|
+
if (url.pathname === "/messages" && req.method === "POST") {
|
|
65
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
66
|
+
const transport = sessionId ? transports.get(sessionId) : null;
|
|
67
|
+
if (transport) {
|
|
68
|
+
await transport.handlePostMessage(req, res);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
72
|
+
res.end(JSON.stringify({ error: "Session not found or expired" }));
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
77
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
server,
|
|
81
|
+
listen: (customPort) => new Promise((resolve) => {
|
|
82
|
+
const p = customPort ?? port;
|
|
83
|
+
server.listen(p, () => {
|
|
84
|
+
const addr = server.address();
|
|
85
|
+
const actualPort = typeof addr === "object" && addr ? addr.port : p;
|
|
86
|
+
console.error(`Boomtick MCP Server running on HTTP port ${actualPort}`);
|
|
87
|
+
resolve(actualPort);
|
|
88
|
+
});
|
|
89
|
+
}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { createMcpHttpServer } from "./http.js";
|
|
4
|
+
describe("createMcpHttpServer", () => {
|
|
5
|
+
let mcpServer;
|
|
6
|
+
let httpServerObj;
|
|
7
|
+
let port;
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
mcpServer = new Server({ name: "test-server", version: "1.0.0" }, { capabilities: {} });
|
|
10
|
+
});
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
if (httpServerObj?.server) {
|
|
13
|
+
httpServerObj.server.close();
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
it("should respond with OAuth protected resource metadata on /.well-known/oauth-protected-resource", async () => {
|
|
17
|
+
httpServerObj = createMcpHttpServer({ port: 0, mcpServer });
|
|
18
|
+
port = await httpServerObj.listen(0);
|
|
19
|
+
const res = await fetch(`http://localhost:${port}/.well-known/oauth-protected-resource`);
|
|
20
|
+
expect(res.status).toBe(200);
|
|
21
|
+
const body = await res.json();
|
|
22
|
+
expect(body.resource).toBe("mcp://boomtick-mcp");
|
|
23
|
+
expect(body.authorization_servers).toBeDefined();
|
|
24
|
+
expect(body.scopes_supported).toEqual(["mcp:read", "mcp:write"]);
|
|
25
|
+
});
|
|
26
|
+
it("should reject requests when Bearer token is missing or invalid", async () => {
|
|
27
|
+
httpServerObj = createMcpHttpServer({ port: 0, bearerToken: "secret123", mcpServer });
|
|
28
|
+
port = await httpServerObj.listen(0);
|
|
29
|
+
// Request without token
|
|
30
|
+
const res1 = await fetch(`http://localhost:${port}/sse`);
|
|
31
|
+
expect(res1.status).toBe(401);
|
|
32
|
+
expect(res1.headers.get("www-authenticate")).toContain('Bearer realm="boomtick-mcp"');
|
|
33
|
+
// Request with wrong token
|
|
34
|
+
const res2 = await fetch(`http://localhost:${port}/sse`, {
|
|
35
|
+
headers: { Authorization: "Bearer wrong" },
|
|
36
|
+
});
|
|
37
|
+
expect(res2.status).toBe(401);
|
|
38
|
+
// Request with valid token
|
|
39
|
+
const res3 = await fetch(`http://localhost:${port}/.well-known/oauth-protected-resource`, {
|
|
40
|
+
headers: { Authorization: "Bearer secret123" },
|
|
41
|
+
});
|
|
42
|
+
expect(res3.status).toBe(200);
|
|
43
|
+
});
|
|
44
|
+
it("should handle 404 for unknown endpoints", async () => {
|
|
45
|
+
httpServerObj = createMcpHttpServer({ port: 0, mcpServer });
|
|
46
|
+
port = await httpServerObj.listen(0);
|
|
47
|
+
const res = await fetch(`http://localhost:${port}/unknown-path`);
|
|
48
|
+
expect(res.status).toBe(404);
|
|
49
|
+
});
|
|
50
|
+
it("should return 500 if bearer token is not configured on server for protected endpoints", async () => {
|
|
51
|
+
httpServerObj = createMcpHttpServer({ port: 0, mcpServer });
|
|
52
|
+
port = await httpServerObj.listen(0);
|
|
53
|
+
const res = await fetch(`http://localhost:${port}/sse`);
|
|
54
|
+
expect(res.status).toBe(500);
|
|
55
|
+
const body = await res.json();
|
|
56
|
+
expect(body.error).toContain("Server misconfiguration");
|
|
57
|
+
});
|
|
58
|
+
});
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
4
|
import { config } from "../config.js";
|
|
5
5
|
import { createSuccessResult, createErrorResult } from "../lib/result.js";
|
|
6
|
-
import { MCP_TOOLS, MCP_PROMPTS, MCP_RESOURCES } from "./definitions.js";
|
|
6
|
+
import { MCP_TOOLS, MCP_PROMPTS, MCP_RESOURCES, MCP_RESOURCE_TEMPLATES } from "./definitions.js";
|
|
7
7
|
import { normalizeArguments } from "../lib/param_utils.js";
|
|
8
8
|
import { healthHandler, HealthCheckInputSchema } from "./tools.js";
|
|
9
9
|
import { searchOpenPrsHandler, SearchOpenPrsInputSchema } from "../tools/github.search_open_prs.js";
|
|
@@ -16,6 +16,7 @@ import { getCommandSchemaHandler, GetCommandSchemaInputSchema } from "../tools/r
|
|
|
16
16
|
import { getPackageScriptsHandler, GetPackageScriptsInputSchema } from "../tools/repo.get_package_scripts.js";
|
|
17
17
|
import { readCiLogsHandler, ReadCiLogsInputSchema } from "../tools/repo.read_ci_logs.js";
|
|
18
18
|
import { repoLogsHandler, RepoLogsInputSchema } from "../tools/repo.logs.js";
|
|
19
|
+
import { getWorkflowRunsHandler, GetWorkflowRunsInputSchema } from "../tools/repo.get_workflow_runs.js";
|
|
19
20
|
import { createRepairBranchHandler, CreateRepairBranchInputSchema } from "../tools/repo.create_repair_branch.js";
|
|
20
21
|
import { createBranchHandler, CreateBranchInputSchema } from "../tools/repo.create_branch.js";
|
|
21
22
|
import { runTestsHandler, RunTestsInputSchema } from "../tools/repo.run_tests.js";
|
|
@@ -24,6 +25,8 @@ import { openReplacementPrHandler, OpenReplacementPrInputSchema } from "../tools
|
|
|
24
25
|
import { commentTriageSummaryHandler, CommentTriageSummaryInputSchema } from "../tools/github.comment_triage_summary.js";
|
|
25
26
|
import { createPullRequestHandler, CreatePullRequestInputSchema } from "../tools/github.create_pull_request.js";
|
|
26
27
|
import { getPrHandler, GetPrInputSchema } from "../tools/github.get_pr.js";
|
|
28
|
+
import { getPrReviewsHandler, GetPrReviewsInputSchema } from "../tools/github.get_pr_reviews.js";
|
|
29
|
+
import { getPrCommitsHandler, GetPrCommitsInputSchema } from "../tools/github.get_pr_commits.js";
|
|
27
30
|
import { issueViewHandler, IssueViewInputSchema } from "../tools/github.issue_view.js";
|
|
28
31
|
import { issueUpdateHandler, IssueUpdateInputSchema } from "../tools/github.issue_update.js";
|
|
29
32
|
import { issueCommentHandler, IssueCommentInputSchema } from "../tools/github.issue_comment.js";
|
|
@@ -40,6 +43,7 @@ import fs from "fs/promises";
|
|
|
40
43
|
import { existsSync, readFileSync } from "fs";
|
|
41
44
|
import path from "path";
|
|
42
45
|
import { spawnSync } from "child_process";
|
|
46
|
+
import { createMcpHttpServer } from "./http.js";
|
|
43
47
|
export class BoomtickMCPServer {
|
|
44
48
|
server;
|
|
45
49
|
constructor() {
|
|
@@ -93,29 +97,50 @@ export class BoomtickMCPServer {
|
|
|
93
97
|
resources: MCP_RESOURCES,
|
|
94
98
|
};
|
|
95
99
|
});
|
|
100
|
+
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
101
|
+
return {
|
|
102
|
+
resourceTemplates: MCP_RESOURCE_TEMPLATES,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
96
105
|
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
97
106
|
const uri = request.params.uri;
|
|
98
|
-
if (uri === "
|
|
107
|
+
if (uri === "mcp://workspace/agent-contract" ||
|
|
108
|
+
uri === "mcp://workspace/agent_contract" ||
|
|
109
|
+
uri === "repo://agent-contract") {
|
|
110
|
+
const contractPath = path.join(config.repoPath, ".agents", "AGENT_CONTRACT.md");
|
|
111
|
+
const content = await fs.readFile(contractPath, "utf-8");
|
|
112
|
+
return {
|
|
113
|
+
contents: [{ uri, mimeType: "text/markdown", text: content }],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (uri === "mcp://git/changed-files" ||
|
|
117
|
+
uri === "repo://changed-files") {
|
|
118
|
+
const changedFiles = await getChangedFilesHandler(GetChangedFilesInputSchema.parse({}));
|
|
119
|
+
return {
|
|
120
|
+
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(changedFiles, null, 2) }],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (uri === "repo://package-json" || uri === "mcp://workspace/package-json") {
|
|
99
124
|
const content = await fs.readFile(path.join(config.repoPath, "package.json"), "utf-8");
|
|
100
125
|
return {
|
|
101
126
|
contents: [{ uri, mimeType: "application/json", text: content }],
|
|
102
127
|
};
|
|
103
128
|
}
|
|
104
|
-
if (uri === "repo://design-tokens") {
|
|
129
|
+
if (uri === "repo://design-tokens" || uri === "mcp://workspace/design-tokens") {
|
|
105
130
|
const tokensPath = path.join(config.repoPath, "src/styles/design-tokens.ts");
|
|
106
131
|
const content = await fs.readFile(tokensPath, "utf-8");
|
|
107
132
|
return {
|
|
108
133
|
contents: [{ uri, mimeType: "text/typescript", text: content }],
|
|
109
134
|
};
|
|
110
135
|
}
|
|
111
|
-
if (uri.startsWith("repo://diff/")) {
|
|
136
|
+
if (uri.startsWith("mcp://git/diff/") || uri.startsWith("repo://diff/")) {
|
|
112
137
|
const prNumber = parseInt(uri.split("/").pop() || "");
|
|
113
138
|
const diff = await getPrDiffHandler({ prNumber });
|
|
114
139
|
return {
|
|
115
140
|
contents: [{ uri, mimeType: "text/plain", text: diff.diffText }],
|
|
116
141
|
};
|
|
117
142
|
}
|
|
118
|
-
if (uri.startsWith("repo://ci/")) {
|
|
143
|
+
if (uri.startsWith("mcp://ci/logs/") || uri.startsWith("repo://ci/")) {
|
|
119
144
|
const prNumber = parseInt(uri.split("/").pop() || "");
|
|
120
145
|
const logs = await readCiLogsHandler({ prNumber });
|
|
121
146
|
return {
|
|
@@ -167,6 +192,9 @@ export class BoomtickMCPServer {
|
|
|
167
192
|
return createSuccessResult(await readCiLogsHandler(ReadCiLogsInputSchema.parse(normArgs)));
|
|
168
193
|
case "repo.logs":
|
|
169
194
|
return createSuccessResult(await repoLogsHandler(RepoLogsInputSchema.parse(normArgs)));
|
|
195
|
+
case "repo.get.workflow.runs":
|
|
196
|
+
case "repo.get_workflow_runs":
|
|
197
|
+
return createSuccessResult(await getWorkflowRunsHandler(GetWorkflowRunsInputSchema.parse(normArgs)));
|
|
170
198
|
case "repo.create.branch":
|
|
171
199
|
case "repo.create_branch":
|
|
172
200
|
return createSuccessResult(await createBranchHandler(CreateBranchInputSchema.parse(normArgs)));
|
|
@@ -191,6 +219,12 @@ export class BoomtickMCPServer {
|
|
|
191
219
|
case "github.get.pr":
|
|
192
220
|
case "github.get_pr":
|
|
193
221
|
return createSuccessResult(await getPrHandler(GetPrInputSchema.parse(normArgs)));
|
|
222
|
+
case "github.get.pr.reviews":
|
|
223
|
+
case "github.get_pr_reviews":
|
|
224
|
+
return createSuccessResult(await getPrReviewsHandler(GetPrReviewsInputSchema.parse(normArgs)));
|
|
225
|
+
case "github.get.pr.commits":
|
|
226
|
+
case "github.get_pr_commits":
|
|
227
|
+
return createSuccessResult(await getPrCommitsHandler(GetPrCommitsInputSchema.parse(normArgs)));
|
|
194
228
|
case "github.issue.view":
|
|
195
229
|
case "github.issue_view":
|
|
196
230
|
return createSuccessResult(await issueViewHandler(IssueViewInputSchema.parse(normArgs)));
|
|
@@ -236,7 +270,7 @@ export class BoomtickMCPServer {
|
|
|
236
270
|
}
|
|
237
271
|
});
|
|
238
272
|
}
|
|
239
|
-
|
|
273
|
+
prepareEnvironmentAndPreflight() {
|
|
240
274
|
const projectRoot = config.repoPath || process.cwd();
|
|
241
275
|
const homeDir = process.env.HOME || "";
|
|
242
276
|
const extraPaths = [];
|
|
@@ -276,9 +310,9 @@ export class BoomtickMCPServer {
|
|
|
276
310
|
.join(path.delimiter);
|
|
277
311
|
// 3. SAFE REPOSITORY RESOLUTION
|
|
278
312
|
const repoPath = config.repoPath || projectRoot;
|
|
279
|
-
// 4. SECURE, UNIX-ONLY PRE-FLIGHT CHECK FOR TD
|
|
313
|
+
// 4. SECURE, UNIX-ONLY PRE-FLIGHT CHECK FOR TD
|
|
280
314
|
try {
|
|
281
|
-
const result = spawnSync("td
|
|
315
|
+
const result = spawnSync("td", ["doctor"], {
|
|
282
316
|
encoding: "utf-8",
|
|
283
317
|
cwd: repoPath,
|
|
284
318
|
shell: false // Explicitly disabled to clear Semgrep rules
|
|
@@ -290,16 +324,34 @@ export class BoomtickMCPServer {
|
|
|
290
324
|
console.error("status:", result.status);
|
|
291
325
|
console.error("stdout:", result.stdout);
|
|
292
326
|
console.error("stderr:", result.stderr);
|
|
293
|
-
throw new Error(`td
|
|
327
|
+
throw new Error(`td doctor returned exit code: ${result.status}`);
|
|
294
328
|
}
|
|
295
|
-
console.error("✅ td
|
|
329
|
+
console.error("✅ td verified on PATH securely");
|
|
296
330
|
}
|
|
297
331
|
catch (error) {
|
|
298
|
-
console.error("❌ Fatal: td
|
|
332
|
+
console.error("❌ Fatal: td is not resolvable on PATH. MCP tools will fail.");
|
|
299
333
|
console.error("Please ensure the project is installed: pip install -e .");
|
|
300
|
-
console.error("❌ Fatal: td
|
|
334
|
+
console.error("❌ Fatal: td pre-flight check failed:", error instanceof Error ? error.message : String(error));
|
|
301
335
|
process.exit(1);
|
|
302
336
|
}
|
|
337
|
+
}
|
|
338
|
+
async startHttp(port = parseInt(process.env.PORT || process.env.HTTP_PORT || "3000", 10), bearerToken) {
|
|
339
|
+
this.prepareEnvironmentAndPreflight();
|
|
340
|
+
const mcpHttp = createMcpHttpServer({ port, bearerToken, mcpServer: this.server });
|
|
341
|
+
return await mcpHttp.listen(port);
|
|
342
|
+
}
|
|
343
|
+
async run() {
|
|
344
|
+
this.prepareEnvironmentAndPreflight();
|
|
345
|
+
const isHttp = process.env.MCP_TRANSPORT === "http" ||
|
|
346
|
+
process.argv.includes("--http") ||
|
|
347
|
+
(process.env.PORT !== undefined && !process.argv.includes("--stdio")) ||
|
|
348
|
+
(process.env.HTTP_PORT !== undefined && !process.argv.includes("--stdio"));
|
|
349
|
+
if (isHttp) {
|
|
350
|
+
const port = parseInt(process.env.PORT || process.env.HTTP_PORT || "3000", 10);
|
|
351
|
+
const mcpHttp = createMcpHttpServer({ port, mcpServer: this.server });
|
|
352
|
+
await mcpHttp.listen(port);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
303
355
|
const transport = new StdioServerTransport();
|
|
304
356
|
await this.server.connect(transport);
|
|
305
357
|
console.error("Boomtick MCP Server running on stdio");
|
package/dist/mcp/server.test.js
CHANGED
|
@@ -4,6 +4,9 @@ import fs from "fs/promises";
|
|
|
4
4
|
import * as healthModule from "./tools.js";
|
|
5
5
|
import * as prDiffModule from "../tools/github.get_pr_diff.js";
|
|
6
6
|
import * as ciLogsModule from "../tools/repo.read_ci_logs.js";
|
|
7
|
+
import * as prReviewsModule from "../tools/github.get_pr_reviews.js";
|
|
8
|
+
import * as prCommitsModule from "../tools/github.get_pr_commits.js";
|
|
9
|
+
import * as workflowRunsModule from "../tools/repo.get_workflow_runs.js";
|
|
7
10
|
vi.mock("fs/promises");
|
|
8
11
|
vi.mock("../config.js", () => ({
|
|
9
12
|
config: {
|
|
@@ -83,8 +86,27 @@ describe("BoomtickMCPServer", () => {
|
|
|
83
86
|
expect(result.resources).toBeDefined();
|
|
84
87
|
expect(Array.isArray(result.resources)).toBe(true);
|
|
85
88
|
});
|
|
89
|
+
it("should return resource templates list", async () => {
|
|
90
|
+
const handler = requestHandlers.get("resources/templates/list");
|
|
91
|
+
expect(handler).toBeDefined();
|
|
92
|
+
const result = await handler({ method: "resources/templates/list" });
|
|
93
|
+
expect(result.resourceTemplates).toBeDefined();
|
|
94
|
+
expect(Array.isArray(result.resourceTemplates)).toBe(true);
|
|
95
|
+
});
|
|
86
96
|
});
|
|
87
97
|
describe("ReadResource", () => {
|
|
98
|
+
it("should handle mcp://workspace/agent-contract", async () => {
|
|
99
|
+
const handler = requestHandlers.get("resources/read");
|
|
100
|
+
vi.mocked(fs.readFile).mockResolvedValue("# Agent Contract\nRule 1");
|
|
101
|
+
const result = await handler({
|
|
102
|
+
method: "resources/read",
|
|
103
|
+
params: { uri: "mcp://workspace/agent-contract" },
|
|
104
|
+
});
|
|
105
|
+
expect(fs.readFile).toHaveBeenCalledWith("/mock/repo/.agents/AGENT_CONTRACT.md", "utf-8");
|
|
106
|
+
expect(result).toEqual({
|
|
107
|
+
contents: [{ uri: "mcp://workspace/agent-contract", mimeType: "text/markdown", text: "# Agent Contract\nRule 1" }],
|
|
108
|
+
});
|
|
109
|
+
});
|
|
88
110
|
it("should handle repo://package-json", async () => {
|
|
89
111
|
const handler = requestHandlers.get("resources/read");
|
|
90
112
|
vi.mocked(fs.readFile).mockResolvedValue('{"name": "test"}');
|
|
@@ -155,7 +177,29 @@ describe("BoomtickMCPServer", () => {
|
|
|
155
177
|
});
|
|
156
178
|
expect(result).toEqual({
|
|
157
179
|
content: [{ type: "text", text: JSON.stringify({ status: "ok" }, null, 2) }],
|
|
180
|
+
structuredContent: { status: "ok" },
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
it("should execute github_get_pr_reviews and github_get_pr_commits via underscore or dot name", async () => {
|
|
184
|
+
const handler = requestHandlers.get("tools/call");
|
|
185
|
+
vi.spyOn(prReviewsModule, "getPrReviewsHandler").mockResolvedValue({ reviews: [] });
|
|
186
|
+
vi.spyOn(prCommitsModule, "getPrCommitsHandler").mockResolvedValue({ commits: [] });
|
|
187
|
+
vi.spyOn(workflowRunsModule, "getWorkflowRunsHandler").mockResolvedValue({ runs: [] });
|
|
188
|
+
const resReviews = await handler({
|
|
189
|
+
method: "tools/call",
|
|
190
|
+
params: { name: "github_get_pr_reviews", arguments: { prNumber: 123 } },
|
|
191
|
+
});
|
|
192
|
+
expect(resReviews.structuredContent).toEqual({ reviews: [] });
|
|
193
|
+
const resCommits = await handler({
|
|
194
|
+
method: "tools/call",
|
|
195
|
+
params: { name: "github_get_pr_commits", arguments: { prNumber: 123 } },
|
|
196
|
+
});
|
|
197
|
+
expect(resCommits.structuredContent).toEqual({ commits: [] });
|
|
198
|
+
const resWorkflow = await handler({
|
|
199
|
+
method: "tools/call",
|
|
200
|
+
params: { name: "repo_get_workflow_runs", arguments: {} },
|
|
158
201
|
});
|
|
202
|
+
expect(resWorkflow.structuredContent).toEqual({ runs: [] });
|
|
159
203
|
});
|
|
160
204
|
it("should return error result for unknown tool", async () => {
|
|
161
205
|
const handler = requestHandlers.get("tools/call");
|