@arii/boomtick-mcp 0.4.2 → 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 +37 -0
- package/.mcp/schemas/github.get_pr_diff.json +5 -0
- package/.mcp/schemas/github.get_pr_reviews.json +37 -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 +37 -0
- package/.mcp/schemas/github_get_pr_diff.json +5 -0
- package/.mcp/schemas/github_get_pr_reviews.json +37 -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 +55 -0
- package/.mcp/schemas/repo.logs.json +19 -0
- package/.mcp/schemas/repo.read_agent_context.json +5 -0
- package/.mcp/schemas/repo.read_ci_logs.json +19 -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 +55 -0
- package/.mcp/schemas/repo_logs.json +19 -0
- package/.mcp/schemas/repo_read_agent_context.json +5 -0
- package/.mcp/schemas/repo_read_ci_logs.json +19 -0
- package/.mcp/schemas/repo_run_tests.json +5 -0
- package/CHANGELOG.md +14 -0
- package/README.md +0 -1
- 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 +7 -19
- package/actions/run-project-gate/action.yml +7 -14
- package/actions/setup/action.yml +1 -1
- package/actions/test-build/action.yml +2 -20
- package/dist/config.js +1 -1
- package/dist/config.test.js +3 -3
- package/dist/lib/git.js +44 -0
- package/dist/lib/git.test.js +82 -0
- 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 +86 -38
- package/dist/mcp/http.js +91 -0
- package/dist/mcp/http.test.js +58 -0
- package/dist/mcp/server.js +51 -57
- package/dist/mcp/server.test.js +19 -32
- package/dist/tools/contract.js +244 -3
- 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_merge_conflict_files.js +10 -18
- package/dist/tools/github.get_pr.js +1 -1
- package/dist/tools/github.get_pr_commits.js +21 -0
- package/dist/tools/github.get_pr_diff.js +1 -1
- package/dist/tools/github.get_pr_reviews.js +21 -0
- package/dist/tools/github.get_pr_reviews.test.js +99 -0
- 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.js +1 -2
- 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.js +1 -2
- package/dist/tools/jules/get-pr.test.js +3 -3
- package/dist/tools/jules/get-session.js +1 -2
- 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 +24 -0
- package/dist/tools/jules/trigger-feedback.js +1 -1
- package/dist/tools/jules/trigger-feedback.test.js +5 -5
- package/dist/tools/repo.get_changed_files.js +2 -13
- package/dist/tools/repo.get_command_schema.js +2 -2
- package/dist/tools/repo.get_workflow_runs.js +29 -0
- package/dist/tools/repo.logs.js +5 -1
- package/dist/tools/repo.read_agent_context.js +27 -1
- package/dist/tools/repo.read_agent_context.test.js +1 -1
- package/dist/tools/repo.read_ci_logs.js +5 -1
- package/dist/tools/repo.run_tests.js +1 -1
- package/package.json +3 -6
- package/scripts/sync-mcp-schemas.ts +2 -0
- package/src/agents/verifier-agent.prompt.md +0 -4
- package/src/config.test.ts +3 -3
- package/src/config.ts +1 -1
- package/src/lib/git.test.ts +110 -0
- package/src/lib/git.ts +75 -0
- 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 +91 -42
- package/src/mcp/http.test.ts +72 -0
- package/src/mcp/http.ts +118 -0
- package/src/mcp/server.test.ts +20 -38
- package/src/mcp/server.ts +60 -54
- package/src/tools/contract.ts +256 -3
- 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_merge_conflict_files.ts +10 -20
- package/src/tools/github.get_pr.ts +1 -1
- package/src/tools/github.get_pr_commits.ts +27 -0
- package/src/tools/github.get_pr_diff.ts +1 -1
- package/src/tools/github.get_pr_reviews.test.ts +110 -0
- package/src/tools/github.get_pr_reviews.ts +27 -0
- 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/create-session.ts +1 -2
- 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/get-pr.ts +1 -2
- package/src/tools/jules/get-session.ts +1 -2
- 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 +35 -0
- package/src/tools/jules/trigger-feedback.test.ts +5 -5
- package/src/tools/jules/trigger-feedback.ts +1 -1
- package/src/tools/repo.get_changed_files.ts +2 -16
- package/src/tools/repo.get_command_schema.ts +2 -2
- package/src/tools/repo.get_workflow_runs.ts +36 -0
- package/src/tools/repo.logs.ts +5 -1
- package/src/tools/repo.read_agent_context.test.ts +1 -1
- package/src/tools/repo.read_agent_context.ts +35 -1
- package/src/tools/repo.read_ci_logs.ts +5 -1
- package/src/tools/repo.run_tests.ts +1 -1
- package/.mcp/schemas/agent.search_ddgs.json +0 -31
- package/.mcp/schemas/agent_search_ddgs.json +0 -31
- package/.mcp/schemas/repo.get_route_map.json +0 -23
- package/.mcp/schemas/repo.run_lighthouse.json +0 -29
- package/.mcp/schemas/repo.run_playwright.json +0 -36
- package/.mcp/schemas/repo_get_route_map.json +0 -23
- package/.mcp/schemas/repo_run_lighthouse.json +0 -29
- package/.mcp/schemas/repo_run_playwright.json +0 -36
- package/dist/lib/td-cli.js +0 -54
- package/dist/tools/ddgs.search.js +0 -50
- package/dist/tools/ddgs.search.test.js +0 -67
- package/dist/tools/ddgs_search.py +0 -23
- package/dist/tools/repo.get_route_map.js +0 -37
- package/dist/tools/repo.run_lighthouse.js +0 -37
- package/dist/tools/repo.run_playwright.js +0 -34
- package/scripts/export-mcp-schema.ts +0 -7
- package/src/lib/td-cli.ts +0 -73
- package/src/tools/ddgs.search.test.ts +0 -92
- package/src/tools/ddgs.search.ts +0 -58
- package/src/tools/ddgs_search.py +0 -23
- package/src/tools/repo.get_route_map.ts +0 -41
- package/src/tools/repo.run_lighthouse.ts +0 -40
- package/src/tools/repo.run_playwright.ts +0 -42
package/dist/config.test.js
CHANGED
|
@@ -15,12 +15,12 @@ describe("config", () => {
|
|
|
15
15
|
process.env = originalEnv;
|
|
16
16
|
});
|
|
17
17
|
describe("initializeConfig", () => {
|
|
18
|
-
it("should load dynamic config from td
|
|
18
|
+
it("should load dynamic config from td config view using active cwd", async () => {
|
|
19
19
|
const mockConfig = { github_repo: "owner/repo", base_branch: "origin/main", vite_base_path: "/app/" };
|
|
20
20
|
vi.mocked(execSync).mockReturnValue(JSON.stringify(mockConfig));
|
|
21
21
|
const { initializeConfig } = await import("./config.js");
|
|
22
22
|
const result = initializeConfig();
|
|
23
|
-
expect(execSync).toHaveBeenCalledWith("td
|
|
23
|
+
expect(execSync).toHaveBeenCalledWith("td config view", expect.objectContaining({
|
|
24
24
|
cwd: process.cwd()
|
|
25
25
|
}));
|
|
26
26
|
expect(result).toEqual(mockConfig);
|
|
@@ -93,7 +93,7 @@ describe("config", () => {
|
|
|
93
93
|
delete process.env.GITHUB_REPOSITORY;
|
|
94
94
|
delete process.env.GH_REPO;
|
|
95
95
|
vi.mocked(execSync).mockImplementation(() => {
|
|
96
|
-
throw new Error("td
|
|
96
|
+
throw new Error("td failed");
|
|
97
97
|
});
|
|
98
98
|
process.env.NODE_ENV = "production";
|
|
99
99
|
process.env.CI = "false";
|
package/dist/lib/git.js
CHANGED
|
@@ -59,3 +59,47 @@ export async function removeWorktree(worktreePath) {
|
|
|
59
59
|
await runCommand("git", ["worktree", "remove", "--force", worktreePath]);
|
|
60
60
|
await runCommand("git", ["worktree", "prune"]);
|
|
61
61
|
}
|
|
62
|
+
export async function getChangedFiles(base = "main", head = "HEAD", cwd) {
|
|
63
|
+
const result = await runCommand("git", ["diff", "--name-status", "-z", `${base}...${head}`], { cwd });
|
|
64
|
+
if (result.exitCode !== 0) {
|
|
65
|
+
throw new Error(`Failed to get changed files: ${result.stderr}`);
|
|
66
|
+
}
|
|
67
|
+
const tokens = result.stdout.split("\0").filter((t) => t.length > 0);
|
|
68
|
+
const files = [];
|
|
69
|
+
let i = 0;
|
|
70
|
+
while (i < tokens.length) {
|
|
71
|
+
const status = tokens[i];
|
|
72
|
+
if (status.startsWith("R") || status.startsWith("C")) {
|
|
73
|
+
const oldPath = tokens[i + 1];
|
|
74
|
+
const newPath = tokens[i + 2];
|
|
75
|
+
if (oldPath && newPath) {
|
|
76
|
+
files.push({ status, path: newPath, oldPath });
|
|
77
|
+
}
|
|
78
|
+
i += 3;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const filePath = tokens[i + 1];
|
|
82
|
+
if (filePath) {
|
|
83
|
+
files.push({ status, path: filePath });
|
|
84
|
+
}
|
|
85
|
+
i += 2;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return files;
|
|
89
|
+
}
|
|
90
|
+
export async function getMergeConflictFiles(baseBranch = "main", headRef = "HEAD", cwd) {
|
|
91
|
+
const result = await runCommand("git", ["merge-tree", "--write-tree", "--name-only", baseBranch, headRef], { cwd });
|
|
92
|
+
// git merge-tree returns exit code 1 if conflicts are present, 0 if clean merge
|
|
93
|
+
if (result.exitCode !== 0 && result.exitCode !== 1) {
|
|
94
|
+
throw new Error(`Git merge-tree failed: ${result.stderr}`);
|
|
95
|
+
}
|
|
96
|
+
const lines = result.stdout
|
|
97
|
+
.split("\n")
|
|
98
|
+
.map((l) => l.trim())
|
|
99
|
+
.filter((l) => l.length > 0);
|
|
100
|
+
// First line is tree hash if successful or conflict output
|
|
101
|
+
// When conflicts exist, git merge-tree --write-tree --name-only outputs conflicted filenames
|
|
102
|
+
// Skip tree hash line if present (40 hex chars)
|
|
103
|
+
const files = lines.filter((line) => !/^[0-9a-f]{40}$/i.test(line));
|
|
104
|
+
return files;
|
|
105
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
2
|
+
import { getChangedFiles, getMergeConflictFiles } from "./git.js";
|
|
3
|
+
import * as shell from "./shell.js";
|
|
4
|
+
vi.mock("./shell.js", () => ({
|
|
5
|
+
runCommand: vi.fn(),
|
|
6
|
+
}));
|
|
7
|
+
describe("Git Helpers", () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
vi.resetAllMocks();
|
|
10
|
+
});
|
|
11
|
+
describe("getChangedFiles", () => {
|
|
12
|
+
it("should parse null-delimited git diff output correctly", async () => {
|
|
13
|
+
// Mock git diff output with null character delimiters:
|
|
14
|
+
// Status M -> file1.txt
|
|
15
|
+
// Status A -> file2.txt
|
|
16
|
+
// Status R100 -> old.txt -> new.txt
|
|
17
|
+
const mockStdout = "M\0file1.txt\0A\0file2.txt\0R100\0old.txt\0new.txt\0";
|
|
18
|
+
vi.mocked(shell.runCommand).mockResolvedValueOnce({
|
|
19
|
+
exitCode: 0,
|
|
20
|
+
stdout: mockStdout,
|
|
21
|
+
stderr: "",
|
|
22
|
+
durationMs: 10,
|
|
23
|
+
command: "git diff --name-status -z main...HEAD",
|
|
24
|
+
});
|
|
25
|
+
const result = await getChangedFiles("main", "HEAD");
|
|
26
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", ["diff", "--name-status", "-z", "main...HEAD"], { cwd: undefined });
|
|
27
|
+
expect(result).toEqual([
|
|
28
|
+
{ status: "M", path: "file1.txt" },
|
|
29
|
+
{ status: "A", path: "file2.txt" },
|
|
30
|
+
{ status: "R100", path: "new.txt", oldPath: "old.txt" },
|
|
31
|
+
]);
|
|
32
|
+
});
|
|
33
|
+
it("should throw error if git diff fails", async () => {
|
|
34
|
+
vi.mocked(shell.runCommand).mockResolvedValueOnce({
|
|
35
|
+
exitCode: 1,
|
|
36
|
+
stdout: "",
|
|
37
|
+
stderr: "fatal: bad revision",
|
|
38
|
+
durationMs: 10,
|
|
39
|
+
command: "git diff --name-status -z invalid...HEAD",
|
|
40
|
+
});
|
|
41
|
+
await expect(getChangedFiles("invalid", "HEAD")).rejects.toThrow("Failed to get changed files: fatal: bad revision");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe("getMergeConflictFiles", () => {
|
|
45
|
+
it("should parse git merge-tree output when conflicts exist", async () => {
|
|
46
|
+
// Output with 40-char tree hash followed by conflicting filenames
|
|
47
|
+
const mockStdout = "5d71843ed2e051b2be91361db7bea0fc4ee1e5a0\nfile1.txt\nsrc/file2.ts\n";
|
|
48
|
+
vi.mocked(shell.runCommand).mockResolvedValueOnce({
|
|
49
|
+
exitCode: 1, // merge-tree exits 1 on conflicts
|
|
50
|
+
stdout: mockStdout,
|
|
51
|
+
stderr: "",
|
|
52
|
+
durationMs: 10,
|
|
53
|
+
command: "git merge-tree --write-tree --name-only main feature",
|
|
54
|
+
});
|
|
55
|
+
const conflicts = await getMergeConflictFiles("main", "feature");
|
|
56
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", ["merge-tree", "--write-tree", "--name-only", "main", "feature"], { cwd: undefined });
|
|
57
|
+
expect(conflicts).toEqual(["file1.txt", "src/file2.ts"]);
|
|
58
|
+
});
|
|
59
|
+
it("should return empty array when no conflicts exist", async () => {
|
|
60
|
+
const mockStdout = "5d71843ed2e051b2be91361db7bea0fc4ee1e5a0\n";
|
|
61
|
+
vi.mocked(shell.runCommand).mockResolvedValueOnce({
|
|
62
|
+
exitCode: 0,
|
|
63
|
+
stdout: mockStdout,
|
|
64
|
+
stderr: "",
|
|
65
|
+
durationMs: 10,
|
|
66
|
+
command: "git merge-tree --write-tree --name-only main feature",
|
|
67
|
+
});
|
|
68
|
+
const conflicts = await getMergeConflictFiles("main", "feature");
|
|
69
|
+
expect(conflicts).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
it("should throw error if git merge-tree exits with unexpected exit code", async () => {
|
|
72
|
+
vi.mocked(shell.runCommand).mockResolvedValueOnce({
|
|
73
|
+
exitCode: 128,
|
|
74
|
+
stdout: "",
|
|
75
|
+
stderr: "fatal: not a valid object name",
|
|
76
|
+
durationMs: 10,
|
|
77
|
+
command: "git merge-tree --write-tree --name-only invalid feature",
|
|
78
|
+
});
|
|
79
|
+
await expect(getMergeConflictFiles("invalid", "feature")).rejects.toThrow("Git merge-tree failed: fatal: not a valid object name");
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
package/dist/lib/result.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export function createSuccessResult(data) {
|
|
2
|
+
const structuredContent = typeof data === "object" && data !== null
|
|
3
|
+
? data
|
|
4
|
+
: { result: data };
|
|
2
5
|
return {
|
|
3
6
|
content: [
|
|
4
7
|
{
|
|
@@ -6,6 +9,7 @@ export function createSuccessResult(data) {
|
|
|
6
9
|
text: typeof data === "string" ? data : JSON.stringify(data, null, 2),
|
|
7
10
|
},
|
|
8
11
|
],
|
|
12
|
+
structuredContent,
|
|
9
13
|
};
|
|
10
14
|
}
|
|
11
15
|
export function createErrorResult(message) {
|
package/dist/lib/result.test.js
CHANGED
|
@@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest";
|
|
|
2
2
|
import { createSuccessResult, createErrorResult } from "./result.js";
|
|
3
3
|
describe("result utils", () => {
|
|
4
4
|
describe("createSuccessResult", () => {
|
|
5
|
-
it("should format string data directly without JSON stringifying", () => {
|
|
5
|
+
it("should format string data directly without JSON stringifying and provide structuredContent", () => {
|
|
6
6
|
const result = createSuccessResult("simple string message");
|
|
7
7
|
expect(result).toEqual({
|
|
8
8
|
content: [
|
|
@@ -11,10 +11,11 @@ describe("result utils", () => {
|
|
|
11
11
|
text: "simple string message",
|
|
12
12
|
},
|
|
13
13
|
],
|
|
14
|
+
structuredContent: { result: "simple string message" },
|
|
14
15
|
});
|
|
15
16
|
expect(result.isError).toBeUndefined();
|
|
16
17
|
});
|
|
17
|
-
it("should format object data as pretty-printed JSON string", () => {
|
|
18
|
+
it("should format object data as pretty-printed JSON string and provide structuredContent", () => {
|
|
18
19
|
const objData = { foo: "bar", count: 42 };
|
|
19
20
|
const result = createSuccessResult(objData);
|
|
20
21
|
expect(result).toEqual({
|
|
@@ -24,6 +25,7 @@ describe("result utils", () => {
|
|
|
24
25
|
text: JSON.stringify(objData, null, 2),
|
|
25
26
|
},
|
|
26
27
|
],
|
|
28
|
+
structuredContent: objData,
|
|
27
29
|
});
|
|
28
30
|
expect(result.isError).toBeUndefined();
|
|
29
31
|
});
|
package/dist/lib/shell.js
CHANGED
|
@@ -5,7 +5,7 @@ import fs from "fs";
|
|
|
5
5
|
export const ALLOWED_COMMANDS = {
|
|
6
6
|
git: "git",
|
|
7
7
|
gh: "gh",
|
|
8
|
-
|
|
8
|
+
td: "td",
|
|
9
9
|
pnpm: "pnpm",
|
|
10
10
|
ls: "ls",
|
|
11
11
|
rm: "rm",
|
|
@@ -20,16 +20,16 @@ export async function runCommand(cmd, args, options = {}) {
|
|
|
20
20
|
const safeCmd = cmd;
|
|
21
21
|
const start = Date.now();
|
|
22
22
|
const timeout = options.timeout || 60000;
|
|
23
|
-
// Resolve path for 'gh' or 'td
|
|
23
|
+
// Resolve path for 'gh' or 'td' if specified
|
|
24
24
|
let finalCmd = ALLOWED_COMMANDS[safeCmd];
|
|
25
25
|
if (safeCmd === "gh") {
|
|
26
26
|
finalCmd = config.ghPath;
|
|
27
27
|
}
|
|
28
|
-
else if (safeCmd === "td
|
|
28
|
+
else if (safeCmd === "td") {
|
|
29
29
|
// If we have a repo-relative .venv, prioritize it
|
|
30
|
-
const venvBin = path.join(config.repoPath, ".venv", "bin", "td
|
|
31
|
-
const localVenvBin = path.join(process.cwd(), ".venv", "bin", "td
|
|
32
|
-
const parentVenvBin = path.join(config.repoPath, "..", ".venv", "bin", "td
|
|
30
|
+
const venvBin = path.join(config.repoPath, ".venv", "bin", "td");
|
|
31
|
+
const localVenvBin = path.join(process.cwd(), ".venv", "bin", "td");
|
|
32
|
+
const parentVenvBin = path.join(config.repoPath, "..", ".venv", "bin", "td");
|
|
33
33
|
// In standalone mode, config.repoPath is the boomtick-pkg root.
|
|
34
34
|
// We check common venv locations to ensure we call the isolated binary.
|
|
35
35
|
if (fs.existsSync(venvBin)) {
|
package/dist/mcp/definitions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HealthCheckInputJsonSchema, SearchPRsInputJsonSchema, GetPrDiffInputJsonSchema, CheckoutBranchInputJsonSchema, GetMergeConflictFilesInputJsonSchema, GetChangedFilesInputJsonSchema, GetPackageScriptsInputJsonSchema,
|
|
1
|
+
import { HealthCheckInputJsonSchema, SearchPRsInputJsonSchema, GetPrDiffInputJsonSchema, CheckoutBranchInputJsonSchema, GetMergeConflictFilesInputJsonSchema, GetChangedFilesInputJsonSchema, GetPackageScriptsInputJsonSchema, ReadCiLogsInputJsonSchema, RepoLogsInputJsonSchema, CreateBranchInputJsonSchema, CreateRepairBranchInputJsonSchema, RunTestsInputJsonSchema, CommitPatchInputJsonSchema, OpenReplacementPrInputJsonSchema, CreatePullRequestInputJsonSchema, CommentTriageSummaryInputJsonSchema, GetPrInputJsonSchema, IssueViewInputJsonSchema, IssueUpdateInputJsonSchema, IssueCommentInputJsonSchema, CreateIssueInputJsonSchema, GetCommandSchemaInputJsonSchema, ReadAgentContextInputJsonSchema as ReadAgentContextSchema, CreateJulesSessionInputJsonSchema, JulesSessionIdInputJsonSchema, JulesSendMessageInputJsonSchema, JulesListSessionsInputJsonSchema, GetPrReviewsInputJsonSchema, GetPrCommitsInputJsonSchema, GetWorkflowRunsInputJsonSchema, } from "../tools/contract.js";
|
|
2
2
|
export const MCP_PROMPTS = [
|
|
3
3
|
{
|
|
4
4
|
name: "conflict-scout",
|
|
@@ -27,16 +27,28 @@ export const MCP_PROMPTS = [
|
|
|
27
27
|
];
|
|
28
28
|
export const MCP_RESOURCES = [
|
|
29
29
|
{
|
|
30
|
-
uri: "
|
|
31
|
-
name: "
|
|
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",
|
|
32
44
|
mimeType: "application/json",
|
|
33
|
-
description: "
|
|
45
|
+
description: "List of changed files in the repository.",
|
|
34
46
|
},
|
|
35
47
|
{
|
|
36
|
-
uri: "repo://
|
|
37
|
-
name: "
|
|
48
|
+
uri: "repo://package-json",
|
|
49
|
+
name: "package.json",
|
|
38
50
|
mimeType: "application/json",
|
|
39
|
-
description: "The
|
|
51
|
+
description: "The root package.json file of the repository.",
|
|
40
52
|
},
|
|
41
53
|
{
|
|
42
54
|
uri: "repo://design-tokens",
|
|
@@ -44,200 +56,236 @@ export const MCP_RESOURCES = [
|
|
|
44
56
|
mimeType: "application/json",
|
|
45
57
|
description: "The design tokens used in the repository.",
|
|
46
58
|
},
|
|
59
|
+
];
|
|
60
|
+
export const MCP_RESOURCE_TEMPLATES = [
|
|
47
61
|
{
|
|
48
|
-
|
|
49
|
-
name: "
|
|
50
|
-
mimeType: "
|
|
51
|
-
description: "
|
|
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.",
|
|
52
66
|
},
|
|
53
67
|
{
|
|
54
|
-
|
|
55
|
-
name: "
|
|
68
|
+
uriTemplate: "mcp://ci/logs/{prNumber}",
|
|
69
|
+
name: "CI Logs",
|
|
56
70
|
mimeType: "application/json",
|
|
57
|
-
description: "
|
|
71
|
+
description: "CI logs for a pull request.",
|
|
58
72
|
},
|
|
59
73
|
{
|
|
60
|
-
|
|
61
|
-
name: "
|
|
74
|
+
uriTemplate: "repo://repair-report/{branch}",
|
|
75
|
+
name: "Repair Report",
|
|
62
76
|
mimeType: "application/json",
|
|
63
|
-
description: "
|
|
77
|
+
description: "The validation report for a specific repair branch.",
|
|
64
78
|
},
|
|
65
79
|
];
|
|
80
|
+
const DEFAULT_OUTPUT_SCHEMA = {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {},
|
|
83
|
+
additionalProperties: true,
|
|
84
|
+
};
|
|
66
85
|
const BASE_MCP_TOOLS = [
|
|
67
86
|
{
|
|
68
87
|
name: "boomtick.health",
|
|
69
88
|
description: "Check the health and configuration of the MCP server.",
|
|
70
89
|
inputSchema: HealthCheckInputJsonSchema,
|
|
90
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
71
91
|
},
|
|
72
92
|
{
|
|
73
93
|
name: "github.search_open_prs",
|
|
74
94
|
description: "Search for open pull requests in the repository.",
|
|
75
95
|
inputSchema: SearchPRsInputJsonSchema,
|
|
96
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
76
97
|
},
|
|
77
98
|
{
|
|
78
99
|
name: "repo.read_agent_context",
|
|
79
100
|
description: "Read the full repository index including file tree, CLI schema, and MCP tools.",
|
|
80
101
|
inputSchema: ReadAgentContextSchema,
|
|
102
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
81
103
|
},
|
|
82
104
|
{
|
|
83
105
|
name: "github.get_pr_diff",
|
|
84
106
|
description: "Get the diff and changed files for a pull request.",
|
|
85
107
|
inputSchema: GetPrDiffInputJsonSchema,
|
|
108
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
86
109
|
},
|
|
87
110
|
{
|
|
88
111
|
name: "github.checkout_branch",
|
|
89
112
|
description: "Checkout a specific branch in the repository or worktree.",
|
|
90
113
|
inputSchema: CheckoutBranchInputJsonSchema,
|
|
114
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
91
115
|
},
|
|
92
116
|
{
|
|
93
117
|
name: "github.get_merge_conflict_files",
|
|
94
118
|
description: "Detect files that conflict when a PR is merged with the base branch.",
|
|
95
119
|
inputSchema: GetMergeConflictFilesInputJsonSchema,
|
|
120
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
96
121
|
},
|
|
97
122
|
{
|
|
98
123
|
name: "repo.get_changed_files",
|
|
99
124
|
description: "Get the list of changed files between two refs.",
|
|
100
125
|
inputSchema: GetChangedFilesInputJsonSchema,
|
|
126
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
101
127
|
},
|
|
102
128
|
{
|
|
103
129
|
name: "repo.get_command_schema",
|
|
104
|
-
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.",
|
|
105
131
|
inputSchema: GetCommandSchemaInputJsonSchema,
|
|
132
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
106
133
|
},
|
|
107
134
|
{
|
|
108
135
|
name: "repo.get_package_scripts",
|
|
109
136
|
description: "Get the scripts defined in package.json.",
|
|
110
137
|
inputSchema: GetPackageScriptsInputJsonSchema,
|
|
111
|
-
|
|
112
|
-
{
|
|
113
|
-
name: "repo.get_route_map",
|
|
114
|
-
description: "Get the mapping of routes to content files.",
|
|
115
|
-
inputSchema: GetRouteMapInputJsonSchema,
|
|
138
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
116
139
|
},
|
|
117
140
|
{
|
|
118
141
|
name: "repo.read_ci_logs",
|
|
119
142
|
description: "Read CI logs for a given pull request.",
|
|
120
143
|
inputSchema: ReadCiLogsInputJsonSchema,
|
|
144
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
121
145
|
},
|
|
122
146
|
{
|
|
123
147
|
name: "repo.logs",
|
|
124
148
|
description: "Stream or grep combined CI logs for all jobs in a pull request.",
|
|
125
149
|
inputSchema: RepoLogsInputJsonSchema,
|
|
150
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
126
151
|
},
|
|
127
152
|
{
|
|
128
153
|
name: "repo.create_branch",
|
|
129
154
|
description: "Creates a new clean git branch from a target base branch.",
|
|
130
155
|
inputSchema: CreateBranchInputJsonSchema,
|
|
156
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
131
157
|
},
|
|
132
158
|
{
|
|
133
159
|
name: "repo.create_repair_branch",
|
|
134
160
|
description: "Creates a fresh worktree and a new repair branch for an existing PR. This isolates the repair work from the main codebase.",
|
|
135
161
|
inputSchema: CreateRepairBranchInputJsonSchema,
|
|
162
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
136
163
|
},
|
|
137
164
|
{
|
|
138
165
|
name: "repo.run_tests",
|
|
139
166
|
description: "Run repository tests and checks.",
|
|
140
167
|
inputSchema: RunTestsInputJsonSchema,
|
|
141
|
-
|
|
142
|
-
{
|
|
143
|
-
name: "repo.run_lighthouse",
|
|
144
|
-
description: "Run Lighthouse CI audits.",
|
|
145
|
-
inputSchema: RunLighthouseInputJsonSchema,
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
name: "repo.run_playwright",
|
|
149
|
-
description: "Run Playwright E2E tests.",
|
|
150
|
-
inputSchema: RunPlaywrightInputJsonSchema,
|
|
168
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
151
169
|
},
|
|
152
170
|
{
|
|
153
171
|
name: "repo.commit_patch",
|
|
154
172
|
description: "Commit verified repair changes in a worktree.",
|
|
155
173
|
inputSchema: CommitPatchInputJsonSchema,
|
|
174
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
156
175
|
},
|
|
157
176
|
{
|
|
158
177
|
name: "github.open_replacement_pr",
|
|
159
178
|
description: "Open a new PR that replaces or repairs the original PR.",
|
|
160
179
|
inputSchema: OpenReplacementPrInputJsonSchema,
|
|
180
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
161
181
|
},
|
|
162
182
|
{
|
|
163
183
|
name: "github.create_pull_request",
|
|
164
184
|
description: "Creates a pull request on GitHub using the MCP server's integrated credentials. Bypasses terminal CLI constraints.",
|
|
165
185
|
inputSchema: CreatePullRequestInputJsonSchema,
|
|
186
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
166
187
|
},
|
|
167
188
|
{
|
|
168
189
|
name: "github.comment_triage_summary",
|
|
169
190
|
description: "Comment on the original PR with a diagnosis and replacement link.",
|
|
170
191
|
inputSchema: CommentTriageSummaryInputJsonSchema,
|
|
192
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
171
193
|
},
|
|
172
194
|
{
|
|
173
195
|
name: "github.get_pr",
|
|
174
196
|
description: "Get details of a GitHub PR including title, state, and URLs.",
|
|
175
197
|
inputSchema: GetPrInputJsonSchema,
|
|
198
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: "github.get_pr_reviews",
|
|
202
|
+
description: "Get top-level reviews, verdicts, states, and bodies for a GitHub PR.",
|
|
203
|
+
inputSchema: GetPrReviewsInputJsonSchema,
|
|
204
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: "github.get_pr_commits",
|
|
208
|
+
description: "List commits and commit metadata (SHAs, authors, messages) for a GitHub PR.",
|
|
209
|
+
inputSchema: GetPrCommitsInputJsonSchema,
|
|
210
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: "repo.get_workflow_runs",
|
|
214
|
+
description: "List recent GitHub Actions workflow runs for the repository.",
|
|
215
|
+
inputSchema: GetWorkflowRunsInputJsonSchema,
|
|
216
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
176
217
|
},
|
|
177
218
|
{
|
|
178
219
|
name: "github.issue_view",
|
|
179
220
|
description: "View details of a GitHub issue including title, body, and state.",
|
|
180
221
|
inputSchema: IssueViewInputJsonSchema,
|
|
222
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
181
223
|
},
|
|
182
224
|
{
|
|
183
225
|
name: "github.issue_update",
|
|
184
226
|
description: "Update a GitHub issue's body, labels, and/or state.",
|
|
185
227
|
inputSchema: IssueUpdateInputJsonSchema,
|
|
228
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
186
229
|
},
|
|
187
230
|
{
|
|
188
231
|
name: "github.issue_comment",
|
|
189
232
|
description: "Add a new comment to a GitHub issue.",
|
|
190
233
|
inputSchema: IssueCommentInputJsonSchema,
|
|
234
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
191
235
|
},
|
|
192
236
|
{
|
|
193
237
|
name: "github.create_issue",
|
|
194
238
|
description: "Create a new GitHub issue.",
|
|
195
239
|
inputSchema: CreateIssueInputJsonSchema,
|
|
240
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
196
241
|
},
|
|
197
242
|
{
|
|
198
243
|
name: "jules.create_session",
|
|
199
244
|
description: "Create a Jules session that performs work externally and may generate a GitHub pull request.",
|
|
200
245
|
inputSchema: CreateJulesSessionInputJsonSchema,
|
|
246
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
201
247
|
},
|
|
202
248
|
{
|
|
203
249
|
name: "jules.get_session",
|
|
204
250
|
description: "Get the status and details of a Jules session.",
|
|
205
251
|
inputSchema: JulesSessionIdInputJsonSchema,
|
|
252
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
206
253
|
},
|
|
207
254
|
{
|
|
208
255
|
name: "jules.send_message",
|
|
209
256
|
description: "Send a message to an active Jules session.",
|
|
210
257
|
inputSchema: JulesSendMessageInputJsonSchema,
|
|
258
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
211
259
|
},
|
|
212
260
|
{
|
|
213
261
|
name: "jules.get_messages",
|
|
214
262
|
description: "Get the message history of a Jules session.",
|
|
215
263
|
inputSchema: JulesSessionIdInputJsonSchema,
|
|
264
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
216
265
|
},
|
|
217
266
|
{
|
|
218
267
|
name: "jules.list_sessions",
|
|
219
268
|
description: "List all Jules sessions.",
|
|
220
269
|
inputSchema: JulesListSessionsInputJsonSchema,
|
|
270
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
221
271
|
},
|
|
222
272
|
{
|
|
223
273
|
name: "jules.cancel_session",
|
|
224
274
|
description: "Cancel an ongoing Jules session.",
|
|
225
275
|
inputSchema: JulesSessionIdInputJsonSchema,
|
|
276
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
226
277
|
},
|
|
227
278
|
{
|
|
228
279
|
name: "jules.get_pr",
|
|
229
280
|
description: "Get the generated pull request url associated with an active Jules agent session.",
|
|
230
281
|
inputSchema: JulesSessionIdInputJsonSchema,
|
|
282
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
231
283
|
},
|
|
232
284
|
{
|
|
233
285
|
name: "jules.trigger_feedback",
|
|
234
286
|
description: "Automatically collect CI status/logs for the PR associated with a Jules session and send them back as feedback.",
|
|
235
287
|
inputSchema: JulesSessionIdInputJsonSchema,
|
|
236
|
-
|
|
237
|
-
{
|
|
238
|
-
name: "agent.search_ddgs",
|
|
239
|
-
description: "Search the web using DuckDuckGo (via ddgs python library).",
|
|
240
|
-
inputSchema: SearchDdgsInputJsonSchema,
|
|
288
|
+
outputSchema: DEFAULT_OUTPUT_SCHEMA,
|
|
241
289
|
},
|
|
242
290
|
];
|
|
243
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
|
+
}
|