zero-config-cli-bridge 1.0.1 → 1.2.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.
@@ -3,5 +3,9 @@ export interface ExecuteResult {
3
3
  stderr: string;
4
4
  exitCode: number;
5
5
  }
6
- export declare function executeCommand(command: string): Promise<ExecuteResult>;
6
+ /**
7
+ * Executes a binary directly with an args array.
8
+ * NO shell intermediary — shell injection is structurally impossible.
9
+ */
10
+ export declare function executeCommand(bin: string, args: string[]): Promise<ExecuteResult>;
7
11
  //# sourceMappingURL=executor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAWD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAqCtE"}
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAcD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC,CAgClF"}
package/dist/executor.js CHANGED
@@ -1,35 +1,38 @@
1
1
  import { spawn } from 'child_process';
2
- const MAX_OUTPUT = 2000;
3
- const TRUNCATION_MSG = '\n...[Output truncated. Use grep/jq to filter]';
4
- const TIMEOUT_MS = 3000;
5
- function truncate(output) {
6
- if (output.length <= MAX_OUTPUT)
7
- return output;
8
- return output.slice(0, MAX_OUTPUT) + TRUNCATION_MSG;
2
+ // stdout carries structured JSON — large ceiling so index.ts can apply item-level truncation.
3
+ const MAX_STDOUT_CHARS = 2_000_000; // 2 MB
4
+ // stderr carries error messages and probe text — keep tight.
5
+ const MAX_STDERR_CHARS = 4_096;
6
+ const RAW_TRUNCATION_MSG = '\n...[Output truncated. Use grep/jq to filter]';
7
+ const TIMEOUT_MS = 15_000;
8
+ function truncate(s, limit) {
9
+ if (s.length <= limit)
10
+ return s;
11
+ return s.slice(0, limit) + RAW_TRUNCATION_MSG;
9
12
  }
10
- export function executeCommand(command) {
13
+ /**
14
+ * Executes a binary directly with an args array.
15
+ * NO shell intermediary — shell injection is structurally impossible.
16
+ */
17
+ export function executeCommand(bin, args) {
11
18
  return new Promise((resolve, reject) => {
12
- const proc = spawn('sh', ['-c', command], {
19
+ const proc = spawn(bin, args, {
13
20
  env: { ...process.env, CI: 'true' },
14
21
  stdio: ['ignore', 'pipe', 'pipe'],
15
22
  });
16
23
  let stdoutBuf = '';
17
24
  let stderrBuf = '';
18
- proc.stdout.on('data', (chunk) => {
19
- stdoutBuf += chunk.toString();
20
- });
21
- proc.stderr.on('data', (chunk) => {
22
- stderrBuf += chunk.toString();
23
- });
25
+ proc.stdout.on('data', (chunk) => { stdoutBuf += chunk.toString(); });
26
+ proc.stderr.on('data', (chunk) => { stderrBuf += chunk.toString(); });
24
27
  const timer = setTimeout(() => {
25
28
  proc.kill('SIGKILL');
26
- reject(new Error(`Command timed out after ${TIMEOUT_MS}ms: ${command}`));
29
+ reject(new Error(`Command timed out after ${TIMEOUT_MS}ms`));
27
30
  }, TIMEOUT_MS);
28
31
  proc.on('close', (code) => {
29
32
  clearTimeout(timer);
30
33
  resolve({
31
- stdout: truncate(stdoutBuf),
32
- stderr: truncate(stderrBuf),
34
+ stdout: truncate(stdoutBuf, MAX_STDOUT_CHARS),
35
+ stderr: truncate(stderrBuf, MAX_STDERR_CHARS),
33
36
  exitCode: code ?? 1,
34
37
  });
35
38
  });
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAQtC,MAAM,UAAU,GAAG,IAAI,CAAC;AACxB,MAAM,cAAc,GAAG,gDAAgD,CAAC;AACxE,MAAM,UAAU,GAAG,IAAI,CAAC;AAExB,SAAS,QAAQ,CAAC,MAAc;IAC9B,IAAI,MAAM,CAAC,MAAM,IAAI,UAAU;QAAE,OAAO,MAAM,CAAC;IAC/C,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG,cAAc,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE;YACxC,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE;YACnC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QAEH,IAAI,SAAS,GAAG,EAAE,CAAC;QACnB,IAAI,SAAS,GAAG,EAAE,CAAC;QAEnB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACvC,SAAS,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QAChC,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACvC,SAAS,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QAChC,CAAC,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACrB,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,UAAU,OAAO,OAAO,EAAE,CAAC,CAAC,CAAC;QAC3E,CAAC,EAAE,UAAU,CAAC,CAAC;QAEf,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAmB,EAAE,EAAE;YACvC,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,CAAC;gBACN,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC;gBAC3B,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC;gBAC3B,QAAQ,EAAE,IAAI,IAAI,CAAC;aACpB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;YAC9B,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAQtC,8FAA8F;AAC9F,MAAM,gBAAgB,GAAG,SAAS,CAAC,CAAC,OAAO;AAC3C,6DAA6D;AAC7D,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAC/B,MAAM,kBAAkB,GAAG,gDAAgD,CAAC;AAC5E,MAAM,UAAU,GAAG,MAAM,CAAC;AAE1B,SAAS,QAAQ,CAAC,CAAS,EAAE,KAAa;IACxC,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,CAAC,CAAC;IAChC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,kBAAkB,CAAC;AAChD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,IAAc;IACxD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;YAC5B,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE;YACnC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QAEH,IAAI,SAAS,GAAG,EAAE,CAAC;QACnB,IAAI,SAAS,GAAG,EAAE,CAAC;QAEnB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,SAAS,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9E,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,SAAS,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAE9E,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACrB,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,UAAU,IAAI,CAAC,CAAC,CAAC;QAC/D,CAAC,EAAE,UAAU,CAAC,CAAC;QAEf,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAmB,EAAE,EAAE;YACvC,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,CAAC;gBACN,MAAM,EAAE,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;gBAC7C,MAAM,EAAE,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;gBAC7C,QAAQ,EAAE,IAAI,IAAI,CAAC;aACpB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;YAC9B,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/index.js CHANGED
@@ -3,52 +3,96 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
5
  import { executeCommand } from './executor.js';
6
- import { validateReadOnly } from './security.js';
7
- import { buildGhCommand, getToolDefinitions } from './schema.js';
8
- const server = new Server({ name: 'zero-config-cli-bridge', version: '1.0.0' }, { capabilities: { tools: {} } });
9
- server.setRequestHandler(ListToolsRequestSchema, async () => {
10
- return { tools: getToolDefinitions() };
11
- });
6
+ const MAX_JSON_ITEMS = 30;
7
+ const JSON_TRUNCATION_MSG = `\n...[Output truncated at ${MAX_JSON_ITEMS} items. Use --limit or filters to narrow results.]`;
8
+ const MAX_TEXT_CHARS = 2000;
9
+ const TEXT_TRUNCATION_MSG = '\n...[Output truncated. Use grep/jq to filter]';
10
+ /**
11
+ * Normalises command output for LLM consumption.
12
+ *
13
+ * JSON path : caps array at MAX_JSON_ITEMS — always returns valid JSON.
14
+ * Text path : caps at MAX_TEXT_CHARS — prevents context exhaustion on
15
+ * error messages and plain-text fallback output.
16
+ *
17
+ * The executor's MAX_RAW_CHARS (4096) is an independent backstop that fires
18
+ * only if this function is somehow bypassed (e.g. future code paths).
19
+ */
20
+ function toSafeOutput(raw) {
21
+ try {
22
+ const parsed = JSON.parse(raw);
23
+ if (Array.isArray(parsed)) {
24
+ if (parsed.length > MAX_JSON_ITEMS) {
25
+ return JSON.stringify(parsed.slice(0, MAX_JSON_ITEMS), null, 2) + JSON_TRUNCATION_MSG;
26
+ }
27
+ return JSON.stringify(parsed, null, 2);
28
+ }
29
+ return JSON.stringify(parsed, null, 2);
30
+ }
31
+ catch {
32
+ // Non-JSON: error messages, plain text — apply character cap
33
+ if (raw.length > MAX_TEXT_CHARS) {
34
+ return raw.slice(0, MAX_TEXT_CHARS) + TEXT_TRUNCATION_MSG;
35
+ }
36
+ return raw;
37
+ }
38
+ }
39
+ import { validateSubcommand, validateArgs } from './security.js';
40
+ import { buildToolDefinitions, buildGhArgs } from './schema.js';
41
+ const server = new Server({ name: 'zero-config-cli-bridge', version: '1.1.0' }, { capabilities: { tools: {} } });
42
+ let toolRegistry = new Map();
43
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
44
+ tools: Array.from(toolRegistry.values()).map(({ name, description, inputSchema }) => ({
45
+ name,
46
+ description,
47
+ inputSchema,
48
+ })),
49
+ }));
12
50
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
13
51
  const { name: toolName, arguments: rawArgs } = request.params;
14
52
  const args = (rawArgs ?? {});
15
- const command = buildGhCommand(toolName, args);
16
- // Security check: block mutating commands
53
+ const tool = toolRegistry.get(toolName);
54
+ if (!tool) {
55
+ return {
56
+ content: [{ type: 'text', text: `Error: Unknown tool "${toolName}".` }],
57
+ isError: true,
58
+ };
59
+ }
60
+ // Security: whitelist subcommand + validate arg values
17
61
  try {
18
- validateReadOnly(command);
62
+ validateSubcommand(tool.subcommand.join(' '));
63
+ validateArgs(args);
19
64
  }
20
65
  catch (err) {
21
- const msg = err instanceof Error ? err.message : String(err);
22
66
  return {
23
- content: [{ type: 'text', text: msg }],
67
+ content: [{ type: 'text', text: err instanceof Error ? err.message : String(err) }],
24
68
  isError: true,
25
69
  };
26
70
  }
27
- // Execute command
71
+ // Direct spawn — no shell, no injection surface
72
+ const ghArgs = buildGhArgs(tool, args);
28
73
  let result;
29
74
  try {
30
- result = await executeCommand(command);
75
+ result = await executeCommand('gh', ghArgs);
31
76
  }
32
77
  catch (err) {
33
- const msg = err instanceof Error ? err.message : String(err);
34
78
  return {
35
- content: [{ type: 'text', text: `Execution error: ${msg}` }],
79
+ content: [{ type: 'text', text: `Execution error: ${err instanceof Error ? err.message : String(err)}` }],
36
80
  isError: true,
37
81
  };
38
82
  }
39
- const output = result.stdout ||
40
- result.stderr ||
41
- `(no output, exit code ${result.exitCode})`;
42
- const isError = result.exitCode !== 0;
83
+ const raw = result.stdout || result.stderr || `(no output, exit code ${result.exitCode})`;
84
+ const output = toSafeOutput(raw);
43
85
  return {
44
86
  content: [{ type: 'text', text: output }],
45
- isError,
87
+ isError: result.exitCode !== 0,
46
88
  };
47
89
  });
48
90
  async function main() {
91
+ // Probe local gh binary for available capabilities before accepting requests
92
+ const tools = await buildToolDefinitions();
93
+ toolRegistry = new Map(tools.map((t) => [t.name, t]));
49
94
  const transport = new StdioServerTransport();
50
95
  await server.connect(transport);
51
- // Server is running on stdio - no console output to avoid corrupting MCP protocol
52
96
  }
53
97
  main().catch((err) => {
54
98
  process.stderr.write(`Fatal error: ${err}\n`);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjE,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,OAAO,EAAE,EACpD,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAC;AAEF,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE;IAC1D,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,CAAC;AACzC,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;IAChE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAC9D,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,CAA4B,CAAC;IAExD,MAAM,OAAO,GAAG,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAE/C,0CAA0C;IAC1C,IAAI,CAAC;QACH,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;YACtC,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,kBAAkB;IAClB,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,oBAAoB,GAAG,EAAE,EAAE,CAAC;YAC5D,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GACV,MAAM,CAAC,MAAM;QACb,MAAM,CAAC,MAAM;QACb,yBAAyB,MAAM,CAAC,QAAQ,GAAG,CAAC;IAE9C,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,KAAK,CAAC,CAAC;IAEtC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QACzC,OAAO;KACR,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,kFAAkF;AACpF,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/schema.d.ts CHANGED
@@ -9,7 +9,16 @@ export interface ToolDefinition {
9
9
  }>;
10
10
  required?: string[];
11
11
  };
12
+ /** gh subcommand tokens, e.g. ['issue', 'list'] */
13
+ subcommand: string[];
14
+ /** JSON fields confirmed available in the local gh binary, filtered to repo-scope-safe set */
15
+ jsonFields: string[];
12
16
  }
13
- export declare function getToolDefinitions(): ToolDefinition[];
14
- export declare function buildGhCommand(toolName: string, args: Record<string, unknown>): string;
17
+ export declare function buildToolDefinitions(): Promise<ToolDefinition[]>;
18
+ /**
19
+ * Builds the gh args array using --flag=value notation throughout.
20
+ * This prevents option injection: a value starting with '-' cannot
21
+ * be misinterpreted as a separate flag by gh's argument parser.
22
+ */
23
+ export declare function buildGhArgs(tool: ToolDefinition, args: Record<string, unknown>): string[];
15
24
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAClE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AAED,wBAAgB,kBAAkB,IAAI,cAAc,EAAE,CAqErD;AAED,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CA+BR"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAClE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;IACF,mDAAmD;IACnD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,8FAA8F;IAC9F,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAoDD,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CA8CtE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAWzF"}
package/dist/schema.js CHANGED
@@ -1,98 +1,116 @@
1
- export function getToolDefinitions() {
1
+ import { executeCommand } from './executor.js';
2
+ /**
3
+ * Fields requiring only the standard `repo` OAuth scope.
4
+ * Explicitly excludes:
5
+ * - `id` — requires read:project scope
6
+ * - `body` — unbounded text; breaks JSON item-level truncation
7
+ */
8
+ const REPO_SCOPE_SAFE_FIELDS = new Set([
9
+ 'number', 'title', 'state', 'labels', 'assignees',
10
+ 'author', 'createdAt', 'updatedAt', 'closedAt', 'url',
11
+ 'comments', 'milestone', 'isDraft', 'locked',
12
+ // PR-specific
13
+ 'baseRefName', 'headRefName', 'headRepository', 'mergedAt', 'mergeCommit',
14
+ 'reviewDecision', 'additions', 'deletions', 'changedFiles',
15
+ ]);
16
+ const FALLBACK_FIELDS = {
17
+ 'issue list': ['number', 'title', 'state', 'labels', 'assignees', 'createdAt', 'url'],
18
+ 'pr list': ['number', 'title', 'state', 'labels', 'assignees', 'createdAt', 'url', 'baseRefName'],
19
+ };
20
+ /**
21
+ * Detects JSON fields available in the local gh binary by calling
22
+ * `gh <subcommand> --json` with no field argument. Recent gh versions
23
+ * output the available field list to stderr in this case — no error
24
+ * injection needed. Falls back to the static list on any failure.
25
+ */
26
+ async function detectJsonFields(subcommand) {
27
+ const key = subcommand.join(' ');
28
+ try {
29
+ // `gh issue list --json` with no fields causes gh to list available fields on stderr.
30
+ // This is documented behaviour, not error scraping.
31
+ const result = await executeCommand('gh', [...subcommand, '--json']);
32
+ const text = result.stderr + result.stdout;
33
+ // Output format: "Use `--json` with one or more of: field1,field2,..."
34
+ // or a newline-separated list after "Available fields:"
35
+ const commaMatch = text.match(/--json`?\s+with[^:]*:\s*([a-zA-Z,\s]+)/i);
36
+ if (commaMatch) {
37
+ const fields = commaMatch[1]
38
+ .split(/[,\s]+/)
39
+ .map((f) => f.trim())
40
+ .filter((f) => /^[a-zA-Z][a-zA-Z0-9]*$/.test(f))
41
+ .filter((f) => REPO_SCOPE_SAFE_FIELDS.has(f));
42
+ if (fields.length > 0)
43
+ return fields;
44
+ }
45
+ }
46
+ catch {
47
+ // gh not installed or timed out
48
+ }
49
+ return FALLBACK_FIELDS[key] ?? [];
50
+ }
51
+ export async function buildToolDefinitions() {
52
+ const [issueFields, prFields] = await Promise.all([
53
+ detectJsonFields(['issue', 'list']),
54
+ detectJsonFields(['pr', 'list']),
55
+ ]);
2
56
  return [
3
57
  {
4
58
  name: 'gh_issue_list',
5
- description: 'List GitHub issues for a repository using the local `gh` CLI. ' +
6
- 'Requires gh to be installed and authenticated.',
59
+ description: 'List GitHub issues as structured JSON. ' +
60
+ 'Uses the local `gh` CLI and its existing authentication — no API key required. ' +
61
+ 'Read-only. Does not create, edit, or delete issues.',
62
+ subcommand: ['issue', 'list'],
63
+ jsonFields: issueFields,
7
64
  inputSchema: {
8
65
  type: 'object',
9
66
  properties: {
10
- repo: {
11
- type: 'string',
12
- description: 'Repository in OWNER/REPO format (e.g. "cli/cli"). ' +
13
- 'If omitted, uses the current directory\'s git remote.',
14
- },
15
- limit: {
16
- type: 'number',
17
- description: 'Maximum number of issues to fetch (default: 30).',
18
- },
19
- state: {
20
- type: 'string',
21
- description: 'Filter by state: "open" (default), "closed", or "all".',
22
- },
23
- label: {
24
- type: 'string',
25
- description: 'Filter by label name.',
26
- },
27
- assignee: {
28
- type: 'string',
29
- description: 'Filter by assignee login.',
30
- },
67
+ repo: { type: 'string', description: 'OWNER/REPO (e.g. "cli/cli"). Omit to use current directory.' },
68
+ limit: { type: 'number', description: 'Max results (default: 30).' },
69
+ state: { type: 'string', description: '"open" (default) | "closed" | "all".' },
70
+ label: { type: 'string', description: 'Filter by label name.' },
71
+ assignee: { type: 'string', description: 'Filter by assignee login.' },
31
72
  },
32
73
  },
33
74
  },
34
75
  {
35
76
  name: 'gh_pr_list',
36
- description: 'List GitHub pull requests for a repository using the local `gh` CLI. ' +
37
- 'Requires gh to be installed and authenticated.',
77
+ description: 'List GitHub pull requests as structured JSON. ' +
78
+ 'Uses the local `gh` CLI and its existing authentication — no API key required. ' +
79
+ 'Read-only. Does not create, edit, merge, or close pull requests.',
80
+ subcommand: ['pr', 'list'],
81
+ jsonFields: prFields,
38
82
  inputSchema: {
39
83
  type: 'object',
40
84
  properties: {
41
- repo: {
42
- type: 'string',
43
- description: 'Repository in OWNER/REPO format (e.g. "cli/cli"). ' +
44
- 'If omitted, uses the current directory\'s git remote.',
45
- },
46
- limit: {
47
- type: 'number',
48
- description: 'Maximum number of PRs to fetch (default: 30).',
49
- },
50
- state: {
51
- type: 'string',
52
- description: 'Filter by state: "open" (default), "closed", or "merged".',
53
- },
54
- base: {
55
- type: 'string',
56
- description: 'Filter by base branch name.',
57
- },
58
- assignee: {
59
- type: 'string',
60
- description: 'Filter by assignee login.',
61
- },
85
+ repo: { type: 'string', description: 'OWNER/REPO (e.g. "cli/cli"). Omit to use current directory.' },
86
+ limit: { type: 'number', description: 'Max results (default: 30).' },
87
+ state: { type: 'string', description: '"open" (default) | "closed" | "merged".' },
88
+ base: { type: 'string', description: 'Filter by base branch.' },
89
+ assignee: { type: 'string', description: 'Filter by assignee login.' },
62
90
  },
63
91
  },
64
92
  },
65
93
  ];
66
94
  }
67
- export function buildGhCommand(toolName, args) {
68
- const parts = ['gh'];
69
- if (toolName === 'gh_issue_list') {
70
- parts.push('issue', 'list');
71
- }
72
- else if (toolName === 'gh_pr_list') {
73
- parts.push('pr', 'list');
74
- }
75
- else {
76
- throw new Error(`Unknown tool: ${toolName}`);
77
- }
78
- if (args['repo'] !== undefined) {
79
- parts.push('--repo', String(args['repo']));
80
- }
81
- if (args['limit'] !== undefined) {
82
- parts.push('--limit', String(args['limit']));
83
- }
84
- if (args['state'] !== undefined) {
85
- parts.push('--state', String(args['state']));
86
- }
87
- if (toolName === 'gh_issue_list' && args['label'] !== undefined) {
88
- parts.push('--label', String(args['label']));
89
- }
90
- if (args['assignee'] !== undefined) {
91
- parts.push('--assignee', String(args['assignee']));
92
- }
93
- if (toolName === 'gh_pr_list' && args['base'] !== undefined) {
94
- parts.push('--base', String(args['base']));
95
- }
96
- return parts.join(' ');
95
+ /**
96
+ * Builds the gh args array using --flag=value notation throughout.
97
+ * This prevents option injection: a value starting with '-' cannot
98
+ * be misinterpreted as a separate flag by gh's argument parser.
99
+ */
100
+ export function buildGhArgs(tool, args) {
101
+ const parts = [...tool.subcommand, `--json=${tool.jsonFields.join(',')}`];
102
+ if (args['repo'] !== undefined)
103
+ parts.push(`--repo=${String(args['repo'])}`);
104
+ if (args['limit'] !== undefined)
105
+ parts.push(`--limit=${String(args['limit'])}`);
106
+ if (args['state'] !== undefined)
107
+ parts.push(`--state=${String(args['state'])}`);
108
+ if (args['label'] !== undefined && tool.name === 'gh_issue_list')
109
+ parts.push(`--label=${String(args['label'])}`);
110
+ if (args['assignee'] !== undefined)
111
+ parts.push(`--assignee=${String(args['assignee'])}`);
112
+ if (args['base'] !== undefined && tool.name === 'gh_pr_list')
113
+ parts.push(`--base=${String(args['base'])}`);
114
+ return parts;
97
115
  }
98
116
  //# sourceMappingURL=schema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,11 @@
1
- export declare function validateReadOnly(command: string): void;
1
+ /**
2
+ * Security layer.
3
+ *
4
+ * With direct spawn(bin, args[]) there is no shell to inject into.
5
+ * This layer provides defense-in-depth by:
6
+ * 1. Whitelisting the exact subcommand paths allowed to execute.
7
+ * 2. Validating individual argument values for anomalous content.
8
+ */
9
+ export declare function validateSubcommand(subcommand: string): void;
10
+ export declare function validateArgs(args: Record<string, unknown>): void;
2
11
  //# sourceMappingURL=security.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAWA,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAWtD"}
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAM3D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAWhE"}
package/dist/security.js CHANGED
@@ -1,20 +1,30 @@
1
- const BLOCKED_KEYWORDS = [
2
- 'create',
3
- 'delete',
4
- 'edit',
5
- 'rm',
6
- 'update',
7
- 'close',
8
- 'reopen',
9
- 'merge',
10
- ];
11
- export function validateReadOnly(command) {
12
- const lower = command.toLowerCase();
13
- for (const keyword of BLOCKED_KEYWORDS) {
14
- // Match keyword as a whole word (surrounded by non-alphanumeric chars or at boundaries)
15
- const pattern = new RegExp(`(?<![a-z0-9])${keyword}(?![a-z0-9])`);
16
- if (pattern.test(lower)) {
17
- throw new Error('Error: Mutating commands are blocked in MVP version. Use read-only commands.');
1
+ /**
2
+ * Security layer.
3
+ *
4
+ * With direct spawn(bin, args[]) there is no shell to inject into.
5
+ * This layer provides defense-in-depth by:
6
+ * 1. Whitelisting the exact subcommand paths allowed to execute.
7
+ * 2. Validating individual argument values for anomalous content.
8
+ */
9
+ /** Only these gh subcommand paths may execute. */
10
+ const ALLOWED_SUBCOMMANDS = new Set([
11
+ 'issue list',
12
+ 'pr list',
13
+ ]);
14
+ export function validateSubcommand(subcommand) {
15
+ if (!ALLOWED_SUBCOMMANDS.has(subcommand)) {
16
+ throw new Error(`Error: Subcommand "${subcommand}" is not in the read-only allow-list.`);
17
+ }
18
+ }
19
+ export function validateArgs(args) {
20
+ for (const [, val] of Object.entries(args)) {
21
+ if (typeof val === 'string') {
22
+ if (val.includes('\0')) {
23
+ throw new Error('Error: Null byte detected in argument value.');
24
+ }
25
+ if (val.length > 512) {
26
+ throw new Error('Error: Argument value exceeds maximum length (512).');
27
+ }
18
28
  }
19
29
  }
20
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"security.js","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAAA,MAAM,gBAAgB,GAAG;IACvB,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,IAAI;IACJ,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,OAAO;CACR,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IACpC,KAAK,MAAM,OAAO,IAAI,gBAAgB,EAAE,CAAC;QACvC,wFAAwF;QACxF,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,gBAAgB,OAAO,cAAc,CAAC,CAAC;QAClE,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,8EAA8E,CAC/E,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,kDAAkD;AAClD,MAAM,mBAAmB,GAAwB,IAAI,GAAG,CAAC;IACvD,YAAY;IACZ,SAAS;CACV,CAAC,CAAC;AAEH,MAAM,UAAU,kBAAkB,CAAC,UAAkB;IACnD,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CACb,sBAAsB,UAAU,uCAAuC,CACxE,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAA6B;IACxD,KAAK,MAAM,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC5B,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvB,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;YAClE,CAAC;YACD,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;gBACrB,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;YACzE,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zero-config-cli-bridge",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "Zero Setup. Zero API Keys. Expose your local authenticated CLIs as MCP tools.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,6 +28,14 @@
28
28
  "agent",
29
29
  "tool"
30
30
  ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/hatyibei/zero-config-cli-bridge"
34
+ },
35
+ "homepage": "https://github.com/hatyibei/zero-config-cli-bridge#readme",
36
+ "bugs": {
37
+ "url": "https://github.com/hatyibei/zero-config-cli-bridge/issues"
38
+ },
31
39
  "license": "MIT",
32
40
  "engines": {
33
41
  "node": ">=20.0.0"