@synkro-sh/cli 1.4.73 → 1.4.75

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/dist/bootstrap.js CHANGED
@@ -536,21 +536,15 @@ function installMcpConfig(opts) {
536
536
  if (entry?.[SYNKRO_MARKER3] === true) delete config.mcpServers[name];
537
537
  }
538
538
  if (opts.local) {
539
- const url2 = "http://127.0.0.1:8931/";
540
- const tokenPath = join2(homedir3(), ".synkro", ".mcp-local-token");
541
- let localToken = "";
542
- try {
543
- localToken = readFileSync3(tokenPath, "utf-8").trim();
544
- } catch {
545
- }
539
+ const proxyScript = join2(homedir3(), ".synkro", "hooks", "mcp-stdio-proxy.ts");
546
540
  config.mcpServers[SYNKRO_SERVER_NAME] = {
547
- type: "http",
548
- url: url2,
549
- ...localToken ? { headers: { Authorization: `Bearer ${localToken}` } } : {},
541
+ type: "stdio",
542
+ command: "bun",
543
+ args: ["run", proxyScript],
550
544
  [SYNKRO_MARKER3]: true
551
545
  };
552
546
  writeClaudeJsonAtomic(config);
553
- return { path: CC_CONFIG_PATH, url: url2 };
547
+ return { path: CC_CONFIG_PATH, url: `stdio://${proxyScript}` };
554
548
  }
555
549
  const url = `${opts.gatewayUrl.replace(/\/$/, "")}/api/v1/mcp/guardrails`;
