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