@keyban/agent-wallet-mcp 0.2.0 → 0.2.2

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/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2026 Keyban
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # @keyban/agent-wallet-mcp
2
+
3
+ Give an AI agent its own blockchain wallet: balances, USDC transfers and
4
+ [x402](https://x402.org) payments, under a spending policy the agent cannot
5
+ change.
6
+
7
+ Registry name: `io.keyban.prod.mcp/agent-wallet`.
8
+
9
+ The server runs locally over stdio, next to your MCP client. It holds one share
10
+ of a FROST threshold key in your OS keyring; Keyban holds the other. No single
11
+ side can move funds alone, and the model never sees either share or your API
12
+ key.
13
+
14
+ ## You need a Keyban wallet first
15
+
16
+ **This server drives a wallet Keyban provisions. It does not create one, and it
17
+ has nothing to drive without one** — installing the package on its own gets you
18
+ a server offering a single `setup_instructions` tool, which points back here. The
19
+ threshold key is the reason: one share lives here, the other lives with us, so
20
+ there is a Keyban side to every wallet by construction and no configuration can
21
+ replace it.
22
+
23
+ So the order is:
24
+
25
+ 1. **Get a Keyban account** at <https://keyban.io>.
26
+ 2. **Create an agent wallet** in the admin app. It provisions the wallet, its
27
+ API key, and a one-shot invite key that authorises this client as a signer.
28
+ 3. **Take the configuration it hands you** — either the environment block for
29
+ the table below, or a ready-to-install `.mcpb` bundle with the values already
30
+ in it.
31
+ 4. **Then** install, with those values in the environment.
32
+
33
+ ## Install
34
+
35
+ Once you have the credentials from step 3:
36
+
37
+ ```bash
38
+ npx -y @keyban/agent-wallet-mcp
39
+ ```
40
+
41
+ With Claude Code:
42
+
43
+ ```bash
44
+ claude mcp add agent-wallet \
45
+ -e KEYBAN_API_KEY=… \
46
+ -e KEYBAN_APP_ID=… \
47
+ -e KEYBAN_INVITE_KEY=0x… \
48
+ -e KEYBAN_WALLET_ADDRESS=0x… \
49
+ -e KEYBAN_NETWORK=… \
50
+ -- npx -y @keyban/agent-wallet-mcp
51
+ ```
52
+
53
+ On first boot the server runs a key generation, keeps its share in your OS
54
+ keyring, and registers itself on chain as a signer of that wallet using the
55
+ invite key. That happens once per machine; the invite key is spent by it.
56
+
57
+ ## Configuration
58
+
59
+ The five required values all come from step 2 — none of them is something you
60
+ can pick yourself.
61
+
62
+ | Variable | Required | What it is |
63
+ |---|---|---|
64
+ | `KEYBAN_API_KEY` | yes | API key of the application the wallet belongs to. Never exposed to the model. |
65
+ | `KEYBAN_APP_ID` | yes | Identifier of the Keyban application. |
66
+ | `KEYBAN_INVITE_KEY` | yes | One-shot ECDSA private key authorizing this client's first-boot registration as a signer. |
67
+ | `KEYBAN_WALLET_ADDRESS` | yes | Address of the account this server drives. |
68
+ | `KEYBAN_NETWORK` | yes | Network the wallet operates on, e.g. `base` or `xdc`. |
69
+ | `KEYBAN_BASE_URL` | no | Backend base URL, or one of `dev`, `testing`, `staging`, `prod`, `sandbox`. Defaults to `https://api.prod.keyban.io`. |
70
+ | `KEYBAN_DKG_BLOB` | no | Pre-generated signer share, base64 JSON. For runtimes with no OS keyring; it does not replace the invite key. |
71
+ | `MCP_LOG_FILE` | no | Path the server writes its log to. |
72
+ | `MCP_LOG_LEVEL` | no | `debug`, `info`, `warn` or `error`. Defaults to `info`. |
73
+
74
+ ## Tools
75
+
76
+ Read-only:
77
+
78
+ | Tool | What it does |
79
+ |---|---|
80
+ | `api_status` | Check Keyban API status |
81
+ | `get_account_info` | Get wallet account info |
82
+ | `balance` | Check wallet balance |
83
+ | `list_expense_transactions` | List wallet transactions |
84
+ | `expense_stats` | Get spending statistics |
85
+ | `wait_for_intent` | Wait for approval decisions |
86
+ | `x402_info` | Inspect the x402 price of a URL |
87
+ | `discover_x402_services` | Search the x402 service directory |
88
+
89
+ Writing:
90
+
91
+ | Tool | What it does |
92
+ |---|---|
93
+ | `sign_message` | Sign a message |
94
+ | `cancel_intent` | Cancel a pending payment intent |
95
+ | `transfer_usdc` | Transfer USDC — moves funds |
96
+ | `x402_pay` | Pay for an x402 resource — moves funds |
97
+
98
+ Every tool carries a `title` and the applicable `readOnlyHint` or
99
+ `destructiveHint`, so a client can tell the two groups apart before calling
100
+ anything.
101
+
102
+ ## Spending policy
103
+
104
+ Transfers and x402 payments are checked against the wallet's policy on the
105
+ Keyban side. A call the policy will not clear on its own does not fail
106
+ silently: it comes back naming a payment intent that needs a human decision.
107
+ `wait_for_intent` blocks until the owner approves or declines, and the agent
108
+ retries once it is approved. The policy lives with the wallet, not with this
109
+ process, so editing this server's configuration cannot widen it.
110
+
111
+ ## Documentation
112
+
113
+ <https://docs.keyban.io/>
114
+
115
+ ## License
116
+
117
+ Apache-2.0. See [LICENSE](./LICENSE).
package/dist/index.mjs CHANGED
@@ -79491,78 +79491,78 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`;
79491
79491
  return 0;
79492
79492
  }
79493
79493
  };
79494
- var import_zod16 = require_zod();
79494
+ var import_zod17 = require_zod();
79495
79495
  var import_zod22 = require_zod();
79496
- var NonEmptyString2 = import_zod16.z.string().min(1);
79497
- var Any2 = import_zod16.z.record(import_zod16.z.unknown());
79498
- var OptionalAny2 = import_zod16.z.record(import_zod16.z.unknown()).optional().nullable();
79496
+ var NonEmptyString2 = import_zod17.z.string().min(1);
79497
+ var Any2 = import_zod17.z.record(import_zod17.z.unknown());
79498
+ var OptionalAny2 = import_zod17.z.record(import_zod17.z.unknown()).optional().nullable();
79499
79499
  var NetworkSchemaV12 = NonEmptyString2;
79500
- var NetworkSchemaV22 = import_zod16.z.string().min(3).refine((val) => val.includes(":"), {
79500
+ var NetworkSchemaV22 = import_zod17.z.string().min(3).refine((val) => val.includes(":"), {
79501
79501
  message: "Network must be in CAIP-2 format (e.g., 'eip155:84532')"
79502
79502
  });
79503
- var NetworkSchema2 = import_zod16.z.union([NetworkSchemaV12, NetworkSchemaV22]);
79504
- var ResourceInfoSchema2 = import_zod16.z.object({
79503
+ var NetworkSchema2 = import_zod17.z.union([NetworkSchemaV12, NetworkSchemaV22]);
79504
+ var ResourceInfoSchema2 = import_zod17.z.object({
79505
79505
  url: NonEmptyString2,
79506
- description: import_zod16.z.string().optional(),
79507
- mimeType: import_zod16.z.string().optional()
79506
+ description: import_zod17.z.string().optional(),
79507
+ mimeType: import_zod17.z.string().optional()
79508
79508
  });
79509
- var PaymentRequirementsV1Schema2 = import_zod16.z.object({
79509
+ var PaymentRequirementsV1Schema2 = import_zod17.z.object({
79510
79510
  scheme: NonEmptyString2,
79511
79511
  network: NetworkSchemaV12,
79512
79512
  maxAmountRequired: NonEmptyString2,
79513
79513
  resource: NonEmptyString2,
79514
79514
  // URL string in V1
79515
- description: import_zod16.z.string(),
79516
- mimeType: import_zod16.z.string().optional(),
79515
+ description: import_zod17.z.string(),
79516
+ mimeType: import_zod17.z.string().optional(),
79517
79517
  outputSchema: Any2.optional().nullable(),
79518
79518
  payTo: NonEmptyString2,
79519
- maxTimeoutSeconds: import_zod16.z.number().positive(),
79519
+ maxTimeoutSeconds: import_zod17.z.number().positive(),
79520
79520
  asset: NonEmptyString2,
79521
79521
  extra: OptionalAny2
79522
79522
  });
79523
- var PaymentRequiredV1Schema2 = import_zod16.z.object({
79524
- x402Version: import_zod16.z.literal(1),
79525
- error: import_zod16.z.string().optional(),
79526
- accepts: import_zod16.z.array(PaymentRequirementsV1Schema2).min(1)
79523
+ var PaymentRequiredV1Schema2 = import_zod17.z.object({
79524
+ x402Version: import_zod17.z.literal(1),
79525
+ error: import_zod17.z.string().optional(),
79526
+ accepts: import_zod17.z.array(PaymentRequirementsV1Schema2).min(1)
79527
79527
  });
79528
- var PaymentPayloadV1Schema2 = import_zod16.z.object({
79529
- x402Version: import_zod16.z.literal(1),
79528
+ var PaymentPayloadV1Schema2 = import_zod17.z.object({
79529
+ x402Version: import_zod17.z.literal(1),
79530
79530
  scheme: NonEmptyString2,
79531
79531
  network: NetworkSchemaV12,
79532
79532
  payload: Any2
79533
79533
  });
79534
- var PaymentRequirementsV2Schema2 = import_zod16.z.object({
79534
+ var PaymentRequirementsV2Schema2 = import_zod17.z.object({
79535
79535
  scheme: NonEmptyString2,
79536
79536
  network: NetworkSchemaV22,
79537
79537
  amount: NonEmptyString2,
79538
79538
  asset: NonEmptyString2,
79539
79539
  payTo: NonEmptyString2,
79540
- maxTimeoutSeconds: import_zod16.z.number().positive(),
79540
+ maxTimeoutSeconds: import_zod17.z.number().positive(),
79541
79541
  extra: OptionalAny2
79542
79542
  });
79543
- var PaymentRequiredV2Schema2 = import_zod16.z.object({
79544
- x402Version: import_zod16.z.literal(2),
79545
- error: import_zod16.z.string().optional(),
79543
+ var PaymentRequiredV2Schema2 = import_zod17.z.object({
79544
+ x402Version: import_zod17.z.literal(2),
79545
+ error: import_zod17.z.string().optional(),
79546
79546
  resource: ResourceInfoSchema2,
79547
- accepts: import_zod16.z.array(PaymentRequirementsV2Schema2).min(1),
79547
+ accepts: import_zod17.z.array(PaymentRequirementsV2Schema2).min(1),
79548
79548
  extensions: OptionalAny2
79549
79549
  });
79550
- var PaymentPayloadV2Schema2 = import_zod16.z.object({
79551
- x402Version: import_zod16.z.literal(2),
79550
+ var PaymentPayloadV2Schema2 = import_zod17.z.object({
79551
+ x402Version: import_zod17.z.literal(2),
79552
79552
  resource: ResourceInfoSchema2.optional(),
79553
79553
  accepted: PaymentRequirementsV2Schema2,
79554
79554
  payload: Any2,
79555
79555
  extensions: OptionalAny2
79556
79556
  });
79557
- var PaymentRequirementsSchema2 = import_zod16.z.union([
79557
+ var PaymentRequirementsSchema2 = import_zod17.z.union([
79558
79558
  PaymentRequirementsV1Schema2,
79559
79559
  PaymentRequirementsV2Schema2
79560
79560
  ]);
79561
- var PaymentRequiredSchema2 = import_zod16.z.discriminatedUnion("x402Version", [
79561
+ var PaymentRequiredSchema2 = import_zod17.z.discriminatedUnion("x402Version", [
79562
79562
  PaymentRequiredV1Schema2,
79563
79563
  PaymentRequiredV2Schema2
79564
79564
  ]);
79565
- var PaymentPayloadSchema2 = import_zod16.z.discriminatedUnion("x402Version", [
79565
+ var PaymentPayloadSchema2 = import_zod17.z.discriminatedUnion("x402Version", [
79566
79566
  PaymentPayloadV1Schema2,
79567
79567
  PaymentPayloadV2Schema2
79568
79568
  ]);
@@ -79977,78 +79977,78 @@ var require_schemas = __commonJS({
79977
79977
  z: () => import_zod22.z
79978
79978
  });
79979
79979
  module.exports = __toCommonJS2(schemas_exports2);
79980
- var import_zod16 = require_zod();
79980
+ var import_zod17 = require_zod();
79981
79981
  var import_zod22 = require_zod();
79982
- var NonEmptyString2 = import_zod16.z.string().min(1);
79983
- var Any2 = import_zod16.z.record(import_zod16.z.unknown());
79984
- var OptionalAny2 = import_zod16.z.record(import_zod16.z.unknown()).optional().nullable();
79982
+ var NonEmptyString2 = import_zod17.z.string().min(1);
79983
+ var Any2 = import_zod17.z.record(import_zod17.z.unknown());
79984
+ var OptionalAny2 = import_zod17.z.record(import_zod17.z.unknown()).optional().nullable();
79985
79985
  var NetworkSchemaV12 = NonEmptyString2;
79986
- var NetworkSchemaV22 = import_zod16.z.string().min(3).refine((val) => val.includes(":"), {
79986
+ var NetworkSchemaV22 = import_zod17.z.string().min(3).refine((val) => val.includes(":"), {
79987
79987
  message: "Network must be in CAIP-2 format (e.g., 'eip155:84532')"
79988
79988
  });
79989
- var NetworkSchema2 = import_zod16.z.union([NetworkSchemaV12, NetworkSchemaV22]);
79990
- var ResourceInfoSchema2 = import_zod16.z.object({
79989
+ var NetworkSchema2 = import_zod17.z.union([NetworkSchemaV12, NetworkSchemaV22]);
79990
+ var ResourceInfoSchema2 = import_zod17.z.object({
79991
79991
  url: NonEmptyString2,
79992
- description: import_zod16.z.string().optional(),
79993
- mimeType: import_zod16.z.string().optional()
79992
+ description: import_zod17.z.string().optional(),
79993
+ mimeType: import_zod17.z.string().optional()
79994
79994
  });
79995
- var PaymentRequirementsV1Schema2 = import_zod16.z.object({
79995
+ var PaymentRequirementsV1Schema2 = import_zod17.z.object({
79996
79996
  scheme: NonEmptyString2,
79997
79997
  network: NetworkSchemaV12,
79998
79998
  maxAmountRequired: NonEmptyString2,
79999
79999
  resource: NonEmptyString2,
80000
80000
  // URL string in V1
80001
- description: import_zod16.z.string(),
80002
- mimeType: import_zod16.z.string().optional(),
80001
+ description: import_zod17.z.string(),
80002
+ mimeType: import_zod17.z.string().optional(),
80003
80003
  outputSchema: Any2.optional().nullable(),
80004
80004
  payTo: NonEmptyString2,
80005
- maxTimeoutSeconds: import_zod16.z.number().positive(),
80005
+ maxTimeoutSeconds: import_zod17.z.number().positive(),
80006
80006
  asset: NonEmptyString2,
80007
80007
  extra: OptionalAny2
80008
80008
  });
80009
- var PaymentRequiredV1Schema2 = import_zod16.z.object({
80010
- x402Version: import_zod16.z.literal(1),
80011
- error: import_zod16.z.string().optional(),
80012
- accepts: import_zod16.z.array(PaymentRequirementsV1Schema2).min(1)
80009
+ var PaymentRequiredV1Schema2 = import_zod17.z.object({
80010
+ x402Version: import_zod17.z.literal(1),
80011
+ error: import_zod17.z.string().optional(),
80012
+ accepts: import_zod17.z.array(PaymentRequirementsV1Schema2).min(1)
80013
80013
  });
80014
- var PaymentPayloadV1Schema2 = import_zod16.z.object({
80015
- x402Version: import_zod16.z.literal(1),
80014
+ var PaymentPayloadV1Schema2 = import_zod17.z.object({
80015
+ x402Version: import_zod17.z.literal(1),
80016
80016
  scheme: NonEmptyString2,
80017
80017
  network: NetworkSchemaV12,
80018
80018
  payload: Any2
80019
80019
  });
80020
- var PaymentRequirementsV2Schema2 = import_zod16.z.object({
80020
+ var PaymentRequirementsV2Schema2 = import_zod17.z.object({
80021
80021
  scheme: NonEmptyString2,
80022
80022
  network: NetworkSchemaV22,
80023
80023
  amount: NonEmptyString2,
80024
80024
  asset: NonEmptyString2,
80025
80025
  payTo: NonEmptyString2,
80026
- maxTimeoutSeconds: import_zod16.z.number().positive(),
80026
+ maxTimeoutSeconds: import_zod17.z.number().positive(),
80027
80027
  extra: OptionalAny2
80028
80028
  });
80029
- var PaymentRequiredV2Schema2 = import_zod16.z.object({
80030
- x402Version: import_zod16.z.literal(2),
80031
- error: import_zod16.z.string().optional(),
80029
+ var PaymentRequiredV2Schema2 = import_zod17.z.object({
80030
+ x402Version: import_zod17.z.literal(2),
80031
+ error: import_zod17.z.string().optional(),
80032
80032
  resource: ResourceInfoSchema2,
80033
- accepts: import_zod16.z.array(PaymentRequirementsV2Schema2).min(1),
80033
+ accepts: import_zod17.z.array(PaymentRequirementsV2Schema2).min(1),
80034
80034
  extensions: OptionalAny2
80035
80035
  });
80036
- var PaymentPayloadV2Schema2 = import_zod16.z.object({
80037
- x402Version: import_zod16.z.literal(2),
80036
+ var PaymentPayloadV2Schema2 = import_zod17.z.object({
80037
+ x402Version: import_zod17.z.literal(2),
80038
80038
  resource: ResourceInfoSchema2.optional(),
80039
80039
  accepted: PaymentRequirementsV2Schema2,
80040
80040
  payload: Any2,
80041
80041
  extensions: OptionalAny2
80042
80042
  });
80043
- var PaymentRequirementsSchema2 = import_zod16.z.union([
80043
+ var PaymentRequirementsSchema2 = import_zod17.z.union([
80044
80044
  PaymentRequirementsV1Schema2,
80045
80045
  PaymentRequirementsV2Schema2
80046
80046
  ]);
80047
- var PaymentRequiredSchema2 = import_zod16.z.discriminatedUnion("x402Version", [
80047
+ var PaymentRequiredSchema2 = import_zod17.z.discriminatedUnion("x402Version", [
80048
80048
  PaymentRequiredV1Schema2,
80049
80049
  PaymentRequiredV2Schema2
80050
80050
  ]);
80051
- var PaymentPayloadSchema2 = import_zod16.z.discriminatedUnion("x402Version", [
80051
+ var PaymentPayloadSchema2 = import_zod17.z.discriminatedUnion("x402Version", [
80052
80052
  PaymentPayloadV1Schema2,
80053
80053
  PaymentPayloadV2Schema2
80054
80054
  ]);
@@ -111151,6 +111151,145 @@ function resolveURL(value) {
111151
111151
  return ENV_URLS[value] ?? value;
111152
111152
  }
111153
111153
 
111154
+ // src/lib/session.ts
111155
+ var VALID_NETWORKS = [
111156
+ "EthereumAnvil",
111157
+ "BaseSepolia",
111158
+ "BaseMainnet",
111159
+ "XdcApothem",
111160
+ "XdcMainnet",
111161
+ "StellarQuickstart",
111162
+ "StellarTestnet",
111163
+ "StellarMainnet"
111164
+ ];
111165
+ function createSession() {
111166
+ return {
111167
+ baseURL: ENV_URLS.prod,
111168
+ apiKey: "",
111169
+ appId: "",
111170
+ network: "",
111171
+ inviteKey: "",
111172
+ walletAddress: "",
111173
+ seededDkgBlob: null,
111174
+ nodeApi: null,
111175
+ signer: null,
111176
+ account: null,
111177
+ frostVerifierAddress: null,
111178
+ initPromise: null,
111179
+ pendingX402: /* @__PURE__ */ new Map()
111180
+ };
111181
+ }
111182
+ function validateNetwork(network) {
111183
+ if (!network || !VALID_NETWORKS.includes(network)) {
111184
+ throw new Error(`Invalid or missing network "${network}". Must be one of: ${VALID_NETWORKS.join(", ")}. Set it via the KEYBAN_NETWORK env var.`);
111185
+ }
111186
+ }
111187
+ function requireCredentials(session) {
111188
+ if (!session.apiKey) {
111189
+ throw new Error("No API key configured. Set the KEYBAN_API_KEY environment variable.");
111190
+ }
111191
+ if (!session.appId) {
111192
+ throw new Error("No application ID configured. Set the KEYBAN_APP_ID environment variable.");
111193
+ }
111194
+ if (!session.inviteKey) {
111195
+ throw new Error(
111196
+ "No invite key configured. Set the KEYBAN_INVITE_KEY environment variable (admin-provisioned wallets only)."
111197
+ );
111198
+ }
111199
+ if (!session.walletAddress) {
111200
+ throw new Error(
111201
+ "No wallet address configured. Set the KEYBAN_WALLET_ADDRESS environment variable (admin-provisioned wallets only)."
111202
+ );
111203
+ }
111204
+ }
111205
+ function requireAccount(session) {
111206
+ requireCredentials(session);
111207
+ if (!session.account || !session.signer || !session.nodeApi || !session.frostVerifierAddress) {
111208
+ throw new Error("Account not initialized. Call the initialize tool first.");
111209
+ }
111210
+ }
111211
+ function buildApiKeyHeaders(session) {
111212
+ return {
111213
+ "X-Api-Key": session.apiKey
111214
+ };
111215
+ }
111216
+
111217
+ // src/lib/configuration.ts
111218
+ var HEX_64 = /^0x[a-fA-F0-9]{64}$/;
111219
+ var HEX_40 = /^0x[a-fA-F0-9]{40}$/;
111220
+ var REQUIRED_VARIABLES = [
111221
+ {
111222
+ name: "KEYBAN_API_KEY",
111223
+ what: "API key of the application the wallet belongs to"
111224
+ },
111225
+ {
111226
+ name: "KEYBAN_APP_ID",
111227
+ what: "identifier of the Keyban application"
111228
+ },
111229
+ {
111230
+ name: "KEYBAN_WALLET_ADDRESS",
111231
+ what: "address of the account this server drives",
111232
+ reject: (value) => HEX_40.test(value) ? null : "must be 0x followed by 40 hex characters"
111233
+ },
111234
+ {
111235
+ name: "KEYBAN_INVITE_KEY",
111236
+ what: "one-shot key authorizing this client as a signer, on first boot",
111237
+ reject: (value) => HEX_64.test(value) ? null : "must be 0x followed by 64 hex characters"
111238
+ },
111239
+ {
111240
+ name: "KEYBAN_NETWORK",
111241
+ what: "network the wallet operates on",
111242
+ reject: (value) => VALID_NETWORKS.includes(value) ? null : `must be one of: ${VALID_NETWORKS.join(", ")}`
111243
+ }
111244
+ ];
111245
+ function findConfigurationProblems(env = process.env) {
111246
+ const problems = [];
111247
+ for (const { name, what, reject } of REQUIRED_VARIABLES) {
111248
+ const value = env[name];
111249
+ if (!value) {
111250
+ problems.push({ name, what, reason: "missing" });
111251
+ continue;
111252
+ }
111253
+ const rejected = reject?.(value);
111254
+ if (rejected) problems.push({ name, what, reason: rejected });
111255
+ }
111256
+ return problems;
111257
+ }
111258
+ var WIZARD_URL = "https://keyban.io";
111259
+ var README_URL = "https://www.npmjs.com/package/@keyban/agent-wallet-mcp";
111260
+ function describeConfigurationProblems(problems) {
111261
+ const list = problems.map(
111262
+ ({ name, what, reason }) => reason === "missing" ? `- ${name} \u2014 not set. It is the ${what}.` : `- ${name} \u2014 set, but ${reason}. It is the ${what}.`
111263
+ ).join("\n");
111264
+ return [
111265
+ "This server drives a blockchain wallet that Keyban provisions. It cannot create",
111266
+ "one, and it will not work until it is given the credentials of an existing one.",
111267
+ "",
111268
+ "What is wrong right now:",
111269
+ "",
111270
+ list,
111271
+ "",
111272
+ "These are not values to invent: the wallet is held by a threshold key whose other",
111273
+ "share stays with Keyban, so every wallet has a Keyban side and no local",
111274
+ "configuration can stand in for it.",
111275
+ "",
111276
+ "How to get them:",
111277
+ "",
111278
+ `1. Create a Keyban account at ${WIZARD_URL}`,
111279
+ "2. Create an agent wallet in the admin app. It provisions the wallet, its API",
111280
+ " key, and a one-shot invite key that authorizes this client as a signer.",
111281
+ "3. Copy the configuration it hands you \u2014 either the environment block, or a",
111282
+ " ready-to-install .mcpb bundle with the values already in it.",
111283
+ "4. Restart this server with those values in its environment.",
111284
+ "",
111285
+ `The README covers the same ground: ${README_URL}`
111286
+ ].join("\n");
111287
+ }
111288
+ function summarizeConfigurationProblems(problems) {
111289
+ const names = problems.map((p) => p.name).join(", ");
111290
+ return `Keyban agent wallet MCP server \u2014 NOT USABLE YET. It drives a wallet that Keyban provisions, and its configuration is incomplete: ${names}. None of the wallet tools are available until that is fixed. Tell the user to create an agent wallet at ${WIZARD_URL}, and call setup_instructions for the steps.`;
111291
+ }
111292
+
111154
111293
  // src/lib/logger.ts
111155
111294
  import { appendFileSync } from "node:fs";
111156
111295
  var LOG_LEVELS = {
@@ -111227,69 +111366,6 @@ function createLogger(context, minLevel) {
111227
111366
  }
111228
111367
  var logger = createLogger("agent-wallet-mcp");
111229
111368
 
111230
- // src/lib/session.ts
111231
- var VALID_NETWORKS = [
111232
- "EthereumAnvil",
111233
- "BaseSepolia",
111234
- "BaseMainnet",
111235
- "XdcApothem",
111236
- "XdcMainnet",
111237
- "StellarQuickstart",
111238
- "StellarTestnet",
111239
- "StellarMainnet"
111240
- ];
111241
- function createSession() {
111242
- return {
111243
- baseURL: ENV_URLS.prod,
111244
- apiKey: "",
111245
- appId: "",
111246
- network: "",
111247
- inviteKey: "",
111248
- walletAddress: "",
111249
- seededDkgBlob: null,
111250
- nodeApi: null,
111251
- signer: null,
111252
- account: null,
111253
- frostVerifierAddress: null,
111254
- initPromise: null,
111255
- pendingX402: /* @__PURE__ */ new Map()
111256
- };
111257
- }
111258
- function validateNetwork(network) {
111259
- if (!network || !VALID_NETWORKS.includes(network)) {
111260
- throw new Error(`Invalid or missing network "${network}". Must be one of: ${VALID_NETWORKS.join(", ")}. Set it via the KEYBAN_NETWORK env var.`);
111261
- }
111262
- }
111263
- function requireCredentials(session) {
111264
- if (!session.apiKey) {
111265
- throw new Error("No API key configured. Set the KEYBAN_API_KEY environment variable.");
111266
- }
111267
- if (!session.appId) {
111268
- throw new Error("No application ID configured. Set the KEYBAN_APP_ID environment variable.");
111269
- }
111270
- if (!session.inviteKey) {
111271
- throw new Error(
111272
- "No invite key configured. Set the KEYBAN_INVITE_KEY environment variable (admin-provisioned wallets only)."
111273
- );
111274
- }
111275
- if (!session.walletAddress) {
111276
- throw new Error(
111277
- "No wallet address configured. Set the KEYBAN_WALLET_ADDRESS environment variable (admin-provisioned wallets only)."
111278
- );
111279
- }
111280
- }
111281
- function requireAccount(session) {
111282
- requireCredentials(session);
111283
- if (!session.account || !session.signer || !session.nodeApi || !session.frostVerifierAddress) {
111284
- throw new Error("Account not initialized. Call the initialize tool first.");
111285
- }
111286
- }
111287
- function buildApiKeyHeaders(session) {
111288
- return {
111289
- "X-Api-Key": session.apiKey
111290
- };
111291
- }
111292
-
111293
111369
  // src/lib/tool-registry.ts
111294
111370
  function registerTool(server, tool) {
111295
111371
  server.registerTool(
@@ -124422,6 +124498,20 @@ function createListExpenseTransactionsTool(session) {
124422
124498
  };
124423
124499
  }
124424
124500
 
124501
+ // src/tools/setup-instructions.ts
124502
+ var setupInstructionsSchema = external_exports.object({});
124503
+ function createSetupInstructionsTool(problems) {
124504
+ const names = problems.map((p) => p.name).join(", ");
124505
+ return {
124506
+ name: "setup_instructions",
124507
+ title: "How to finish setting up this wallet",
124508
+ annotations: { readOnlyHint: true, openWorldHint: false },
124509
+ description: `This Keyban agent wallet has no wallet to drive yet, so none of its usual tools are available. Incomplete configuration: ${names}. Call this tool for the steps to obtain those values from keyban.io.`,
124510
+ inputSchema: setupInstructionsSchema,
124511
+ handler: async (_input) => describeConfigurationProblems(problems)
124512
+ };
124513
+ }
124514
+
124425
124515
  // src/index.ts
124426
124516
  var SERVER_NAME = "agent-wallet-mcp";
124427
124517
  var SERVER_VERSION = "0.0.0";
@@ -124436,27 +124526,16 @@ async function main() {
124436
124526
  if (process.env.KEYBAN_NETWORK) session.network = process.env.KEYBAN_NETWORK;
124437
124527
  logger.info("API key configured from environment");
124438
124528
  }
124439
- const envInviteKey = process.env.KEYBAN_INVITE_KEY;
124440
- const envWalletAddress = process.env.KEYBAN_WALLET_ADDRESS;
124441
- if (!envInviteKey) {
124442
- throw new Error(
124443
- "KEYBAN_INVITE_KEY is required (one-shot ECDSA private key, 0x + 64 hex)."
124444
- );
124445
- }
124446
- if (!envWalletAddress) {
124447
- throw new Error(
124448
- "KEYBAN_WALLET_ADDRESS is required (deployed SchnorrAccount address, 0x + 40 hex)."
124449
- );
124450
- }
124451
- if (!/^0x[a-fA-F0-9]{64}$/.test(envInviteKey)) {
124452
- throw new Error("KEYBAN_INVITE_KEY must be 0x followed by 64 hex characters.");
124453
- }
124454
- if (!/^0x[a-fA-F0-9]{40}$/.test(envWalletAddress)) {
124455
- throw new Error("KEYBAN_WALLET_ADDRESS must be 0x followed by 40 hex characters.");
124529
+ const configurationProblems = findConfigurationProblems();
124530
+ const unconfigured = configurationProblems.length > 0;
124531
+ if (unconfigured) {
124532
+ const summary = configurationProblems.map((p) => `${p.name} (${p.reason})`).join(", ");
124533
+ logger.warn(`Incomplete configuration: ${summary}`);
124534
+ } else {
124535
+ session.inviteKey = process.env.KEYBAN_INVITE_KEY ?? "";
124536
+ session.walletAddress = process.env.KEYBAN_WALLET_ADDRESS ?? "";
124537
+ logger.info("Agent-wallet credentials configured");
124456
124538
  }
124457
- session.inviteKey = envInviteKey;
124458
- session.walletAddress = envWalletAddress;
124459
- logger.info("Agent-wallet credentials configured");
124460
124539
  const envDkgBlob = process.env.KEYBAN_DKG_BLOB;
124461
124540
  if (envDkgBlob) {
124462
124541
  try {
@@ -124478,10 +124557,10 @@ async function main() {
124478
124557
  version: SERVER_VERSION
124479
124558
  },
124480
124559
  {
124481
- instructions: "Keyban agent wallet MCP server. Provides tools to manage blockchain wallets: check balances, transfer USDC, sign messages, fetch x402 payment metadata and x402 payment.."
124560
+ instructions: unconfigured ? summarizeConfigurationProblems(configurationProblems) : "Keyban agent wallet MCP server. Provides tools to manage blockchain wallets: check balances, transfer USDC, sign messages, fetch x402 payment metadata and x402 payment.."
124482
124561
  }
124483
124562
  );
124484
- const tools = [
124563
+ const tools = unconfigured ? [createSetupInstructionsTool(configurationProblems)] : [
124485
124564
  createGetAccountInfoTool(session),
124486
124565
  createApiStatusTool(session),
124487
124566
  createSignMessageTool(session),