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