@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.
Files changed (156) hide show
  1. package/.mcp/schemas/boomtick.health.json +5 -0
  2. package/.mcp/schemas/boomtick_health.json +5 -0
  3. package/.mcp/schemas/github.checkout_branch.json +5 -0
  4. package/.mcp/schemas/github.comment_triage_summary.json +5 -0
  5. package/.mcp/schemas/github.create_issue.json +5 -0
  6. package/.mcp/schemas/github.create_pull_request.json +5 -0
  7. package/.mcp/schemas/github.get_merge_conflict_files.json +5 -0
  8. package/.mcp/schemas/github.get_pr.json +5 -0
  9. package/.mcp/schemas/github.get_pr_commits.json +5 -0
  10. package/.mcp/schemas/github.get_pr_diff.json +5 -0
  11. package/.mcp/schemas/github.get_pr_reviews.json +5 -0
  12. package/.mcp/schemas/github.issue_comment.json +5 -0
  13. package/.mcp/schemas/github.issue_update.json +5 -0
  14. package/.mcp/schemas/github.issue_view.json +5 -0
  15. package/.mcp/schemas/github.open_replacement_pr.json +5 -0
  16. package/.mcp/schemas/github.search_open_prs.json +5 -0
  17. package/.mcp/schemas/github_checkout_branch.json +5 -0
  18. package/.mcp/schemas/github_comment_triage_summary.json +5 -0
  19. package/.mcp/schemas/github_create_issue.json +5 -0
  20. package/.mcp/schemas/github_create_pull_request.json +5 -0
  21. package/.mcp/schemas/github_get_merge_conflict_files.json +5 -0
  22. package/.mcp/schemas/github_get_pr.json +5 -0
  23. package/.mcp/schemas/github_get_pr_commits.json +5 -0
  24. package/.mcp/schemas/github_get_pr_diff.json +5 -0
  25. package/.mcp/schemas/github_get_pr_reviews.json +5 -0
  26. package/.mcp/schemas/github_issue_comment.json +5 -0
  27. package/.mcp/schemas/github_issue_update.json +5 -0
  28. package/.mcp/schemas/github_issue_view.json +5 -0
  29. package/.mcp/schemas/github_open_replacement_pr.json +5 -0
  30. package/.mcp/schemas/github_search_open_prs.json +5 -0
  31. package/.mcp/schemas/jules.cancel_session.json +5 -0
  32. package/.mcp/schemas/jules.create_session.json +5 -0
  33. package/.mcp/schemas/jules.get_messages.json +5 -0
  34. package/.mcp/schemas/jules.get_pr.json +5 -0
  35. package/.mcp/schemas/jules.get_session.json +5 -0
  36. package/.mcp/schemas/jules.list_sessions.json +5 -0
  37. package/.mcp/schemas/jules.send_message.json +5 -0
  38. package/.mcp/schemas/jules.trigger_feedback.json +5 -0
  39. package/.mcp/schemas/jules_cancel_session.json +5 -0
  40. package/.mcp/schemas/jules_create_session.json +5 -0
  41. package/.mcp/schemas/jules_get_messages.json +5 -0
  42. package/.mcp/schemas/jules_get_pr.json +5 -0
  43. package/.mcp/schemas/jules_get_session.json +5 -0
  44. package/.mcp/schemas/jules_list_sessions.json +5 -0
  45. package/.mcp/schemas/jules_send_message.json +5 -0
  46. package/.mcp/schemas/jules_trigger_feedback.json +5 -0
  47. package/.mcp/schemas/repo.commit_patch.json +5 -0
  48. package/.mcp/schemas/repo.create_branch.json +5 -0
  49. package/.mcp/schemas/repo.create_repair_branch.json +5 -0
  50. package/.mcp/schemas/repo.get_changed_files.json +5 -0
  51. package/.mcp/schemas/repo.get_command_schema.json +6 -1
  52. package/.mcp/schemas/repo.get_package_scripts.json +5 -0
  53. package/.mcp/schemas/repo.get_workflow_runs.json +5 -0
  54. package/.mcp/schemas/repo.logs.json +5 -0
  55. package/.mcp/schemas/repo.read_agent_context.json +5 -0
  56. package/.mcp/schemas/repo.read_ci_logs.json +5 -0
  57. package/.mcp/schemas/repo.run_tests.json +5 -0
  58. package/.mcp/schemas/repo_commit_patch.json +5 -0
  59. package/.mcp/schemas/repo_create_branch.json +5 -0
  60. package/.mcp/schemas/repo_create_repair_branch.json +5 -0
  61. package/.mcp/schemas/repo_get_changed_files.json +5 -0
  62. package/.mcp/schemas/repo_get_command_schema.json +6 -1
  63. package/.mcp/schemas/repo_get_package_scripts.json +5 -0
  64. package/.mcp/schemas/repo_get_workflow_runs.json +5 -0
  65. package/.mcp/schemas/repo_logs.json +5 -0
  66. package/.mcp/schemas/repo_read_agent_context.json +5 -0
  67. package/.mcp/schemas/repo_read_ci_logs.json +5 -0
  68. package/.mcp/schemas/repo_run_tests.json +5 -0
  69. package/CHANGELOG.md +7 -0
  70. package/actions/ai-review/action.yml +2 -2
  71. package/actions/audit/action.yml +1 -1
  72. package/actions/ci-validate/action.yml +2 -2
  73. package/actions/impact-analysis/action.yml +3 -3
  74. package/actions/run-project-gate/action.yml +5 -5
  75. package/actions/setup/action.yml +1 -1
  76. package/actions/test-build/action.yml +1 -1
  77. package/dist/config.js +1 -1
  78. package/dist/config.test.js +3 -3
  79. package/dist/lib/result.js +4 -0
  80. package/dist/lib/result.test.js +4 -2
  81. package/dist/lib/shell.js +6 -6
  82. package/dist/mcp/definitions.js +73 -2
  83. package/dist/mcp/http.js +91 -0
  84. package/dist/mcp/http.test.js +58 -0
  85. package/dist/mcp/server.js +53 -13
  86. package/dist/mcp/server.test.js +20 -0
  87. package/dist/tools/contract.js +109 -1
  88. package/dist/tools/github.checkout_branch.js +1 -1
  89. package/dist/tools/github.comment_triage_summary.js +1 -1
  90. package/dist/tools/github.create_issue.js +1 -1
  91. package/dist/tools/github.create_issue.test.js +6 -6
  92. package/dist/tools/github.create_pull_request.js +1 -1
  93. package/dist/tools/github.create_pull_request.test.js +4 -4
  94. package/dist/tools/github.get_pr.js +1 -1
  95. package/dist/tools/github.get_pr_commits.js +1 -1
  96. package/dist/tools/github.get_pr_diff.js +1 -1
  97. package/dist/tools/github.get_pr_reviews.js +1 -1
  98. package/dist/tools/github.get_pr_reviews.test.js +9 -9
  99. package/dist/tools/github.issue_comment.js +1 -1
  100. package/dist/tools/github.issue_update.js +1 -1
  101. package/dist/tools/github.issue_view.js +1 -1
  102. package/dist/tools/github.search_open_prs.js +1 -1
  103. package/dist/tools/jules/cancel-session.js +1 -1
  104. package/dist/tools/jules/create-session.test.js +11 -11
  105. package/dist/tools/jules/get-messages.js +1 -1
  106. package/dist/tools/jules/get-messages.test.js +3 -3
  107. package/dist/tools/jules/get-pr.test.js +3 -3
  108. package/dist/tools/jules/list-sessions.js +1 -1
  109. package/dist/tools/jules/send-message.js +1 -1
  110. package/dist/tools/jules/send-message.test.js +5 -5
  111. package/dist/tools/jules/shared.js +4 -4
  112. package/dist/tools/jules/trigger-feedback.js +1 -1
  113. package/dist/tools/jules/trigger-feedback.test.js +5 -5
  114. package/dist/tools/repo.get_command_schema.js +2 -2
  115. package/dist/tools/repo.get_workflow_runs.js +1 -1
  116. package/package.json +1 -1
  117. package/scripts/sync-mcp-schemas.ts +2 -0
  118. package/src/config.test.ts +3 -3
  119. package/src/config.ts +1 -1
  120. package/src/lib/result.test.ts +4 -2
  121. package/src/lib/result.ts +6 -0
  122. package/src/lib/shell.ts +6 -6
  123. package/src/mcp/definitions.ts +76 -3
  124. package/src/mcp/http.test.ts +72 -0
  125. package/src/mcp/http.ts +118 -0
  126. package/src/mcp/server.test.ts +24 -0
  127. package/src/mcp/server.ts +63 -12
  128. package/src/tools/contract.ts +109 -1
  129. package/src/tools/github.checkout_branch.ts +1 -1
  130. package/src/tools/github.comment_triage_summary.ts +1 -1
  131. package/src/tools/github.create_issue.test.ts +6 -6
  132. package/src/tools/github.create_issue.ts +1 -1
  133. package/src/tools/github.create_pull_request.test.ts +4 -4
  134. package/src/tools/github.create_pull_request.ts +1 -1
  135. package/src/tools/github.get_pr.ts +1 -1
  136. package/src/tools/github.get_pr_commits.ts +1 -1
  137. package/src/tools/github.get_pr_diff.ts +1 -1
  138. package/src/tools/github.get_pr_reviews.test.ts +9 -9
  139. package/src/tools/github.get_pr_reviews.ts +1 -1
  140. package/src/tools/github.issue_comment.ts +1 -1
  141. package/src/tools/github.issue_update.ts +1 -1
  142. package/src/tools/github.issue_view.ts +1 -1
  143. package/src/tools/github.search_open_prs.ts +1 -1
  144. package/src/tools/jules/cancel-session.ts +1 -1
  145. package/src/tools/jules/create-session.test.ts +11 -11
  146. package/src/tools/jules/get-messages.test.ts +3 -3
  147. package/src/tools/jules/get-messages.ts +1 -1
  148. package/src/tools/jules/get-pr.test.ts +3 -3
  149. package/src/tools/jules/list-sessions.ts +1 -1
  150. package/src/tools/jules/send-message.test.ts +5 -5
  151. package/src/tools/jules/send-message.ts +1 -1
  152. package/src/tools/jules/shared.ts +4 -4
  153. package/src/tools/jules/trigger-feedback.test.ts +5 -5
  154. package/src/tools/jules/trigger-feedback.ts +1 -1
  155. package/src/tools/repo.get_command_schema.ts +2 -2
  156. 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-cli", args);
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-cli returned error: ${output.message ?? "Unknown error"}`);
15
+ throw new Error(`td returned error: ${output.message ?? "Unknown error"}`);
16
16
  }
17
17
  if (result.exitCode !== 0) {
18
- throw new Error(`td-cli command failed (${args.join(" ")}): ${result.stderr || stdout}`);
18
+ throw new Error(`td command failed (${args.join(" ")}): ${result.stderr || stdout}`);
19
19
  }
20
20
  if (!output) {
21
- throw new Error(`td-cli returned non-JSON output with exit code 0: ${stdout}`);
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-cli", ["agent", "trigger-feedback", input.sessionId]);
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-cli", async () => {
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-cli agent trigger-feedback"
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-cli", [
27
+ expect(shell.runCommand).toHaveBeenCalledWith("td", [
28
28
  "agent", "trigger-feedback", "123"
29
29
  ]);
30
30
  });
31
- it("should throw error if td-cli returns error", async () => {
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-cli agent trigger-feedback"
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-cli", ["schema", sanitizedPath]);
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-cli schema failed for", sanitizedPath, ":", result.stderr);
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-cli", cliArgs);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arii/boomtick-mcp",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Boomtick MCP Guide: PR Rescue + Merge Conflict Agent",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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`;
@@ -20,14 +20,14 @@ describe("config", () => {
20
20
  });
21
21
 
22
22
  describe("initializeConfig", () => {
23
- it("should load dynamic config from td-cli config view using active cwd", async () => {
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-cli config view", expect.objectContaining({
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-cli failed");
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-cli config view`;
44
+ const cmd = `td config view`;
45
45
  const output = execSync(cmd, {
46
46
  cwd,
47
47
  encoding: "utf-8",
@@ -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
- "td-cli": "td-cli",
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-cli' if specified
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-cli") {
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-cli");
49
- const localVenvBin = path.join(process.cwd(), ".venv", "bin", "td-cli");
50
- const parentVenvBin = path.join(config.repoPath, "..", ".venv", "bin", "td-cli");
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.
@@ -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
- uri: "repo://repair-report/{branch}",
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-cli subcommand. Use this instead of reading the entire cli-schema.json to save tokens.",
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
+ });