@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.cjs CHANGED
@@ -7,6 +7,7 @@ var os = require('os');
7
7
  var path = require('path');
8
8
  var child_process = require('child_process');
9
9
  var util = require('util');
10
+ var mcp = require('@haven_ai/mcp');
10
11
  var signer = require('@haven_ai/signer');
11
12
 
12
13
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -50,10 +51,13 @@ function createConnectApiClient(baseUrl, fetchImpl = fetch) {
50
51
  body: JSON.stringify({
51
52
  runtime: input.runtime,
52
53
  connector_version: input.connectorVersion,
54
+ runtime_mcp_mode: input.runtimeMcpMode,
53
55
  hosted_mcp_configured: input.hostedMcpConfigured,
54
56
  local_signer_configured: input.localSignerConfigured,
57
+ local_mcp_configured: input.localMcpConfigured,
55
58
  credential_files_written: input.credentialFilesWritten,
56
59
  signer_acknowledged: input.signerAcknowledged,
60
+ local_mcp_acknowledged: input.localMcpAcknowledged,
57
61
  activation_command_available: input.activationCommandAvailable,
58
62
  probe_result: input.probeResult,
59
63
  restart_required: input.restartRequired,
@@ -136,6 +140,7 @@ async function writeCredentialFiles(input) {
136
140
  signerPath,
137
141
  {
138
142
  delegate_key: input.delegateKey,
143
+ delegate_address: input.delegateAddress,
139
144
  agent_id: input.agentId,
140
145
  safe_address: input.safeAddress,
141
146
  chain_id: input.chainId,
@@ -155,6 +160,7 @@ async function writeCredentialFiles(input) {
155
160
  network: input.network,
156
161
  api_url: input.apiUrl,
157
162
  hosted_mcp_url: input.hostedMcpUrl,
163
+ agent_budget: input.agentBudget,
158
164
  note: "Haven API key identifies the agent only. It cannot spend without the local signer key and on-chain Haven wallet rules."
159
165
  },
160
166
  input.warn
@@ -215,6 +221,8 @@ async function writeRuntimeConfig(input) {
215
221
  return {
216
222
  hostedConfigured: false,
217
223
  signerConfigured: false,
224
+ localMcpConfigured: false,
225
+ runtimeMcpMode: "manual",
218
226
  target: "manual runtime setup",
219
227
  changed: false,
220
228
  restartRequired: true,
@@ -256,17 +264,13 @@ function mergeJsonMcpConfig(existingJson, serverRoot, hostedServer, signerServer
256
264
  return `${JSON.stringify(config, null, 2)}
257
265
  `;
258
266
  }
259
- function mergeCodexToml(existingToml, hostedMcpUrl, signerPath) {
267
+ function mergeCodexToml(existingToml, identityPath, signerPath) {
260
268
  let next = removeTomlTable(removeTomlTable(existingToml, "mcp_servers.haven"), "mcp_servers.haven_signer");
261
269
  next = next.trimEnd();
262
270
  const block = [
263
271
  "[mcp_servers.haven]",
264
- `url = ${tomlString(hostedMcpUrl)}`,
265
- 'bearer_token_env_var = "HAVEN_TOKEN"',
266
- "",
267
- "[mcp_servers.haven_signer]",
268
272
  'command = "npx"',
269
- `args = ["-y", ${tomlString(signerPackageName())}, "--credentials", ${tomlString(signerPath)}]`
273
+ `args = ["-y", ${tomlString(localMcpPackageName())}, "--identity", ${tomlString(identityPath)}, "--signer", ${tomlString(signerPath)}]`
270
274
  ].join("\n");
271
275
  return `${next ? `${next}
272
276
 
@@ -286,6 +290,8 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
286
290
  return {
287
291
  hostedConfigured: true,
288
292
  signerConfigured: true,
293
+ localMcpConfigured: false,
294
+ runtimeMcpMode: "hosted_plus_signer",
289
295
  target: configTargetLabel(input.runtime),
290
296
  changed: existing !== merged,
291
297
  restartRequired: input.runtime === "claude-desktop",
@@ -295,6 +301,8 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
295
301
  return {
296
302
  hostedConfigured: false,
297
303
  signerConfigured: false,
304
+ localMcpConfigured: false,
305
+ runtimeMcpMode: "hosted_plus_signer",
298
306
  target: configTargetLabel(input.runtime),
299
307
  changed: false,
300
308
  restartRequired: true,
@@ -305,41 +313,29 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
305
313
  }
306
314
  async function writeCodexConfig(input) {
307
315
  const target = codexConfigPath(input.homeDir);
308
- const envTarget = path.join(input.credentialDirectory, "identity.env");
309
- const launchTarget = path.join(input.credentialDirectory, "start-codex.sh");
310
316
  try {
311
317
  const existing = await readOptional(target);
312
- const merged = mergeCodexToml(existing ?? "", input.hostedMcpUrl, input.signerPath);
318
+ const merged = mergeCodexToml(existing ?? "", input.identityPath, input.signerPath);
313
319
  await writeOwnerOnlyText(target, merged);
314
- await writeOwnerOnlyText(envTarget, `export HAVEN_TOKEN=${shellToken(input.apiKey)}
315
- `);
316
- await writeOwnerExecutableText(
317
- launchTarget,
318
- [
319
- "#!/bin/sh",
320
- "set -eu",
321
- `. ${shellToken(envTarget)}`,
322
- 'exec codex "$@"',
323
- ""
324
- ].join("\n")
325
- );
326
320
  return {
327
- hostedConfigured: true,
321
+ hostedConfigured: false,
328
322
  signerConfigured: true,
323
+ localMcpConfigured: true,
324
+ runtimeMcpMode: "local_stdio",
329
325
  target: "Codex CLI config",
330
326
  changed: existing !== merged,
331
327
  restartRequired: true,
332
- activationCommand: shellToken(launchTarget),
333
328
  messages: [
334
- "Updated Haven MCP entries in Codex CLI config.",
335
- "Wrote the hosted MCP token to a private env file.",
336
- `Restart Codex with: ${shellToken(launchTarget)}`
329
+ "Updated local Haven MCP entry in Codex CLI config.",
330
+ "After Haven approval, restart Codex normally so it can load Haven tools."
337
331
  ]
338
332
  };
339
333
  } catch (err) {
340
334
  return {
341
335
  hostedConfigured: false,
342
336
  signerConfigured: false,
337
+ localMcpConfigured: false,
338
+ runtimeMcpMode: "local_stdio",
343
339
  target: "Codex CLI config",
344
340
  changed: false,
345
341
  restartRequired: true,
@@ -361,11 +357,6 @@ async function writeOwnerOnlyText(path$1, value) {
361
357
  await promises.writeFile(path$1, value, { mode: 384 });
362
358
  await promises.chmod(path$1, 384).catch(() => void 0);
363
359
  }
364
- async function writeOwnerExecutableText(path$1, value) {
365
- await promises.mkdir(path.dirname(path$1), { recursive: true, mode: 448 });
366
- await promises.writeFile(path$1, value, { mode: 448 });
367
- await promises.chmod(path$1, 448).catch(() => void 0);
368
- }
369
360
  function parseJsonObject(value) {
370
361
  const parsed = JSON.parse(value);
371
362
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -394,9 +385,6 @@ function removeTomlTable(toml, table) {
394
385
  function tomlString(value) {
395
386
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
396
387
  }
397
- function shellToken(value) {
398
- return `'${value.replace(/'/g, `'\\''`)}'`;
399
- }
400
388
  function cursorConfigPath(homeDir = os.homedir()) {
401
389
  return path.resolve(homeDir, ".cursor", "mcp.json");
402
390
  }
@@ -434,6 +422,85 @@ function configTargetLabel(runtime) {
434
422
  function signerPackageName() {
435
423
  return `@haven_ai/signer@${signer.SIGNER_VERSION}`;
436
424
  }
425
+ function localMcpPackageName() {
426
+ return `@haven_ai/mcp@${mcp.MCP_VERSION}`;
427
+ }
428
+ async function acknowledgeLocalMcpConsent(identityPath, signerPath, log) {
429
+ try {
430
+ const input = await buildLocalMcpConsentInput(identityPath, signerPath);
431
+ const decision = await mcp.ensureConsent(input, {
432
+ credentialsPath: identityPath,
433
+ writeAck: true,
434
+ out: log ? { write: (chunk) => writeLogChunk(log, chunk) } : void 0
435
+ });
436
+ return {
437
+ acknowledged: decision.ok,
438
+ hash: decision.hash,
439
+ reason: decision.reason
440
+ };
441
+ } catch (err) {
442
+ return {
443
+ acknowledged: false,
444
+ error: err instanceof Error ? err.message : String(err)
445
+ };
446
+ }
447
+ }
448
+ async function getLocalMcpConsentStatus(identityPath, signerPath) {
449
+ try {
450
+ const input = await buildLocalMcpConsentInput(identityPath, signerPath);
451
+ const hash = mcp.computeConsentHash(input);
452
+ const stored = await readLocalMcpAckFile(localMcpAckPath(identityPath));
453
+ if (stored === hash) {
454
+ return { acknowledged: true, hash, reason: "ack_file_match" };
455
+ }
456
+ return {
457
+ acknowledged: false,
458
+ hash,
459
+ reason: stored ? "ack_file_mismatch" : "ack_file_missing"
460
+ };
461
+ } catch (err) {
462
+ return {
463
+ acknowledged: false,
464
+ error: err instanceof Error ? err.message : String(err)
465
+ };
466
+ }
467
+ }
468
+ function localMcpAckPath(identityPath) {
469
+ return path.resolve(`${identityPath}.ack.json`);
470
+ }
471
+ async function buildLocalMcpConsentInput(identityPath, signerPath) {
472
+ const credentials = await mcp.loadCredentials({ identityPath, signerPath });
473
+ const unavailableDuringSetup = {
474
+ getAllowances: async () => {
475
+ throw new Error("Haven approval is not complete yet.");
476
+ }
477
+ };
478
+ return mcp.consentInputFromClient(
479
+ unavailableDuringSetup,
480
+ {
481
+ apiKey: credentials.apiKey,
482
+ apiUrl: credentials.apiUrl,
483
+ agentId: credentials.agentId,
484
+ safeAddress: credentials.safeAddress,
485
+ delegateAddress: credentials.delegateAddress,
486
+ chainId: credentials.chainId,
487
+ allowanceSummary: credentials.allowanceSummary
488
+ },
489
+ mcp.registeredToolNames()
490
+ );
491
+ }
492
+ async function readLocalMcpAckFile(path) {
493
+ try {
494
+ const parsed = JSON.parse(await promises.readFile(path, "utf8"));
495
+ return typeof parsed.ack === "string" ? parsed.ack : null;
496
+ } catch {
497
+ return null;
498
+ }
499
+ }
500
+ function writeLogChunk(log, chunk) {
501
+ const message = String(chunk).trimEnd();
502
+ if (message) log(message);
503
+ }
437
504
  async function probeHostedMcpTools(apiKey, hostedMcpUrl, fetchImpl = fetch) {
438
505
  let response;
439
506
  try {
@@ -588,7 +655,7 @@ async function acknowledgeLocalSignerConsent(signerPath, log) {
588
655
  const decision = await signer.ensureSignerConsent(input, {
589
656
  credentialsPath: signerPath,
590
657
  writeAck: true,
591
- out: log ? { write: (chunk) => writeLogChunk(log, chunk) } : void 0
658
+ out: log ? { write: (chunk) => writeLogChunk2(log, chunk) } : void 0
592
659
  });
593
660
  return {
594
661
  acknowledged: decision.ok,
@@ -647,7 +714,7 @@ async function readSignerAckFile(path) {
647
714
  return null;
648
715
  }
649
716
  }
650
- function writeLogChunk(log, chunk) {
717
+ function writeLogChunk2(log, chunk) {
651
718
  const message = String(chunk).trimEnd();
652
719
  if (message) log(message);
653
720
  }
@@ -657,23 +724,28 @@ var execFileAsync = util.promisify(child_process.execFile);
657
724
  async function installRuntime(input, deps = {}) {
658
725
  const runtime = normalizeRuntime(input.runtime, deps.env);
659
726
  const profile = runtimeProfile(runtime, deps.env);
660
- const signerConsentMessages = [];
661
- const signerConsent = await resolveSignerConsent(input, signerConsentMessages);
727
+ const localRuntime = usesLocalMcp(runtime);
728
+ const consentMessages = [];
729
+ const localMcpConsent = localRuntime ? await resolveLocalMcpConsent(input, consentMessages) : void 0;
730
+ const signerConsent = localRuntime ? void 0 : await resolveSignerConsent(input, consentMessages);
662
731
  if (runtime === "other") {
663
732
  const signerCredentialReady2 = await probeLocalSignerCredential(input.signerPath);
664
- const signerReady = signerCredentialReady2 && signerConsent.acknowledged;
733
+ const signerReady = signerCredentialReady2 && signerConsent?.acknowledged;
665
734
  return {
666
735
  runtime,
736
+ runtimeMcpMode: "manual",
667
737
  hostedMcpConfigured: false,
668
738
  localSignerConfigured: false,
739
+ localMcpConfigured: false,
669
740
  probeResult: signerReady ? "manual_runtime_setup_required_local_signer_ready" : "manual_runtime_setup_required_local_signer_unavailable",
670
741
  restartRequired: true,
671
742
  nextUserAction: "return_to_haven_for_wallet_approval_then_configure_runtime",
672
743
  errorCode: "manual_runtime_setup_required",
673
744
  configTarget: "manual runtime setup",
674
- signerAcknowledged: signerConsent.acknowledged,
745
+ signerAcknowledged: signerConsent?.acknowledged,
746
+ localMcpAcknowledged: false,
675
747
  messages: [
676
- ...signerConsentMessages,
748
+ ...consentMessages,
677
749
  "Runtime was not recognized. Keep the local credentials and add Haven MCP entries manually after wallet approval."
678
750
  ]
679
751
  };
@@ -682,6 +754,7 @@ async function installRuntime(input, deps = {}) {
682
754
  runtime,
683
755
  hostedMcpUrl: input.hostedMcpUrl,
684
756
  apiKey: input.apiKey,
757
+ identityPath: input.identityPath,
685
758
  signerPath: input.signerPath,
686
759
  credentialDirectory: input.credentialDirectory,
687
760
  homeDir: deps.homeDir
@@ -691,21 +764,25 @@ async function installRuntime(input, deps = {}) {
691
764
  probeLocalSignerCredential(input.signerPath)
692
765
  ]);
693
766
  const hostedOk = configResult.hostedConfigured && hostedProbe.status !== "unauthorized";
694
- const signerOk = configResult.signerConfigured && signerCredentialReady && signerConsent.acknowledged;
767
+ const localMcpOk = configResult.runtimeMcpMode === "local_stdio" && configResult.localMcpConfigured && signerCredentialReady && Boolean(localMcpConsent?.acknowledged);
768
+ const signerOk = configResult.runtimeMcpMode === "local_stdio" ? localMcpOk : configResult.signerConfigured && signerCredentialReady && Boolean(signerConsent?.acknowledged);
695
769
  const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
696
- const errorCode = configResult.errorCode ?? signerConsentErrorCode(signerCredentialReady, signerConsent);
770
+ const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpConsentErrorCode(signerCredentialReady, localMcpConsent) : signerConsentErrorCode(signerCredentialReady, signerConsent));
697
771
  return {
698
772
  runtime,
773
+ runtimeMcpMode: configResult.runtimeMcpMode,
699
774
  hostedMcpConfigured: hostedOk,
700
775
  localSignerConfigured: signerOk,
701
- probeResult: buildProbeResult(configResult.hostedConfigured, hostedProbe.status, signerOk),
776
+ localMcpConfigured: localMcpOk,
777
+ probeResult: buildProbeResult(configResult.runtimeMcpMode, configResult.hostedConfigured, hostedProbe.status, signerOk, localMcpOk),
702
778
  restartRequired,
703
779
  nextUserAction: nextAction(runtime, profile.restartMode, errorCode),
704
780
  errorCode,
705
781
  configTarget: configResult.target,
706
- signerAcknowledged: signerConsent.acknowledged,
782
+ signerAcknowledged: signerConsent?.acknowledged,
783
+ localMcpAcknowledged: localMcpConsent?.acknowledged,
707
784
  activationCommand: configResult.activationCommand,
708
- messages: [...signerConsentMessages, ...configResult.messages]
785
+ messages: [...consentMessages, ...configResult.messages]
709
786
  };
710
787
  }
711
788
  function runtimeInstallCapabilities(runtime, env = process.env) {
@@ -721,39 +798,36 @@ async function configureClaudeCode(input, deps) {
721
798
  await runCommand("claude", [
722
799
  "mcp",
723
800
  "add",
724
- "--transport",
725
- "http",
726
801
  "haven",
727
- input.hostedMcpUrl,
728
- "--header",
729
- `Authorization: Bearer ${input.apiKey}`
730
- ]);
731
- await runCommand("claude", [
732
- "mcp",
733
- "add",
734
- "haven-signer",
735
802
  "--",
736
803
  "npx",
737
804
  "-y",
738
- signerPackageName2(),
739
- "--credentials",
805
+ localMcpPackageName2(),
806
+ "--identity",
807
+ input.identityPath,
808
+ "--signer",
740
809
  input.signerPath
741
810
  ]);
811
+ await runCommand("claude", ["mcp", "remove", "haven-signer"]).catch(() => void 0);
742
812
  return {
743
- hostedConfigured: true,
813
+ hostedConfigured: false,
744
814
  signerConfigured: true,
815
+ localMcpConfigured: true,
816
+ runtimeMcpMode: "local_stdio",
745
817
  target: "Claude Code MCP config",
746
818
  changed: true,
747
819
  restartRequired: true,
748
820
  messages: [
749
- "Updated Haven MCP entries with Claude Code.",
750
- "Restart Claude Code after Haven approval so it can load the Haven tools."
821
+ "Updated local Haven MCP entry with Claude Code.",
822
+ "After Haven approval, restart Claude Code normally so it can load Haven tools."
751
823
  ]
752
824
  };
753
825
  } catch (err) {
754
826
  return {
755
827
  hostedConfigured: false,
756
828
  signerConfigured: false,
829
+ localMcpConfigured: false,
830
+ runtimeMcpMode: "local_stdio",
757
831
  target: "Claude Code MCP config",
758
832
  changed: false,
759
833
  restartRequired: true,
@@ -768,13 +842,28 @@ async function configureClaudeCode(input, deps) {
768
842
  async function defaultRunCommand(command, args) {
769
843
  await execFileAsync(command, args, { timeout: 1e4 });
770
844
  }
771
- function buildProbeResult(hostedConfigured, hostedStatus, signerReady) {
845
+ function buildProbeResult(mode, hostedConfigured, hostedStatus, signerReady, localMcpReady) {
846
+ if (mode === "local_stdio") {
847
+ return localMcpReady ? "local_stdio_mcp_ready" : "local_stdio_mcp_unavailable";
848
+ }
772
849
  const hostedPart = hostedConfigured ? `hosted_${hostedStatus}` : "hosted_not_configured";
773
850
  const signerPart = signerReady ? "local_signer_ready" : "local_signer_unavailable";
774
851
  return `${hostedPart}_${signerPart}`.slice(0, 120);
775
852
  }
853
+ async function resolveLocalMcpConsent(input, messages) {
854
+ if (input.ackLocalTools || input.ackSigner) {
855
+ const status = await acknowledgeLocalMcpConsent(input.identityPath, input.signerPath, (message) => messages.push(message));
856
+ if (status.acknowledged) {
857
+ messages.push("Prepared the local Haven tools acknowledgement.");
858
+ } else {
859
+ messages.push("Local Haven tools acknowledgement still needs attention.");
860
+ }
861
+ return status;
862
+ }
863
+ return getLocalMcpConsentStatus(input.identityPath, input.signerPath);
864
+ }
776
865
  async function resolveSignerConsent(input, messages) {
777
- if (input.ackSigner) {
866
+ if (input.ackSigner || input.ackLocalTools) {
778
867
  const status = await acknowledgeLocalSignerConsent(input.signerPath, (message) => messages.push(message));
779
868
  if (status.acknowledged) {
780
869
  messages.push("Prepared the local Haven signer acknowledgement.");
@@ -787,24 +876,32 @@ async function resolveSignerConsent(input, messages) {
787
876
  }
788
877
  function signerConsentErrorCode(signerCredentialReady, signerConsent) {
789
878
  if (!signerCredentialReady) return "local_signer_credential_unavailable";
790
- if (!signerConsent.acknowledged) return "local_signer_ack_required";
879
+ if (!signerConsent?.acknowledged) return "local_signer_ack_required";
880
+ return void 0;
881
+ }
882
+ function localMcpConsentErrorCode(signerCredentialReady, localMcpConsent) {
883
+ if (!signerCredentialReady) return "local_signer_credential_unavailable";
884
+ if (!localMcpConsent?.acknowledged) return "local_mcp_ack_required";
791
885
  return void 0;
792
886
  }
793
887
  function nextAction(runtime, restartMode, errorCode) {
794
888
  if (errorCode) return "return_to_haven_for_wallet_approval_then_finish_runtime_setup";
795
889
  if (restartMode === "hot-reload") return "return_to_haven_for_wallet_approval";
796
- if (runtime === "codex-cli") return "return_to_haven_for_wallet_approval_then_restart_codex_with_haven_env";
890
+ if (runtime === "codex-cli") return "return_to_haven_for_wallet_approval_then_restart_codex";
797
891
  if (runtime === "claude-code") return "return_to_haven_for_wallet_approval_then_restart_claude_code";
798
892
  if (restartMode === "restart-app") return "return_to_haven_for_wallet_approval_then_restart_app";
799
893
  if (restartMode === "restart-session") return "return_to_haven_for_wallet_approval_then_restart_agent_session";
800
894
  return "return_to_haven_for_wallet_approval_then_configure_runtime";
801
895
  }
802
- function signerPackageName2() {
803
- return `@haven_ai/signer@${signer.SIGNER_VERSION}`;
896
+ function localMcpPackageName2() {
897
+ return `@haven_ai/mcp@${mcp.MCP_VERSION}`;
898
+ }
899
+ function usesLocalMcp(runtime) {
900
+ return runtime === "codex-cli" || runtime === "claude-code";
804
901
  }
805
902
 
806
903
  // src/runtime.ts
807
- var CONNECTOR_VERSION = "0.1.1-alpha";
904
+ var CONNECTOR_VERSION = "0.1.2";
808
905
  async function runConnect(options, deps = {}) {
809
906
  const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
810
907
  const api = deps.api ?? createConnectApiClient(options.apiBaseUrl);
@@ -850,9 +947,15 @@ async function runConnect(options, deps = {}) {
850
947
  agentId: registration.agent_id,
851
948
  apiKey: localApiKey,
852
949
  delegateKey: localKey.privateKey,
950
+ delegateAddress: localKey.address,
853
951
  safeAddress: setup.haven_wallet.address,
854
952
  chainId: setup.haven_wallet.chain_id,
855
953
  network: setup.haven_wallet.network,
954
+ agentBudget: setup.agent_budget.map((budget) => ({
955
+ token_symbol: budget.token_symbol,
956
+ allowance_amount: budget.allowance_amount,
957
+ reset_period_min: budget.reset_period_min
958
+ })),
856
959
  apiUrl: options.apiBaseUrl,
857
960
  hostedMcpUrl: registration.hosted_mcp_url,
858
961
  warn: log
@@ -867,17 +970,21 @@ async function runConnect(options, deps = {}) {
867
970
  identityPath: credentialPaths.identityPath,
868
971
  credentialDirectory: credentialPaths.directory,
869
972
  environmentLabel: options.environmentLabel ?? "Local workspace",
870
- ackSigner: options.ackSigner
973
+ ackSigner: options.ackSigner,
974
+ ackLocalTools: options.ackLocalTools
871
975
  });
872
976
  printRuntimeInstall(runtimeInstall, log);
873
977
  try {
874
978
  await api.updateInstallStatus(registration.setup_id, localApiKey, {
875
979
  runtime: runtimeInstall.runtime,
876
980
  connectorVersion,
981
+ runtimeMcpMode: runtimeInstall.runtimeMcpMode,
877
982
  hostedMcpConfigured: runtimeInstall.hostedMcpConfigured,
878
983
  localSignerConfigured: runtimeInstall.localSignerConfigured,
984
+ localMcpConfigured: runtimeInstall.localMcpConfigured,
879
985
  credentialFilesWritten: true,
880
986
  signerAcknowledged: runtimeInstall.signerAcknowledged,
987
+ localMcpAcknowledged: runtimeInstall.localMcpAcknowledged,
881
988
  activationCommandAvailable: Boolean(runtimeInstall.activationCommand),
882
989
  probeResult: runtimeInstall.probeResult,
883
990
  restartRequired: runtimeInstall.restartRequired,
@@ -890,7 +997,7 @@ async function runConnect(options, deps = {}) {
890
997
  }
891
998
  log("Return to Haven to approve the agent rules.");
892
999
  if (runtimeInstall.restartRequired) {
893
- log("Restart this agent session after approval if your runtime loads MCP tools at startup.");
1000
+ log("After approval, restart this agent normally so it can load Haven tools.");
894
1001
  }
895
1002
  return {
896
1003
  setupId: registration.setup_id,
@@ -916,10 +1023,12 @@ function secureLogger(log) {
916
1023
  }
917
1024
  function printRuntimeInstall(result, log) {
918
1025
  for (const message of result.messages) log(message);
919
- if (result.hostedMcpConfigured) {
1026
+ if (result.localMcpConfigured) {
1027
+ log("Configured local Haven MCP tools.");
1028
+ } else if (result.hostedMcpConfigured) {
920
1029
  log("Configured hosted Haven MCP identity.");
921
1030
  } else {
922
- log("Hosted Haven MCP identity still needs runtime setup.");
1031
+ log("Haven MCP tools still need runtime setup.");
923
1032
  }
924
1033
  if (result.localSignerConfigured) {
925
1034
  log("Configured local Haven signer.");
@@ -949,8 +1058,11 @@ function parseArgs(argv, env = process.env) {
949
1058
  options.credentialsDir = requireValue(argv, ++i, arg);
950
1059
  } else if (arg === "--environment-label") {
951
1060
  options.environmentLabel = requireValue(argv, ++i, arg);
1061
+ } else if (arg === "--ack-local-tools") {
1062
+ options.ackLocalTools = true;
952
1063
  } else if (arg === "--ack-signer") {
953
1064
  options.ackSigner = true;
1065
+ options.ackLocalTools = true;
954
1066
  } else if (arg === "--version") {
955
1067
  process.stdout.write(`${CONNECTOR_VERSION}
956
1068
  `);
@@ -979,7 +1091,7 @@ function helpText() {
979
1091
  "sends Haven only the public signing address plus a proof signature.",
980
1092
  "",
981
1093
  "Usage:",
982
- " npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --ack-signer --runtime claude-code",
1094
+ " npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --ack-local-tools --runtime claude-code",
983
1095
  "",
984
1096
  "Options:",
985
1097
  " --setup <token> Short-lived setup token from Haven.",
@@ -987,7 +1099,8 @@ function helpText() {
987
1099
  " --runtime <name> Agent runtime hint, such as claude-code, codex-cli, cursor, vscode, or claude-desktop.",
988
1100
  " --credentials-dir <path> Credential directory fallback. Defaults to ~/.haven/agents.",
989
1101
  " --environment-label <text> Non-sensitive label shown in Haven setup review.",
990
- " --ack-signer Write the one-time local signer acknowledgement during setup.",
1102
+ " --ack-local-tools Write the one-time local Haven tools acknowledgement during setup.",
1103
+ " --ack-signer Backward-compatible alias for --ack-local-tools.",
991
1104
  " --help Show this help.",
992
1105
  "",
993
1106
  "The connector never prints the private key and never sends it to Haven."