@arii/boomtick-mcp 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +7 -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 +53 -13
- package/dist/mcp/server.test.js +20 -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 +1 -1
- package/dist/tools/github.get_pr_diff.js +1 -1
- package/dist/tools/github.get_pr_reviews.js +1 -1
- 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 +1 -1
- 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 +24 -0
- package/src/mcp/server.ts +63 -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 +1 -1
- 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 +1 -1
- 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 +1 -1
|
@@ -8,17 +8,17 @@ function tryParseJson(text) {
|
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
export async function runTDCli(args) {
|
|
11
|
-
const result = await runCommand("td
|
|
11
|
+
const result = await runCommand("td", args);
|
|
12
12
|
const stdout = result.stdout.trim();
|
|
13
13
|
const output = stdout.startsWith("{") ? tryParseJson(stdout) : null;
|
|
14
14
|
if (output?.status === "error") {
|
|
15
|
-
throw new Error(`td
|
|
15
|
+
throw new Error(`td returned error: ${output.message ?? "Unknown error"}`);
|
|
16
16
|
}
|
|
17
17
|
if (result.exitCode !== 0) {
|
|
18
|
-
throw new Error(`td
|
|
18
|
+
throw new Error(`td command failed (${args.join(" ")}): ${result.stderr || stdout}`);
|
|
19
19
|
}
|
|
20
20
|
if (!output) {
|
|
21
|
-
throw new Error(`td
|
|
21
|
+
throw new Error(`td returned non-JSON output with exit code 0: ${stdout}`);
|
|
22
22
|
}
|
|
23
23
|
return output;
|
|
24
24
|
}
|
|
@@ -4,7 +4,7 @@ export const TriggerJulesFeedbackInputSchema = z.object({
|
|
|
4
4
|
sessionId: z.string(),
|
|
5
5
|
});
|
|
6
6
|
export async function triggerJulesFeedbackHandler(input) {
|
|
7
|
-
const result = await runCommand("td
|
|
7
|
+
const result = await runCommand("td", ["agent", "trigger-feedback", input.sessionId]);
|
|
8
8
|
if (result.exitCode !== 0) {
|
|
9
9
|
throw new Error(`Failed to trigger feedback: ${result.stderr}`);
|
|
10
10
|
}
|
|
@@ -13,28 +13,28 @@ describe("triggerJulesFeedbackHandler", () => {
|
|
|
13
13
|
teardownTestEnv();
|
|
14
14
|
vi.clearAllMocks();
|
|
15
15
|
});
|
|
16
|
-
it("should return success output from td
|
|
16
|
+
it("should return success output from td", async () => {
|
|
17
17
|
vi.mocked(shell.runCommand).mockResolvedValue({
|
|
18
18
|
stdout: JSON.stringify({ status: "success", feedback: "All checks passed successfully. You may proceed." }),
|
|
19
19
|
stderr: "",
|
|
20
20
|
exitCode: 0,
|
|
21
21
|
durationMs: 0,
|
|
22
|
-
command: "td
|
|
22
|
+
command: "td agent trigger-feedback"
|
|
23
23
|
});
|
|
24
24
|
const result = await triggerJulesFeedbackHandler({ sessionId: "123" });
|
|
25
25
|
expect(result.status).toBe("success");
|
|
26
26
|
expect(result.feedback).toBe("All checks passed successfully. You may proceed.");
|
|
27
|
-
expect(shell.runCommand).toHaveBeenCalledWith("td
|
|
27
|
+
expect(shell.runCommand).toHaveBeenCalledWith("td", [
|
|
28
28
|
"agent", "trigger-feedback", "123"
|
|
29
29
|
]);
|
|
30
30
|
});
|
|
31
|
-
it("should throw error if td
|
|
31
|
+
it("should throw error if td returns error", async () => {
|
|
32
32
|
vi.mocked(shell.runCommand).mockResolvedValue({
|
|
33
33
|
stdout: JSON.stringify({ status: "error", message: "Session not found" }),
|
|
34
34
|
stderr: "",
|
|
35
35
|
exitCode: 0,
|
|
36
36
|
durationMs: 0,
|
|
37
|
-
command: "td
|
|
37
|
+
command: "td agent trigger-feedback"
|
|
38
38
|
});
|
|
39
39
|
await expect(triggerJulesFeedbackHandler({ sessionId: "123" }))
|
|
40
40
|
.rejects.toThrow("Failed to trigger feedback: Session not found");
|
|
@@ -6,11 +6,11 @@ export const GetCommandSchemaInputSchema = z.object({
|
|
|
6
6
|
export async function getCommandSchemaHandler(args) {
|
|
7
7
|
// Sanitize input to prevent command injection (allow only alphanumeric, spaces, hyphens, and underscores)
|
|
8
8
|
const sanitizedPath = args.commandPath.replace(/[^a-zA-Z0-9-_ ]/g, "");
|
|
9
|
-
const result = await runCommand("td
|
|
9
|
+
const result = await runCommand("td", ["schema", sanitizedPath]);
|
|
10
10
|
if (result.exitCode !== 0) {
|
|
11
11
|
// Log stderr for debugging but don't expose it to the user
|
|
12
12
|
// Using comma-separated arguments to satisfy semgrep unsafe-formatstring
|
|
13
|
-
console.error("td
|
|
13
|
+
console.error("td schema failed for", sanitizedPath, ":", result.stderr);
|
|
14
14
|
throw new Error(`Failed to get command schema for '${sanitizedPath}'`);
|
|
15
15
|
}
|
|
16
16
|
try {
|
|
@@ -17,7 +17,7 @@ export async function getWorkflowRunsHandler(args) {
|
|
|
17
17
|
if (params.event) {
|
|
18
18
|
cliArgs.push("--event", params.event);
|
|
19
19
|
}
|
|
20
|
-
const result = await runCommand("td
|
|
20
|
+
const result = await runCommand("td", cliArgs);
|
|
21
21
|
if (result.exitCode !== 0) {
|
|
22
22
|
throw new Error(`Failed to fetch workflow runs: ${sanitizeError(result.stderr)}`);
|
|
23
23
|
}
|
package/package.json
CHANGED
|
@@ -19,6 +19,7 @@ function isWritable(dir: string): boolean {
|
|
|
19
19
|
interface ToolDefinition {
|
|
20
20
|
name: string;
|
|
21
21
|
inputSchema: Record<string, unknown>;
|
|
22
|
+
outputSchema?: Record<string, unknown>;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -73,6 +74,7 @@ async function syncSchemas() {
|
|
|
73
74
|
name: tool.name,
|
|
74
75
|
description: tool.description,
|
|
75
76
|
parameters: tool.inputSchema,
|
|
77
|
+
outputSchema: tool.outputSchema,
|
|
76
78
|
};
|
|
77
79
|
const schemaContent = JSON.stringify(exportedSchema, null, 2);
|
|
78
80
|
const fileName = `${tool.name}.json`;
|
package/src/config.test.ts
CHANGED
|
@@ -20,14 +20,14 @@ describe("config", () => {
|
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
describe("initializeConfig", () => {
|
|
23
|
-
it("should load dynamic config from td
|
|
23
|
+
it("should load dynamic config from td config view using active cwd", async () => {
|
|
24
24
|
const mockConfig = { github_repo: "owner/repo", base_branch: "origin/main", vite_base_path: "/app/" };
|
|
25
25
|
vi.mocked(execSync).mockReturnValue(JSON.stringify(mockConfig));
|
|
26
26
|
|
|
27
27
|
const { initializeConfig } = await import("./config.js");
|
|
28
28
|
const result = initializeConfig();
|
|
29
29
|
|
|
30
|
-
expect(execSync).toHaveBeenCalledWith("td
|
|
30
|
+
expect(execSync).toHaveBeenCalledWith("td config view", expect.objectContaining({
|
|
31
31
|
cwd: process.cwd()
|
|
32
32
|
}));
|
|
33
33
|
expect(result).toEqual(mockConfig);
|
|
@@ -119,7 +119,7 @@ describe("config", () => {
|
|
|
119
119
|
delete process.env.GITHUB_REPOSITORY;
|
|
120
120
|
delete process.env.GH_REPO;
|
|
121
121
|
vi.mocked(execSync).mockImplementation(() => {
|
|
122
|
-
throw new Error("td
|
|
122
|
+
throw new Error("td failed");
|
|
123
123
|
});
|
|
124
124
|
|
|
125
125
|
process.env.NODE_ENV = "production";
|
package/src/config.ts
CHANGED
|
@@ -41,7 +41,7 @@ export function initializeConfig() {
|
|
|
41
41
|
try {
|
|
42
42
|
// Attempt to load core properties from the Python CLI in the active workspace
|
|
43
43
|
const cwd = process.env.BOOMTICK_REPO_PATH || process.cwd();
|
|
44
|
-
const cmd = `td
|
|
44
|
+
const cmd = `td config view`;
|
|
45
45
|
const output = execSync(cmd, {
|
|
46
46
|
cwd,
|
|
47
47
|
encoding: "utf-8",
|
package/src/lib/result.test.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createSuccessResult, createErrorResult } from "./result.js";
|
|
|
3
3
|
|
|
4
4
|
describe("result utils", () => {
|
|
5
5
|
describe("createSuccessResult", () => {
|
|
6
|
-
it("should format string data directly without JSON stringifying", () => {
|
|
6
|
+
it("should format string data directly without JSON stringifying and provide structuredContent", () => {
|
|
7
7
|
const result = createSuccessResult("simple string message");
|
|
8
8
|
expect(result).toEqual({
|
|
9
9
|
content: [
|
|
@@ -12,11 +12,12 @@ describe("result utils", () => {
|
|
|
12
12
|
text: "simple string message",
|
|
13
13
|
},
|
|
14
14
|
],
|
|
15
|
+
structuredContent: { result: "simple string message" },
|
|
15
16
|
});
|
|
16
17
|
expect(result.isError).toBeUndefined();
|
|
17
18
|
});
|
|
18
19
|
|
|
19
|
-
it("should format object data as pretty-printed JSON string", () => {
|
|
20
|
+
it("should format object data as pretty-printed JSON string and provide structuredContent", () => {
|
|
20
21
|
const objData = { foo: "bar", count: 42 };
|
|
21
22
|
const result = createSuccessResult(objData);
|
|
22
23
|
expect(result).toEqual({
|
|
@@ -26,6 +27,7 @@ describe("result utils", () => {
|
|
|
26
27
|
text: JSON.stringify(objData, null, 2),
|
|
27
28
|
},
|
|
28
29
|
],
|
|
30
|
+
structuredContent: objData,
|
|
29
31
|
});
|
|
30
32
|
expect(result.isError).toBeUndefined();
|
|
31
33
|
});
|
package/src/lib/result.ts
CHANGED
|
@@ -5,6 +5,11 @@ export interface ToolResult extends CallToolResult {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function createSuccessResult(data: any): ToolResult {
|
|
8
|
+
const structuredContent =
|
|
9
|
+
typeof data === "object" && data !== null
|
|
10
|
+
? data
|
|
11
|
+
: { result: data };
|
|
12
|
+
|
|
8
13
|
return {
|
|
9
14
|
content: [
|
|
10
15
|
{
|
|
@@ -12,6 +17,7 @@ export function createSuccessResult(data: any): ToolResult {
|
|
|
12
17
|
text: typeof data === "string" ? data : JSON.stringify(data, null, 2),
|
|
13
18
|
},
|
|
14
19
|
],
|
|
20
|
+
structuredContent,
|
|
15
21
|
};
|
|
16
22
|
}
|
|
17
23
|
|
package/src/lib/shell.ts
CHANGED
|
@@ -6,7 +6,7 @@ import fs from "fs";
|
|
|
6
6
|
export const ALLOWED_COMMANDS = {
|
|
7
7
|
git: "git",
|
|
8
8
|
gh: "gh",
|
|
9
|
-
|
|
9
|
+
td: "td",
|
|
10
10
|
pnpm: "pnpm",
|
|
11
11
|
ls: "ls",
|
|
12
12
|
rm: "rm",
|
|
@@ -39,15 +39,15 @@ export async function runCommand(
|
|
|
39
39
|
const start = Date.now();
|
|
40
40
|
const timeout = options.timeout || 60000;
|
|
41
41
|
|
|
42
|
-
// Resolve path for 'gh' or 'td
|
|
42
|
+
// Resolve path for 'gh' or 'td' if specified
|
|
43
43
|
let finalCmd = ALLOWED_COMMANDS[safeCmd] as string;
|
|
44
44
|
if (safeCmd === "gh") {
|
|
45
45
|
finalCmd = config.ghPath;
|
|
46
|
-
} else if (safeCmd === "td
|
|
46
|
+
} else if (safeCmd === "td") {
|
|
47
47
|
// If we have a repo-relative .venv, prioritize it
|
|
48
|
-
const venvBin = path.join(config.repoPath, ".venv", "bin", "td
|
|
49
|
-
const localVenvBin = path.join(process.cwd(), ".venv", "bin", "td
|
|
50
|
-
const parentVenvBin = path.join(config.repoPath, "..", ".venv", "bin", "td
|
|
48
|
+
const venvBin = path.join(config.repoPath, ".venv", "bin", "td");
|
|
49
|
+
const localVenvBin = path.join(process.cwd(), ".venv", "bin", "td");
|
|
50
|
+
const parentVenvBin = path.join(config.repoPath, "..", ".venv", "bin", "td");
|
|
51
51
|
|
|
52
52
|
// In standalone mode, config.repoPath is the boomtick-pkg root.
|
|
53
53
|
// We check common venv locations to ensure we call the isolated binary.
|
package/src/mcp/definitions.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Tool, Prompt, Resource } from "@modelcontextprotocol/sdk/types.js";
|
|
1
|
+
import { Tool, Prompt, Resource, ResourceTemplate } from "@modelcontextprotocol/sdk/types.js";
|
|
2
2
|
import {
|
|
3
3
|
HealthCheckInputJsonSchema,
|
|
4
4
|
SearchPRsInputJsonSchema,
|
|
@@ -60,6 +60,24 @@ export const MCP_PROMPTS: Prompt[] = [
|
|
|
60
60
|
];
|
|
61
61
|
|
|
62
62
|
export const MCP_RESOURCES: Resource[] = [
|
|
63
|
+
{
|
|
64
|
+
uri: "mcp://workspace/agent-contract",
|
|
65
|
+
name: "Agent Contract",
|
|
66
|
+
mimeType: "text/markdown",
|
|
67
|
+
description: "The agent contract specification file (.agents/AGENT_CONTRACT.md).",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
uri: "repo://agent-contract",
|
|
71
|
+
name: "Agent Contract (Legacy URI)",
|
|
72
|
+
mimeType: "text/markdown",
|
|
73
|
+
description: "The agent contract specification file (.agents/AGENT_CONTRACT.md).",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
uri: "mcp://git/changed-files",
|
|
77
|
+
name: "Git Changed Files",
|
|
78
|
+
mimeType: "application/json",
|
|
79
|
+
description: "List of changed files in the repository.",
|
|
80
|
+
},
|
|
63
81
|
{
|
|
64
82
|
uri: "repo://package-json",
|
|
65
83
|
name: "package.json",
|
|
@@ -72,184 +90,239 @@ export const MCP_RESOURCES: Resource[] = [
|
|
|
72
90
|
mimeType: "application/json",
|
|
73
91
|
description: "The design tokens used in the repository.",
|
|
74
92
|
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
export const MCP_RESOURCE_TEMPLATES: ResourceTemplate[] = [
|
|
96
|
+
{
|
|
97
|
+
uriTemplate: "mcp://git/diff/{prNumber}",
|
|
98
|
+
name: "Git PR Diff",
|
|
99
|
+
mimeType: "text/plain",
|
|
100
|
+
description: "Diff and changed files for a pull request.",
|
|
101
|
+
},
|
|
75
102
|
{
|
|
76
|
-
|
|
103
|
+
uriTemplate: "mcp://ci/logs/{prNumber}",
|
|
104
|
+
name: "CI Logs",
|
|
105
|
+
mimeType: "application/json",
|
|
106
|
+
description: "CI logs for a pull request.",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
uriTemplate: "repo://repair-report/{branch}",
|
|
77
110
|
name: "Repair Report",
|
|
78
111
|
mimeType: "application/json",
|
|
79
112
|
description: "The validation report for a specific repair branch.",
|
|
80
113
|
},
|
|
81
114
|
];
|
|
82
115
|
|
|
116
|
+
const DEFAULT_OUTPUT_SCHEMA = {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {},
|
|
119
|
+
additionalProperties: true,
|
|
120
|
+
} as const;
|
|
121
|
+
|
|
83
122
|
const BASE_MCP_TOOLS: Tool[] = [
|
|
84
123
|
{
|
|
85
124
|
name: "boomtick.health",
|
|
86
125
|
description: "Check the health and configuration of the MCP server.",
|
|
87
126
|
inputSchema: HealthCheckInputJsonSchema as any,
|
|
127
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
88
128
|
},
|
|
89
129
|
{
|
|
90
130
|
name: "github.search_open_prs",
|
|
91
131
|
description: "Search for open pull requests in the repository.",
|
|
92
132
|
inputSchema: SearchPRsInputJsonSchema as any,
|
|
133
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
93
134
|
},
|
|
94
135
|
{
|
|
95
136
|
name: "repo.read_agent_context",
|
|
96
137
|
description: "Read the full repository index including file tree, CLI schema, and MCP tools.",
|
|
97
138
|
inputSchema: ReadAgentContextSchema as any,
|
|
139
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
98
140
|
},
|
|
99
141
|
{
|
|
100
142
|
name: "github.get_pr_diff",
|
|
101
143
|
description: "Get the diff and changed files for a pull request.",
|
|
102
144
|
inputSchema: GetPrDiffInputJsonSchema as any,
|
|
145
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
103
146
|
},
|
|
104
147
|
{
|
|
105
148
|
name: "github.checkout_branch",
|
|
106
149
|
description: "Checkout a specific branch in the repository or worktree.",
|
|
107
150
|
inputSchema: CheckoutBranchInputJsonSchema as any,
|
|
151
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
108
152
|
},
|
|
109
153
|
{
|
|
110
154
|
name: "github.get_merge_conflict_files",
|
|
111
155
|
description: "Detect files that conflict when a PR is merged with the base branch.",
|
|
112
156
|
inputSchema: GetMergeConflictFilesInputJsonSchema as any,
|
|
157
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
113
158
|
},
|
|
114
159
|
{
|
|
115
160
|
name: "repo.get_changed_files",
|
|
116
161
|
description: "Get the list of changed files between two refs.",
|
|
117
162
|
inputSchema: GetChangedFilesInputJsonSchema as any,
|
|
163
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
118
164
|
},
|
|
119
165
|
{
|
|
120
166
|
name: "repo.get_command_schema",
|
|
121
|
-
description: "Retrieve a lightweight, targeted schema for a specific td
|
|
167
|
+
description: "Retrieve a lightweight, targeted schema for a specific td subcommand. Use this instead of reading the entire cli-schema.json to save tokens.",
|
|
122
168
|
inputSchema: GetCommandSchemaInputJsonSchema as any,
|
|
169
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
123
170
|
},
|
|
124
171
|
{
|
|
125
172
|
name: "repo.get_package_scripts",
|
|
126
173
|
description: "Get the scripts defined in package.json.",
|
|
127
174
|
inputSchema: GetPackageScriptsInputJsonSchema as any,
|
|
175
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
128
176
|
},
|
|
129
177
|
{
|
|
130
178
|
name: "repo.read_ci_logs",
|
|
131
179
|
description: "Read CI logs for a given pull request.",
|
|
132
180
|
inputSchema: ReadCiLogsInputJsonSchema as any,
|
|
181
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
133
182
|
},
|
|
134
183
|
{
|
|
135
184
|
name: "repo.logs",
|
|
136
185
|
description: "Stream or grep combined CI logs for all jobs in a pull request.",
|
|
137
186
|
inputSchema: RepoLogsInputJsonSchema as any,
|
|
187
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
138
188
|
},
|
|
139
189
|
{
|
|
140
190
|
name: "repo.create_branch",
|
|
141
191
|
description: "Creates a new clean git branch from a target base branch.",
|
|
142
192
|
inputSchema: CreateBranchInputJsonSchema as any,
|
|
193
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
143
194
|
},
|
|
144
195
|
{
|
|
145
196
|
name: "repo.create_repair_branch",
|
|
146
197
|
description: "Creates a fresh worktree and a new repair branch for an existing PR. This isolates the repair work from the main codebase.",
|
|
147
198
|
inputSchema: CreateRepairBranchInputJsonSchema as any,
|
|
199
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
148
200
|
},
|
|
149
201
|
{
|
|
150
202
|
name: "repo.run_tests",
|
|
151
203
|
description: "Run repository tests and checks.",
|
|
152
204
|
inputSchema: RunTestsInputJsonSchema as any,
|
|
205
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
153
206
|
},
|
|
154
207
|
{
|
|
155
208
|
name: "repo.commit_patch",
|
|
156
209
|
description: "Commit verified repair changes in a worktree.",
|
|
157
210
|
inputSchema: CommitPatchInputJsonSchema as any,
|
|
211
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
158
212
|
},
|
|
159
213
|
{
|
|
160
214
|
name: "github.open_replacement_pr",
|
|
161
215
|
description: "Open a new PR that replaces or repairs the original PR.",
|
|
162
216
|
inputSchema: OpenReplacementPrInputJsonSchema as any,
|
|
217
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
163
218
|
},
|
|
164
219
|
{
|
|
165
220
|
name: "github.create_pull_request",
|
|
166
221
|
description: "Creates a pull request on GitHub using the MCP server's integrated credentials. Bypasses terminal CLI constraints.",
|
|
167
222
|
inputSchema: CreatePullRequestInputJsonSchema as any,
|
|
223
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
168
224
|
},
|
|
169
225
|
{
|
|
170
226
|
name: "github.comment_triage_summary",
|
|
171
227
|
description: "Comment on the original PR with a diagnosis and replacement link.",
|
|
172
228
|
inputSchema: CommentTriageSummaryInputJsonSchema as any,
|
|
229
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
173
230
|
},
|
|
174
231
|
{
|
|
175
232
|
name: "github.get_pr",
|
|
176
233
|
description: "Get details of a GitHub PR including title, state, and URLs.",
|
|
177
234
|
inputSchema: GetPrInputJsonSchema as any,
|
|
235
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
178
236
|
},
|
|
179
237
|
{
|
|
180
238
|
name: "github.get_pr_reviews",
|
|
181
239
|
description: "Get top-level reviews, verdicts, states, and bodies for a GitHub PR.",
|
|
182
240
|
inputSchema: GetPrReviewsInputJsonSchema as any,
|
|
241
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
183
242
|
},
|
|
184
243
|
{
|
|
185
244
|
name: "github.get_pr_commits",
|
|
186
245
|
description: "List commits and commit metadata (SHAs, authors, messages) for a GitHub PR.",
|
|
187
246
|
inputSchema: GetPrCommitsInputJsonSchema as any,
|
|
247
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
188
248
|
},
|
|
189
249
|
{
|
|
190
250
|
name: "repo.get_workflow_runs",
|
|
191
251
|
description: "List recent GitHub Actions workflow runs for the repository.",
|
|
192
252
|
inputSchema: GetWorkflowRunsInputJsonSchema as any,
|
|
253
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
193
254
|
},
|
|
194
255
|
{
|
|
195
256
|
name: "github.issue_view",
|
|
196
257
|
description: "View details of a GitHub issue including title, body, and state.",
|
|
197
258
|
inputSchema: IssueViewInputJsonSchema as any,
|
|
259
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
198
260
|
},
|
|
199
261
|
{
|
|
200
262
|
name: "github.issue_update",
|
|
201
263
|
description: "Update a GitHub issue's body, labels, and/or state.",
|
|
202
264
|
inputSchema: IssueUpdateInputJsonSchema as any,
|
|
265
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
203
266
|
},
|
|
204
267
|
{
|
|
205
268
|
name: "github.issue_comment",
|
|
206
269
|
description: "Add a new comment to a GitHub issue.",
|
|
207
270
|
inputSchema: IssueCommentInputJsonSchema as any,
|
|
271
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
208
272
|
},
|
|
209
273
|
{
|
|
210
274
|
name: "github.create_issue",
|
|
211
275
|
description: "Create a new GitHub issue.",
|
|
212
276
|
inputSchema: CreateIssueInputJsonSchema as any,
|
|
277
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
213
278
|
},
|
|
214
279
|
{
|
|
215
280
|
name: "jules.create_session",
|
|
216
281
|
description: "Create a Jules session that performs work externally and may generate a GitHub pull request.",
|
|
217
282
|
inputSchema: CreateJulesSessionInputJsonSchema as any,
|
|
283
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
218
284
|
},
|
|
219
285
|
{
|
|
220
286
|
name: "jules.get_session",
|
|
221
287
|
description: "Get the status and details of a Jules session.",
|
|
222
288
|
inputSchema: JulesSessionIdInputJsonSchema as any,
|
|
289
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
223
290
|
},
|
|
224
291
|
{
|
|
225
292
|
name: "jules.send_message",
|
|
226
293
|
description: "Send a message to an active Jules session.",
|
|
227
294
|
inputSchema: JulesSendMessageInputJsonSchema as any,
|
|
295
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
228
296
|
},
|
|
229
297
|
{
|
|
230
298
|
name: "jules.get_messages",
|
|
231
299
|
description: "Get the message history of a Jules session.",
|
|
232
300
|
inputSchema: JulesSessionIdInputJsonSchema as any,
|
|
301
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
233
302
|
},
|
|
234
303
|
{
|
|
235
304
|
name: "jules.list_sessions",
|
|
236
305
|
description: "List all Jules sessions.",
|
|
237
306
|
inputSchema: JulesListSessionsInputJsonSchema as any,
|
|
307
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
238
308
|
},
|
|
239
309
|
{
|
|
240
310
|
name: "jules.cancel_session",
|
|
241
311
|
description: "Cancel an ongoing Jules session.",
|
|
242
312
|
inputSchema: JulesSessionIdInputJsonSchema as any,
|
|
313
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
243
314
|
},
|
|
244
315
|
{
|
|
245
316
|
name: "jules.get_pr",
|
|
246
317
|
description: "Get the generated pull request url associated with an active Jules agent session.",
|
|
247
318
|
inputSchema: JulesSessionIdInputJsonSchema as any,
|
|
319
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
248
320
|
},
|
|
249
321
|
{
|
|
250
322
|
name: "jules.trigger_feedback",
|
|
251
323
|
description: "Automatically collect CI status/logs for the PR associated with a Jules session and send them back as feedback.",
|
|
252
324
|
inputSchema: JulesSessionIdInputJsonSchema as any,
|
|
325
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
|
|
253
326
|
},
|
|
254
327
|
];
|
|
255
328
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { createMcpHttpServer } from "./http.js";
|
|
5
|
+
|
|
6
|
+
describe("createMcpHttpServer", () => {
|
|
7
|
+
let mcpServer: Server;
|
|
8
|
+
let httpServerObj: ReturnType<typeof createMcpHttpServer>;
|
|
9
|
+
let port: number;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
mcpServer = new Server({ name: "test-server", version: "1.0.0" }, { capabilities: {} });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
if (httpServerObj?.server) {
|
|
17
|
+
httpServerObj.server.close();
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should respond with OAuth protected resource metadata on /.well-known/oauth-protected-resource", async () => {
|
|
22
|
+
httpServerObj = createMcpHttpServer({ port: 0, mcpServer });
|
|
23
|
+
port = await httpServerObj.listen(0);
|
|
24
|
+
|
|
25
|
+
const res = await fetch(`http://localhost:${port}/.well-known/oauth-protected-resource`);
|
|
26
|
+
expect(res.status).toBe(200);
|
|
27
|
+
const body = await res.json();
|
|
28
|
+
expect(body.resource).toBe("mcp://boomtick-mcp");
|
|
29
|
+
expect(body.authorization_servers).toBeDefined();
|
|
30
|
+
expect(body.scopes_supported).toEqual(["mcp:read", "mcp:write"]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should reject requests when Bearer token is missing or invalid", async () => {
|
|
34
|
+
httpServerObj = createMcpHttpServer({ port: 0, bearerToken: "secret123", mcpServer });
|
|
35
|
+
port = await httpServerObj.listen(0);
|
|
36
|
+
|
|
37
|
+
// Request without token
|
|
38
|
+
const res1 = await fetch(`http://localhost:${port}/sse`);
|
|
39
|
+
expect(res1.status).toBe(401);
|
|
40
|
+
expect(res1.headers.get("www-authenticate")).toContain('Bearer realm="boomtick-mcp"');
|
|
41
|
+
|
|
42
|
+
// Request with wrong token
|
|
43
|
+
const res2 = await fetch(`http://localhost:${port}/sse`, {
|
|
44
|
+
headers: { Authorization: "Bearer wrong" },
|
|
45
|
+
});
|
|
46
|
+
expect(res2.status).toBe(401);
|
|
47
|
+
|
|
48
|
+
// Request with valid token
|
|
49
|
+
const res3 = await fetch(`http://localhost:${port}/.well-known/oauth-protected-resource`, {
|
|
50
|
+
headers: { Authorization: "Bearer secret123" },
|
|
51
|
+
});
|
|
52
|
+
expect(res3.status).toBe(200);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should handle 404 for unknown endpoints", async () => {
|
|
56
|
+
httpServerObj = createMcpHttpServer({ port: 0, mcpServer });
|
|
57
|
+
port = await httpServerObj.listen(0);
|
|
58
|
+
|
|
59
|
+
const res = await fetch(`http://localhost:${port}/unknown-path`);
|
|
60
|
+
expect(res.status).toBe(404);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should return 500 if bearer token is not configured on server for protected endpoints", async () => {
|
|
64
|
+
httpServerObj = createMcpHttpServer({ port: 0, mcpServer });
|
|
65
|
+
port = await httpServerObj.listen(0);
|
|
66
|
+
|
|
67
|
+
const res = await fetch(`http://localhost:${port}/sse`);
|
|
68
|
+
expect(res.status).toBe(500);
|
|
69
|
+
const body = await res.json();
|
|
70
|
+
expect(body.error).toContain("Server misconfiguration");
|
|
71
|
+
});
|
|
72
|
+
});
|