@haven_ai/signer 0.0.0-dev.202609250737.3bd5a51 → 0.0.0-dev.202609251409.98ed67a
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 +1 -0
- package/dist/cli.cjs +65 -16
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +66 -17
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +65 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +66 -17
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { X402ExpectedAuth, X402PaymentRequired, X402PaymentOption, SweepAuthorization, SweepExpectedAuth } from '@haven_ai/sdk/edge';
|
|
1
|
+
import { X402ExpectedAuth, X402PaymentRequired, X402PaymentOption, SweepAuthorization, SweepExpectedAuth, HavenClientUpdate } from '@haven_ai/sdk/edge';
|
|
2
2
|
export { deriveDelegateAccountAddress } from '@haven_ai/sdk/edge';
|
|
3
3
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
4
|
import { z as z$1 } from 'zod/v3';
|
|
@@ -725,8 +725,10 @@ interface ToolFailure {
|
|
|
725
725
|
* the direct `/payments/:id/sign-context` fetch since #3271.
|
|
726
726
|
*/
|
|
727
727
|
http_status?: number;
|
|
728
|
-
/** #3001: the backend's own `error_code` on `SIGN_CONTEXT_REFUSED` (`expired`, `already_executed`, `not_signable`, `sign_context_unavailable`). */
|
|
728
|
+
/** #3001: the backend's own `error_code` on `SIGN_CONTEXT_REFUSED` (`expired`, `already_executed`, `not_signable`, `sign_context_unavailable`, and since #3303 `client_outdated`). */
|
|
729
729
|
backend_error_code?: string;
|
|
730
|
+
/** #3303: on `client_outdated`, the backend's update hint — `upgrade_command` is what updates this signer. */
|
|
731
|
+
client_update?: HavenClientUpdate;
|
|
730
732
|
}
|
|
731
733
|
type ToolPayload<T = unknown> = ToolSuccess<T> | ToolFailure;
|
|
732
734
|
interface ToolHandlerOptions {
|
|
@@ -741,6 +743,8 @@ interface ToolHandlerOptions {
|
|
|
741
743
|
signContext?: {
|
|
742
744
|
loadIdentity: () => Promise<HavenIdentity | null>;
|
|
743
745
|
fetchImpl?: typeof fetch;
|
|
746
|
+
/** #3303: `@haven_ai/signer/<version>`, sent as `X-Haven-Client` on every sign-context read. */
|
|
747
|
+
clientIdentity?: string;
|
|
744
748
|
};
|
|
745
749
|
}
|
|
746
750
|
declare function createToolHandlers(signer: EdgeSigner, options?: ToolHandlerOptions): Record<SignerToolName, (input: unknown) => Promise<ToolPayload>>;
|
|
@@ -772,7 +776,7 @@ declare function renderSignerConsentBlock(input: SignerConsentInput, hash: strin
|
|
|
772
776
|
declare function ensureSignerConsent(input: SignerConsentInput, options?: SignerConsentOptions): Promise<SignerConsentDecision>;
|
|
773
777
|
|
|
774
778
|
declare const SIGNER_NAME = "@haven_ai/signer";
|
|
775
|
-
declare const SIGNER_VERSION = "0.0.0-dev.
|
|
779
|
+
declare const SIGNER_VERSION = "0.0.0-dev.202609251409.98ed67a";
|
|
776
780
|
interface SignerOptions {
|
|
777
781
|
/** Path to a Haven credential JSON file (delegate_key is read from it). */
|
|
778
782
|
credentialsPath?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { X402ExpectedAuth, X402PaymentRequired, X402PaymentOption, SweepAuthorization, SweepExpectedAuth } from '@haven_ai/sdk/edge';
|
|
1
|
+
import { X402ExpectedAuth, X402PaymentRequired, X402PaymentOption, SweepAuthorization, SweepExpectedAuth, HavenClientUpdate } from '@haven_ai/sdk/edge';
|
|
2
2
|
export { deriveDelegateAccountAddress } from '@haven_ai/sdk/edge';
|
|
3
3
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
4
|
import { z as z$1 } from 'zod/v3';
|
|
@@ -725,8 +725,10 @@ interface ToolFailure {
|
|
|
725
725
|
* the direct `/payments/:id/sign-context` fetch since #3271.
|
|
726
726
|
*/
|
|
727
727
|
http_status?: number;
|
|
728
|
-
/** #3001: the backend's own `error_code` on `SIGN_CONTEXT_REFUSED` (`expired`, `already_executed`, `not_signable`, `sign_context_unavailable`). */
|
|
728
|
+
/** #3001: the backend's own `error_code` on `SIGN_CONTEXT_REFUSED` (`expired`, `already_executed`, `not_signable`, `sign_context_unavailable`, and since #3303 `client_outdated`). */
|
|
729
729
|
backend_error_code?: string;
|
|
730
|
+
/** #3303: on `client_outdated`, the backend's update hint — `upgrade_command` is what updates this signer. */
|
|
731
|
+
client_update?: HavenClientUpdate;
|
|
730
732
|
}
|
|
731
733
|
type ToolPayload<T = unknown> = ToolSuccess<T> | ToolFailure;
|
|
732
734
|
interface ToolHandlerOptions {
|
|
@@ -741,6 +743,8 @@ interface ToolHandlerOptions {
|
|
|
741
743
|
signContext?: {
|
|
742
744
|
loadIdentity: () => Promise<HavenIdentity | null>;
|
|
743
745
|
fetchImpl?: typeof fetch;
|
|
746
|
+
/** #3303: `@haven_ai/signer/<version>`, sent as `X-Haven-Client` on every sign-context read. */
|
|
747
|
+
clientIdentity?: string;
|
|
744
748
|
};
|
|
745
749
|
}
|
|
746
750
|
declare function createToolHandlers(signer: EdgeSigner, options?: ToolHandlerOptions): Record<SignerToolName, (input: unknown) => Promise<ToolPayload>>;
|
|
@@ -772,7 +776,7 @@ declare function renderSignerConsentBlock(input: SignerConsentInput, hash: strin
|
|
|
772
776
|
declare function ensureSignerConsent(input: SignerConsentInput, options?: SignerConsentOptions): Promise<SignerConsentDecision>;
|
|
773
777
|
|
|
774
778
|
declare const SIGNER_NAME = "@haven_ai/signer";
|
|
775
|
-
declare const SIGNER_VERSION = "0.0.0-dev.
|
|
779
|
+
declare const SIGNER_VERSION = "0.0.0-dev.202609251409.98ed67a";
|
|
776
780
|
interface SignerOptions {
|
|
777
781
|
/** Path to a Haven credential JSON file (delegate_key is read from it). */
|
|
778
782
|
credentialsPath?: string;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID, createHash } from 'crypto';
|
|
2
2
|
import { recoverTypedDataAddress, hashTypedData, hashMessage } from 'viem';
|
|
3
3
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
4
|
-
import { createNextStepBuilder, addressFromKey, HavenSigningError, buildSweepTypedData, selectStandardPaymentOption, HavenApiError, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, isSettlementChildTypedData, chainIdForNetwork, assertOwnSettlementChild, isPackedUserOperationTypedData, assertUserOpTypedDataBinding, assertBoundDirectPaymentUserOp, assertFundingLegPaysDelegate, HavenTypedDataRefusedError, buildSweepAuthorizationMessage, verifySignature, x402AuthorizationAmount, SignerRefusalCode, connectorRerunCommand, SIGNER_UPDATE_FALLBACK, HavenUnsupportedSignerVersionError, buildX402ExpectedMessage, DIRECT_SIGN_CONTEXT_VERSION, AgentPaymentNextAction, HavenError, AgentPaymentFailureCode, isSupportedNodeVersion, unsupportedNodeVersionMessage, HavenUserOpBindingError } from '@haven_ai/sdk/edge';
|
|
4
|
+
import { createNextStepBuilder, addressFromKey, HavenSigningError, buildSweepTypedData, selectStandardPaymentOption, HavenApiError, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, isSettlementChildTypedData, chainIdForNetwork, assertOwnSettlementChild, isPackedUserOperationTypedData, assertUserOpTypedDataBinding, assertBoundDirectPaymentUserOp, assertFundingLegPaysDelegate, HavenTypedDataRefusedError, buildSweepAuthorizationMessage, verifySignature, x402AuthorizationAmount, SignerRefusalCode, connectorRerunCommand, SIGNER_UPDATE_FALLBACK, HavenUnsupportedSignerVersionError, buildX402ExpectedMessage, DIRECT_SIGN_CONTEXT_VERSION, AgentPaymentNextAction, HavenError, AgentPaymentFailureCode, havenClientIdentity, isSupportedNodeVersion, unsupportedNodeVersionMessage, HavenUserOpBindingError, readClientUpdate, HAVEN_CLIENT_HEADER } from '@haven_ai/sdk/edge';
|
|
5
5
|
export { deriveDelegateAccountAddress } from '@haven_ai/sdk/edge';
|
|
6
6
|
import { stat, lstat, chmod, readFile, mkdir, rename, appendFile, writeFile } from 'fs/promises';
|
|
7
7
|
import { resolve, dirname, join } from 'path';
|
|
@@ -430,6 +430,12 @@ var HavenSignContextError = class extends HavenSigningError {
|
|
|
430
430
|
http_status;
|
|
431
431
|
/** Present only for `SIGN_CONTEXT_REFUSED`: the backend's own `error_code`. */
|
|
432
432
|
backend_error_code;
|
|
433
|
+
/**
|
|
434
|
+
* #3303: on a 426 `client_outdated` refusal, the backend's update hint —
|
|
435
|
+
* this signer is below the minimum the deployment set, and
|
|
436
|
+
* `client_update.upgrade_command` is what updates it.
|
|
437
|
+
*/
|
|
438
|
+
client_update;
|
|
433
439
|
constructor(message, code, refusal, paymentId, flow = "x402") {
|
|
434
440
|
super(message);
|
|
435
441
|
this.code = code;
|
|
@@ -438,7 +444,15 @@ var HavenSignContextError = class extends HavenSigningError {
|
|
|
438
444
|
if (code === "SIGN_CONTEXT_REFUSED") {
|
|
439
445
|
this.http_status = refusal?.httpStatus;
|
|
440
446
|
this.backend_error_code = refusal?.errorCode;
|
|
441
|
-
if (
|
|
447
|
+
if (refusal?.errorCode === "client_outdated") {
|
|
448
|
+
this.client_update = refusal.clientUpdate;
|
|
449
|
+
this.next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
450
|
+
step = signerRefusalStep({
|
|
451
|
+
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
452
|
+
nextTool: null,
|
|
453
|
+
nextToolOmittedReason: `this signer is below the minimum version Haven accepts \u2014 tell the user to run ${refusal.clientUpdate?.upgrade_command ?? "the connector again"}, restart the agent runtime, then retry the same haven_sign / haven_sign_x402 call`
|
|
454
|
+
});
|
|
455
|
+
} else if (flow === "direct" && refusal?.httpStatus === 404) {
|
|
442
456
|
this.fallback = "typed_data_b64";
|
|
443
457
|
this.next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
444
458
|
step = signerRefusalStep({
|
|
@@ -496,13 +510,26 @@ async function loadHavenIdentity(credentialsPath) {
|
|
|
496
510
|
}
|
|
497
511
|
}
|
|
498
512
|
var SIGN_CONTEXT_TIMEOUT_MS = 15e3;
|
|
499
|
-
|
|
513
|
+
function signContextHeaders(identity, clientIdentity) {
|
|
514
|
+
return {
|
|
515
|
+
Authorization: `Bearer ${identity.apiKey}`,
|
|
516
|
+
...clientIdentity ? { [HAVEN_CLIENT_HEADER]: clientIdentity } : {}
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function refusalOf(status, body) {
|
|
520
|
+
return {
|
|
521
|
+
httpStatus: status,
|
|
522
|
+
errorCode: typeof body.error_code === "string" ? body.error_code : void 0,
|
|
523
|
+
clientUpdate: readClientUpdate(body.client_update)
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
|
|
500
527
|
let response;
|
|
501
528
|
try {
|
|
502
529
|
response = await fetchImpl(
|
|
503
530
|
`${identity.apiUrl}/x402/${encodeURIComponent(paymentId)}/sign-context`,
|
|
504
531
|
{
|
|
505
|
-
headers:
|
|
532
|
+
headers: signContextHeaders(identity, clientIdentity),
|
|
506
533
|
signal: AbortSignal.timeout(timeoutMs)
|
|
507
534
|
}
|
|
508
535
|
);
|
|
@@ -534,7 +561,7 @@ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, time
|
|
|
534
561
|
throw new HavenSignContextError(
|
|
535
562
|
`Haven refused the signing-context fetch for ${paymentId}: ${detail}` + (response.status === 404 ? " \u2014 check the payment_id came from this agent\u2019s own quote." : response.status === 410 ? " Re-run the quote with the same idempotency key, then sign the fresh payment_id." : ""),
|
|
536
563
|
"SIGN_CONTEXT_REFUSED",
|
|
537
|
-
|
|
564
|
+
refusalOf(response.status, body),
|
|
538
565
|
paymentId
|
|
539
566
|
);
|
|
540
567
|
}
|
|
@@ -554,17 +581,18 @@ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, time
|
|
|
554
581
|
payloadHash: signData.hash,
|
|
555
582
|
typedData: signData.typed_data,
|
|
556
583
|
x402Expected,
|
|
557
|
-
paymentRequired: paymentRequired && typeof paymentRequired === "object" && !Array.isArray(paymentRequired) ? paymentRequired : null
|
|
584
|
+
paymentRequired: paymentRequired && typeof paymentRequired === "object" && !Array.isArray(paymentRequired) ? paymentRequired : null,
|
|
585
|
+
clientUpdate: readClientUpdate(body.client_update)
|
|
558
586
|
};
|
|
559
587
|
}
|
|
560
588
|
var SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS = [DIRECT_SIGN_CONTEXT_VERSION];
|
|
561
|
-
async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS) {
|
|
589
|
+
async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
|
|
562
590
|
let response;
|
|
563
591
|
try {
|
|
564
592
|
response = await fetchImpl(
|
|
565
593
|
`${identity.apiUrl}/payments/${encodeURIComponent(paymentId)}/sign-context`,
|
|
566
594
|
{
|
|
567
|
-
headers:
|
|
595
|
+
headers: signContextHeaders(identity, clientIdentity),
|
|
568
596
|
signal: AbortSignal.timeout(timeoutMs)
|
|
569
597
|
}
|
|
570
598
|
);
|
|
@@ -598,7 +626,7 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
598
626
|
throw new HavenSignContextError(
|
|
599
627
|
`Haven refused the direct-payment signing-context fetch for ${paymentId}: ${detail}` + (response.status === 404 ? " \u2014 either the payment_id is not from this agent\u2019s own POST /payments call, or this Haven backend predates #3271. Pass payload_hash and typed_data_b64 from the haven_send / haven_pay result, unchanged, instead." : body.error_code === "expired" ? " The payment window has expired; call haven_send / haven_pay again with the same idempotency_key." : ""),
|
|
600
628
|
"SIGN_CONTEXT_REFUSED",
|
|
601
|
-
|
|
629
|
+
refusalOf(response.status, body),
|
|
602
630
|
paymentId,
|
|
603
631
|
"direct"
|
|
604
632
|
);
|
|
@@ -619,7 +647,8 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
619
647
|
payloadHash: signData.hash,
|
|
620
648
|
typedData: signData.typed_data,
|
|
621
649
|
status: typeof body.status === "string" ? body.status : "pending_signature",
|
|
622
|
-
expiresAt: typeof body.expires_at === "string" ? body.expires_at : void 0
|
|
650
|
+
expiresAt: typeof body.expires_at === "string" ? body.expires_at : void 0,
|
|
651
|
+
clientUpdate: readClientUpdate(body.client_update)
|
|
623
652
|
};
|
|
624
653
|
}
|
|
625
654
|
|
|
@@ -1146,6 +1175,9 @@ function parseFetchedExpected(fetched) {
|
|
|
1146
1175
|
}
|
|
1147
1176
|
return parsed.data;
|
|
1148
1177
|
}
|
|
1178
|
+
function clientUpdateField(resolved) {
|
|
1179
|
+
return resolved?.ctx.clientUpdate ? { client_update: resolved.ctx.clientUpdate } : {};
|
|
1180
|
+
}
|
|
1149
1181
|
function resolveTypedData(args) {
|
|
1150
1182
|
if (!args.typed_data_b64) return args.typed_data;
|
|
1151
1183
|
try {
|
|
@@ -1179,14 +1211,26 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1179
1211
|
);
|
|
1180
1212
|
}
|
|
1181
1213
|
try {
|
|
1182
|
-
const ctx = await fetchX402SignContext(
|
|
1214
|
+
const ctx = await fetchX402SignContext(
|
|
1215
|
+
identity,
|
|
1216
|
+
args.payment_id,
|
|
1217
|
+
options.signContext?.fetchImpl,
|
|
1218
|
+
void 0,
|
|
1219
|
+
options.signContext?.clientIdentity
|
|
1220
|
+
);
|
|
1183
1221
|
checkPayloadHashMatch(args.payload_hash, ctx.payloadHash, args.payment_id);
|
|
1184
1222
|
return { kind: "x402", ctx };
|
|
1185
1223
|
} catch (err) {
|
|
1186
1224
|
if (opts.allowDirectFallback && err instanceof HavenSignContextError && err.http_status === 409 && err.backend_error_code === "sign_context_unavailable") {
|
|
1187
1225
|
let ctx;
|
|
1188
1226
|
try {
|
|
1189
|
-
ctx = await fetchDirectSignContext(
|
|
1227
|
+
ctx = await fetchDirectSignContext(
|
|
1228
|
+
identity,
|
|
1229
|
+
args.payment_id,
|
|
1230
|
+
options.signContext?.fetchImpl,
|
|
1231
|
+
void 0,
|
|
1232
|
+
options.signContext?.clientIdentity
|
|
1233
|
+
);
|
|
1190
1234
|
} catch (directErr) {
|
|
1191
1235
|
if (directErr instanceof HavenSignContextError && directErr.http_status === 409 && directErr.backend_error_code === "sign_context_unavailable") {
|
|
1192
1236
|
throw err;
|
|
@@ -1236,12 +1280,12 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1236
1280
|
assertBoundDirectPaymentUserOp2(typedData, signer.delegateAddress);
|
|
1237
1281
|
const signature = await signer.signDelegationTypedData(typedData);
|
|
1238
1282
|
await auditSigning("haven_sign", hashTypedData(typedData));
|
|
1239
|
-
return { signature };
|
|
1283
|
+
return { signature, ...clientUpdateField(resolved) };
|
|
1240
1284
|
}
|
|
1241
1285
|
throw new HavenBareHashRefusedError();
|
|
1242
1286
|
}
|
|
1243
1287
|
await auditSigning("haven_sign", hashTypedData(typedData));
|
|
1244
|
-
return { signature: result.signature, x402_binding: result.x402Binding };
|
|
1288
|
+
return { signature: result.signature, x402_binding: result.x402Binding, ...clientUpdateField(resolved) };
|
|
1245
1289
|
}),
|
|
1246
1290
|
haven_x402_sign_header: async (input) => runTool(async () => {
|
|
1247
1291
|
const args = parse("haven_x402_sign_header", coercePaymentRequired(input));
|
|
@@ -1287,7 +1331,8 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1287
1331
|
signature: funding.signature,
|
|
1288
1332
|
x402_binding: funding.x402Binding,
|
|
1289
1333
|
payment_header: header.paymentHeader,
|
|
1290
|
-
accepted: header.accepted
|
|
1334
|
+
accepted: header.accepted,
|
|
1335
|
+
...clientUpdateField(resolved)
|
|
1291
1336
|
};
|
|
1292
1337
|
}),
|
|
1293
1338
|
haven_sign_sweep_delegate: async (input) => runTool(async () => {
|
|
@@ -1426,6 +1471,7 @@ function normalizeError(err) {
|
|
|
1426
1471
|
...err.retry_with_new_quote ? { retry_with_new_quote: true } : {},
|
|
1427
1472
|
...err.http_status !== void 0 ? { http_status: err.http_status } : {},
|
|
1428
1473
|
...err.backend_error_code !== void 0 ? { backend_error_code: err.backend_error_code } : {},
|
|
1474
|
+
...err.client_update ? { client_update: err.client_update } : {},
|
|
1429
1475
|
// #3103: the typed step the error decided beside its action.
|
|
1430
1476
|
...err.next_tool ? { next_tool: err.next_tool } : {},
|
|
1431
1477
|
...err.next_tool_server ? { next_tool_server: err.next_tool_server } : {},
|
|
@@ -1600,7 +1646,7 @@ async function writeAckFile(path, hash) {
|
|
|
1600
1646
|
);
|
|
1601
1647
|
}
|
|
1602
1648
|
var SIGNER_NAME = "@haven_ai/signer";
|
|
1603
|
-
var SIGNER_VERSION = "0.0.0-dev.
|
|
1649
|
+
var SIGNER_VERSION = "0.0.0-dev.202609251409.98ed67a";
|
|
1604
1650
|
async function resolveEdgeSigner(options = {}) {
|
|
1605
1651
|
const { signer } = await resolveSignerRuntime(options);
|
|
1606
1652
|
return signer;
|
|
@@ -1649,7 +1695,10 @@ function buildSignerMcpServer(signer, options = {}) {
|
|
|
1649
1695
|
// The signer CORE stays network-free; fetched bytes still pass the same
|
|
1650
1696
|
// binding verification + digest re-derivation as tool-argument bytes.
|
|
1651
1697
|
signContext: {
|
|
1652
|
-
loadIdentity: () => loadHavenIdentity(credentialsPath)
|
|
1698
|
+
loadIdentity: () => loadHavenIdentity(credentialsPath),
|
|
1699
|
+
// #3303: the backend refuses a signer below a minimum it has set, at
|
|
1700
|
+
// sign-context — the one place this signer meets it.
|
|
1701
|
+
clientIdentity: havenClientIdentity(SIGNER_NAME, SIGNER_VERSION)
|
|
1653
1702
|
}
|
|
1654
1703
|
});
|
|
1655
1704
|
const registerTool = server.tool.bind(server);
|