claude-mcp-bridge 0.1.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 (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +312 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +256 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/tools/ping.d.ts +18 -0
  8. package/dist/tools/ping.d.ts.map +1 -0
  9. package/dist/tools/ping.js +78 -0
  10. package/dist/tools/ping.js.map +1 -0
  11. package/dist/tools/query.d.ts +27 -0
  12. package/dist/tools/query.d.ts.map +1 -0
  13. package/dist/tools/query.js +138 -0
  14. package/dist/tools/query.js.map +1 -0
  15. package/dist/tools/review.d.ts +31 -0
  16. package/dist/tools/review.d.ts.map +1 -0
  17. package/dist/tools/review.js +204 -0
  18. package/dist/tools/review.js.map +1 -0
  19. package/dist/tools/search.d.ts +23 -0
  20. package/dist/tools/search.d.ts.map +1 -0
  21. package/dist/tools/search.js +50 -0
  22. package/dist/tools/search.js.map +1 -0
  23. package/dist/tools/structured.d.ts +27 -0
  24. package/dist/tools/structured.d.ts.map +1 -0
  25. package/dist/tools/structured.js +88 -0
  26. package/dist/tools/structured.js.map +1 -0
  27. package/dist/utils/env.d.ts +3 -0
  28. package/dist/utils/env.d.ts.map +1 -0
  29. package/dist/utils/env.js +30 -0
  30. package/dist/utils/env.js.map +1 -0
  31. package/dist/utils/errors.d.ts +5 -0
  32. package/dist/utils/errors.d.ts.map +1 -0
  33. package/dist/utils/errors.js +58 -0
  34. package/dist/utils/errors.js.map +1 -0
  35. package/dist/utils/files.d.ts +22 -0
  36. package/dist/utils/files.d.ts.map +1 -0
  37. package/dist/utils/files.js +60 -0
  38. package/dist/utils/files.js.map +1 -0
  39. package/dist/utils/git.d.ts +14 -0
  40. package/dist/utils/git.d.ts.map +1 -0
  41. package/dist/utils/git.js +57 -0
  42. package/dist/utils/git.js.map +1 -0
  43. package/dist/utils/model.d.ts +7 -0
  44. package/dist/utils/model.d.ts.map +1 -0
  45. package/dist/utils/model.js +45 -0
  46. package/dist/utils/model.js.map +1 -0
  47. package/dist/utils/parse.d.ts +28 -0
  48. package/dist/utils/parse.d.ts.map +1 -0
  49. package/dist/utils/parse.js +141 -0
  50. package/dist/utils/parse.js.map +1 -0
  51. package/dist/utils/prompts.d.ts +19 -0
  52. package/dist/utils/prompts.d.ts.map +1 -0
  53. package/dist/utils/prompts.js +37 -0
  54. package/dist/utils/prompts.js.map +1 -0
  55. package/dist/utils/retry.d.ts +9 -0
  56. package/dist/utils/retry.d.ts.map +1 -0
  57. package/dist/utils/retry.js +29 -0
  58. package/dist/utils/retry.js.map +1 -0
  59. package/dist/utils/security.d.ts +18 -0
  60. package/dist/utils/security.d.ts.map +1 -0
  61. package/dist/utils/security.js +37 -0
  62. package/dist/utils/security.js.map +1 -0
  63. package/dist/utils/spawn.d.ts +30 -0
  64. package/dist/utils/spawn.d.ts.map +1 -0
  65. package/dist/utils/spawn.js +170 -0
  66. package/dist/utils/spawn.js.map +1 -0
  67. package/package.json +61 -0
  68. package/prompts/review-agentic.md +23 -0
  69. package/prompts/review-quick.md +25 -0
  70. package/prompts/search.md +13 -0
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Load a prompt template from prompts/ and replace placeholders.
3
+ * Placeholders use the format {{KEY}}. Only keys present in `vars`
4
+ * are replaced; unknown placeholders pass through unchanged.
5
+ */
6
+ export declare function loadPrompt(filename: string, vars: Record<string, string>): string;
7
+ /**
8
+ * Build a length limit instruction string from a word count.
9
+ * Returns empty string when no limit is set, suitable for use
10
+ * as a {{LENGTH_LIMIT}} template variable.
11
+ */
12
+ export declare function buildLengthLimit(maxWords?: number): string;
13
+ /**
14
+ * Append a length limit instruction to a prompt string.
15
+ * No-op when maxWords is not set. Use this for tools that don't
16
+ * have a prompt template with a {{LENGTH_LIMIT}} placeholder.
17
+ */
18
+ export declare function appendLengthLimit(prompt: string, maxWords?: number): string;
19
+ //# sourceMappingURL=prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/utils/prompts.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAMjF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAG3E"}
@@ -0,0 +1,37 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ import { basename, dirname, resolve } from "node:path";
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ const PROMPTS_DIR = resolve(__dirname, "../../prompts");
6
+ /**
7
+ * Load a prompt template from prompts/ and replace placeholders.
8
+ * Placeholders use the format {{KEY}}. Only keys present in `vars`
9
+ * are replaced; unknown placeholders pass through unchanged.
10
+ */
11
+ export function loadPrompt(filename, vars) {
12
+ let result = readFileSync(resolve(PROMPTS_DIR, basename(filename)), "utf8");
13
+ for (const [key, value] of Object.entries(vars)) {
14
+ result = result.replaceAll(`{{${key}}}`, value);
15
+ }
16
+ return result;
17
+ }
18
+ /**
19
+ * Build a length limit instruction string from a word count.
20
+ * Returns empty string when no limit is set, suitable for use
21
+ * as a {{LENGTH_LIMIT}} template variable.
22
+ */
23
+ export function buildLengthLimit(maxWords) {
24
+ if (!maxWords || maxWords <= 0)
25
+ return "";
26
+ return `Keep your response under ${maxWords} words.`;
27
+ }
28
+ /**
29
+ * Append a length limit instruction to a prompt string.
30
+ * No-op when maxWords is not set. Use this for tools that don't
31
+ * have a prompt template with a {{LENGTH_LIMIT}} placeholder.
32
+ */
33
+ export function appendLengthLimit(prompt, maxWords) {
34
+ const limit = buildLengthLimit(maxWords);
35
+ return limit ? `${prompt}\n\n${limit}` : prompt;
36
+ }
37
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/utils/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEvD,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,WAAW,GAAG,OAAO,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;AAExD;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,QAAgB,EAAE,IAA4B;IACvE,IAAI,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5E,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAiB;IAChD,IAAI,CAAC,QAAQ,IAAI,QAAQ,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,OAAO,4BAA4B,QAAQ,SAAS,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc,EAAE,QAAiB;IACjE,MAAM,KAAK,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACzC,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;AAClD,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { SpawnResult } from "./spawn.js";
2
+ export declare const HARD_TIMEOUT_CAP = 600000;
3
+ export interface FallbackResult {
4
+ result: SpawnResult;
5
+ fallbackUsed: boolean;
6
+ fallbackModel?: string;
7
+ }
8
+ export declare function withModelFallback(model: string | undefined, spawnFn: (model: string | undefined, timeout: number) => Promise<SpawnResult>, totalTimeout: number): Promise<FallbackResult>;
9
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAK9C,eAAO,MAAM,gBAAgB,SAAU,CAAC;AAExC,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,WAAW,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,EAC7E,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,cAAc,CAAC,CA4BzB"}
@@ -0,0 +1,29 @@
1
+ import { isRetryableError } from "./errors.js";
2
+ import { getFallbackModel } from "./model.js";
3
+ const MIN_RETRY_BUDGET = 10_000;
4
+ export const HARD_TIMEOUT_CAP = 600_000;
5
+ export async function withModelFallback(model, spawnFn, totalTimeout) {
6
+ const capped = Math.min(totalTimeout, HARD_TIMEOUT_CAP);
7
+ const deadline = Date.now() + capped;
8
+ const result = await spawnFn(model, capped);
9
+ if (result.timedOut) {
10
+ return { result, fallbackUsed: false };
11
+ }
12
+ if (!isRetryableError(result.exitCode, result.stdout, result.stderr)) {
13
+ return { result, fallbackUsed: false };
14
+ }
15
+ const fallback = getFallbackModel();
16
+ if (!fallback || fallback === model) {
17
+ return { result, fallbackUsed: false };
18
+ }
19
+ const remaining = deadline - Date.now();
20
+ if (remaining < MIN_RETRY_BUDGET) {
21
+ return { result, fallbackUsed: false };
22
+ }
23
+ return {
24
+ result: await spawnFn(fallback, remaining),
25
+ fallbackUsed: true,
26
+ fallbackModel: fallback,
27
+ };
28
+ }
29
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAChC,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC;AAQxC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAyB,EACzB,OAA6E,EAC7E,YAAoB;IAEpB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;IAErC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC5C,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;IACzC,CAAC;IAED,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QACrE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;IACzC,CAAC;IAED,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;IACpC,IAAI,CAAC,QAAQ,IAAI,QAAQ,KAAK,KAAK,EAAE,CAAC;QACpC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;IACzC,CAAC;IAED,MAAM,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACxC,IAAI,SAAS,GAAG,gBAAgB,EAAE,CAAC;QACjC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;IACzC,CAAC;IAED,OAAO;QACL,MAAM,EAAE,MAAM,OAAO,CAAC,QAAQ,EAAE,SAAS,CAAC;QAC1C,YAAY,EAAE,IAAI;QAClB,aAAa,EAAE,QAAQ;KACxB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,18 @@
1
+ /** Maximum file size in bytes (1MB). */
2
+ export declare const MAX_FILE_SIZE = 1000000;
3
+ /** Maximum number of files that can be attached to a query. */
4
+ export declare const MAX_FILES = 20;
5
+ /**
6
+ * Resolve a path and verify it's within the allowed root directory.
7
+ * Prevents path traversal attacks via symlinks, `..`, etc.
8
+ */
9
+ export declare function resolveAndVerify(filePath: string, rootDir: string): Promise<string>;
10
+ /**
11
+ * Verify a directory exists and is actually a directory.
12
+ */
13
+ export declare function verifyDirectory(dir: string): Promise<string>;
14
+ /**
15
+ * Check if a file is within size limits.
16
+ */
17
+ export declare function checkFileSize(filePath: string): Promise<number>;
18
+ //# sourceMappingURL=security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/utils/security.ts"],"names":[],"mappings":"AAGA,wCAAwC;AACxC,eAAO,MAAM,aAAa,UAAY,CAAC;AAEvC,+DAA+D;AAC/D,eAAO,MAAM,SAAS,KAAK,CAAC;AAE5B;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOlE;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGrE"}
@@ -0,0 +1,37 @@
1
+ import { realpath, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ /** Maximum file size in bytes (1MB). */
4
+ export const MAX_FILE_SIZE = 1_000_000;
5
+ /** Maximum number of files that can be attached to a query. */
6
+ export const MAX_FILES = 20;
7
+ /**
8
+ * Resolve a path and verify it's within the allowed root directory.
9
+ * Prevents path traversal attacks via symlinks, `..`, etc.
10
+ */
11
+ export async function resolveAndVerify(filePath, rootDir) {
12
+ const resolved = await realpath(path.resolve(rootDir, filePath));
13
+ const resolvedRoot = await realpath(rootDir);
14
+ if (!resolved.startsWith(resolvedRoot + path.sep) && resolved !== resolvedRoot) {
15
+ throw new Error(`Path traversal blocked: "${filePath}" resolves outside allowed root "${rootDir}"`);
16
+ }
17
+ return resolved;
18
+ }
19
+ /**
20
+ * Verify a directory exists and is actually a directory.
21
+ */
22
+ export async function verifyDirectory(dir) {
23
+ const resolved = await realpath(dir);
24
+ const s = await stat(resolved);
25
+ if (!s.isDirectory()) {
26
+ throw new Error(`Not a directory: ${dir}`);
27
+ }
28
+ return resolved;
29
+ }
30
+ /**
31
+ * Check if a file is within size limits.
32
+ */
33
+ export async function checkFileSize(filePath) {
34
+ const s = await stat(filePath);
35
+ return s.size;
36
+ }
37
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/utils/security.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,wCAAwC;AACxC,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAC;AAEvC,+DAA+D;AAC/D,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAE5B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAAgB,EAChB,OAAe;IAEf,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IACjE,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;IAE7C,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,YAAY,EAAE,CAAC;QAC/E,MAAM,IAAI,KAAK,CACb,4BAA4B,QAAQ,oCAAoC,OAAO,GAAG,CACnF,CAAC;IACJ,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW;IAC/C,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/B,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,oBAAoB,GAAG,EAAE,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB;IAClD,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,CAAC,IAAI,CAAC;AAChB,CAAC"}
@@ -0,0 +1,30 @@
1
+ /** Hard maximum timeout — no request can exceed this. */
2
+ export declare const HARD_TIMEOUT_CAP = 600000;
3
+ export interface SpawnOptions {
4
+ args: string[];
5
+ cwd: string;
6
+ stdin?: string;
7
+ timeout?: number;
8
+ }
9
+ export interface SpawnResult {
10
+ stdout: string;
11
+ stderr: string;
12
+ exitCode: number | null;
13
+ timedOut: boolean;
14
+ }
15
+ export declare function findClaudeBinary(): string;
16
+ export declare function spawnClaude(options: SpawnOptions): Promise<SpawnResult>;
17
+ export declare function resetConcurrency(): void;
18
+ export interface ClaudeArgsOptions {
19
+ model?: string;
20
+ fallbackModel?: string;
21
+ maxBudgetUsd?: number;
22
+ effort?: string;
23
+ sessionId?: string;
24
+ noSessionPersistence?: boolean;
25
+ allowedTools?: string[];
26
+ jsonSchema?: string;
27
+ prompt?: string;
28
+ }
29
+ export declare function buildClaudeArgs(options: ClaudeArgsOptions): string[];
30
+ //# sourceMappingURL=spawn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../../src/utils/spawn.ts"],"names":[],"mappings":"AAGA,yDAAyD;AACzD,eAAO,MAAM,gBAAgB,SAAU,CAAC;AAKxC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;CACnB;AA6CD,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAS7E;AAgGD,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,EAAE,CAcpE"}
@@ -0,0 +1,170 @@
1
+ import { spawn } from "node:child_process";
2
+ import { buildSubprocessEnv } from "./env.js";
3
+ /** Hard maximum timeout — no request can exceed this. */
4
+ export const HARD_TIMEOUT_CAP = 600_000;
5
+ const DEFAULT_MAX_CONCURRENT = 3;
6
+ const QUEUE_TIMEOUT = 30_000;
7
+ let activeCount = 0;
8
+ const parsedConcurrent = parseInt(process.env["CLAUDE_MAX_CONCURRENT"] ?? String(DEFAULT_MAX_CONCURRENT), 10);
9
+ const maxConcurrent = Number.isNaN(parsedConcurrent) || parsedConcurrent <= 0
10
+ ? DEFAULT_MAX_CONCURRENT
11
+ : parsedConcurrent;
12
+ const waitQueue = [];
13
+ function acquireSlot() {
14
+ if (activeCount < maxConcurrent) {
15
+ activeCount++;
16
+ return Promise.resolve();
17
+ }
18
+ return new Promise((resolve, reject) => {
19
+ const timer = setTimeout(() => {
20
+ const idx = waitQueue.findIndex((w) => w.resolve === resolve);
21
+ if (idx !== -1)
22
+ waitQueue.splice(idx, 1);
23
+ reject(new Error(`Concurrency queue timeout after ${QUEUE_TIMEOUT}ms — ${activeCount} processes active`));
24
+ }, QUEUE_TIMEOUT);
25
+ waitQueue.push({
26
+ resolve: () => {
27
+ clearTimeout(timer);
28
+ activeCount++;
29
+ resolve();
30
+ },
31
+ reject,
32
+ });
33
+ });
34
+ }
35
+ function releaseSlot() {
36
+ activeCount--;
37
+ const next = waitQueue.shift();
38
+ if (next)
39
+ next.resolve();
40
+ }
41
+ export function findClaudeBinary() {
42
+ return process.env["CLAUDE_CLI_PATH"] ?? "claude";
43
+ }
44
+ export async function spawnClaude(options) {
45
+ const timeout = Math.min(options.timeout ?? 60_000, HARD_TIMEOUT_CAP);
46
+ await acquireSlot();
47
+ try {
48
+ return await doSpawn(options, timeout);
49
+ }
50
+ finally {
51
+ releaseSlot();
52
+ }
53
+ }
54
+ async function doSpawn(options, timeout) {
55
+ const binary = findClaudeBinary();
56
+ const env = buildSubprocessEnv();
57
+ return new Promise((resolve, reject) => {
58
+ let child;
59
+ const detached = process.platform !== "win32";
60
+ try {
61
+ child = spawn(binary, options.args, {
62
+ cwd: options.cwd,
63
+ env,
64
+ shell: false,
65
+ stdio: ["pipe", "pipe", "pipe"],
66
+ detached,
67
+ });
68
+ }
69
+ catch (e) {
70
+ reject(new Error(`Failed to spawn Claude CLI: ${e}`));
71
+ return;
72
+ }
73
+ let stdout = "";
74
+ let stderr = "";
75
+ let timedOut = false;
76
+ let settled = false;
77
+ let killTimer;
78
+ const timer = setTimeout(() => {
79
+ timedOut = true;
80
+ killTimer = killProcessGroup(child);
81
+ }, timeout);
82
+ child.stdout?.on("data", (chunk) => {
83
+ stdout += chunk.toString();
84
+ });
85
+ child.stderr?.on("data", (chunk) => {
86
+ stderr += chunk.toString();
87
+ });
88
+ child.on("error", (err) => {
89
+ clearTimeout(timer);
90
+ if (killTimer)
91
+ clearTimeout(killTimer);
92
+ if (!settled) {
93
+ settled = true;
94
+ if (err.code === "ENOENT") {
95
+ reject(new Error("claude CLI not found. Install Claude Code and ensure `claude` is on PATH."));
96
+ }
97
+ else {
98
+ reject(new Error(`Failed to run Claude CLI: ${err.message}`));
99
+ }
100
+ }
101
+ });
102
+ child.on("close", (code) => {
103
+ clearTimeout(timer);
104
+ if (killTimer)
105
+ clearTimeout(killTimer);
106
+ if (!settled) {
107
+ settled = true;
108
+ resolve({ stdout, stderr, exitCode: code, timedOut });
109
+ }
110
+ });
111
+ if (options.stdin) {
112
+ child.stdin?.write(options.stdin);
113
+ }
114
+ child.stdin?.end();
115
+ });
116
+ }
117
+ function killProcessGroup(child) {
118
+ const pid = child.pid;
119
+ if (!pid)
120
+ return undefined;
121
+ const useGroupKill = process.platform !== "win32";
122
+ const kill = (signal) => {
123
+ try {
124
+ if (useGroupKill) {
125
+ process.kill(-pid, signal);
126
+ }
127
+ else {
128
+ child.kill(signal);
129
+ }
130
+ }
131
+ catch {
132
+ try {
133
+ child.kill(signal);
134
+ }
135
+ catch {
136
+ // Already dead.
137
+ }
138
+ }
139
+ };
140
+ kill("SIGTERM");
141
+ return setTimeout(() => kill("SIGKILL"), 5000);
142
+ }
143
+ export function resetConcurrency() {
144
+ activeCount = 0;
145
+ waitQueue.length = 0;
146
+ }
147
+ export function buildClaudeArgs(options) {
148
+ const args = ["-p", "--bare", "--disable-slash-commands", "--output-format", "json"];
149
+ if (options.model)
150
+ args.push("--model", options.model);
151
+ if (options.fallbackModel)
152
+ args.push("--fallback-model", options.fallbackModel);
153
+ if (options.maxBudgetUsd && options.maxBudgetUsd > 0)
154
+ args.push("--max-budget-usd", String(options.maxBudgetUsd));
155
+ if (options.effort)
156
+ args.push("--effort", options.effort);
157
+ if (options.sessionId)
158
+ args.push("--resume", options.sessionId);
159
+ if (options.noSessionPersistence)
160
+ args.push("--no-session-persistence");
161
+ if (options.allowedTools && options.allowedTools.length > 0) {
162
+ args.push("--allowed-tools", options.allowedTools.join(" "));
163
+ }
164
+ if (options.jsonSchema)
165
+ args.push("--json-schema", options.jsonSchema);
166
+ if (options.prompt)
167
+ args.push(options.prompt);
168
+ return args;
169
+ }
170
+ //# sourceMappingURL=spawn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn.js","sourceRoot":"","sources":["../../src/utils/spawn.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "claude-mcp-bridge",
3
+ "version": "0.1.0",
4
+ "description": "MCP server that wraps Claude Code CLI for query, structured output, and health checks",
5
+ "author": "Tim Birrell",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "bin": {
11
+ "claude-mcp-bridge": "dist/index.js"
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "prompts"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "dev": "tsc --watch",
20
+ "start": "node dist/index.js",
21
+ "lint": "eslint src/ tests/",
22
+ "typecheck": "tsc --noEmit",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest",
25
+ "test:integration": "CLAUDE_INTEGRATION=1 vitest run tests/integration/",
26
+ "smoke": "node scripts/smoke-test.mjs",
27
+ "prepublishOnly": "npm run build"
28
+ },
29
+ "keywords": [
30
+ "mcp",
31
+ "claude",
32
+ "claude-code",
33
+ "anthropic",
34
+ "code-review",
35
+ "ai-tools",
36
+ "model-context-protocol"
37
+ ],
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/hampsterx/claude-mcp-bridge.git"
41
+ },
42
+ "bugs": {
43
+ "url": "https://github.com/hampsterx/claude-mcp-bridge/issues"
44
+ },
45
+ "homepage": "https://github.com/hampsterx/claude-mcp-bridge#readme",
46
+ "engines": {
47
+ "node": ">=18"
48
+ },
49
+ "dependencies": {
50
+ "@modelcontextprotocol/sdk": "^1.12.1",
51
+ "strip-ansi": "^7.1.0",
52
+ "zod": "^3.24.0"
53
+ },
54
+ "devDependencies": {
55
+ "@types/node": "^22.0.0",
56
+ "eslint": "^9.0.0",
57
+ "typescript": "^5.7.0",
58
+ "typescript-eslint": "^8.0.0",
59
+ "vitest": "^3.0.0"
60
+ }
61
+ }
@@ -0,0 +1,23 @@
1
+ You are an expert code reviewer operating inside a git repository.
2
+
3
+ Review the changes by using this diff command as your starting point:
4
+
5
+ {{DIFF_SPEC}}
6
+
7
+ Instructions:
8
+ 1. Run the diff command.
9
+ 2. Read the changed files and any directly relevant surrounding code.
10
+ 3. Focus on real bugs, regressions, security issues, incorrect assumptions, and missing tests.
11
+ 4. Prefer findings over summaries.
12
+ 5. If there are no meaningful issues, say so briefly and mention any residual risk.
13
+
14
+ For each issue found, provide:
15
+ - **Severity**: critical / warning / suggestion
16
+ - **File**: the file path
17
+ - **Line**: approximate line number
18
+ - **Issue**: clear description
19
+ - **Suggestion**: how to fix it
20
+
21
+ {{FOCUS_SECTION}}
22
+
23
+ {{LENGTH_LIMIT}}
@@ -0,0 +1,25 @@
1
+ You are an expert code reviewer. Review the following git diff carefully.
2
+
3
+ For each issue found, provide:
4
+ - **Severity**: critical / warning / suggestion
5
+ - **File**: the file path
6
+ - **Line**: approximate line number from the diff
7
+ - **Issue**: clear description
8
+ - **Suggestion**: how to fix it
9
+
10
+ Focus on:
11
+ - Bugs and logic errors
12
+ - Security vulnerabilities
13
+ - Performance issues
14
+ - Missing error handling
15
+ - Significant maintainability risks
16
+
17
+ {{FOCUS_SECTION}}
18
+
19
+ Keep the review concise. Findings first. If the diff looks good, say so briefly. Do not invent issues.
20
+
21
+ {{LENGTH_LIMIT}}
22
+
23
+ ---
24
+
25
+ {{DIFF}}
@@ -0,0 +1,13 @@
1
+ Search the web for the following query, then synthesize a clear answer with source URLs.
2
+
3
+ ## Query
4
+
5
+ {{QUERY}}
6
+
7
+ ## Instructions
8
+
9
+ 1. Search for relevant, current information.
10
+ 2. Synthesize findings into a direct answer.
11
+ 3. Include source URLs for key claims.
12
+ 4. If the search results are insufficient, say so rather than guessing.
13
+ 5. {{LENGTH_LIMIT}}