@haven_ai/mcp 0.1.2-alpha → 0.1.4-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.d.cts CHANGED
@@ -7,7 +7,11 @@ interface HavenCredentialFile {
7
7
  delegateKey: string;
8
8
  agentId?: string;
9
9
  safeAddress?: string;
10
+ delegateAddress?: string;
11
+ chainId?: number;
12
+ network?: string;
10
13
  apiUrl?: string;
14
+ allowanceSummary?: readonly HavenCredentialAllowance[];
11
15
  /**
12
16
  * Absolute path the credentials were loaded from, if any. Set when the
13
17
  * caller pointed at a JSON file via `--credentials` or `HAVEN_CREDENTIALS`;
@@ -17,6 +21,18 @@ interface HavenCredentialFile {
17
21
  * credential path was supplied.
18
22
  */
19
23
  sourcePath?: string;
24
+ identityPath?: string;
25
+ signerPath?: string;
26
+ }
27
+ interface HavenCredentialAllowance {
28
+ token: string;
29
+ amount: string;
30
+ resetMinutes: number | null;
31
+ }
32
+ interface HavenCredentialSource {
33
+ credentialsPath?: string;
34
+ identityPath?: string;
35
+ signerPath?: string;
20
36
  }
21
37
  /**
22
38
  * Load Haven agent credentials for the MCP server.
@@ -34,10 +50,16 @@ interface HavenCredentialFile {
34
50
  * The values still live only in the agent operator's process environment;
35
51
  * Haven's backend never sees the delegate key either way.
36
52
  */
37
- declare function loadCredentials(path?: string | undefined): Promise<HavenCredentialFile>;
53
+ declare function loadCredentials(source?: string | HavenCredentialSource | undefined): Promise<HavenCredentialFile>;
38
54
 
39
55
  type HavenMcpToolName = 'haven_quote_x402' | 'haven_pay_x402_quote' | 'haven_resume_x402_payment' | 'haven_quote_mpp' | 'haven_pay_mpp_challenge' | 'haven_resume_mpp_payment' | 'haven_get_payment_status' | 'haven_get_resume_state' | 'haven_get_agent' | 'haven_get_allowances' | 'haven_list_receipts';
40
56
  declare const toolSchemas: Record<HavenMcpToolName, z.ZodRawShape>;
57
+ /**
58
+ * MCP tool descriptions, composed from the shared semantic source in
59
+ * `@haven_ai/sdk`'s `tool-descriptions.ts`. Keeping both the SDK tool-calling
60
+ * surface and the MCP surface pointed at the same prose source means new
61
+ * guidance lands in both places at once and a parity test can catch drift.
62
+ */
41
63
  declare const toolDescriptions: Record<HavenMcpToolName, string>;
