@haven_ai/mcp 0.2.1-alpha.0 → 0.4.0-alpha.0
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 +6 -3
- package/dist/cli.cjs +70 -27
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +70 -27
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +70 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +99 -21
- package/dist/index.d.ts +99 -21
- package/dist/index.js +70 -27
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -44,9 +44,12 @@ Create a private JSON file from the values in the Haven agent handoff:
|
|
|
44
44
|
`delegate_key` is required. Without it the MCP server cannot sign locally.
|
|
45
45
|
`account_address` is the name the connector writes since #2908; a file that
|
|
46
46
|
still says `safe_address` (or `safeAddress`) is read the same way, permanently.
|
|
47
|
-
The
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
The environment is **not** the same: only `HAVEN_ACCOUNT_ADDRESS` is read.
|
|
48
|
+
`HAVEN_WALLET_ADDRESS` and `HAVEN_SAFE_ADDRESS` were accepted for one release
|
|
49
|
+
and #2914 removed them, so a machine still configured through either resolves
|
|
50
|
+
no account address at all. The credential-FILE fallback above is permanent —
|
|
51
|
+
a file on disk never rewrites itself — and the environment is not, which is
|
|
52
|
+
the whole difference between the two paragraphs.
|
|
50
53
|
|
|
51
54
|
The Haven connector may also write split credentials:
|
|
52
55
|
|
package/dist/cli.cjs
CHANGED
|
@@ -56,7 +56,6 @@ async function loadCredentialsFromFile(path) {
|
|
|
56
56
|
delegateKey,
|
|
57
57
|
agentId: stringField(raw.agent_id ?? raw.agentId),
|
|
58
58
|
accountAddress,
|
|
59
|
-
safeAddress: accountAddress,
|
|
60
59
|
delegateAddress: stringField(raw.delegate_address ?? raw.delegateAddress),
|
|
61
60
|
chainId: numberField(raw.chain_id ?? raw.chainId),
|
|
62
61
|
network: stringField(raw.network),
|
|
@@ -86,13 +85,11 @@ async function loadCredentialsFromSplitFiles(identityPath, signerPath) {
|
|
|
86
85
|
identity.agent_id ?? identity.agentId,
|
|
87
86
|
signer.agent_id ?? signer.agentId
|
|
88
87
|
),
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
{ caseInsensitive: true }
|
|
95
|
-
)
|
|
88
|
+
accountAddress: matchingStringField(
|
|
89
|
+
"account_address",
|
|
90
|
+
readAccountAddressField(identity),
|
|
91
|
+
readAccountAddressField(signer),
|
|
92
|
+
{ caseInsensitive: true }
|
|
96
93
|
),
|
|
97
94
|
delegateAddress: matchingStringField(
|
|
98
95
|
"delegate_address",
|
|
@@ -147,7 +144,7 @@ function loadCredentialsFromEnv() {
|
|
|
147
144
|
apiKey,
|
|
148
145
|
delegateKey,
|
|
149
146
|
agentId: stringField(process.env.HAVEN_AGENT_ID),
|
|
150
|
-
|
|
147
|
+
accountAddress: readAccountAddressEnv(process.env),
|
|
151
148
|
chainId: numberField(process.env.HAVEN_CHAIN_ID),
|
|
152
149
|
network: stringField(process.env.HAVEN_NETWORK),
|
|
153
150
|
apiUrl: stringField(process.env.HAVEN_API_URL)
|
|
@@ -157,10 +154,22 @@ function readAccountAddressField(raw) {
|
|
|
157
154
|
return stringField(raw.account_address ?? raw.safe_address ?? raw.safeAddress);
|
|
158
155
|
}
|
|
159
156
|
function readAccountAddressEnv(env) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
157
|
+
const current = stringField(env.HAVEN_ACCOUNT_ADDRESS);
|
|
158
|
+
for (const name of ["HAVEN_WALLET_ADDRESS", "HAVEN_SAFE_ADDRESS"]) {
|
|
159
|
+
const retired = stringField(env[name]);
|
|
160
|
+
if (!retired) continue;
|
|
161
|
+
if (!current) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`${name} is retired (#2906) \u2014 Haven accounts are addressed as accounts, not Safes. Set HAVEN_ACCOUNT_ADDRESS to the same value. Refusing rather than ignoring it, because an unset account address silently skips the sweep-destination check.`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
if (retired.toLowerCase() !== current.toLowerCase()) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`${name} and HAVEN_ACCOUNT_ADDRESS were both set to different addresses. Remove the retired name, or make them match \u2014 picking one silently would hide the mismatch.`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return current;
|
|
164
173
|
}
|
|
165
174
|
function stringField(value) {
|
|
166
175
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
@@ -295,11 +304,14 @@ var toolSchemas = {
|
|
|
295
304
|
website: v3.z.string().optional()
|
|
296
305
|
},
|
|
297
306
|
haven_list_receipts: {
|
|
298
|
-
limit: v3.z.number().int().min(1).max(100).optional()
|
|
307
|
+
limit: v3.z.number().int().min(1).max(100).optional(),
|
|
308
|
+
/** #3128: the previous page's next_cursor (a receipt id). */
|
|
309
|
+
cursor: v3.z.string().min(1).optional()
|
|
299
310
|
},
|
|
300
311
|
haven_verify_receipt: {
|
|
301
312
|
receipt: v3.z.unknown()
|
|
302
313
|
}
|
|
314
|
+
// #3101: keys survive on the type (see the hosted server's contracts.ts).
|
|
303
315
|
};
|
|
304
316
|
var toolDescriptions = {
|
|
305
317
|
haven_send: sdk.composeDescription(sdk.toolDescriptions.send),
|
|
@@ -490,12 +502,32 @@ function createToolHandlers(haven) {
|
|
|
490
502
|
source: entry.source,
|
|
491
503
|
domain_verified: entry.domainVerified,
|
|
492
504
|
verified_payable: entry.verifiedPayable,
|
|
493
|
-
// Which Haven pay tool reaches this entry from the local MCP surface
|
|
505
|
+
// Which Haven pay tool reaches this entry from the local MCP surface,
|
|
506
|
+
// and — #3100 (epic #3105, decision 4) — the arguments that tool
|
|
507
|
+
// accepts VERBATIM, spelled in ITS vocabulary (`merchant_url`, not
|
|
508
|
+
// the entry's `resource_url`). The local surface keeps pointing at
|
|
509
|
+
// its pay tools: they need no cap, so a verbatim hint exists.
|
|
494
510
|
// #1328: the 'mpp' rail's only-ever catalog row (the Haven MPP demo
|
|
495
511
|
// resource) is delisted with the mpp_demo retirement, so this
|
|
496
512
|
// fallback is unreachable today; it stays x402 rather than naming a
|
|
497
513
|
// deleted tool in case a future non-demo 'mpp' rail entry appears.
|
|
498
|
-
|
|
514
|
+
// A row without a tool_name cannot get a verbatim hint —
|
|
515
|
+
// haven_pay_mcp_tool requires tool_name — so it gets the REASON
|
|
516
|
+
// instead of a hint its own tool refuses (haven-reviewer on #3113;
|
|
517
|
+
// the epic's "omitted + reason, never null" shape, decision 3).
|
|
518
|
+
...entry.protocol === "mcp" ? entry.toolName ? {
|
|
519
|
+
suggested_tool: "haven_pay_mcp_tool",
|
|
520
|
+
suggested_arguments: {
|
|
521
|
+
merchant_url: entry.resourceUrl,
|
|
522
|
+
tool_name: entry.toolName,
|
|
523
|
+
...entry.toolArguments ? { arguments: entry.toolArguments } : {}
|
|
524
|
+
}
|
|
525
|
+
} : {
|
|
526
|
+
suggested_tool_omitted_reason: "this catalog row carries no tool_name, which haven_pay_mcp_tool requires; read the merchant's tool list yourself, then call haven_pay_mcp_tool with merchant_url, tool_name and arguments"
|
|
527
|
+
} : {
|
|
528
|
+
suggested_tool: "haven_pay_x402",
|
|
529
|
+
suggested_arguments: { url: entry.resourceUrl }
|
|
530
|
+
}
|
|
499
531
|
}));
|
|
500
532
|
});
|
|
501
533
|
},
|
|
@@ -515,7 +547,7 @@ function createToolHandlers(haven) {
|
|
|
515
547
|
},
|
|
516
548
|
haven_list_receipts: async (input) => {
|
|
517
549
|
const args = objectInput("haven_list_receipts", input);
|
|
518
|
-
return runTool(async () => haven.
|
|
550
|
+
return runTool(async () => haven.listReceiptsPage({ limit: args.limit, cursor: args.cursor }));
|
|
519
551
|
},
|
|
520
552
|
haven_verify_receipt: async (input) => {
|
|
521
553
|
const args = objectInput("haven_verify_receipt", input);
|
|
@@ -660,7 +692,11 @@ function normalizeError(err) {
|
|
|
660
692
|
message: err.message,
|
|
661
693
|
statusCode: err.statusCode,
|
|
662
694
|
nextAction: err.nextAction,
|
|
663
|
-
|
|
695
|
+
next_action: err.nextAction,
|
|
696
|
+
retry_with_new_quote: err.retryWithNewQuote,
|
|
697
|
+
// #3103: the local runtime's one decision site — no tool can act until
|
|
698
|
+
// the merchant recovers; the message carries retry_after_s.
|
|
699
|
+
next_tool_omitted_reason: "the merchant needs to recover first; re-quote after the retry_after_s in the message"
|
|
664
700
|
};
|
|
665
701
|
}
|
|
666
702
|
if (err instanceof sdk.HavenPaymentStateError) {
|
|
@@ -673,6 +709,7 @@ function normalizeError(err) {
|
|
|
673
709
|
status: err.status,
|
|
674
710
|
phase: err.phase,
|
|
675
711
|
nextAction: err.nextAction,
|
|
712
|
+
next_action: err.nextAction,
|
|
676
713
|
resume_state: err.resumeState,
|
|
677
714
|
body: err.body
|
|
678
715
|
};
|
|
@@ -694,6 +731,7 @@ function normalizeError(err) {
|
|
|
694
731
|
paymentId: err.paymentId,
|
|
695
732
|
phase: stringOrUndefined(body?.phase),
|
|
696
733
|
nextAction: stringOrUndefined(body?.nextAction) ?? stringOrUndefined(body?.next_action) ?? sdk.AgentPaymentNextAction.StopAndTellUser,
|
|
734
|
+
next_action: stringOrUndefined(body?.nextAction) ?? stringOrUndefined(body?.next_action) ?? sdk.AgentPaymentNextAction.StopAndTellUser,
|
|
697
735
|
body: err.body
|
|
698
736
|
};
|
|
699
737
|
}
|
|
@@ -710,7 +748,11 @@ function normalizeError(err) {
|
|
|
710
748
|
success: false,
|
|
711
749
|
code: "UNKNOWN_ERROR",
|
|
712
750
|
message: err instanceof Error ? err.message : String(err),
|
|
713
|
-
nextAction: sdk.AgentPaymentNextAction.StopAndTellUser
|
|
751
|
+
nextAction: sdk.AgentPaymentNextAction.StopAndTellUser,
|
|
752
|
+
next_action: sdk.AgentPaymentNextAction.StopAndTellUser,
|
|
753
|
+
// #3103: the second local decision site — nothing structured can follow an
|
|
754
|
+
// error this runtime did not recognise.
|
|
755
|
+
next_tool_omitted_reason: "an error this runtime does not recognise; tell the user what the message says"
|
|
714
756
|
};
|
|
715
757
|
}
|
|
716
758
|
function stringOrUndefined(value) {
|
|
@@ -723,7 +765,7 @@ function computeConsentHash(input) {
|
|
|
723
765
|
input.apiKeyPrefix,
|
|
724
766
|
input.apiUrl ?? "",
|
|
725
767
|
input.agentId ?? "",
|
|
726
|
-
(input.
|
|
768
|
+
(input.accountAddress ?? "").toLowerCase(),
|
|
727
769
|
(input.delegateAddress ?? "").toLowerCase(),
|
|
728
770
|
input.chainId ?? ""
|
|
729
771
|
].join("|");
|
|
@@ -742,7 +784,7 @@ function renderConsentBlock(input, hash) {
|
|
|
742
784
|
];
|
|
743
785
|
if (input.apiUrl) lines.push(`Haven API: ${input.apiUrl}`);
|
|
744
786
|
if (input.agentId) lines.push(`Agent ID: ${input.agentId}`);
|
|
745
|
-
if (input.
|
|
787
|
+
if (input.accountAddress) lines.push(`Haven wallet (Safe): ${input.accountAddress}`);
|
|
746
788
|
if (input.delegateAddress) lines.push(`Delegate (local signer): ${input.delegateAddress}`);
|
|
747
789
|
if (typeof input.chainId === "number") lines.push(`Chain ID: ${input.chainId}`);
|
|
748
790
|
lines.push("");
|
|
@@ -849,14 +891,14 @@ async function writeAckFile(path$1, hash) {
|
|
|
849
891
|
}
|
|
850
892
|
async function consentInputFromClient(haven, seed, toolNames) {
|
|
851
893
|
let allowanceSummary = seed.allowanceSummary ?? [];
|
|
852
|
-
let
|
|
894
|
+
let accountAddress = seed.accountAddress;
|
|
853
895
|
let delegateAddress = seed.delegateAddress;
|
|
854
896
|
let chainId = seed.chainId;
|
|
855
897
|
try {
|
|
856
898
|
const summary = await haven.getAllowances();
|
|
857
899
|
const list = isAllowanceSummary(summary) ? summary.allowances : Array.isArray(summary) ? summary : [];
|
|
858
900
|
if (isAllowanceSummary(summary)) {
|
|
859
|
-
|
|
901
|
+
accountAddress = summary.accountAddress ?? accountAddress;
|
|
860
902
|
delegateAddress = summary.delegateAddress;
|
|
861
903
|
chainId = typeof summary.chainId === "number" ? summary.chainId : chainId;
|
|
862
904
|
}
|
|
@@ -872,7 +914,7 @@ async function consentInputFromClient(haven, seed, toolNames) {
|
|
|
872
914
|
apiKeyPrefix: derivePrefix(seed.apiKey),
|
|
873
915
|
apiUrl: seed.apiUrl,
|
|
874
916
|
agentId: seed.agentId,
|
|
875
|
-
|
|
917
|
+
accountAddress,
|
|
876
918
|
delegateAddress,
|
|
877
919
|
chainId,
|
|
878
920
|
toolNames,
|
|
@@ -906,7 +948,7 @@ async function resolveHavenClient(options = {}) {
|
|
|
906
948
|
return { client, credentials };
|
|
907
949
|
}
|
|
908
950
|
var MCP_NAME = "@haven_ai/mcp";
|
|
909
|
-
var MCP_VERSION = "0.
|
|
951
|
+
var MCP_VERSION = "0.4.0-alpha.0";
|
|
910
952
|
var MCP_INSTRUCTIONS = [
|
|
911
953
|
"Haven local MCP server: signs in-process with the delegate key it holds on",
|
|
912
954
|
"this machine \u2014 the key never leaves this process. Call haven_get_agent",
|
|
@@ -915,7 +957,8 @@ var MCP_INSTRUCTIONS = [
|
|
|
915
957
|
"",
|
|
916
958
|
"To pay: haven_discover_tools to find a payable service, then",
|
|
917
959
|
"haven_pay_mcp_tool for MCP merchants or haven_pay_x402 for any x402",
|
|
918
|
-
"paywall. Tool responses carry nextAction
|
|
960
|
+
"paywall. Tool responses carry next_action (nextAction is the same value, kept one more",
|
|
961
|
+
"release) and, when no tool follows, next_tool_omitted_reason \u2014 follow them; description prose",
|
|
919
962
|
"is fallback, not the source of truth.",
|
|
920
963
|
"",
|
|
921
964
|
"A payment outside the agent budget is declined before any money moves. Haven",
|
|
@@ -979,7 +1022,7 @@ async function runConsentGate(haven, credentials, options) {
|
|
|
979
1022
|
apiKey: credentials.apiKey,
|
|
980
1023
|
apiUrl: credentials.apiUrl,
|
|
981
1024
|
agentId: credentials.agentId,
|
|
982
|
-
|
|
1025
|
+
accountAddress: credentials.accountAddress,
|
|
983
1026
|
delegateAddress: credentials.delegateAddress,
|
|
984
1027
|
chainId: credentials.chainId,
|
|
985
1028
|
allowanceSummary: credentials.allowanceSummary
|