556
550
  config.mcpServers[SYNKRO_SERVER_NAME] = {
@@ -612,15 +606,15 @@ function installCursorMcpConfig(opts) {
612
606
  }
613
607
  if (opts.local) {
614
608
  const url2 = "http://127.0.0.1:8931/";
615
- const tokenPath = join2(homedir3(), ".synkro", ".mcp-local-token");
616
- let localToken = "";
609
+ const jwtPath = join2(homedir3(), ".synkro", ".mcp-jwt");
610
+ let jwt2 = "";
617
611
  try {
618
- localToken = readFileSync3(tokenPath, "utf-8").trim();
612
+ jwt2 = readFileSync3(jwtPath, "utf-8").trim();
619
613
  } catch {
620
614
  }
621
615
  config.mcpServers[SYNKRO_SERVER_NAME] = {
622
616
  url: url2,
623
- ...localToken ? { headers: { Authorization: `Bearer ${localToken}` } } : {},
617
+ ...jwt2 ? { headers: { Authorization: `Bearer ${jwt2}` } } : {},
624
618
  [SYNKRO_MARKER3]: true
625
619
  };
626
620
  writeCursorMcpJsonAtomic(config);
@@ -6267,6 +6261,7 @@ function writeHookScripts() {
6267
6261
  const cursorBashJudgePath = join11(HOOKS_DIR, "cursor-bash-judge.ts");
6268
6262
  const cursorEditCapturePath = join11(HOOKS_DIR, "cursor-edit-capture.ts");
6269
6263
  const mcpLocalServerPath = join11(HOOKS_DIR, "mcp-local-server.ts");
6264
+ const mcpStdioProxyPath = join11(HOOKS_DIR, "mcp-stdio-proxy.ts");
6270
6265
  writeFileSync7(bashScriptPath, BASH_JUDGE_TS, "utf-8");
6271
6266
  writeFileSync7(bashFollowupScriptPath, BASH_FOLLOWUP_TS, "utf-8");
6272
6267
  writeFileSync7(editPrecheckScriptPath, EDIT_PRECHECK_TS, "utf-8");
@@ -6292,30 +6287,17 @@ import { existsSync, readFileSync, writeFileSync, renameSync, appendFileSync, mk
6292
6287
  import { homedir } from 'node:os';
6293
6288
  import { join } from 'node:path';
6294
6289
 
6295
- import { randomBytes } from 'node:crypto';
6296
-
6297
6290
  const PORT = parseInt(process.env.SYNKRO_MCP_PORT || '8931', 10);
6298
6291
  const HOME = homedir();
6299
6292
  const RULES_PATH = join(HOME, '.synkro', 'rules.json');
6300
6293
  const TELEMETRY_PATH = join(HOME, '.synkro', 'telemetry.jsonl');
6301
- const TOKEN_PATH = join(HOME, '.synkro', '.mcp-local-token');
6294
+ const JWT_TOKEN_PATH = join(HOME, '.synkro', '.mcp-jwt');
6302
6295
 
6303
- // File-based shared secret \u2014 generated once, required on all POST requests.
6304
- function getOrCreateToken(): string {
6305
- try {
6306
- return readFileSync(TOKEN_PATH, 'utf-8').trim();
6307
- } catch {}
6308
- const token = randomBytes(32).toString('hex');
6309
- mkdirSync(join(HOME, '.synkro'), { recursive: true });
6310
- try {
6311
- writeFileSync(TOKEN_PATH, token + '\\n', { mode: 0o600, flag: 'wx' });
6312
- return token;
6313
- } catch {
6314
- return readFileSync(TOKEN_PATH, 'utf-8').trim();
6315
- }
6316
- }
6317
-
6318
- const SERVER_TOKEN = getOrCreateToken();
6296
+ // Synkro-signed long-lived JWT \u2014 minted during \`synkro install\`, required on all POST requests.
6297
+ // If missing, the server still starts (for GET health checks) but rejects all tool calls.
6298
+ let SERVER_TOKEN = '';
6299
+ try { SERVER_TOKEN = readFileSync(JWT_TOKEN_PATH, 'utf-8').trim(); } catch {}
6300
+ if (!SERVER_TOKEN) console.warn('[synkro] \u26A0 No MCP JWT found \u2014 run \`synkro install\` to authenticate.');
6319
6301
  const MAX_BODY_BYTES = 1_048_576;
6320
6302
 
6321
6303
  let _writeLock: Promise<void> = Promise.resolve();
@@ -7370,6 +7352,7 @@ const server = Bun.serve({
7370
7352
 
7371
7353
  console.log(\`[synkro] local MCP guardrails server listening on http://127.0.0.1:\${server.port}\`);
7372
7354
  `, "utf-8");
7355
+ writeFileSync7(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
7373
7356
  chmodSync2(bashScriptPath, 493);
7374
7357
  chmodSync2(bashFollowupScriptPath, 493);
7375
7358
  chmodSync2(editPrecheckScriptPath, 493);
@@ -7386,6 +7369,7 @@ console.log(\`[synkro] local MCP guardrails server listening on http://127.0.0.1
7386
7369
  chmodSync2(cursorBashJudgePath, 493);
7387
7370
  chmodSync2(cursorEditCapturePath, 493);
7388
7371
  chmodSync2(mcpLocalServerPath, 493);
7372
+ chmodSync2(mcpStdioProxyPath, 493);
7389
7373
  return {
7390
7374
  bashScript: bashScriptPath,
7391
7375
  bashFollowupScript: bashFollowupScriptPath,
@@ -7432,7 +7416,7 @@ function writeConfigEnv(opts) {
7432
7416
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
7433
7417
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
7434
7418
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
7435
- `SYNKRO_VERSION=${shellQuoteSingle("1.4.73")}`
7419
+ `SYNKRO_VERSION=${shellQuoteSingle("1.4.75")}`
7436
7420
  ];
7437
7421
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
7438
7422
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -7915,7 +7899,20 @@ async function installCommand(opts = {}) {
7915
7899
  if (hasClaudeCode && !opts.noMcp) {
7916
7900
  if (useLocalMcp) {
7917
7901
  try {
7918
- const mcp = installMcpConfig({ gatewayUrl, bearerToken: "", local: true });
7902
+ const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {
7903
+ method: "POST",
7904
+ headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
7905
+ body: "{}"
7906
+ });
7907
+ let mcpJwt = "";
7908
+ if (mintResp.ok) {
7909
+ const minted = await mintResp.json();
7910
+ mcpJwt = minted.token;
7911
+ writeFileSync7(join11(SYNKRO_DIR2, ".mcp-jwt"), mcpJwt + "\n", { mode: 384 });
7912
+ } else {
7913
+ console.warn(" \u26A0 Could not mint MCP token \u2014 local server will reject requests until re-installed.");
7914
+ }
7915
+ const mcp = installMcpConfig({ gatewayUrl, bearerToken: mcpJwt, local: true });
7919
7916
  console.log(`Registered local MCP guardrails server in ${mcp.path}`);
7920
7917
  console.log(` url: ${mcp.url}`);
7921
7918
  console.log(" (rules stored in ~/.synkro/rules.json)");
@@ -7942,6 +7939,7 @@ async function installCommand(opts = {}) {
7942
7939
  throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
7943
7940
  }
7944
7941
  const minted = await mintResp.json();
7942
+ writeFileSync7(join11(SYNKRO_DIR2, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
7945
7943
  const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });
7946
7944
  console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
7947
7945
  console.log(` url: ${mcp.url}`);
@@ -7958,6 +7956,18 @@ async function installCommand(opts = {}) {
7958
7956
  if (hasCursor && !opts.noMcp) {
7959
7957
  try {
7960
7958
  if (useLocalMcp) {
7959
+ const jwtPath = join11(SYNKRO_DIR2, ".mcp-jwt");
7960
+ if (!existsSync10(jwtPath)) {
7961
+ const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {
7962
+ method: "POST",
7963
+ headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
7964
+ body: "{}"
7965
+ });
7966
+ if (mintResp.ok) {
7967
+ const minted = await mintResp.json();
7968
+ writeFileSync7(jwtPath, minted.token + "\n", { mode: 384 });
7969
+ }
7970
+ }
7961
7971
  const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: "", local: true });
7962
7972
  console.log(`Registered local MCP guardrails server in ${mcp.path}`);
7963
7973
  console.log(` url: ${mcp.url}`);
@@ -7975,6 +7985,7 @@ async function installCommand(opts = {}) {
7975
7985
  throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
7976
7986
  }
7977
7987
  const minted = await mintResp.json();
7988
+ writeFileSync7(join11(SYNKRO_DIR2, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
7978
7989
  const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });
7979
7990
  console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
7980
7991
  console.log(` url: ${mcp.url}`);
@@ -8314,7 +8325,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
8314
8325
  }
8315
8326
  return { sessions: totalSessions, messages: totalMessages };
8316
8327
  }
8317
- var SYNKRO_DIR2, HOOKS_DIR, BIN_DIR, CONFIG_PATH3, OFFSETS_DIR, RULES_PATH, MCP_LOCAL_PORT;
8328
+ var SYNKRO_DIR2, HOOKS_DIR, BIN_DIR, CONFIG_PATH3, MCP_STDIO_PROXY_SRC, OFFSETS_DIR, RULES_PATH, MCP_LOCAL_PORT;
8318
8329
  var init_install2 = __esm({
8319
8330
  "cli/commands/install.ts"() {
8320
8331
  "use strict";
@@ -8337,6 +8348,52 @@ var init_install2 = __esm({
8337
8348
  HOOKS_DIR = join11(SYNKRO_DIR2, "hooks");
8338
8349
  BIN_DIR = join11(SYNKRO_DIR2, "bin");
8339
8350
  CONFIG_PATH3 = join11(SYNKRO_DIR2, "config.env");
8351
+ MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun
8352
+ import { readFileSync } from 'node:fs';
8353
+ import { homedir } from 'node:os';
8354
+ import { join } from 'node:path';
8355
+ import { createInterface } from 'node:readline';
8356
+
8357
+ const HOME = homedir();
8358
+ const TOKEN_PATH = join(HOME, '.synkro', '.mcp-jwt');
8359
+ const PORT = parseInt(process.env.SYNKRO_MCP_PORT || '8931', 10);
8360
+ const URL = \`http://127.0.0.1:\${PORT}\`;
8361
+
8362
+ let token = '';
8363
+ try { token = readFileSync(TOKEN_PATH, 'utf-8').trim(); } catch {}
8364
+
8365
+ const rl = createInterface({ input: process.stdin, terminal: false });
8366
+
8367
+ rl.on('line', async (line) => {
8368
+ if (!line.trim()) return;
8369
+ let msg;
8370
+ try { msg = JSON.parse(line); } catch { return; }
8371
+ if (!msg.id && msg.method?.startsWith('notifications/')) return;
8372
+
8373
+ try {
8374
+ const resp = await fetch(URL, {
8375
+ method: 'POST',
8376
+ headers: {
8377
+ 'Content-Type': 'application/json',
8378
+ 'Authorization': \`Bearer \${token}\`,
8379
+ },
8380
+ body: line,
8381
+ signal: AbortSignal.timeout(30000),
8382
+ });
8383
+ if (resp.status === 204) return;
8384
+ const body = await resp.text();
8385
+ process.stdout.write(body + '\\n');
8386
+ } catch (err) {
8387
+ if (msg.id != null) {
8388
+ process.stdout.write(JSON.stringify({
8389
+ jsonrpc: '2.0',
8390
+ id: msg.id,
8391
+ error: { code: -32603, message: 'MCP proxy: HTTP server unreachable' },
8392
+ }) + '\\n');
8393
+ }
8394
+ }
8395
+ });
8396
+ `;
8340
8397
  OFFSETS_DIR = join11(SYNKRO_DIR2, ".transcript-offsets");
8341
8398
  RULES_PATH = join11(SYNKRO_DIR2, "rules.json");
8342
8399
  MCP_LOCAL_PORT = 8931;