@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/README.md +31 -0
- package/dist/cli.cjs +142 -23
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +144 -25
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +140 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +135 -2
- package/dist/index.d.ts +135 -2
- package/dist/index.js +136 -26
- package/dist/index.js.map +1 -1
- package/package.json +3 -5
- package/bin/haven-mcp +0 -5
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(
|
|
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(
|
|
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(
|
|
11
|
-
if (
|
|
12
|
-
return loadCredentialsFromFile(
|
|
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:
|
|
118
|
-
haven_pay_x402_quote:
|
|
119
|
-
haven_resume_x402_payment:
|
|
120
|
-
haven_quote_mpp:
|
|
121
|
-
haven_pay_mpp_challenge:
|
|
122
|
-
haven_resume_mpp_payment:
|
|
123
|
-
haven_get_payment_status:
|
|
124
|
-
haven_get_resume_state:
|
|
125
|
-
haven_get_agent:
|
|
126
|
-
haven_get_allowances:
|
|
127
|
-
haven_list_receipts:
|
|
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
|
-
|
|
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
|
|
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:
|
|
484
|
-
version:
|
|
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
|