42
64
  interface ToolSuccess<T> {
43
65
  success: true;
@@ -58,8 +80,107 @@ interface ToolFailure {
58
80
  type ToolPayload<T = unknown> = ToolSuccess<T> | ToolFailure;
59
81
  declare function createToolHandlers(haven: HavenClient): Record<HavenMcpToolName, (input: unknown) => Promise<ToolPayload>>;
60
82
 
83
+ /**
84
+ * First-launch consent gate for the Haven MCP server.
85
+ *
86
+ * Why this exists (option A of issue #163): an agent runtime that loads a
87
+ * Haven credential file is about to expose Haven payment tools to a model.
88
+ * Before the server starts taking JSON-RPC calls we want the operator to
89
+ * acknowledge — exactly once per credential + tool set — what those tools
90
+ * can do and what the on-chain allowance cap actually is. The on-chain
91
+ * AllowanceModule remains the policy primitive; this gate is informational
92
+ * rather than enforcement.
93
+ *
94
+ * Resolution:
95
+ * - `HAVEN_MCP_ACK=<hash>` env var matching the current consent hash → pass.
96
+ * - `HAVEN_MCP_ACK=skip` → pass (intended for CI / scripted setups).
97
+ * - sidecar file `<credentials>.ack.json` containing `{ ack: <hash> }` → pass.
98
+ * - `--ack` CLI flag → write the sidecar file, print the consent block, pass.
99
+ * - otherwise → print the consent block to stderr and exit non-zero.
100
+ *
101
+ * The hash binds the api-key prefix to the registered tool set and the
102
+ * agent's current allowance summary, so a configuration change re-triggers
103
+ * the prompt.
104
+ */
105
+ interface ConsentInput {
106
+ apiKeyPrefix: string;
107
+ /** Haven API base URL the credential will hit. */
108
+ apiUrl?: string;
109
+ /** Agent identity from the credential file, when present. */
110
+ agentId?: string;
111
+ /** Haven wallet (Safe) the agent spends from. */
112
+ safeAddress?: string;
113
+ /** Agent's delegate EOA — the local signer. */
114
+ delegateAddress?: string;
115
+ /** Chain the agent operates on. */
116
+ chainId?: number;
117
+ toolNames: readonly HavenMcpToolName[];
118
+ allowanceSummary: readonly {
119
+ token: string;
120
+ amount: string;
121
+ resetMinutes: number | null;
122
+ }[];
123
+ }
124
+ interface ConsentDecision {
125
+ /** True if the gate is satisfied and the server may start. */
126
+ ok: boolean;
127
+ /** Hash representing the current consent surface. */
128
+ hash: string;
129
+ /** Reason the gate accepted (or rejected) the run. */
130
+ reason: 'env_var_match' | 'env_var_skip' | 'ack_file_match' | 'wrote_ack_file' | 'env_var_mismatch' | 'no_acknowledgement';
131
+ }
132
+ interface ConsentOptions {
133
+ /** Path to the credential file; used to locate the sidecar `<path>.ack.json`. */
134
+ credentialsPath?: string;
135
+ /** When true, write the sidecar file with the current hash and accept. */
136
+ writeAck?: boolean;
137
+ /** Override the environment lookup (testing). */
138
+ env?: Record<string, string | undefined>;
139
+ /** Override the writable stream the consent block is printed to (testing). */
140
+ out?: {
141
+ write: (chunk: string) => unknown;
142
+ };
143
+ }
144
+ declare function computeConsentHash(input: ConsentInput): string;
145
+ declare function renderConsentBlock(input: ConsentInput, hash: string): string;
146
+ /** Resolve the consent gate. Does not exit the process; the caller decides. */
147
+ declare function ensureConsent(input: ConsentInput, options?: ConsentOptions): Promise<ConsentDecision>;
148
+ interface CredentialIdentitySeed {
149
+ apiKey: string;
150
+ apiUrl?: string;
151
+ agentId?: string;
152
+ /** Safe address from the credential file, used as a fallback. */
153
+ safeAddress?: string;
154
+ /** Delegate address from the credential file, used before live allowance metadata is available. */
155
+ delegateAddress?: string;
156
+ /** Chain from the credential file, used as a fallback. */
157
+ chainId?: number;
158
+ /** Intended agent budget from the setup flow, used before on-chain approval is visible. */
159
+ allowanceSummary?: readonly {
160
+ token: string;
161
+ amount: string;
162
+ resetMinutes: number | null;
163
+ }[];
164
+ }
165
+ /**
166
+ * Build the consent input from credential identity plus a live allowance
167
+ * lookup. The on-chain (or configured) allowance is what the operator
168
+ * actually cares about — that's the real spend ceiling — but we also bind
169
+ * the hash to the Haven wallet / delegate / chain so a credential swap
170
+ * cannot quietly reuse a prior sidecar acknowledgement.
171
+ *
172
+ * If `getAllowances()` fails (e.g. backend unreachable on first launch) we
173
+ * fall through to whatever identity fields the credential file provided,
174
+ * so the operator at least sees the tool list and the api-key prefix.
175
+ */
176
+ declare function consentInputFromClient(haven: HavenClient, seed: CredentialIdentitySeed, toolNames: readonly HavenMcpToolName[]): Promise<ConsentInput>;
177
+ /** Convenience: the canonical tool list registered by the server. */
178
+ declare function registeredToolNames(): HavenMcpToolName[];
179
+
61
180
  interface HavenMcpServerOptions {
62
181
  credentialsPath?: string;
182
+ identityPath?: string;
183
+ signerPath?: string;
63
184
  credentials?: HavenCredentialFile;
64
185
  /**
65
186
  * When true, write the consent sidecar file (`<credentials>.ack.json`)
@@ -75,6 +196,18 @@ interface HavenMcpServerOptions {
75
196
  }
76
197
  declare function createHavenClient(options?: HavenMcpServerOptions): Promise<HavenClient>;
77
198
  declare function createHavenMcpServer(options?: HavenMcpServerOptions): Promise<McpServer>;
199
+ /**
200
+ * Build an MCP server bound to the supplied Haven client.
201
+ *
202
+ * Each tool dispatch is wrapped in `haven.withRequestContext` so every
203
+ * Haven API request the dispatch issues carries `X-Haven-MCP-Tool: <name>`
204
+ * — and *only* that dispatch's requests see the header. The SDK uses an
205
+ * `AsyncLocalStorage` for the context, so two tool calls running
206
+ * concurrently cannot leak headers into each other and the backend
207
+ * `agent_tool_invocations` rows are always attributed to the right tool.
208
+ */
209
+ declare const MCP_NAME = "@haven_ai/mcp";
210
+ declare const MCP_VERSION = "0.1.4-alpha";
78
211
  declare function runStdioServer(options?: HavenMcpServerOptions): Promise<void>;
79
212
 
80
- export { type HavenCredentialFile, type HavenMcpServerOptions, type HavenMcpToolName, type ToolFailure, type ToolPayload, type ToolSuccess, createHavenClient, createHavenMcpServer, createToolHandlers, loadCredentials, runStdioServer, toolDescriptions, toolSchemas };
213
+ export { type ConsentDecision, type ConsentInput, type ConsentOptions, type CredentialIdentitySeed, type HavenCredentialAllowance, type HavenCredentialFile, type HavenCredentialSource, type HavenMcpServerOptions, type HavenMcpToolName, MCP_NAME, MCP_VERSION, type ToolFailure, type ToolPayload, type ToolSuccess, computeConsentHash, consentInputFromClient, createHavenClient, createHavenMcpServer, createToolHandlers, ensureConsent, loadCredentials, registeredToolNames, renderConsentBlock, runStdioServer, toolDescriptions, toolSchemas };
package/dist/index.d.ts CHANGED
@@ -7,7 +7,11 @@ interface HavenCredentialFile {
7
7
  delegateKey: string;
8
8
  agentId?: string;
9
9
  safeAddress?: string;
10
+ delegateAddress?: string;
11
+ chainId?: number;
12
+ network?: string;
10
13
  apiUrl?: string;
14
+ allowanceSummary?: readonly HavenCredentialAllowance[];
11
15
  /**
12
16
  * Absolute path the credentials were loaded from, if any. Set when the
13
17
  * caller pointed at a JSON file via `--credentials` or `HAVEN_CREDENTIALS`;
@@ -17,6 +21,18 @@ interface HavenCredentialFile {
17
21
  * credential path was supplied.
18
22
  */
19
23
  sourcePath?: string;
24
+ identityPath?: string;
25
+ signerPath?: string;
26
+ }
27
+ interface HavenCredentialAllowance {
28
+ token: string;
29
+ amount: string;
30
+ resetMinutes: number | null;
31
+ }
32
+ interface HavenCredentialSource {
33
+ credentialsPath?: string;
34
+ identityPath?: string;
35
+ signerPath?: string;
20
36
  }
21
37
  /**
22
38
  * Load Haven agent credentials for the MCP server.
@@ -34,10 +50,16 @@ interface HavenCredentialFile {
34
50
  * The values still live only in the agent operator's process environment;
35
51
  * Haven's backend never sees the delegate key either way.
36
52
  */
37
- declare function loadCredentials(path?: string | undefined): Promise<HavenCredentialFile>;
53
+ declare function loadCredentials(source?: string | HavenCredentialSource | undefined): Promise<HavenCredentialFile>;
38
54
 
39
55
  type HavenMcpToolName = 'haven_quote_x402' | 'haven_pay_x402_quote' | 'haven_resume_x402_payment' | 'haven_quote_mpp' | 'haven_pay_mpp_challenge' | 'haven_resume_mpp_payment' | 'haven_get_payment_status' | 'haven_get_resume_state' | 'haven_get_agent' | 'haven_get_allowances' | 'haven_list_receipts';
40
56
  declare const toolSchemas: Record<HavenMcpToolName, z.ZodRawShape>;
57
+ /**
58
+ * MCP tool descriptions, composed from the shared semantic source in
59
+ * `@haven_ai/sdk`'s `tool-descriptions.ts`. Keeping both the SDK tool-calling
60
+ * surface and the MCP surface pointed at the same prose source means new
61
+ * guidance lands in both places at once and a parity test can catch drift.
62
+ */
41
63
  declare const toolDescriptions: Record<HavenMcpToolName, string>;
42
64
  interface ToolSuccess<T> {
43
65
  success: true;
@@ -58,8 +80,107 @@ interface ToolFailure {
58
80
  type ToolPayload<T = unknown> = ToolSuccess<T> | ToolFailure;
59
81
  declare function createToolHandlers(haven: HavenClient): Record<HavenMcpToolName, (input: unknown) => Promise<ToolPayload>>;
60
82
 
83
+ /**
84
+ * First-launch consent gate for the Haven MCP server.
85
+ *
86
+ * Why this exists (option A of issue #163): an agent runtime that loads a
87
+ * Haven credential file is about to expose Haven payment tools to a model.
88
+ * Before the server starts taking JSON-RPC calls we want the operator to
89
+ * acknowledge — exactly once per credential + tool set — what those tools
90
+ * can do and what the on-chain allowance cap actually is. The on-chain
91
+ * AllowanceModule remains the policy primitive; this gate is informational
92
+ * rather than enforcement.
93
+ *
94
+ * Resolution:
95
+ * - `HAVEN_MCP_ACK=<hash>` env var matching the current consent hash → pass.
96
+ * - `HAVEN_MCP_ACK=skip` → pass (intended for CI / scripted setups).
97
+ * - sidecar file `<credentials>.ack.json` containing `{ ack: <hash> }` → pass.
98
+ * - `--ack` CLI flag → write the sidecar file, print the consent block, pass.
99
+ * - otherwise → print the consent block to stderr and exit non-zero.
100
+ *
101
+ * The hash binds the api-key prefix to the registered tool set and the
102
+ * agent's current allowance summary, so a configuration change re-triggers
103
+ * the prompt.
104
+ */
105
+ interface ConsentInput {
106
+ apiKeyPrefix: string;
107
+ /** Haven API base URL the credential will hit. */
108
+ apiUrl?: string;
109
+ /** Agent identity from the credential file, when present. */
110
+ agentId?: string;
111
+ /** Haven wallet (Safe) the agent spends from. */
112
+ safeAddress?: string;
113
+ /** Agent's delegate EOA — the local signer. */
114
+ delegateAddress?: string;
115
+ /** Chain the agent operates on. */
116
+ chainId?: number;
117
+ toolNames: readonly HavenMcpToolName[];
118
+ allowanceSummary: readonly {
119
+ token: string;
120
+ amount: string;
121
+ resetMinutes: number | null;
122
+ }[];
123
+ }
124
+ interface ConsentDecision {
125
+ /** True if the gate is satisfied and the server may start. */
126
+ ok: boolean;
127
+ /** Hash representing the current consent surface. */
128
+ hash: string;
129
+ /** Reason the gate accepted (or rejected) the run. */
130
+ reason: 'env_var_match' | 'env_var_skip' | 'ack_file_match' | 'wrote_ack_file' | 'env_var_mismatch' | 'no_acknowledgement';
131
+ }
132
+ interface ConsentOptions {
133
+ /** Path to the credential file; used to locate the sidecar `<path>.ack.json`. */
134
+ credentialsPath?: string;
135
+ /** When true, write the sidecar file with the current hash and accept. */
136
+ writeAck?: boolean;
137
+ /** Override the environment lookup (testing). */
138
+ env?: Record<string, string | undefined>;
139
+ /** Override the writable stream the consent block is printed to (testing). */
140
+ out?: {
141
+ write: (chunk: string) => unknown;
142
+ };
143
+ }
144
+ declare function computeConsentHash(input: ConsentInput): string;
145
+ declare function renderConsentBlock(input: ConsentInput, hash: string): string;
146
+ /** Resolve the consent gate. Does not exit the process; the caller decides. */
147
+ declare function ensureConsent(input: ConsentInput, options?: ConsentOptions): Promise<ConsentDecision>;
148
+ interface CredentialIdentitySeed {
149
+ apiKey: string;
150
+ apiUrl?: string;
151
+ agentId?: string;
152
+ /** Safe address from the credential file, used as a fallback. */
153
+ safeAddress?: string;
154
+ /** Delegate address from the credential file, used before live allowance metadata is available. */
155
+ delegateAddress?: string;
156
+ /** Chain from the credential file, used as a fallback. */
157
+ chainId?: number;
158
+ /** Intended agent budget from the setup flow, used before on-chain approval is visible. */
159
+ allowanceSummary?: readonly {
160
+ token: string;
161
+ amount: string;
162
+ resetMinutes: number | null;
163
+ }[];
164
+ }
165
+ /**
166
+ * Build the consent input from credential identity plus a live allowance
167
+ * lookup. The on-chain (or configured) allowance is what the operator
168
+ * actually cares about — that's the real spend ceiling — but we also bind
169
+ * the hash to the Haven wallet / delegate / chain so a credential swap
170
+ * cannot quietly reuse a prior sidecar acknowledgement.
171
+ *
172
+ * If `getAllowances()` fails (e.g. backend unreachable on first launch) we
173
+ * fall through to whatever identity fields the credential file provided,
174
+ * so the operator at least sees the tool list and the api-key prefix.
175
+ */
176
+ declare function consentInputFromClient(haven: HavenClient, seed: CredentialIdentitySeed, toolNames: readonly HavenMcpToolName[]): Promise<ConsentInput>;
177
+ /** Convenience: the canonical tool list registered by the server. */
178
+ declare function registeredToolNames(): HavenMcpToolName[];
179
+
61
180
  interface HavenMcpServerOptions {
62
181
  credentialsPath?: string;
182
+ identityPath?: string;
183
+ signerPath?: string;
63
184
  credentials?: HavenCredentialFile;
64
185
  /**
65
186
  * When true, write the consent sidecar file (`<credentials>.ack.json`)
@@ -75,6 +196,18 @@ interface HavenMcpServerOptions {
75
196
  }
76
197
  declare function createHavenClient(options?: HavenMcpServerOptions): Promise<HavenClient>;
77
198
  declare function createHavenMcpServer(options?: HavenMcpServerOptions): Promise<McpServer>;
199
+ /**
200
+ * Build an MCP server bound to the supplied Haven client.
201
+ *
202
+ * Each tool dispatch is wrapped in `haven.withRequestContext` so every
203
+ * Haven API request the dispatch issues carries `X-Haven-MCP-Tool: <name>`
204
+ * — and *only* that dispatch's requests see the header. The SDK uses an
205
+ * `AsyncLocalStorage` for the context, so two tool calls running
206
+ * concurrently cannot leak headers into each other and the backend
207
+ * `agent_tool_invocations` rows are always attributed to the right tool.
208
+ */
209
+ declare const MCP_NAME = "@haven_ai/mcp";
210
+ declare const MCP_VERSION = "0.1.4-alpha";
78
211
  declare function runStdioServer(options?: HavenMcpServerOptions): Promise<void>;
79
212
 
80
- export { type HavenCredentialFile, type HavenMcpServerOptions, type HavenMcpToolName, type ToolFailure, type ToolPayload, type ToolSuccess, createHavenClient, createHavenMcpServer, createToolHandlers, loadCredentials, runStdioServer, toolDescriptions, toolSchemas };
213
+ export { type ConsentDecision, type ConsentInput, type ConsentOptions, type CredentialIdentitySeed, type HavenCredentialAllowance, type HavenCredentialFile, type HavenCredentialSource, type HavenMcpServerOptions, type HavenMcpToolName, MCP_NAME, MCP_VERSION, type ToolFailure, type ToolPayload, type ToolSuccess, computeConsentHash, consentInputFromClient, createHavenClient, createHavenMcpServer, createToolHandlers, ensureConsent, loadCredentials, registeredToolNames, renderConsentBlock, runStdioServer, toolDescriptions, toolSchemas };
package/dist/index.js CHANGED
@@ -1,15 +1,24 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import { HavenPaymentStateError, HavenSigningError, HavenApiError, AgentPaymentNextAction, HavenError, HavenClient } from '@haven_ai/sdk';
4
- import { readFile, mkdir, writeFile } from 'fs/promises';
3
+ import { composeDescription, toolDescriptions as toolDescriptions$1, HavenPaymentStateError, HavenSigningError, HavenApiError, AgentPaymentNextAction, HavenError, HavenClient } from '@haven_ai/sdk';
4
+ import { readFile, stat, mkdir, writeFile } from 'fs/promises';
5
5
  import { z } from 'zod/v3';
6
6
  import { createHash } from 'crypto';
7
7
  import { resolve, dirname } from 'path';
8
8
 
9
9
  // src/server.ts
10
- async function loadCredentials(path = process.env.HAVEN_CREDENTIALS) {
11
- if (path) {
12
- return loadCredentialsFromFile(path);
10
+ async function loadCredentials(source = process.env.HAVEN_CREDENTIALS) {
11
+ if (typeof source === "string") {
12
+ return loadCredentialsFromFile(source);
13
+ }
14
+ if (source?.credentialsPath) {
15
+ return loadCredentialsFromFile(source.credentialsPath);
16
+ }
17
+ if (source?.identityPath || source?.signerPath) {
18
+ if (!source.identityPath || !source.signerPath) {
19
+ throw new Error("Haven split credentials require both --identity and --signer paths.");
20
+ }
21
+ return loadCredentialsFromSplitFiles(source.identityPath, source.signerPath);
13
22
  }
14
23
  const envCreds = loadCredentialsFromEnv();
15
24
  if (envCreds) return envCreds;
@@ -24,6 +33,7 @@ async function loadCredentialsFromFile(path) {
24
33
  } catch (err) {
25
34
  throw new Error(`Could not read Haven credentials at ${path}: ${err instanceof Error ? err.message : String(err)}`);
26
35
  }
36
+ await warnIfCredentialFilePermissive(path);
27
37
  let raw;
28
38
  try {
29
39
  raw = JSON.parse(rawText);
@@ -43,10 +53,57 @@ async function loadCredentialsFromFile(path) {
43
53
  delegateKey,
44
54
  agentId: stringField(raw.agent_id ?? raw.agentId),
45
55
  safeAddress: stringField(raw.safe_address ?? raw.safeAddress),
56
+ delegateAddress: stringField(raw.delegate_address ?? raw.delegateAddress),
57
+ chainId: numberField(raw.chain_id ?? raw.chainId),
58
+ network: stringField(raw.network),
46
59
  apiUrl: stringField(raw.api_url ?? raw.apiUrl),
60
+ allowanceSummary: allowanceSummaryField(raw.allowance_summary ?? raw.allowanceSummary ?? raw.agent_budget ?? raw.agentBudget),
47
61
  sourcePath: path
48
62
  };
49
63
  }
64
+ async function loadCredentialsFromSplitFiles(identityPath, signerPath) {
65
+ const identity = await readJsonFile(identityPath, "Haven identity credentials");
66
+ const signer = await readJsonFile(signerPath, "Haven signer credentials");
67
+ await warnIfCredentialFilePermissive(identityPath);
68
+ await warnIfCredentialFilePermissive(signerPath);
69
+ const apiKey = stringField(identity.api_key ?? identity.apiKey);
70
+ const delegateKey = stringField(signer.delegate_key ?? signer.delegateKey);
71
+ if (!apiKey) {
72
+ throw new Error("Haven identity credentials are missing api_key.");
73
+ }
74
+ if (!delegateKey) {
75
+ throw new Error("Haven signer credentials are missing delegate_key.");
76
+ }
77
+ return {
78
+ apiKey,
79
+ delegateKey,
80
+ agentId: stringField(identity.agent_id ?? identity.agentId ?? signer.agent_id ?? signer.agentId),
81
+ safeAddress: stringField(identity.safe_address ?? identity.safeAddress ?? signer.safe_address ?? signer.safeAddress),
82
+ delegateAddress: stringField(signer.delegate_address ?? signer.delegateAddress ?? identity.delegate_address ?? identity.delegateAddress),
83
+ chainId: numberField(identity.chain_id ?? identity.chainId ?? signer.chain_id ?? signer.chainId),
84
+ network: stringField(identity.network ?? signer.network),
85
+ apiUrl: stringField(identity.api_url ?? identity.apiUrl),
86
+ allowanceSummary: allowanceSummaryField(
87
+ identity.allowance_summary ?? identity.allowanceSummary ?? identity.agent_budget ?? identity.agentBudget
88
+ ),
89
+ sourcePath: identityPath,
90
+ identityPath,
91
+ signerPath
92
+ };
93
+ }
94
+ async function readJsonFile(path, label) {
95
+ let rawText;
96
+ try {
97
+ rawText = await readFile(path, "utf8");
98
+ } catch (err) {
99
+ throw new Error(`Could not read ${label} at ${path}: ${err instanceof Error ? err.message : String(err)}`);
100
+ }
101
+ try {
102
+ return JSON.parse(rawText);
103
+ } catch {
104
+ throw new Error(`${label} must be JSON.`);
105
+ }
106
+ }
50
107
  function loadCredentialsFromEnv() {
51
108
  const apiKey = stringField(process.env.HAVEN_API_KEY);
52
109
  const delegateKey = stringField(process.env.HAVEN_DELEGATE_KEY);
@@ -62,12 +119,54 @@ function loadCredentialsFromEnv() {
62
119
  delegateKey,
63
120
  agentId: stringField(process.env.HAVEN_AGENT_ID),
64
121
  safeAddress: stringField(process.env.HAVEN_SAFE_ADDRESS),
122
+ chainId: numberField(process.env.HAVEN_CHAIN_ID),
123
+ network: stringField(process.env.HAVEN_NETWORK),
65
124
  apiUrl: stringField(process.env.HAVEN_API_URL)
66
125
  };
67
126
  }
68
127
  function stringField(value) {
69
128
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
70
129
  }
130
+ function numberField(value) {
131
+ if (typeof value === "number" && Number.isFinite(value)) return value;
132
+ if (typeof value === "string" && value.trim() && /^\d+$/.test(value.trim())) return Number(value.trim());
133
+ return void 0;
134
+ }
135
+ function allowanceSummaryField(value) {
136
+ if (!Array.isArray(value)) return void 0;
137
+ const allowances = value.flatMap((item) => {
138
+ if (!item || typeof item !== "object") return [];
139
+ const raw = item;
140
+ const token = stringField(raw.token ?? raw.token_symbol ?? raw.tokenSymbol);
141
+ const amount = stringField(raw.amount ?? raw.allowance_amount ?? raw.allowanceAmount);
142
+ const reset = raw.resetMinutes ?? raw.reset_minutes ?? raw.reset_period_min ?? raw.resetPeriodMin;
143
+ if (!token || !amount) return [];
144
+ return [{
145
+ token,
146
+ amount,
147
+ resetMinutes: reset === null ? null : numberField(reset) ?? null
148
+ }];
149
+ });
150
+ return allowances.length > 0 ? allowances : void 0;
151
+ }
152
+ async function warnIfCredentialFilePermissive(path, log = (message) => process.stderr.write(`${message}
153
+ `), platform = process.platform) {
154
+ if (platform === "win32") return;
155
+ let mode;
156
+ try {
157
+ const stats = await stat(path);
158
+ mode = stats.mode;
159
+ } catch {
160
+ return;
161
+ }
162
+ const groupOrOther = mode & 63;
163
+ if (groupOrOther !== 0) {
164
+ const octal = (mode & 511).toString(8).padStart(4, "0");
165
+ log(
166
+ `haven-mcp: warning: credential file at ${path} is readable beyond the owner (mode ${octal}). Run: chmod 600 ${path}`
167
+ );
168
+ }
169
+ }
71
170
  var headersSchema = z.record(z.string(), z.string()).optional();
72
171
  var toolSchemas = {
73
172
  haven_quote_x402: {
@@ -114,17 +213,17 @@ var toolSchemas = {
114
213
  }
115
214
  };
116
215
  var toolDescriptions = {
117
- haven_quote_x402: "Inspect an HTTP 402 x402 paid resource without creating a Haven payment, signature, approval, or on-chain transaction.",
118
- haven_pay_x402_quote: "Pay a previously inspected x402 quote. The delegate key signs locally; Haven only validates and relays signed, on-chain-constrained payment transactions. If approval is needed, preserve the returned resume_state and wait for nextAction=retry_original_x402_request before resuming.",
119
- haven_resume_x402_payment: "Resume an x402 payment after the Haven wallet owner approved the funding step. Accepts either resume_state or payment_id. Only use when get status returns nextAction=retry_original_x402_request; do not start a new merchant session.",
120
- haven_quote_mpp: "Inspect a Haven MPP challenge or paid MPP URL without creating a Haven payment, signature, approval, or on-chain transaction.",
121
- haven_pay_mpp_challenge: "Pay a previously inspected MPP challenge. The delegate key signs locally; Haven only validates and relays signed, on-chain-constrained payment transactions. If approval is needed, preserve resume_state or payment_id.",
122
- haven_resume_mpp_payment: "Resume an MPP payment after the Haven wallet owner approved the funding step. Accepts either resume_state or payment_id and retries the original paid resource.",
123
- haven_get_payment_status: "Fetch structured Haven payment status, including phase and nextAction taxonomy for agent recovery.",
124
- haven_get_resume_state: "Rehydrate stored x402/MPP resume_state by payment_id. This returns context only; signing still happens locally when a resume tool is called.",
125
- haven_get_agent: "Return the authenticated agent identity, Haven wallet, delegate address, chain, and status.",
126
- haven_get_allowances: "Return configured and on-chain allowance state for the authenticated agent. On-chain allowance is the real spend gate.",
127
- haven_list_receipts: "List recent machine-payment receipts/evidence for bookkeeping. Proof header values are not returned."
216
+ haven_quote_x402: composeDescription(toolDescriptions$1.quoteX402),
217
+ haven_pay_x402_quote: composeDescription(toolDescriptions$1.payX402),
218
+ haven_resume_x402_payment: composeDescription(toolDescriptions$1.resumeX402),
219
+ haven_quote_mpp: composeDescription(toolDescriptions$1.quoteMpp),
220
+ haven_pay_mpp_challenge: composeDescription(toolDescriptions$1.payMpp),
221
+ haven_resume_mpp_payment: composeDescription(toolDescriptions$1.resumeMpp),
222
+ haven_get_payment_status: composeDescription(toolDescriptions$1.getPaymentStatus),
223
+ haven_get_resume_state: composeDescription(toolDescriptions$1.getResumeState),
224
+ haven_get_agent: composeDescription(toolDescriptions$1.getAgent),
225
+ haven_get_allowances: composeDescription(toolDescriptions$1.getAllowances),
226
+ haven_list_receipts: composeDescription(toolDescriptions$1.listReceipts)
128
227
  };
129
228
  function createToolHandlers(haven) {
130
229
  return {
@@ -420,10 +519,10 @@ async function writeAckFile(path, hash) {
420
519
  );
421
520
  }
422
521
  async function consentInputFromClient(haven, seed, toolNames) {
423
- let allowanceSummary = [];
522
+ let allowanceSummary = seed.allowanceSummary ?? [];
424
523
  let safeAddress = seed.safeAddress;
425
- let delegateAddress;
426
- let chainId;
524
+ let delegateAddress = seed.delegateAddress;
525
+ let chainId = seed.chainId;
427
526
  try {
428
527
  const summary = await haven.getAllowances();
429
528
  const list = isAllowanceSummary(summary) ? summary.allowances : Array.isArray(summary) ? summary : [];
@@ -432,11 +531,12 @@ async function consentInputFromClient(haven, seed, toolNames) {
432
531
  delegateAddress = summary.delegateAddress;
433
532
  chainId = typeof summary.chainId === "number" ? summary.chainId : chainId;
434
533
  }
435
- allowanceSummary = list.map((a) => ({
534
+ const liveAllowanceSummary = list.map((a) => ({
436
535
  token: a.tokenSymbol ?? "UNKNOWN",
437
536
  amount: a.onchain?.amount ?? a.configuredAmount ?? "0",
438
537
  resetMinutes: typeof a.onchain?.resetTimeMin === "number" ? a.onchain.resetTimeMin : typeof a.resetPeriodMin === "number" ? a.resetPeriodMin : null
439
538
  }));
539
+ allowanceSummary = liveAllowanceSummary;
440
540
  } catch {
441
541
  }
442
542
  return {
@@ -466,7 +566,12 @@ async function createHavenClient(options = {}) {
466
566
  return client;
467
567
  }
468
568
  async function resolveHavenClient(options = {}) {
469
- const credentials = options.credentials ?? await loadCredentials(options.credentialsPath);
569
+ const credentialSource = options.credentialsPath || options.identityPath || options.signerPath ? {
570
+ credentialsPath: options.credentialsPath,
571
+ identityPath: options.identityPath,
572
+ signerPath: options.signerPath
573
+ } : void 0;
574
+ const credentials = options.credentials ?? await loadCredentials(credentialSource);
470
575
  const client = new HavenClient({
471
576
  apiKey: credentials.apiKey,
472
577
  delegateKey: credentials.delegateKey,
@@ -478,10 +583,12 @@ async function createHavenMcpServer(options = {}) {
478
583
  const haven = await createHavenClient(options);
479
584
  return buildMcpServer(haven);
480
585
  }
586
+ var MCP_NAME = "@haven_ai/mcp";
587
+ var MCP_VERSION = "0.1.4-alpha";
481
588
  function buildMcpServer(haven) {
482
589
  const server = new McpServer({
483
- name: "@haven_ai/mcp",
484
- version: "0.1.0-alpha"
590
+ name: MCP_NAME,
591
+ version: MCP_VERSION
485
592
  });
486
593
  const handlers = createToolHandlers(haven);
487
594
  const registerTool = server.tool.bind(server);
@@ -521,11 +628,14 @@ async function runConsentGate(haven, credentials, options) {
521
628
  apiKey: credentials.apiKey,
522
629
  apiUrl: credentials.apiUrl,
523
630
  agentId: credentials.agentId,
524
- safeAddress: credentials.safeAddress
631
+ safeAddress: credentials.safeAddress,
632
+ delegateAddress: credentials.delegateAddress,
633
+ chainId: credentials.chainId,
634
+ allowanceSummary: credentials.allowanceSummary
525
635
  },
526
636
  toolNames
527
637
  );
528
- const credentialsPath = options.credentialsPath ?? credentials.sourcePath;
638
+ const credentialsPath = options.identityPath ?? options.credentialsPath ?? credentials.sourcePath;
529
639
  return ensureConsent(input, {
530
640
  credentialsPath,
531
641
  writeAck: options.writeAck
@@ -543,6 +653,6 @@ function toMcpResult(payload) {
543
653
  };
544
654
  }
545
655
 
546
- export { createHavenClient, createHavenMcpServer, createToolHandlers, loadCredentials, runStdioServer, toolDescriptions, toolSchemas };
656
+ export { MCP_NAME, MCP_VERSION, computeConsentHash, consentInputFromClient, createHavenClient, createHavenMcpServer, createToolHandlers, ensureConsent, loadCredentials, registeredToolNames, renderConsentBlock, runStdioServer, toolDescriptions, toolSchemas };
547
657
  //# sourceMappingURL=index.js.map
548
658
  //# sourceMappingURL=index.js.map