@haven_ai/mcp 0.1.0-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,556 @@
1
+ 'use strict';
2
+
3
+ var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
4
+ var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
5
+ var sdk = require('@haven_ai/sdk');
6
+ var promises = require('fs/promises');
7
+ var v3 = require('zod/v3');
8
+ var crypto = require('crypto');
9
+ var path = require('path');
10
+
11
+ // src/server.ts
12
+ async function loadCredentials(path = process.env.HAVEN_CREDENTIALS) {
13
+ if (path) {
14
+ return loadCredentialsFromFile(path);
15
+ }
16
+ const envCreds = loadCredentialsFromEnv();
17
+ if (envCreds) return envCreds;
18
+ throw new Error(
19
+ "No Haven credentials found. Set HAVEN_CREDENTIALS to a Haven agent credential JSON file, pass --credentials <path>, or set HAVEN_API_KEY and HAVEN_DELEGATE_KEY environment variables."
20
+ );
21
+ }
22
+ async function loadCredentialsFromFile(path) {
23
+ let rawText;
24
+ try {
25
+ rawText = await promises.readFile(path, "utf8");
26
+ } catch (err) {
27
+ throw new Error(`Could not read Haven credentials at ${path}: ${err instanceof Error ? err.message : String(err)}`);
28
+ }
29
+ let raw;
30
+ try {
31
+ raw = JSON.parse(rawText);
32
+ } catch {
33
+ throw new Error("Haven credentials must be JSON with api_key and delegate_key fields.");
34
+ }
35
+ const apiKey = stringField(raw.api_key ?? raw.apiKey);
36
+ const delegateKey = stringField(raw.delegate_key ?? raw.delegateKey);
37
+ if (!apiKey) {
38
+ throw new Error("Haven credentials are missing api_key.");
39
+ }
40
+ if (!delegateKey) {
41
+ throw new Error("Haven MCP requires delegate_key so payments can be signed locally.");
42
+ }
43
+ return {
44
+ apiKey,
45
+ delegateKey,
46
+ agentId: stringField(raw.agent_id ?? raw.agentId),
47
+ safeAddress: stringField(raw.safe_address ?? raw.safeAddress),
48
+ apiUrl: stringField(raw.api_url ?? raw.apiUrl),
49
+ sourcePath: path
50
+ };
51
+ }
52
+ function loadCredentialsFromEnv() {
53
+ const apiKey = stringField(process.env.HAVEN_API_KEY);
54
+ const delegateKey = stringField(process.env.HAVEN_DELEGATE_KEY);
55
+ if (!apiKey && !delegateKey) return null;
56
+ if (!apiKey) {
57
+ throw new Error("HAVEN_DELEGATE_KEY is set but HAVEN_API_KEY is missing.");
58
+ }
59
+ if (!delegateKey) {
60
+ throw new Error("HAVEN_API_KEY is set but HAVEN_DELEGATE_KEY is missing. Haven MCP requires a delegate key so payments can be signed locally.");
61
+ }
62
+ return {
63
+ apiKey,
64
+ delegateKey,
65
+ agentId: stringField(process.env.HAVEN_AGENT_ID),
66
+ safeAddress: stringField(process.env.HAVEN_SAFE_ADDRESS),
67
+ apiUrl: stringField(process.env.HAVEN_API_URL)
68
+ };
69
+ }
70
+ function stringField(value) {
71
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
72
+ }
73
+ var headersSchema = v3.z.record(v3.z.string(), v3.z.string()).optional();
74
+ var toolSchemas = {
75
+ haven_quote_x402: {
76
+ url: v3.z.string().url(),
77
+ method: v3.z.string().optional(),
78
+ headers: headersSchema,
79
+ body: v3.z.string().optional(),
80
+ idempotencyKey: v3.z.string().optional()
81
+ },
82
+ haven_pay_x402_quote: {
83
+ quote: v3.z.unknown(),
84
+ idempotencyKey: v3.z.string().optional()
85
+ },
86
+ haven_resume_x402_payment: {
87
+ payment_id: v3.z.string().optional(),
88
+ resume_state: v3.z.unknown().optional()
89
+ },
90
+ haven_quote_mpp: {
91
+ url: v3.z.string().url().optional(),
92
+ challenge: v3.z.unknown().optional(),
93
+ method: v3.z.string().optional(),
94
+ headers: headersSchema,
95
+ body: v3.z.string().optional(),
96
+ idempotencyKey: v3.z.string().optional()
97
+ },
98
+ haven_pay_mpp_challenge: {
99
+ quote: v3.z.unknown(),
100
+ idempotencyKey: v3.z.string().optional()
101
+ },
102
+ haven_resume_mpp_payment: {
103
+ payment_id: v3.z.string().optional(),
104
+ resume_state: v3.z.unknown().optional()
105
+ },
106
+ haven_get_payment_status: {
107
+ payment_id: v3.z.string()
108
+ },
109
+ haven_get_resume_state: {
110
+ payment_id: v3.z.string()
111
+ },
112
+ haven_get_agent: {},
113
+ haven_get_allowances: {},
114
+ haven_list_receipts: {
115
+ limit: v3.z.number().int().min(1).max(100).optional()
116
+ }
117
+ };
118
+ var toolDescriptions = {
119
+ haven_quote_x402: "Inspect an HTTP 402 x402 paid resource without creating a Haven payment, signature, approval, or on-chain transaction.",
120
+ 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.",
121
+ 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.",
122
+ haven_quote_mpp: "Inspect a Haven MPP challenge or paid MPP URL without creating a Haven payment, signature, approval, or on-chain transaction.",
123
+ 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.",
124
+ 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.",
125
+ haven_get_payment_status: "Fetch structured Haven payment status, including phase and nextAction taxonomy for agent recovery.",
126
+ 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.",
127
+ haven_get_agent: "Return the authenticated agent identity, Haven wallet, delegate address, chain, and status.",
128
+ haven_get_allowances: "Return configured and on-chain allowance state for the authenticated agent. On-chain allowance is the real spend gate.",
129
+ haven_list_receipts: "List recent machine-payment receipts/evidence for bookkeeping. Proof header values are not returned."
130
+ };
131
+ function createToolHandlers(haven) {
132
+ return {
133
+ haven_quote_x402: async (input) => {
134
+ const args = objectInput("haven_quote_x402", input);
135
+ return runTool(async () => haven.quoteX402(args.url, requestInit(args), { idempotencyKey: args.idempotencyKey }));
136
+ },
137
+ haven_pay_x402_quote: async (input) => {
138
+ const args = objectInput("haven_pay_x402_quote", input);
139
+ return runTool(async () => {
140
+ const response = await haven.payX402Quote(args.quote, { idempotencyKey: args.idempotencyKey });
141
+ return responsePayload(response);
142
+ });
143
+ },
144
+ haven_resume_x402_payment: async (input) => {
145
+ const args = objectInput("haven_resume_x402_payment", input);
146
+ return runTool(async () => {
147
+ const state = await resumeState(args, "x402");
148
+ const response = await haven.resumeX402Payment(state);
149
+ return responsePayload(response);
150
+ });
151
+ },
152
+ haven_quote_mpp: async (input) => {
153
+ const args = objectInput("haven_quote_mpp", input);
154
+ return runTool(async () => {
155
+ if (args.challenge) {
156
+ return haven.quoteMpp(args.challenge, requestInit(args), {
157
+ idempotencyKey: args.idempotencyKey
158
+ });
159
+ }
160
+ if (!args.url) {
161
+ throw new sdk.HavenApiError("haven_quote_mpp requires either url or challenge.", 400);
162
+ }
163
+ return haven.quoteMpp(args.url, requestInit(args), { idempotencyKey: args.idempotencyKey });
164
+ });
165
+ },
166
+ haven_pay_mpp_challenge: async (input) => {
167
+ const args = objectInput("haven_pay_mpp_challenge", input);
168
+ return runTool(async () => {
169
+ const response = await haven.payMppChallenge(args.quote, { idempotencyKey: args.idempotencyKey });
170
+ return responsePayload(response);
171
+ });
172
+ },
173
+ haven_resume_mpp_payment: async (input) => {
174
+ const args = objectInput("haven_resume_mpp_payment", input);
175
+ return runTool(async () => {
176
+ const state = await resumeState(args, "mpp");
177
+ const response = await haven.resumeMppPayment(state);
178
+ return responsePayload(response);
179
+ });
180
+ },
181
+ haven_get_payment_status: async (input) => {
182
+ const args = objectInput("haven_get_payment_status", input);
183
+ return runTool(async () => haven.getPaymentStatus(args.payment_id));
184
+ },
185
+ haven_get_resume_state: async (input) => {
186
+ const args = objectInput("haven_get_resume_state", input);
187
+ return runTool(async () => haven.getResumeState(args.payment_id));
188
+ },
189
+ haven_get_agent: async () => runTool(async () => haven.getAgent()),
190
+ haven_get_allowances: async () => runTool(async () => haven.getAllowances()),
191
+ haven_list_receipts: async (input) => {
192
+ const args = objectInput("haven_list_receipts", input);
193
+ return runTool(async () => haven.listReceipts({ limit: args.limit }));
194
+ }
195
+ };
196
+ async function resumeState(args, rail) {
197
+ const state = args.resume_state ?? (args.payment_id ? await haven.getResumeState(args.payment_id) : void 0);
198
+ if (!state || typeof state !== "object") {
199
+ throw new sdk.HavenApiError(`haven_resume_${rail}_payment requires resume_state or payment_id.`, 400);
200
+ }
201
+ if (state.rail !== rail) {
202
+ throw new sdk.HavenApiError(`Resume state is not for the ${rail} rail.`, 409, state);
203
+ }
204
+ return state;
205
+ }
206
+ }
207
+ function objectInput(name, input) {
208
+ return v3.z.object(toolSchemas[name]).parse(input ?? {});
209
+ }
210
+ function requestInit(input) {
211
+ if (!input.method && !input.headers && input.body === void 0) return void 0;
212
+ return {
213
+ method: input.method,
214
+ headers: input.headers,
215
+ body: input.body
216
+ };
217
+ }
218
+ async function runTool(fn) {
219
+ try {
220
+ return { success: true, data: await fn() };
221
+ } catch (err) {
222
+ return normalizeError(err);
223
+ }
224
+ }
225
+ async function responsePayload(response) {
226
+ const text = await response.text();
227
+ return {
228
+ status: response.status,
229
+ statusText: response.statusText,
230
+ headers: Object.fromEntries(response.headers.entries()),
231
+ body: parseMaybeJson(text)
232
+ };
233
+ }
234
+ function parseMaybeJson(text) {
235
+ if (!text) return null;
236
+ try {
237
+ return JSON.parse(text);
238
+ } catch {
239
+ return text;
240
+ }
241
+ }
242
+ function normalizeError(err) {
243
+ if (err instanceof sdk.HavenPaymentStateError) {
244
+ return {
245
+ success: false,
246
+ code: err.code,
247
+ message: err.message,
248
+ statusCode: err.statusCode,
249
+ paymentId: err.paymentId,
250
+ status: err.status,
251
+ phase: err.phase,
252
+ nextAction: err.nextAction,
253
+ resume_state: err.resumeState,
254
+ body: err.body
255
+ };
256
+ }
257
+ if (err instanceof sdk.HavenSigningError) {
258
+ return {
259
+ success: false,
260
+ code: err.code,
261
+ message: err.message
262
+ };
263
+ }
264
+ if (err instanceof sdk.HavenApiError) {
265
+ const body = err.body;
266
+ return {
267
+ success: false,
268
+ code: err.code,
269
+ message: err.message,
270
+ statusCode: err.statusCode,
271
+ paymentId: err.paymentId,
272
+ phase: stringOrUndefined(body?.phase),
273
+ nextAction: stringOrUndefined(body?.nextAction) ?? stringOrUndefined(body?.next_action) ?? sdk.AgentPaymentNextAction.StopAndTellUser,
274
+ body: err.body
275
+ };
276
+ }
277
+ if (err instanceof sdk.HavenError) {
278
+ return {
279
+ success: false,
280
+ code: err.code,
281
+ message: err.message,
282
+ statusCode: err.statusCode,
283
+ paymentId: err.paymentId
284
+ };
285
+ }
286
+ return {
287
+ success: false,
288
+ code: "UNKNOWN_ERROR",
289
+ message: err instanceof Error ? err.message : String(err),
290
+ nextAction: sdk.AgentPaymentNextAction.StopAndTellUser
291
+ };
292
+ }
293
+ function stringOrUndefined(value) {
294
+ return typeof value === "string" ? value : void 0;
295
+ }
296
+ function computeConsentHash(input) {
297
+ const allowanceCanonical = [...input.allowanceSummary].map((a) => `${a.token}:${a.amount}:${a.resetMinutes ?? "none"}`).sort().join("|");
298
+ const toolCanonical = [...input.toolNames].sort().join(",");
299
+ const identity = [
300
+ input.apiKeyPrefix,
301
+ input.apiUrl ?? "",
302
+ input.agentId ?? "",
303
+ (input.safeAddress ?? "").toLowerCase(),
304
+ (input.delegateAddress ?? "").toLowerCase(),
305
+ input.chainId ?? ""
306
+ ].join("|");
307
+ return crypto.createHash("sha256").update(`${identity}
308
+ ${toolCanonical}
309
+ ${allowanceCanonical}`).digest("hex").slice(0, 16);
310
+ }
311
+ function renderConsentBlock(input, hash) {
312
+ const lines = [
313
+ "",
314
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
315
+ "Haven MCP server \u2014 first-launch consent",
316
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
317
+ "",
318
+ `Credential: ${input.apiKeyPrefix}\u2026`
319
+ ];
320
+ if (input.apiUrl) lines.push(`Haven API: ${input.apiUrl}`);
321
+ if (input.agentId) lines.push(`Agent ID: ${input.agentId}`);
322
+ if (input.safeAddress) lines.push(`Haven wallet (Safe): ${input.safeAddress}`);
323
+ if (input.delegateAddress) lines.push(`Delegate (local signer): ${input.delegateAddress}`);
324
+ if (typeof input.chainId === "number") lines.push(`Chain ID: ${input.chainId}`);
325
+ lines.push("");
326
+ lines.push("Confirm these match the Haven wallet and chain you intend the");
327
+ lines.push("agent runtime to use. The delegate above is the only key that");
328
+ lines.push("signs payments \u2014 it lives in this process, not on Haven's backend.");
329
+ lines.push("");
330
+ lines.push("Tools this server will expose to your agent runtime:");
331
+ for (const name of input.toolNames) {
332
+ lines.push(` \u2022 ${name}`);
333
+ lines.push(` ${toolDescriptions[name]}`);
334
+ }
335
+ lines.push("");
336
+ if (input.allowanceSummary.length === 0) {
337
+ lines.push("On-chain allowance: none configured.");
338
+ lines.push(" Any payment will queue for manual approval. The on-chain");
339
+ lines.push(" Safe AllowanceModule is the real spend gate.");
340
+ } else {
341
+ lines.push("On-chain allowance (the real spend gate, Safe AllowanceModule):");
342
+ for (const a of input.allowanceSummary) {
343
+ const reset = a.resetMinutes ? ` per ${a.resetMinutes} min` : " (no reset)";
344
+ lines.push(` \u2022 up to ${a.amount} ${a.token}${reset}`);
345
+ }
346
+ }
347
+ lines.push("");
348
+ lines.push("Anything above the on-chain allowance pauses for owner approval");
349
+ lines.push("in the Haven dashboard. Revoking the agent on-chain disables");
350
+ lines.push("every MCP tool that would spend.");
351
+ lines.push("");
352
+ lines.push(`Consent hash: ${hash}`);
353
+ lines.push("");
354
+ lines.push("To acknowledge, EITHER:");
355
+ lines.push(` \u2022 set HAVEN_MCP_ACK=${hash} in this process's environment, OR`);
356
+ lines.push(" \u2022 re-run with --ack to write the acknowledgement next to your");
357
+ lines.push(" credential file (sidecar <credentials>.ack.json).");
358
+ lines.push("");
359
+ lines.push("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
360
+ lines.push("");
361
+ return lines.join("\n");
362
+ }
363
+ async function ensureConsent(input, options = {}) {
364
+ const env = options.env ?? process.env;
365
+ const out = options.out ?? process.stderr;
366
+ const hash = computeConsentHash(input);
367
+ if (env.HAVEN_MCP_ACK === "skip") {
368
+ return { ok: true, hash, reason: "env_var_skip" };
369
+ }
370
+ if (typeof env.HAVEN_MCP_ACK === "string" && env.HAVEN_MCP_ACK.length > 0) {
371
+ if (env.HAVEN_MCP_ACK === hash) {
372
+ return { ok: true, hash, reason: "env_var_match" };
373
+ }
374
+ out.write(renderConsentBlock(input, hash));
375
+ out.write(
376
+ `HAVEN_MCP_ACK was set but did not match the current consent hash.
377
+ Expected: ${hash}
378
+ Got: ${env.HAVEN_MCP_ACK}
379
+ Re-acknowledge with the new hash above, or run with --ack.
380
+
381
+ `
382
+ );
383
+ return { ok: false, hash, reason: "env_var_mismatch" };
384
+ }
385
+ const ackPath = sidecarPath(options.credentialsPath);
386
+ if (ackPath) {
387
+ const stored = await readAckFile(ackPath);
388
+ if (stored?.ack === hash) {
389
+ return { ok: true, hash, reason: "ack_file_match" };
390
+ }
391
+ }
392
+ if (options.writeAck && ackPath) {
393
+ out.write(renderConsentBlock(input, hash));
394
+ await writeAckFile(ackPath, hash);
395
+ out.write(`Wrote acknowledgement to ${ackPath}
396
+
397
+ `);
398
+ return { ok: true, hash, reason: "wrote_ack_file" };
399
+ }
400
+ out.write(renderConsentBlock(input, hash));
401
+ return { ok: false, hash, reason: "no_acknowledgement" };
402
+ }
403
+ function sidecarPath(credentialsPath) {
404
+ if (!credentialsPath) return null;
405
+ return path.resolve(`${credentialsPath}.ack.json`);
406
+ }
407
+ async function readAckFile(path) {
408
+ try {
409
+ const raw = await promises.readFile(path, "utf8");
410
+ const parsed = JSON.parse(raw);
411
+ return { ack: typeof parsed.ack === "string" ? parsed.ack : void 0 };
412
+ } catch {
413
+ return null;
414
+ }
415
+ }
416
+ async function writeAckFile(path$1, hash) {
417
+ await promises.mkdir(path.dirname(path$1), { recursive: true });
418
+ await promises.writeFile(
419
+ path$1,
420
+ JSON.stringify({ ack: hash, at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
421
+ "utf8"
422
+ );
423
+ }
424
+ async function consentInputFromClient(haven, seed, toolNames) {
425
+ let allowanceSummary = [];
426
+ let safeAddress = seed.safeAddress;
427
+ let delegateAddress;
428
+ let chainId;
429
+ try {
430
+ const summary = await haven.getAllowances();
431
+ const list = isAllowanceSummary(summary) ? summary.allowances : Array.isArray(summary) ? summary : [];
432
+ if (isAllowanceSummary(summary)) {
433
+ safeAddress = summary.safeAddress ?? safeAddress;
434
+ delegateAddress = summary.delegateAddress;
435
+ chainId = typeof summary.chainId === "number" ? summary.chainId : chainId;
436
+ }
437
+ allowanceSummary = list.map((a) => ({
438
+ token: a.tokenSymbol ?? "UNKNOWN",
439
+ amount: a.onchain?.amount ?? a.configuredAmount ?? "0",
440
+ resetMinutes: typeof a.onchain?.resetTimeMin === "number" ? a.onchain.resetTimeMin : typeof a.resetPeriodMin === "number" ? a.resetPeriodMin : null
441
+ }));
442
+ } catch {
443
+ }
444
+ return {
445
+ apiKeyPrefix: derivePrefix(seed.apiKey),
446
+ apiUrl: seed.apiUrl,
447
+ agentId: seed.agentId,
448
+ safeAddress,
449
+ delegateAddress,
450
+ chainId,
451
+ toolNames,
452
+ allowanceSummary
453
+ };
454
+ }
455
+ function isAllowanceSummary(value) {
456
+ return typeof value === "object" && value !== null && "allowances" in value && Array.isArray(value.allowances);
457
+ }
458
+ function derivePrefix(apiKey) {
459
+ return apiKey.slice(0, 12);
460
+ }
461
+ function registeredToolNames() {
462
+ return Object.keys(toolSchemas);
463
+ }
464
+
465
+ // src/server.ts
466
+ async function createHavenClient(options = {}) {
467
+ const { client } = await resolveHavenClient(options);
468
+ return client;
469
+ }
470
+ async function resolveHavenClient(options = {}) {
471
+ const credentials = options.credentials ?? await loadCredentials(options.credentialsPath);
472
+ const client = new sdk.HavenClient({
473
+ apiKey: credentials.apiKey,
474
+ delegateKey: credentials.delegateKey,
475
+ baseUrl: credentials.apiUrl
476
+ });
477
+ return { client, credentials };
478
+ }
479
+ async function createHavenMcpServer(options = {}) {
480
+ const haven = await createHavenClient(options);
481
+ return buildMcpServer(haven);
482
+ }
483
+ function buildMcpServer(haven) {
484
+ const server = new mcp_js.McpServer({
485
+ name: "@haven_ai/mcp",
486
+ version: "0.1.0-alpha"
487
+ });
488
+ const handlers = createToolHandlers(haven);
489
+ const registerTool = server.tool.bind(server);
490
+ for (const name of Object.keys(toolSchemas)) {
491
+ registerTool(
492
+ name,
493
+ toolDescriptions[name],
494
+ toolSchemas[name],
495
+ async (args) => haven.withRequestContext(
496
+ { "X-Haven-MCP-Tool": name },
497
+ async () => toMcpResult(await handlers[name](args))
498
+ )
499
+ );
500
+ }
501
+ return server;
502
+ }
503
+ async function runStdioServer(options = {}) {
504
+ const { client: haven, credentials } = await resolveHavenClient(options);
505
+ if (!options.skipConsent) {
506
+ const decision = await runConsentGate(haven, credentials, options);
507
+ if (!decision.ok) {
508
+ const err = new Error(
509
+ decision.reason === "env_var_mismatch" ? "Haven MCP consent acknowledgement does not match the current configuration." : "Haven MCP server requires a one-time consent acknowledgement before starting."
510
+ );
511
+ err.code = "HAVEN_MCP_NO_CONSENT";
512
+ throw err;
513
+ }
514
+ }
515
+ const server = buildMcpServer(haven);
516
+ await server.connect(new stdio_js.StdioServerTransport());
517
+ }
518
+ async function runConsentGate(haven, credentials, options) {
519
+ const toolNames = registeredToolNames();
520
+ const input = await consentInputFromClient(
521
+ haven,
522
+ {
523
+ apiKey: credentials.apiKey,
524
+ apiUrl: credentials.apiUrl,
525
+ agentId: credentials.agentId,
526
+ safeAddress: credentials.safeAddress
527
+ },
528
+ toolNames
529
+ );
530
+ const credentialsPath = options.credentialsPath ?? credentials.sourcePath;
531
+ return ensureConsent(input, {
532
+ credentialsPath,
533
+ writeAck: options.writeAck
534
+ });
535
+ }
536
+ function toMcpResult(payload) {
537
+ return {
538
+ isError: !payload.success,
539
+ content: [
540
+ {
541
+ type: "text",
542
+ text: JSON.stringify(payload, null, 2)
543
+ }
544
+ ]
545
+ };
546
+ }
547
+
548
+ exports.createHavenClient = createHavenClient;
549
+ exports.createHavenMcpServer = createHavenMcpServer;
550
+ exports.createToolHandlers = createToolHandlers;
551
+ exports.loadCredentials = loadCredentials;
552
+ exports.runStdioServer = runStdioServer;
553
+ exports.toolDescriptions = toolDescriptions;
554
+ exports.toolSchemas = toolSchemas;
555
+ //# sourceMappingURL=index.cjs.map
556
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/credentials.ts","../src/tools.ts","../src/consent.ts","../src/server.ts"],"names":["readFile","z","HavenApiError","HavenPaymentStateError","HavenSigningError","AgentPaymentNextAction","HavenError","createHash","resolve","path","mkdir","dirname","writeFile","HavenClient","McpServer","StdioServerTransport"],"mappings":"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","file":"index.cjs","sourcesContent":["import { readFile } from 'node:fs/promises'\n\nexport interface HavenCredentialFile {\n apiKey: string\n delegateKey: string\n agentId?: string\n safeAddress?: string\n apiUrl?: string\n /**\n * Absolute path the credentials were loaded from, if any. Set when the\n * caller pointed at a JSON file via `--credentials` or `HAVEN_CREDENTIALS`;\n * left undefined when credentials came purely from environment variables.\n * The MCP server uses this to locate the consent sidecar\n * (`<sourcePath>.ack.json`) so `--ack` works regardless of how the\n * credential path was supplied.\n */\n sourcePath?: string\n}\n\ninterface RawCredentialFile {\n api_key?: unknown\n apiKey?: unknown\n delegate_key?: unknown\n delegateKey?: unknown\n agent_id?: unknown\n agentId?: unknown\n safe_address?: unknown\n safeAddress?: unknown\n api_url?: unknown\n apiUrl?: unknown\n}\n\n/**\n * Load Haven agent credentials for the MCP server.\n *\n * Resolution order — earlier sources win, later sources are fallbacks:\n *\n * 1. Explicit `path` argument (typically from `--credentials <path>`).\n * 2. `HAVEN_CREDENTIALS` env var pointing at a credential JSON file.\n * 3. Inline env vars: `HAVEN_API_KEY` + `HAVEN_DELEGATE_KEY` (+ optional\n * `HAVEN_AGENT_ID`, `HAVEN_SAFE_ADDRESS`, `HAVEN_API_URL`).\n *\n * The inline-env path exists so that runtime config snippets emitted by the\n * Haven dashboard (Claude Desktop / Cursor / generic MCP configs) can be a\n * single self-contained block — paste the snippet, restart the runtime, done.\n * The values still live only in the agent operator's process environment;\n * Haven's backend never sees the delegate key either way.\n */\nexport async function loadCredentials(\n path: string | undefined = process.env.HAVEN_CREDENTIALS,\n): Promise<HavenCredentialFile> {\n if (path) {\n return loadCredentialsFromFile(path)\n }\n\n const envCreds = loadCredentialsFromEnv()\n if (envCreds) return envCreds\n\n throw new Error(\n 'No Haven credentials found. Set HAVEN_CREDENTIALS to a Haven agent credential JSON file, ' +\n 'pass --credentials <path>, or set HAVEN_API_KEY and HAVEN_DELEGATE_KEY environment variables.',\n )\n}\n\nasync function loadCredentialsFromFile(path: string): Promise<HavenCredentialFile> {\n let rawText: string\n try {\n rawText = await readFile(path, 'utf8')\n } catch (err) {\n throw new Error(`Could not read Haven credentials at ${path}: ${err instanceof Error ? err.message : String(err)}`)\n }\n\n let raw: RawCredentialFile\n try {\n raw = JSON.parse(rawText) as RawCredentialFile\n } catch {\n throw new Error('Haven credentials must be JSON with api_key and delegate_key fields.')\n }\n\n const apiKey = stringField(raw.api_key ?? raw.apiKey)\n const delegateKey = stringField(raw.delegate_key ?? raw.delegateKey)\n\n if (!apiKey) {\n throw new Error('Haven credentials are missing api_key.')\n }\n if (!delegateKey) {\n throw new Error('Haven MCP requires delegate_key so payments can be signed locally.')\n }\n\n return {\n apiKey,\n delegateKey,\n agentId: stringField(raw.agent_id ?? raw.agentId),\n safeAddress: stringField(raw.safe_address ?? raw.safeAddress),\n apiUrl: stringField(raw.api_url ?? raw.apiUrl),\n sourcePath: path,\n }\n}\n\nfunction loadCredentialsFromEnv(): HavenCredentialFile | null {\n const apiKey = stringField(process.env.HAVEN_API_KEY)\n const delegateKey = stringField(process.env.HAVEN_DELEGATE_KEY)\n\n if (!apiKey && !delegateKey) return null\n\n if (!apiKey) {\n throw new Error('HAVEN_DELEGATE_KEY is set but HAVEN_API_KEY is missing.')\n }\n if (!delegateKey) {\n throw new Error('HAVEN_API_KEY is set but HAVEN_DELEGATE_KEY is missing. Haven MCP requires a delegate key so payments can be signed locally.')\n }\n\n return {\n apiKey,\n delegateKey,\n agentId: stringField(process.env.HAVEN_AGENT_ID),\n safeAddress: stringField(process.env.HAVEN_SAFE_ADDRESS),\n apiUrl: stringField(process.env.HAVEN_API_URL),\n }\n}\n\nfunction stringField(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() ? value.trim() : undefined\n}\n","import {\n AgentPaymentNextAction,\n HavenApiError,\n HavenClient,\n HavenError,\n HavenPaymentStateError,\n HavenSigningError,\n type MachinePaymentChallenge,\n type MppQuote,\n type MppResumeState,\n type X402Quote,\n type X402ResumeState,\n} from '@haven_ai/sdk'\nimport { z } from 'zod/v3'\n\nconst headersSchema = z.record(z.string(), z.string()).optional()\n\nexport type HavenMcpToolName =\n | 'haven_quote_x402'\n | 'haven_pay_x402_quote'\n | 'haven_resume_x402_payment'\n | 'haven_quote_mpp'\n | 'haven_pay_mpp_challenge'\n | 'haven_resume_mpp_payment'\n | 'haven_get_payment_status'\n | 'haven_get_resume_state'\n | 'haven_get_agent'\n | 'haven_get_allowances'\n | 'haven_list_receipts'\n\nexport const toolSchemas: Record<HavenMcpToolName, z.ZodRawShape> = {\n haven_quote_x402: {\n url: z.string().url(),\n method: z.string().optional(),\n headers: headersSchema,\n body: z.string().optional(),\n idempotencyKey: z.string().optional(),\n },\n haven_pay_x402_quote: {\n quote: z.unknown(),\n idempotencyKey: z.string().optional(),\n },\n haven_resume_x402_payment: {\n payment_id: z.string().optional(),\n resume_state: z.unknown().optional(),\n },\n haven_quote_mpp: {\n url: z.string().url().optional(),\n challenge: z.unknown().optional(),\n method: z.string().optional(),\n headers: headersSchema,\n body: z.string().optional(),\n idempotencyKey: z.string().optional(),\n },\n haven_pay_mpp_challenge: {\n quote: z.unknown(),\n idempotencyKey: z.string().optional(),\n },\n haven_resume_mpp_payment: {\n payment_id: z.string().optional(),\n resume_state: z.unknown().optional(),\n },\n haven_get_payment_status: {\n payment_id: z.string(),\n },\n haven_get_resume_state: {\n payment_id: z.string(),\n },\n haven_get_agent: {},\n haven_get_allowances: {},\n haven_list_receipts: {\n limit: z.number().int().min(1).max(100).optional(),\n },\n}\n\nexport const toolDescriptions: Record<HavenMcpToolName, string> = {\n haven_quote_x402:\n 'Inspect an HTTP 402 x402 paid resource without creating a Haven payment, signature, approval, or on-chain transaction.',\n haven_pay_x402_quote:\n '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.',\n haven_resume_x402_payment:\n '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.',\n haven_quote_mpp:\n 'Inspect a Haven MPP challenge or paid MPP URL without creating a Haven payment, signature, approval, or on-chain transaction.',\n haven_pay_mpp_challenge:\n '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.',\n haven_resume_mpp_payment:\n '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.',\n haven_get_payment_status:\n 'Fetch structured Haven payment status, including phase and nextAction taxonomy for agent recovery.',\n haven_get_resume_state:\n 'Rehydrate stored x402/MPP resume_state by payment_id. This returns context only; signing still happens locally when a resume tool is called.',\n haven_get_agent:\n 'Return the authenticated agent identity, Haven wallet, delegate address, chain, and status.',\n haven_get_allowances:\n 'Return configured and on-chain allowance state for the authenticated agent. On-chain allowance is the real spend gate.',\n haven_list_receipts:\n 'List recent machine-payment receipts/evidence for bookkeeping. Proof header values are not returned.',\n}\n\nexport interface ToolSuccess<T> {\n success: true\n data: T\n}\n\nexport interface ToolFailure {\n success: false\n code: string\n message: string\n statusCode?: number\n paymentId?: string\n status?: string\n phase?: string\n nextAction?: string\n resume_state?: unknown\n body?: unknown\n}\n\nexport type ToolPayload<T = unknown> = ToolSuccess<T> | ToolFailure\n\nexport function createToolHandlers(haven: HavenClient): Record<HavenMcpToolName, (input: unknown) => Promise<ToolPayload>> {\n return {\n haven_quote_x402: async (input) => {\n const args = objectInput('haven_quote_x402', input)\n return runTool(async () => haven.quoteX402(args.url, requestInit(args), { idempotencyKey: args.idempotencyKey }))\n },\n\n haven_pay_x402_quote: async (input) => {\n const args = objectInput('haven_pay_x402_quote', input)\n return runTool(async () => {\n const response = await haven.payX402Quote(args.quote as X402Quote, { idempotencyKey: args.idempotencyKey })\n return responsePayload(response)\n })\n },\n\n haven_resume_x402_payment: async (input) => {\n const args = objectInput('haven_resume_x402_payment', input)\n return runTool(async () => {\n const state = await resumeState(args, 'x402')\n const response = await haven.resumeX402Payment(state)\n return responsePayload(response)\n })\n },\n\n haven_quote_mpp: async (input) => {\n const args = objectInput('haven_quote_mpp', input)\n return runTool(async () => {\n if (args.challenge) {\n return haven.quoteMpp(args.challenge as MachinePaymentChallenge, requestInit(args), {\n idempotencyKey: args.idempotencyKey,\n })\n }\n if (!args.url) {\n throw new HavenApiError('haven_quote_mpp requires either url or challenge.', 400)\n }\n return haven.quoteMpp(args.url, requestInit(args), { idempotencyKey: args.idempotencyKey })\n })\n },\n\n haven_pay_mpp_challenge: async (input) => {\n const args = objectInput('haven_pay_mpp_challenge', input)\n return runTool(async () => {\n const response = await haven.payMppChallenge(args.quote as MppQuote, { idempotencyKey: args.idempotencyKey })\n return responsePayload(response)\n })\n },\n\n haven_resume_mpp_payment: async (input) => {\n const args = objectInput('haven_resume_mpp_payment', input)\n return runTool(async () => {\n const state = await resumeState(args, 'mpp')\n const response = await haven.resumeMppPayment(state)\n return responsePayload(response)\n })\n },\n\n haven_get_payment_status: async (input) => {\n const args = objectInput('haven_get_payment_status', input)\n return runTool(async () => haven.getPaymentStatus(args.payment_id))\n },\n\n haven_get_resume_state: async (input) => {\n const args = objectInput('haven_get_resume_state', input)\n return runTool(async () => haven.getResumeState(args.payment_id))\n },\n\n haven_get_agent: async () => runTool(async () => haven.getAgent()),\n haven_get_allowances: async () => runTool(async () => haven.getAllowances()),\n haven_list_receipts: async (input) => {\n const args = objectInput('haven_list_receipts', input)\n return runTool(async () => haven.listReceipts({ limit: args.limit }))\n },\n }\n\n async function resumeState(\n args: { payment_id?: string; resume_state?: unknown },\n rail: 'x402' | 'mpp',\n ): Promise<X402ResumeState | MppResumeState> {\n const state =\n args.resume_state ??\n (args.payment_id ? await haven.getResumeState(args.payment_id) : undefined)\n\n if (!state || typeof state !== 'object') {\n throw new HavenApiError(`haven_resume_${rail}_payment requires resume_state or payment_id.`, 400)\n }\n\n if ((state as { rail?: unknown }).rail !== rail) {\n throw new HavenApiError(`Resume state is not for the ${rail} rail.`, 409, state)\n }\n\n return state as X402ResumeState | MppResumeState\n }\n}\n\nfunction objectInput<TName extends HavenMcpToolName>(\n name: TName,\n input: unknown,\n): Record<string, any> {\n return z.object(toolSchemas[name]).parse(input ?? {})\n}\n\nfunction requestInit(input: { method?: string; headers?: Record<string, string>; body?: string }): RequestInit | undefined {\n if (!input.method && !input.headers && input.body === undefined) return undefined\n return {\n method: input.method,\n headers: input.headers,\n body: input.body,\n }\n}\n\nasync function runTool<T>(fn: () => Promise<T>): Promise<ToolPayload<T>> {\n try {\n return { success: true, data: await fn() }\n } catch (err) {\n return normalizeError(err)\n }\n}\n\nasync function responsePayload(response: Response): Promise<Record<string, unknown>> {\n const text = await response.text()\n return {\n status: response.status,\n statusText: response.statusText,\n headers: Object.fromEntries(response.headers.entries()),\n body: parseMaybeJson(text),\n }\n}\n\nfunction parseMaybeJson(text: string): unknown {\n if (!text) return null\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n\nfunction normalizeError(err: unknown): ToolFailure {\n if (err instanceof HavenPaymentStateError) {\n return {\n success: false,\n code: err.code,\n message: err.message,\n statusCode: err.statusCode,\n paymentId: err.paymentId,\n status: err.status,\n phase: err.phase,\n nextAction: err.nextAction,\n resume_state: err.resumeState,\n body: err.body,\n }\n }\n\n if (err instanceof HavenSigningError) {\n return {\n success: false,\n code: err.code,\n message: err.message,\n }\n }\n\n if (err instanceof HavenApiError) {\n const body = err.body as Record<string, unknown> | undefined\n return {\n success: false,\n code: err.code,\n message: err.message,\n statusCode: err.statusCode,\n paymentId: err.paymentId,\n phase: stringOrUndefined(body?.phase),\n nextAction:\n stringOrUndefined(body?.nextAction) ??\n stringOrUndefined(body?.next_action) ??\n AgentPaymentNextAction.StopAndTellUser,\n body: err.body,\n }\n }\n\n if (err instanceof HavenError) {\n return {\n success: false,\n code: err.code,\n message: err.message,\n statusCode: err.statusCode,\n paymentId: err.paymentId,\n }\n }\n\n return {\n success: false,\n code: 'UNKNOWN_ERROR',\n message: err instanceof Error ? err.message : String(err),\n nextAction: AgentPaymentNextAction.StopAndTellUser,\n }\n}\n\nfunction stringOrUndefined(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined\n}\n","import { createHash } from 'node:crypto'\nimport { mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname, resolve } from 'node:path'\nimport type { HavenAllowance, HavenAllowanceSummary, HavenClient } from '@haven_ai/sdk'\nimport { toolDescriptions, toolSchemas, type HavenMcpToolName } from './tools.js'\n\n/**\n * First-launch consent gate for the Haven MCP server.\n *\n * Why this exists (option A of issue #163): an agent runtime that loads a\n * Haven credential file is about to expose Haven payment tools to a model.\n * Before the server starts taking JSON-RPC calls we want the operator to\n * acknowledge — exactly once per credential + tool set — what those tools\n * can do and what the on-chain allowance cap actually is. The on-chain\n * AllowanceModule remains the policy primitive; this gate is informational\n * rather than enforcement.\n *\n * Resolution:\n * - `HAVEN_MCP_ACK=<hash>` env var matching the current consent hash → pass.\n * - `HAVEN_MCP_ACK=skip` → pass (intended for CI / scripted setups).\n * - sidecar file `<credentials>.ack.json` containing `{ ack: <hash> }` → pass.\n * - `--ack` CLI flag → write the sidecar file, print the consent block, pass.\n * - otherwise → print the consent block to stderr and exit non-zero.\n *\n * The hash binds the api-key prefix to the registered tool set and the\n * agent's current allowance summary, so a configuration change re-triggers\n * the prompt.\n */\n\nexport interface ConsentInput {\n apiKeyPrefix: string\n /** Haven API base URL the credential will hit. */\n apiUrl?: string\n /** Agent identity from the credential file, when present. */\n agentId?: string\n /** Haven wallet (Safe) the agent spends from. */\n safeAddress?: string\n /** Agent's delegate EOA — the local signer. */\n delegateAddress?: string\n /** Chain the agent operates on. */\n chainId?: number\n toolNames: readonly HavenMcpToolName[]\n allowanceSummary: readonly { token: string; amount: string; resetMinutes: number | null }[]\n}\n\nexport interface ConsentDecision {\n /** True if the gate is satisfied and the server may start. */\n ok: boolean\n /** Hash representing the current consent surface. */\n hash: string\n /** Reason the gate accepted (or rejected) the run. */\n reason:\n | 'env_var_match'\n | 'env_var_skip'\n | 'ack_file_match'\n | 'wrote_ack_file'\n | 'env_var_mismatch'\n | 'no_acknowledgement'\n}\n\nexport interface ConsentOptions {\n /** Path to the credential file; used to locate the sidecar `<path>.ack.json`. */\n credentialsPath?: string\n /** When true, write the sidecar file with the current hash and accept. */\n writeAck?: boolean\n /** Override the environment lookup (testing). */\n env?: Record<string, string | undefined>\n /** Override the writable stream the consent block is printed to (testing). */\n out?: { write: (chunk: string) => unknown }\n}\n\nexport function computeConsentHash(input: ConsentInput): string {\n const allowanceCanonical = [...input.allowanceSummary]\n .map((a) => `${a.token}:${a.amount}:${a.resetMinutes ?? 'none'}`)\n .sort()\n .join('|')\n const toolCanonical = [...input.toolNames].sort().join(',')\n // Identity fields are included in the hash so swapping the credential\n // to a different Haven wallet / delegate / chain — even one with an\n // identical allowance set — invalidates the prior sidecar and re-prompts\n // the operator. Addresses are normalised to lowercase so casing changes\n // in the credential file don't gratuitously re-prompt.\n const identity = [\n input.apiKeyPrefix,\n input.apiUrl ?? '',\n input.agentId ?? '',\n (input.safeAddress ?? '').toLowerCase(),\n (input.delegateAddress ?? '').toLowerCase(),\n input.chainId ?? '',\n ].join('|')\n return createHash('sha256')\n .update(`${identity}\\n${toolCanonical}\\n${allowanceCanonical}`)\n .digest('hex')\n .slice(0, 16)\n}\n\nexport function renderConsentBlock(input: ConsentInput, hash: string): string {\n const lines: string[] = [\n '',\n '────────────────────────────────────────────────────────────',\n 'Haven MCP server — first-launch consent',\n '────────────────────────────────────────────────────────────',\n '',\n `Credential: ${input.apiKeyPrefix}…`,\n ]\n if (input.apiUrl) lines.push(`Haven API: ${input.apiUrl}`)\n if (input.agentId) lines.push(`Agent ID: ${input.agentId}`)\n if (input.safeAddress) lines.push(`Haven wallet (Safe): ${input.safeAddress}`)\n if (input.delegateAddress) lines.push(`Delegate (local signer): ${input.delegateAddress}`)\n if (typeof input.chainId === 'number') lines.push(`Chain ID: ${input.chainId}`)\n lines.push('')\n lines.push('Confirm these match the Haven wallet and chain you intend the')\n lines.push('agent runtime to use. The delegate above is the only key that')\n lines.push('signs payments — it lives in this process, not on Haven\\'s backend.')\n lines.push('')\n lines.push('Tools this server will expose to your agent runtime:')\n for (const name of input.toolNames) {\n lines.push(` • ${name}`)\n lines.push(` ${toolDescriptions[name]}`)\n }\n lines.push('')\n if (input.allowanceSummary.length === 0) {\n lines.push('On-chain allowance: none configured.')\n lines.push(' Any payment will queue for manual approval. The on-chain')\n lines.push(' Safe AllowanceModule is the real spend gate.')\n } else {\n lines.push('On-chain allowance (the real spend gate, Safe AllowanceModule):')\n for (const a of input.allowanceSummary) {\n const reset = a.resetMinutes ? ` per ${a.resetMinutes} min` : ' (no reset)'\n lines.push(` • up to ${a.amount} ${a.token}${reset}`)\n }\n }\n lines.push('')\n lines.push('Anything above the on-chain allowance pauses for owner approval')\n lines.push('in the Haven dashboard. Revoking the agent on-chain disables')\n lines.push('every MCP tool that would spend.')\n lines.push('')\n lines.push(`Consent hash: ${hash}`)\n lines.push('')\n lines.push('To acknowledge, EITHER:')\n lines.push(` • set HAVEN_MCP_ACK=${hash} in this process\\'s environment, OR`)\n lines.push(' • re-run with --ack to write the acknowledgement next to your')\n lines.push(' credential file (sidecar <credentials>.ack.json).')\n lines.push('')\n lines.push('────────────────────────────────────────────────────────────')\n lines.push('')\n return lines.join('\\n')\n}\n\n/** Resolve the consent gate. Does not exit the process; the caller decides. */\nexport async function ensureConsent(\n input: ConsentInput,\n options: ConsentOptions = {},\n): Promise<ConsentDecision> {\n const env = options.env ?? process.env\n const out = options.out ?? process.stderr\n const hash = computeConsentHash(input)\n\n // 1) Explicit skip — for CI and scripted environments.\n if (env.HAVEN_MCP_ACK === 'skip') {\n return { ok: true, hash, reason: 'env_var_skip' }\n }\n\n // 2) Env var hash match.\n if (typeof env.HAVEN_MCP_ACK === 'string' && env.HAVEN_MCP_ACK.length > 0) {\n if (env.HAVEN_MCP_ACK === hash) {\n return { ok: true, hash, reason: 'env_var_match' }\n }\n out.write(renderConsentBlock(input, hash))\n out.write(\n `HAVEN_MCP_ACK was set but did not match the current consent hash.\\n` +\n `Expected: ${hash}\\n` +\n `Got: ${env.HAVEN_MCP_ACK}\\n` +\n `Re-acknowledge with the new hash above, or run with --ack.\\n\\n`,\n )\n return { ok: false, hash, reason: 'env_var_mismatch' }\n }\n\n // 3) Sidecar ack file (only meaningful when we loaded from a file).\n const ackPath = sidecarPath(options.credentialsPath)\n if (ackPath) {\n const stored = await readAckFile(ackPath)\n if (stored?.ack === hash) {\n return { ok: true, hash, reason: 'ack_file_match' }\n }\n }\n\n // 4) --ack: write the sidecar and accept.\n if (options.writeAck && ackPath) {\n out.write(renderConsentBlock(input, hash))\n await writeAckFile(ackPath, hash)\n out.write(`Wrote acknowledgement to ${ackPath}\\n\\n`)\n return { ok: true, hash, reason: 'wrote_ack_file' }\n }\n\n // 5) Otherwise: print and refuse.\n out.write(renderConsentBlock(input, hash))\n return { ok: false, hash, reason: 'no_acknowledgement' }\n}\n\nfunction sidecarPath(credentialsPath?: string): string | null {\n if (!credentialsPath) return null\n return resolve(`${credentialsPath}.ack.json`)\n}\n\nasync function readAckFile(path: string): Promise<{ ack?: string } | null> {\n try {\n const raw = await readFile(path, 'utf8')\n const parsed = JSON.parse(raw) as { ack?: unknown }\n return { ack: typeof parsed.ack === 'string' ? parsed.ack : undefined }\n } catch {\n return null\n }\n}\n\nasync function writeAckFile(path: string, hash: string): Promise<void> {\n await mkdir(dirname(path), { recursive: true })\n await writeFile(\n path,\n JSON.stringify({ ack: hash, at: new Date().toISOString() }, null, 2),\n 'utf8',\n )\n}\n\nexport interface CredentialIdentitySeed {\n apiKey: string\n apiUrl?: string\n agentId?: string\n /** Safe address from the credential file, used as a fallback. */\n safeAddress?: string\n}\n\n/**\n * Build the consent input from credential identity plus a live allowance\n * lookup. The on-chain (or configured) allowance is what the operator\n * actually cares about — that's the real spend ceiling — but we also bind\n * the hash to the Haven wallet / delegate / chain so a credential swap\n * cannot quietly reuse a prior sidecar acknowledgement.\n *\n * If `getAllowances()` fails (e.g. backend unreachable on first launch) we\n * fall through to whatever identity fields the credential file provided,\n * so the operator at least sees the tool list and the api-key prefix.\n */\nexport async function consentInputFromClient(\n haven: HavenClient,\n seed: CredentialIdentitySeed,\n toolNames: readonly HavenMcpToolName[],\n): Promise<ConsentInput> {\n let allowanceSummary: ConsentInput['allowanceSummary'] = []\n let safeAddress = seed.safeAddress\n let delegateAddress: string | undefined\n let chainId: number | undefined\n\n try {\n const summary = await haven.getAllowances()\n const list: HavenAllowance[] = isAllowanceSummary(summary)\n ? summary.allowances\n : Array.isArray(summary)\n ? (summary as HavenAllowance[])\n : []\n if (isAllowanceSummary(summary)) {\n safeAddress = summary.safeAddress ?? safeAddress\n delegateAddress = summary.delegateAddress\n chainId = typeof summary.chainId === 'number' ? summary.chainId : chainId\n }\n allowanceSummary = list.map((a) => ({\n token: a.tokenSymbol ?? 'UNKNOWN',\n amount: a.onchain?.amount ?? a.configuredAmount ?? '0',\n resetMinutes:\n typeof a.onchain?.resetTimeMin === 'number'\n ? a.onchain.resetTimeMin\n : typeof a.resetPeriodMin === 'number'\n ? a.resetPeriodMin\n : null,\n }))\n } catch {\n // Identity falls back to what the credential file gave us.\n }\n\n return {\n apiKeyPrefix: derivePrefix(seed.apiKey),\n apiUrl: seed.apiUrl,\n agentId: seed.agentId,\n safeAddress,\n delegateAddress,\n chainId,\n toolNames,\n allowanceSummary,\n }\n}\n\nfunction isAllowanceSummary(value: unknown): value is HavenAllowanceSummary {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'allowances' in value &&\n Array.isArray((value as { allowances: unknown }).allowances)\n )\n}\n\n/**\n * Use the leading characters of the api key (which already begins with the\n * non-secret `sk_agent_` prefix) as a stable, low-information identifier.\n * Twelve characters is enough to disambiguate credentials in front of a\n * human but not enough to reveal the secret.\n */\nfunction derivePrefix(apiKey: string): string {\n return apiKey.slice(0, 12)\n}\n\n/** Convenience: the canonical tool list registered by the server. */\nexport function registeredToolNames(): HavenMcpToolName[] {\n return Object.keys(toolSchemas) as HavenMcpToolName[]\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { HavenClient } from '@haven_ai/sdk'\nimport { loadCredentials, type HavenCredentialFile } from './credentials.js'\nimport {\n createToolHandlers,\n toolDescriptions,\n toolSchemas,\n type HavenMcpToolName,\n type ToolPayload,\n} from './tools.js'\nimport {\n consentInputFromClient,\n ensureConsent,\n registeredToolNames,\n type ConsentDecision,\n} from './consent.js'\n\nexport interface HavenMcpServerOptions {\n credentialsPath?: string\n credentials?: HavenCredentialFile\n /**\n * When true, write the consent sidecar file (`<credentials>.ack.json`)\n * with the current consent hash and proceed. Surfaced via the `--ack`\n * CLI flag.\n */\n writeAck?: boolean\n /**\n * When true, skip the consent gate entirely. Reserved for tests and\n * controlled embedding — production CLIs should not set this.\n */\n skipConsent?: boolean\n}\n\nexport interface ResolvedHavenClient {\n client: HavenClient\n credentials: HavenCredentialFile\n}\n\nexport async function createHavenClient(options: HavenMcpServerOptions = {}): Promise<HavenClient> {\n const { client } = await resolveHavenClient(options)\n return client\n}\n\nexport async function resolveHavenClient(options: HavenMcpServerOptions = {}): Promise<ResolvedHavenClient> {\n const credentials = options.credentials ?? await loadCredentials(options.credentialsPath)\n const client = new HavenClient({\n apiKey: credentials.apiKey,\n delegateKey: credentials.delegateKey,\n baseUrl: credentials.apiUrl,\n })\n return { client, credentials }\n}\n\nexport async function createHavenMcpServer(options: HavenMcpServerOptions = {}): Promise<McpServer> {\n const haven = await createHavenClient(options)\n return buildMcpServer(haven)\n}\n\n/**\n * Build an MCP server bound to the supplied Haven client.\n *\n * Each tool dispatch is wrapped in `haven.withRequestContext` so every\n * Haven API request the dispatch issues carries `X-Haven-MCP-Tool: <name>`\n * — and *only* that dispatch's requests see the header. The SDK uses an\n * `AsyncLocalStorage` for the context, so two tool calls running\n * concurrently cannot leak headers into each other and the backend\n * `agent_tool_invocations` rows are always attributed to the right tool.\n */\nexport function buildMcpServer(haven: HavenClient): McpServer {\n const server = new McpServer({\n name: '@haven_ai/mcp',\n version: '0.1.0-alpha',\n })\n\n const handlers = createToolHandlers(haven)\n const registerTool = (server as any).tool.bind(server)\n for (const name of Object.keys(toolSchemas) as HavenMcpToolName[]) {\n registerTool(\n name,\n toolDescriptions[name],\n toolSchemas[name],\n async (args: unknown) =>\n haven.withRequestContext({ 'X-Haven-MCP-Tool': name }, async () =>\n toMcpResult(await handlers[name](args)),\n ),\n )\n }\n\n return server\n}\n\nexport async function runStdioServer(options: HavenMcpServerOptions = {}): Promise<void> {\n const { client: haven, credentials } = await resolveHavenClient(options)\n\n if (!options.skipConsent) {\n const decision = await runConsentGate(haven, credentials, options)\n if (!decision.ok) {\n // The consent block has already been printed by `ensureConsent`.\n const err: NodeJS.ErrnoException = new Error(\n decision.reason === 'env_var_mismatch'\n ? 'Haven MCP consent acknowledgement does not match the current configuration.'\n : 'Haven MCP server requires a one-time consent acknowledgement before starting.',\n )\n err.code = 'HAVEN_MCP_NO_CONSENT'\n throw err\n }\n }\n\n const server = buildMcpServer(haven)\n await server.connect(new StdioServerTransport())\n}\n\nexport async function runConsentGate(\n haven: HavenClient,\n credentials: HavenCredentialFile,\n options: HavenMcpServerOptions,\n): Promise<ConsentDecision> {\n const toolNames = registeredToolNames()\n const input = await consentInputFromClient(\n haven,\n {\n apiKey: credentials.apiKey,\n apiUrl: credentials.apiUrl,\n agentId: credentials.agentId,\n safeAddress: credentials.safeAddress,\n },\n toolNames,\n )\n // Prefer the path actually used to load credentials (covers\n // HAVEN_CREDENTIALS as well as --credentials). If neither file path is\n // available the operator must use HAVEN_MCP_ACK; --ack has nowhere to\n // write a sidecar in that case.\n const credentialsPath = options.credentialsPath ?? credentials.sourcePath\n return ensureConsent(input, {\n credentialsPath,\n writeAck: options.writeAck,\n })\n}\n\nfunction toMcpResult(payload: ToolPayload) {\n return {\n isError: !payload.success,\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(payload, null, 2),\n },\n ],\n }\n}\n"]}