@haven_ai/connect 0.1.1-alpha → 0.1.2-alpha

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/index.js CHANGED
@@ -5,7 +5,8 @@ import { homedir, platform } from 'os';
5
5
  import { join, resolve, dirname } from 'path';
6
6
  import { execFile } from 'child_process';
7
7
  import { promisify } from 'util';
8
- import { SIGNER_VERSION, ensureSignerConsent, computeSignerConsentHash, loadSignerCredentials, createEdgeSigner, toolSchemas } from '@haven_ai/signer';
8
+ import { MCP_VERSION, ensureConsent, computeConsentHash, loadCredentials, consentInputFromClient, registeredToolNames } from '@haven_ai/mcp';
9
+ import { ensureSignerConsent, computeSignerConsentHash, loadSignerCredentials, createEdgeSigner, toolSchemas, SIGNER_VERSION } from '@haven_ai/signer';
9
10
 
10
11
  // src/api.ts
11
12
  function createConnectApiClient(baseUrl, fetchImpl = fetch) {
@@ -44,10 +45,13 @@ function createConnectApiClient(baseUrl, fetchImpl = fetch) {
44
45
  body: JSON.stringify({
45
46
  runtime: input.runtime,
46
47
  connector_version: input.connectorVersion,
48
+ runtime_mcp_mode: input.runtimeMcpMode,
47
49
  hosted_mcp_configured: input.hostedMcpConfigured,
48
50
  local_signer_configured: input.localSignerConfigured,
51
+ local_mcp_configured: input.localMcpConfigured,
49
52
  credential_files_written: input.credentialFilesWritten,
50
53
  signer_acknowledged: input.signerAcknowledged,
54
+ local_mcp_acknowledged: input.localMcpAcknowledged,
51
55
  activation_command_available: input.activationCommandAvailable,
52
56
  probe_result: input.probeResult,
53
57
  restart_required: input.restartRequired,
@@ -130,6 +134,7 @@ async function writeCredentialFiles(input) {
130
134
  signerPath,
131
135
  {
132
136
  delegate_key: input.delegateKey,
137
+ delegate_address: input.delegateAddress,
133
138
  agent_id: input.agentId,
134
139
  safe_address: input.safeAddress,
135
140
  chain_id: input.chainId,
@@ -149,6 +154,7 @@ async function writeCredentialFiles(input) {
149
154
  network: input.network,
150
155
  api_url: input.apiUrl,
151
156
  hosted_mcp_url: input.hostedMcpUrl,
157
+ agent_budget: input.agentBudget,
152
158
  note: "Haven API key identifies the agent only. It cannot spend without the local signer key and on-chain Haven wallet rules."
153
159
  },
154
160
  input.warn
@@ -209,6 +215,8 @@ async function writeRuntimeConfig(input) {
209
215
  return {
210
216
  hostedConfigured: false,
211
217
  signerConfigured: false,
218
+ localMcpConfigured: false,
219
+ runtimeMcpMode: "manual",
212
220
  target: "manual runtime setup",
213
221
  changed: false,
214
222
  restartRequired: true,
@@ -250,17 +258,13 @@ function mergeJsonMcpConfig(existingJson, serverRoot, hostedServer, signerServer
250
258
  return `${JSON.stringify(config, null, 2)}
251
259
  `;
252
260
  }
253
- function mergeCodexToml(existingToml, hostedMcpUrl, signerPath) {
261
+ function mergeCodexToml(existingToml, identityPath, signerPath) {
254
262
  let next = removeTomlTable(removeTomlTable(existingToml, "mcp_servers.haven"), "mcp_servers.haven_signer");
255
263
  next = next.trimEnd();
256
264
  const block = [
257
265
  "[mcp_servers.haven]",
258
- `url = ${tomlString(hostedMcpUrl)}`,
259
- 'bearer_token_env_var = "HAVEN_TOKEN"',
260
- "",
261
- "[mcp_servers.haven_signer]",
262
266
  'command = "npx"',
263
- `args = ["-y", ${tomlString(signerPackageName())}, "--credentials", ${tomlString(signerPath)}]`
267
+ `args = ["-y", ${tomlString(localMcpPackageName())}, "--identity", ${tomlString(identityPath)}, "--signer", ${tomlString(signerPath)}]`
264
268
  ].join("\n");
265
269
  return `${next ? `${next}
266
270
 
@@ -280,6 +284,8 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
280
284
  return {
281
285
  hostedConfigured: true,
282
286
  signerConfigured: true,
287
+ localMcpConfigured: false,
288
+ runtimeMcpMode: "hosted_plus_signer",
283
289
  target: configTargetLabel(input.runtime),
284
290
  changed: existing !== merged,
285
291
  restartRequired: input.runtime === "claude-desktop",
@@ -289,6 +295,8 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
289
295
  return {
290
296
  hostedConfigured: false,
291
297
  signerConfigured: false,
298
+ localMcpConfigured: false,
299
+ runtimeMcpMode: "hosted_plus_signer",
292
300
  target: configTargetLabel(input.runtime),
293
301
  changed: false,
294
302
  restartRequired: true,
@@ -299,41 +307,29 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
299
307
  }
300
308
  async function writeCodexConfig(input) {
301
309
  const target = codexConfigPath(input.homeDir);
302
- const envTarget = join(input.credentialDirectory, "identity.env");
303
- const launchTarget = join(input.credentialDirectory, "start-codex.sh");
304
310
  try {
305
311
  const existing = await readOptional(target);
306
- const merged = mergeCodexToml(existing ?? "", input.hostedMcpUrl, input.signerPath);
312
+ const merged = mergeCodexToml(existing ?? "", input.identityPath, input.signerPath);
307
313
  await writeOwnerOnlyText(target, merged);
308
- await writeOwnerOnlyText(envTarget, `export HAVEN_TOKEN=${shellToken(input.apiKey)}
309
- `);
310
- await writeOwnerExecutableText(
311
- launchTarget,
312
- [
313
- "#!/bin/sh",
314
- "set -eu",
315
- `. ${shellToken(envTarget)}`,
316
- 'exec codex "$@"',
317
- ""
318
- ].join("\n")
319
- );
320
314
  return {
321
- hostedConfigured: true,
315
+ hostedConfigured: false,
322
316
  signerConfigured: true,
317
+ localMcpConfigured: true,
318
+ runtimeMcpMode: "local_stdio",
323
319
  target: "Codex CLI config",
324
320
  changed: existing !== merged,
325
321
  restartRequired: true,
326
- activationCommand: shellToken(launchTarget),
327
322
  messages: [
328
- "Updated Haven MCP entries in Codex CLI config.",
329
- "Wrote the hosted MCP token to a private env file.",
330
- `Restart Codex with: ${shellToken(launchTarget)}`
323
+ "Updated local Haven MCP entry in Codex CLI config.",
324
+ "After Haven approval, restart Codex normally so it can load Haven tools."
331
325
  ]
332
326
  };
333
327
  } catch (err) {
334
328
  return {
335
329
  hostedConfigured: false,
336
330
  signerConfigured: false,
331
+ localMcpConfigured: false,
332
+ runtimeMcpMode: "local_stdio",
337
333
  target: "Codex CLI config",
338
334
  changed: false,
339
335
  restartRequired: true,
@@ -355,11 +351,6 @@ async function writeOwnerOnlyText(path, value) {
355
351
  await writeFile(path, value, { mode: 384 });
356
352
  await chmod(path, 384).catch(() => void 0);
357
353
  }
358
- async function writeOwnerExecutableText(path, value) {
359
- await mkdir(dirname(path), { recursive: true, mode: 448 });
360
- await writeFile(path, value, { mode: 448 });
361
- await chmod(path, 448).catch(() => void 0);
362
- }
363
354
  function parseJsonObject(value) {
364
355
  const parsed = JSON.parse(value);
365
356
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -388,9 +379,6 @@ function removeTomlTable(toml, table) {
388
379
  function tomlString(value) {
389
380
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
390
381
  }
391
- function shellToken(value) {
392
- return `'${value.replace(/'/g, `'\\''`)}'`;
393
- }
394
382
  function cursorConfigPath(homeDir = homedir()) {
395
383
  return resolve(homeDir, ".cursor", "mcp.json");
396
384
  }
@@ -428,6 +416,85 @@ function configTargetLabel(runtime) {
428
416
  function signerPackageName() {
429
417
  return `@haven_ai/signer@${SIGNER_VERSION}`;
430
418
  }
419
+ function localMcpPackageName() {
420
+ return `@haven_ai/mcp@${MCP_VERSION}`;
421
+ }
422
+ async function acknowledgeLocalMcpConsent(identityPath, signerPath, log) {
423
+ try {
424
+ const input = await buildLocalMcpConsentInput(identityPath, signerPath);
425
+ const decision = await ensureConsent(input, {
426
+ credentialsPath: identityPath,
427
+ writeAck: true,
428
+ out: log ? { write: (chunk) => writeLogChunk(log, chunk) } : void 0
429
+ });
430
+ return {
431
+ acknowledged: decision.ok,
432
+ hash: decision.hash,
433
+ reason: decision.reason
434
+ };
435
+ } catch (err) {
436
+ return {
437
+ acknowledged: false,
438
+ error: err instanceof Error ? err.message : String(err)
439
+ };
440
+ }
441
+ }
442
+ async function getLocalMcpConsentStatus(identityPath, signerPath) {
443
+ try {
444
+ const input = await buildLocalMcpConsentInput(identityPath, signerPath);
445
+ const hash = computeConsentHash(input);
446
+ const stored = await readLocalMcpAckFile(localMcpAckPath(identityPath));
447
+ if (stored === hash) {
448
+ return { acknowledged: true, hash, reason: "ack_file_match" };
449
+ }
450
+ return {
451
+ acknowledged: false,
452
+ hash,
453
+ reason: stored ? "ack_file_mismatch" : "ack_file_missing"
454
+ };
455
+ } catch (err) {
456
+ return {
457
+ acknowledged: false,
458
+ error: err instanceof Error ? err.message : String(err)
459
+ };
460
+ }
461
+ }
462
+ function localMcpAckPath(identityPath) {
463
+ return resolve(`${identityPath}.ack.json`);
464
+ }
465
+ async function buildLocalMcpConsentInput(identityPath, signerPath) {
466
+ const credentials = await loadCredentials({ identityPath, signerPath });
467
+ const unavailableDuringSetup = {
468
+ getAllowances: async () => {
469
+ throw new Error("Haven approval is not complete yet.");
470
+ }
471
+ };
472
+ return consentInputFromClient(
473
+ unavailableDuringSetup,
474
+ {
475
+ apiKey: credentials.apiKey,
476
+ apiUrl: credentials.apiUrl,
477
+ agentId: credentials.agentId,
478
+ safeAddress: credentials.safeAddress,
479
+ delegateAddress: credentials.delegateAddress,
480
+ chainId: credentials.chainId,
481
+ allowanceSummary: credentials.allowanceSummary
482
+ },
483
+ registeredToolNames()
484
+ );
485
+ }
486
+ async function readLocalMcpAckFile(path) {
487
+ try {
488
+ const parsed = JSON.parse(await readFile(path, "utf8"));
489
+ return typeof parsed.ack === "string" ? parsed.ack : null;
490
+ } catch {
491
+ return null;
492
+ }
493
+ }
494
+ function writeLogChunk(log, chunk) {
495
+ const message = String(chunk).trimEnd();
496
+ if (message) log(message);
497
+ }
431
498
  async function probeHostedMcpTools(apiKey, hostedMcpUrl, fetchImpl = fetch) {
432
499
  let response;
433
500
  try {
@@ -582,7 +649,7 @@ async function acknowledgeLocalSignerConsent(signerPath, log) {
582
649
  const decision = await ensureSignerConsent(input, {
583
650
  credentialsPath: signerPath,
584
651
  writeAck: true,
585
- out: log ? { write: (chunk) => writeLogChunk(log, chunk) } : void 0
652
+ out: log ? { write: (chunk) => writeLogChunk2(log, chunk) } : void 0
586
653
  });
587
654
  return {
588
655
  acknowledged: decision.ok,
@@ -641,7 +708,7 @@ async function readSignerAckFile(path) {
641
708
  return null;
642
709
  }
643
710
  }
644
- function writeLogChunk(log, chunk) {
711
+ function writeLogChunk2(log, chunk) {
645
712
  const message = String(chunk).trimEnd();
646
713
  if (message) log(message);
647
714
  }
@@ -651,23 +718,28 @@ var execFileAsync = promisify(execFile);
651
718
  async function installRuntime(input, deps = {}) {
652
719
  const runtime = normalizeRuntime(input.runtime, deps.env);
653
720
  const profile = runtimeProfile(runtime, deps.env);
654
- const signerConsentMessages = [];
655
- const signerConsent = await resolveSignerConsent(input, signerConsentMessages);
721
+ const localRuntime = usesLocalMcp(runtime);
722
+ const consentMessages = [];
723
+ const localMcpConsent = localRuntime ? await resolveLocalMcpConsent(input, consentMessages) : void 0;
724
+ const signerConsent = localRuntime ? void 0 : await resolveSignerConsent(input, consentMessages);
656
725
  if (runtime === "other") {
657
726
  const signerCredentialReady2 = await probeLocalSignerCredential(input.signerPath);
658
- const signerReady = signerCredentialReady2 && signerConsent.acknowledged;
727
+ const signerReady = signerCredentialReady2 && signerConsent?.acknowledged;
659
728
  return {
660
729
  runtime,
730
+ runtimeMcpMode: "manual",
661
731
  hostedMcpConfigured: false,
662
732
  localSignerConfigured: false,
733
+ localMcpConfigured: false,
663
734
  probeResult: signerReady ? "manual_runtime_setup_required_local_signer_ready" : "manual_runtime_setup_required_local_signer_unavailable",
664
735
  restartRequired: true,
665
736
  nextUserAction: "return_to_haven_for_wallet_approval_then_configure_runtime",
666
737
  errorCode: "manual_runtime_setup_required",
667
738
  configTarget: "manual runtime setup",
668
- signerAcknowledged: signerConsent.acknowledged,
739
+ signerAcknowledged: signerConsent?.acknowledged,
740
+ localMcpAcknowledged: false,
669
741
  messages: [
670
- ...signerConsentMessages,
742
+ ...consentMessages,
671
743
  "Runtime was not recognized. Keep the local credentials and add Haven MCP entries manually after wallet approval."
672
744
  ]
673
745
  };
@@ -676,6 +748,7 @@ async function installRuntime(input, deps = {}) {
676
748
  runtime,
677
749
  hostedMcpUrl: input.hostedMcpUrl,
678
750
  apiKey: input.apiKey,
751
+ identityPath: input.identityPath,
679
752
  signerPath: input.signerPath,
680
753
  credentialDirectory: input.credentialDirectory,
681
754
  homeDir: deps.homeDir
@@ -685,21 +758,25 @@ async function installRuntime(input, deps = {}) {
685
758
  probeLocalSignerCredential(input.signerPath)
686
759
  ]);
687
760
  const hostedOk = configResult.hostedConfigured && hostedProbe.status !== "unauthorized";
688
- const signerOk = configResult.signerConfigured && signerCredentialReady && signerConsent.acknowledged;
761
+ const localMcpOk = configResult.runtimeMcpMode === "local_stdio" && configResult.localMcpConfigured && signerCredentialReady && Boolean(localMcpConsent?.acknowledged);
762
+ const signerOk = configResult.runtimeMcpMode === "local_stdio" ? localMcpOk : configResult.signerConfigured && signerCredentialReady && Boolean(signerConsent?.acknowledged);
689
763
  const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
690
- const errorCode = configResult.errorCode ?? signerConsentErrorCode(signerCredentialReady, signerConsent);
764
+ const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpConsentErrorCode(signerCredentialReady, localMcpConsent) : signerConsentErrorCode(signerCredentialReady, signerConsent));
691
765
  return {
692
766
  runtime,
767
+ runtimeMcpMode: configResult.runtimeMcpMode,
693
768
  hostedMcpConfigured: hostedOk,
694
769
  localSignerConfigured: signerOk,
695
- probeResult: buildProbeResult(configResult.hostedConfigured, hostedProbe.status, signerOk),
770
+ localMcpConfigured: localMcpOk,
771
+ probeResult: buildProbeResult(configResult.runtimeMcpMode, configResult.hostedConfigured, hostedProbe.status, signerOk, localMcpOk),
696
772
  restartRequired,
697
773
  nextUserAction: nextAction(runtime, profile.restartMode, errorCode),
698
774
  errorCode,
699
775
  configTarget: configResult.target,
700
- signerAcknowledged: signerConsent.acknowledged,
776
+ signerAcknowledged: signerConsent?.acknowledged,
777
+ localMcpAcknowledged: localMcpConsent?.acknowledged,
701
778
  activationCommand: configResult.activationCommand,
702
- messages: [...signerConsentMessages, ...configResult.messages]
779
+ messages: [...consentMessages, ...configResult.messages]
703
780
  };
704
781
  }
705
782
  function runtimeInstallCapabilities(runtime, env = process.env) {
@@ -715,39 +792,36 @@ async function configureClaudeCode(input, deps) {
715
792
  await runCommand("claude", [
716
793
  "mcp",
717
794
  "add",
718
- "--transport",
719
- "http",
720
795
  "haven",
721
- input.hostedMcpUrl,
722
- "--header",
723
- `Authorization: Bearer ${input.apiKey}`
724
- ]);
725
- await runCommand("claude", [
726
- "mcp",
727
- "add",
728
- "haven-signer",
729
796
  "--",
730
797
  "npx",
731
798
  "-y",
732
- signerPackageName2(),
733
- "--credentials",
799
+ localMcpPackageName2(),
800
+ "--identity",
801
+ input.identityPath,
802
+ "--signer",
734
803
  input.signerPath
735
804
  ]);
805
+ await runCommand("claude", ["mcp", "remove", "haven-signer"]).catch(() => void 0);
736
806
  return {
737
- hostedConfigured: true,
807
+ hostedConfigured: false,
738
808
  signerConfigured: true,
809
+ localMcpConfigured: true,
810
+ runtimeMcpMode: "local_stdio",
739
811
  target: "Claude Code MCP config",
740
812
  changed: true,
741
813
  restartRequired: true,
742
814
  messages: [
743
- "Updated Haven MCP entries with Claude Code.",
744
- "Restart Claude Code after Haven approval so it can load the Haven tools."
815
+ "Updated local Haven MCP entry with Claude Code.",
816
+ "After Haven approval, restart Claude Code normally so it can load Haven tools."
745
817
  ]
746
818
  };
747
819
  } catch (err) {
748
820
  return {
749
821
  hostedConfigured: false,
750
822
  signerConfigured: false,
823
+ localMcpConfigured: false,
824
+ runtimeMcpMode: "local_stdio",
751
825
  target: "Claude Code MCP config",
752
826
  changed: false,
753
827
  restartRequired: true,
@@ -762,13 +836,28 @@ async function configureClaudeCode(input, deps) {
762
836
  async function defaultRunCommand(command, args) {
763
837
  await execFileAsync(command, args, { timeout: 1e4 });
764
838
  }
765
- function buildProbeResult(hostedConfigured, hostedStatus, signerReady) {
839
+ function buildProbeResult(mode, hostedConfigured, hostedStatus, signerReady, localMcpReady) {
840
+ if (mode === "local_stdio") {
841
+ return localMcpReady ? "local_stdio_mcp_ready" : "local_stdio_mcp_unavailable";
842
+ }
766
843
  const hostedPart = hostedConfigured ? `hosted_${hostedStatus}` : "hosted_not_configured";
767
844
  const signerPart = signerReady ? "local_signer_ready" : "local_signer_unavailable";
768
845
  return `${hostedPart}_${signerPart}`.slice(0, 120);
769
846
  }
847
+ async function resolveLocalMcpConsent(input, messages) {
848
+ if (input.ackLocalTools || input.ackSigner) {
849
+ const status = await acknowledgeLocalMcpConsent(input.identityPath, input.signerPath, (message) => messages.push(message));
850
+ if (status.acknowledged) {
851
+ messages.push("Prepared the local Haven tools acknowledgement.");
852
+ } else {
853
+ messages.push("Local Haven tools acknowledgement still needs attention.");
854
+ }
855
+ return status;
856
+ }
857
+ return getLocalMcpConsentStatus(input.identityPath, input.signerPath);
858
+ }
770
859
  async function resolveSignerConsent(input, messages) {
771
- if (input.ackSigner) {
860
+ if (input.ackSigner || input.ackLocalTools) {
772
861
  const status = await acknowledgeLocalSignerConsent(input.signerPath, (message) => messages.push(message));
773
862
  if (status.acknowledged) {
774
863
  messages.push("Prepared the local Haven signer acknowledgement.");
@@ -781,24 +870,32 @@ async function resolveSignerConsent(input, messages) {
781
870
  }
782
871
  function signerConsentErrorCode(signerCredentialReady, signerConsent) {
783
872
  if (!signerCredentialReady) return "local_signer_credential_unavailable";
784
- if (!signerConsent.acknowledged) return "local_signer_ack_required";
873
+ if (!signerConsent?.acknowledged) return "local_signer_ack_required";
874
+ return void 0;
875
+ }
876
+ function localMcpConsentErrorCode(signerCredentialReady, localMcpConsent) {
877
+ if (!signerCredentialReady) return "local_signer_credential_unavailable";
878
+ if (!localMcpConsent?.acknowledged) return "local_mcp_ack_required";
785
879
  return void 0;
786
880
  }
787
881
  function nextAction(runtime, restartMode, errorCode) {
788
882
  if (errorCode) return "return_to_haven_for_wallet_approval_then_finish_runtime_setup";
789
883
  if (restartMode === "hot-reload") return "return_to_haven_for_wallet_approval";
790
- if (runtime === "codex-cli") return "return_to_haven_for_wallet_approval_then_restart_codex_with_haven_env";
884
+ if (runtime === "codex-cli") return "return_to_haven_for_wallet_approval_then_restart_codex";
791
885
  if (runtime === "claude-code") return "return_to_haven_for_wallet_approval_then_restart_claude_code";
792
886
  if (restartMode === "restart-app") return "return_to_haven_for_wallet_approval_then_restart_app";
793
887
  if (restartMode === "restart-session") return "return_to_haven_for_wallet_approval_then_restart_agent_session";
794
888
  return "return_to_haven_for_wallet_approval_then_configure_runtime";
795
889
  }
796
- function signerPackageName2() {
797
- return `@haven_ai/signer@${SIGNER_VERSION}`;
890
+ function localMcpPackageName2() {
891
+ return `@haven_ai/mcp@${MCP_VERSION}`;
892
+ }
893
+ function usesLocalMcp(runtime) {
894
+ return runtime === "codex-cli" || runtime === "claude-code";
798
895
  }
799
896
 
800
897
  // src/runtime.ts
801
- var CONNECTOR_VERSION = "0.1.1-alpha";
898
+ var CONNECTOR_VERSION = "0.1.2";
802
899
  async function runConnect(options, deps = {}) {
803
900
  const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
804
901
  const api = deps.api ?? createConnectApiClient(options.apiBaseUrl);
@@ -844,9 +941,15 @@ async function runConnect(options, deps = {}) {
844
941
  agentId: registration.agent_id,
845
942
  apiKey: localApiKey,
846
943
  delegateKey: localKey.privateKey,
944
+ delegateAddress: localKey.address,
847
945
  safeAddress: setup.haven_wallet.address,
848
946
  chainId: setup.haven_wallet.chain_id,
849
947
  network: setup.haven_wallet.network,
948
+ agentBudget: setup.agent_budget.map((budget) => ({
949
+ token_symbol: budget.token_symbol,
950
+ allowance_amount: budget.allowance_amount,
951
+ reset_period_min: budget.reset_period_min
952
+ })),
850
953
  apiUrl: options.apiBaseUrl,
851
954
  hostedMcpUrl: registration.hosted_mcp_url,
852
955
  warn: log
@@ -861,17 +964,21 @@ async function runConnect(options, deps = {}) {
861
964
  identityPath: credentialPaths.identityPath,
862
965
  credentialDirectory: credentialPaths.directory,
863
966
  environmentLabel: options.environmentLabel ?? "Local workspace",
864
- ackSigner: options.ackSigner
967
+ ackSigner: options.ackSigner,
968
+ ackLocalTools: options.ackLocalTools
865
969
  });
866
970
  printRuntimeInstall(runtimeInstall, log);
867
971
  try {
868
972
  await api.updateInstallStatus(registration.setup_id, localApiKey, {
869
973
  runtime: runtimeInstall.runtime,
870
974
  connectorVersion,
975
+ runtimeMcpMode: runtimeInstall.runtimeMcpMode,
871
976
  hostedMcpConfigured: runtimeInstall.hostedMcpConfigured,
872
977
  localSignerConfigured: runtimeInstall.localSignerConfigured,
978
+ localMcpConfigured: runtimeInstall.localMcpConfigured,
873
979
  credentialFilesWritten: true,
874
980
  signerAcknowledged: runtimeInstall.signerAcknowledged,
981
+ localMcpAcknowledged: runtimeInstall.localMcpAcknowledged,
875
982
  activationCommandAvailable: Boolean(runtimeInstall.activationCommand),
876
983
  probeResult: runtimeInstall.probeResult,
877
984
  restartRequired: runtimeInstall.restartRequired,
@@ -884,7 +991,7 @@ async function runConnect(options, deps = {}) {
884
991
  }
885
992
  log("Return to Haven to approve the agent rules.");
886
993
  if (runtimeInstall.restartRequired) {
887
- log("Restart this agent session after approval if your runtime loads MCP tools at startup.");
994
+ log("After approval, restart this agent normally so it can load Haven tools.");
888
995
  }
889
996
  return {
890
997
  setupId: registration.setup_id,
@@ -910,10 +1017,12 @@ function secureLogger(log) {
910
1017
  }
911
1018
  function printRuntimeInstall(result, log) {
912
1019
  for (const message of result.messages) log(message);
913
- if (result.hostedMcpConfigured) {
1020
+ if (result.localMcpConfigured) {
1021
+ log("Configured local Haven MCP tools.");
1022
+ } else if (result.hostedMcpConfigured) {
914
1023
  log("Configured hosted Haven MCP identity.");
915
1024
  } else {
916
- log("Hosted Haven MCP identity still needs runtime setup.");
1025
+ log("Haven MCP tools still need runtime setup.");
917
1026
  }
918
1027
  if (result.localSignerConfigured) {
919
1028
  log("Configured local Haven signer.");
@@ -943,8 +1052,11 @@ function parseArgs(argv, env = process.env) {
943
1052
  options.credentialsDir = requireValue(argv, ++i, arg);
944
1053
  } else if (arg === "--environment-label") {
945
1054
  options.environmentLabel = requireValue(argv, ++i, arg);
1055
+ } else if (arg === "--ack-local-tools") {
1056
+ options.ackLocalTools = true;
946
1057
  } else if (arg === "--ack-signer") {
947
1058
  options.ackSigner = true;
1059
+ options.ackLocalTools = true;
948
1060
  } else if (arg === "--version") {
949
1061
  process.stdout.write(`${CONNECTOR_VERSION}
950
1062
  `);
@@ -973,7 +1085,7 @@ function helpText() {
973
1085
  "sends Haven only the public signing address plus a proof signature.",
974
1086
  "",
975
1087
  "Usage:",
976
- " npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --ack-signer --runtime claude-code",
1088
+ " npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --ack-local-tools --runtime claude-code",
977
1089
  "",
978
1090
  "Options:",
979
1091
  " --setup <token> Short-lived setup token from Haven.",
@@ -981,7 +1093,8 @@ function helpText() {
981
1093
  " --runtime <name> Agent runtime hint, such as claude-code, codex-cli, cursor, vscode, or claude-desktop.",
982
1094
  " --credentials-dir <path> Credential directory fallback. Defaults to ~/.haven/agents.",
983
1095
  " --environment-label <text> Non-sensitive label shown in Haven setup review.",
984
- " --ack-signer Write the one-time local signer acknowledgement during setup.",
1096
+ " --ack-local-tools Write the one-time local Haven tools acknowledgement during setup.",
1097
+ " --ack-signer Backward-compatible alias for --ack-local-tools.",
985
1098
  " --help Show this help.",
986
1099
  "",
987
1100
  "The connector never prints the private key and never sends it to Haven."