@elisym/mcp 0.3.3 → 0.5.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/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import { LIMITS, DEFAULT_KIND_OFFSET, SolanaPaymentStrategy, ElisymIdentity, ElisymClient, RELAYS, validateAgentName, serializeConfig, toDTag, getProtocolProgramId, getProtocolConfig } from '@elisym/sdk';
3
- import { generateKeyPairSigner, createKeyPairSignerFromBytes, createSolanaRpc, address, createSolanaRpcSubscriptions, sendAndConfirmTransactionFactory, getSignatureFromTransaction, pipe, createTransactionMessage, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, signTransactionMessageWithSigners, isAddress } from '@solana/kit';
2
+ import { LIMITS, DEFAULT_KIND_OFFSET, SolanaPaymentStrategy, makeCensor, DEFAULT_REDACT_PATHS, validateAgentName, RELAYS, toDTag, ElisymIdentity, ElisymClient, getProtocolProgramId, getProtocolConfig } from '@elisym/sdk';
3
+ import { generateKeyPairSigner, address, createSolanaRpcSubscriptions, sendAndConfirmTransactionFactory, getSignatureFromTransaction, pipe, createTransactionMessage, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, signTransactionMessageWithSigners, createKeyPairSignerFromBytes, createSolanaRpc, isAddress } from '@solana/kit';
4
4
  import bs58 from 'bs58';
5
5
  import { Command } from 'commander';
6
- import { nip19, generateSecretKey, getPublicKey } from 'nostr-tools';
7
- import { readFile, readdir, stat, mkdir, writeFile, rename, unlink } from 'node:fs/promises';
6
+ import { generateSecretKey, nip19, getPublicKey } from 'nostr-tools';
7
+ import { listAgents, createAgentDir, writeYaml, writeSecrets, loadAgent } from '@elisym/sdk/agent-store';
8
+ import { readFile, writeFile, rename, unlink } from 'node:fs/promises';
8
9
  import { homedir, platform } from 'node:os';
9
10
  import { dirname, join } from 'node:path';
10
- import { parseConfig } from '@elisym/sdk/node';
11
11
  import { readFileSync } from 'node:fs';
12
12
  import { fileURLToPath } from 'node:url';
13
13
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
@@ -15,158 +15,75 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
15
15
  import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
16
16
  import { z, ZodError } from 'zod';
17
17
  import { zodToJsonSchema } from 'zod-to-json-schema';
18
+ import pino from 'pino';
18
19
  import { randomBytes } from 'node:crypto';
19
20
  import { getTransferSolInstruction } from '@solana-program/system';
20
21
 
21
- async function writeFileAtomic(path, content, mode = 384) {
22
- const tmpPath = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
23
- try {
24
- await writeFile(tmpPath, content, { mode });
25
- await rename(tmpPath, path);
26
- } catch (err) {
27
- try {
28
- await unlink(tmpPath);
29
- } catch {
30
- }
31
- throw err;
32
- }
33
- }
34
-
35
- // src/config.ts
36
- function rawConfigIsEncrypted(raw) {
37
- return raw.includes('"encrypted:v1:');
38
- }
39
- function agentsDir() {
40
- return join(homedir(), ".elisym", "agents");
41
- }
42
- function agentConfigPath(name) {
43
- return join(agentsDir(), name, "config.json");
44
- }
45
- function coerceNetwork(raw, agentName) {
22
+ function coerceNetwork(raw, name) {
46
23
  if (raw === void 0 || raw === "devnet") {
47
24
  return "devnet";
48
25
  }
49
26
  if (raw === "mainnet") {
50
27
  throw new Error(
51
- `Agent "${agentName}" is configured for mainnet, which is not supported until the elisym-config program is deployed there. Re-create the agent with --network devnet: rm -rf ~/.elisym/agents/${agentName} && elisym-mcp init ${agentName} --network devnet`
28
+ `Agent "${name}" is configured for mainnet, which is not supported until the elisym-config program is deployed there. Re-create the agent with --network devnet: rm -rf ~/.elisym/${name} && elisym-mcp init ${name} --network devnet`
52
29
  );
53
30
  }
54
- throw new Error(`Agent "${agentName}" has unsupported network "${raw}". Expected "devnet".`);
31
+ throw new Error(`Agent "${name}" has unsupported network "${raw}". Expected "devnet".`);
55
32
  }
56
33
  async function loadAgentConfig(name, passphrase) {
57
34
  validateAgentName(name);
58
- const configPath = agentConfigPath(name);
59
- const raw = await readFile(configPath, "utf-8");
60
- const rawEncrypted = rawConfigIsEncrypted(raw);
61
- const effectivePassphrase = process.env.ELISYM_PASSPHRASE;
62
- if (rawEncrypted && !effectivePassphrase) {
63
- throw new Error(
64
- `Agent "${name}" has an encrypted config but no passphrase. Set the ELISYM_PASSPHRASE environment variable.`
65
- );
66
- }
67
- let config;
68
- try {
69
- config = parseConfig(raw, effectivePassphrase);
70
- } catch (e) {
71
- const msg = e instanceof Error ? e.message : String(e);
72
- throw new Error(
73
- `Failed to load agent "${name}": ${msg}. If this config was created by an earlier version, delete ~/.elisym/agents/${name} and run "elisym-mcp init ${name}" again.`
74
- );
75
- }
76
- const network = coerceNetwork(config.wallet?.network ?? config.payments?.[0]?.network, name);
35
+ const loaded = await loadAgent(name, process.cwd(), passphrase);
36
+ const solPayment = loaded.yaml.payments.find((entry) => entry.chain === "solana");
37
+ const network = coerceNetwork(solPayment?.network, name);
77
38
  return {
78
- nostrSecretKey: config.identity.secret_key,
79
- solanaSecretKey: config.wallet?.secret_key,
80
- relays: config.relays?.length ? config.relays : void 0,
39
+ nostrSecretKey: loaded.secrets.nostr_secret_key,
40
+ solanaSecretKey: loaded.secrets.solana_secret_key,
41
+ relays: loaded.yaml.relays.length > 0 ? loaded.yaml.relays : void 0,
81
42
  network,
82
- payments: config.payments,
83
- security: config.security ?? {},
84
- encrypted: rawEncrypted
43
+ payments: loaded.yaml.payments.length > 0 ? loaded.yaml.payments.map((payment2) => ({
44
+ chain: payment2.chain,
45
+ network: payment2.network,
46
+ address: payment2.address
47
+ })) : void 0,
48
+ security: {
49
+ withdrawals_enabled: loaded.yaml.security.withdrawals_enabled,
50
+ agent_switch_enabled: loaded.yaml.security.agent_switch_enabled
51
+ },
52
+ encrypted: loaded.encrypted
85
53
  };
86
54
  }
87
- async function saveAgentConfig(name, config) {
55
+ async function saveAgentConfig(name, input) {
88
56
  validateAgentName(name);
89
- const agentDirPath = join(agentsDir(), name);
90
- await mkdir(agentDirPath, { recursive: true, mode: 448 });
91
- const network = config.network ?? "devnet";
92
- const { encryptSecret } = await import('@elisym/sdk/node');
93
- const encrypt = (v) => config.passphrase ? encryptSecret(v, config.passphrase) : v;
94
- const nostrSecret = encrypt(config.nostrSecretKey);
95
- const solanaSecret = config.solanaSecretKey ? encrypt(config.solanaSecretKey) : void 0;
96
- const agentConfig = {
97
- identity: {
98
- secret_key: nostrSecret,
99
- name: config.name,
100
- description: config.description
101
- },
102
- relays: config.relays,
103
- capabilities: config.capabilities ?? [],
104
- ...config.solanaAddress && {
105
- payments: [
106
- {
107
- chain: "solana",
108
- network,
109
- address: config.solanaAddress
110
- }
111
- ]
112
- },
113
- ...solanaSecret && {
114
- wallet: {
115
- chain: "solana",
116
- network,
117
- secret_key: solanaSecret
118
- }
57
+ const created = await createAgentDir({ target: "home", name, cwd: process.cwd() });
58
+ const network = input.network ?? "devnet";
59
+ await writeYaml(created.dir, {
60
+ display_name: void 0,
61
+ description: input.description,
62
+ picture: void 0,
63
+ banner: void 0,
64
+ relays: input.relays,
65
+ payments: input.solanaAddress ? [{ chain: "solana", network, address: input.solanaAddress }] : [],
66
+ llm: void 0,
67
+ security: input.security ?? {}
68
+ });
69
+ await writeSecrets(
70
+ created.dir,
71
+ {
72
+ nostr_secret_key: input.nostrSecretKey,
73
+ solana_secret_key: input.solanaSecretKey
119
74
  },
120
- ...config.security && { security: config.security }
121
- };
122
- await writeFileAtomic(join(agentDirPath, "config.json"), serializeConfig(agentConfig), 384);
75
+ input.passphrase
76
+ );
123
77
  }
124
78
  async function updateAgentSecurity(name, patch, passphrase) {
125
- validateAgentName(name);
126
- const configPath = agentConfigPath(name);
127
- const raw = await readFile(configPath, "utf-8");
128
- const effectivePassphrase = process.env.ELISYM_PASSPHRASE;
129
- const config = parseConfig(raw, effectivePassphrase);
130
- const rawEncrypted = rawConfigIsEncrypted(raw);
131
- if (rawEncrypted && !effectivePassphrase) {
132
- throw new Error(
133
- `Agent "${name}" is encrypted - set ELISYM_PASSPHRASE to update security flags.`
134
- );
135
- }
136
- const merged = { ...config.security, ...patch };
137
- const { encryptSecret } = await import('@elisym/sdk/node');
138
- const encrypt = (v) => effectivePassphrase ? encryptSecret(v, effectivePassphrase) : v;
139
- const next = {
140
- ...config,
141
- // parseConfig returns decrypted secrets; re-encrypt them if the file was encrypted
142
- identity: {
143
- ...config.identity,
144
- secret_key: rawEncrypted ? encrypt(config.identity.secret_key) : config.identity.secret_key
145
- },
146
- wallet: config.wallet ? {
147
- ...config.wallet,
148
- secret_key: rawEncrypted ? encrypt(config.wallet.secret_key) : config.wallet.secret_key
149
- } : void 0,
150
- security: merged
151
- };
152
- await writeFileAtomic(configPath, serializeConfig(next), 384);
79
+ const loaded = await loadAgent(name, process.cwd(), passphrase);
80
+ const merged = { ...loaded.yaml.security, ...patch };
81
+ await writeYaml(loaded.dir, { ...loaded.yaml, security: merged });
153
82
  return merged;
154
83
  }
155
84
  async function listAgentNames() {
156
- try {
157
- const entries = await readdir(agentsDir());
158
- const names = [];
159
- for (const entry of entries) {
160
- try {
161
- await stat(join(agentsDir(), entry, "config.json"));
162
- names.push(entry);
163
- } catch {
164
- }
165
- }
166
- return names;
167
- } catch {
168
- return [];
169
- }
85
+ const agents = await listAgents(process.cwd());
86
+ return agents.map((agent) => agent.name);
170
87
  }
171
88
  function rpcUrlFor(_network) {
172
89
  return "https://api.devnet.solana.com";
@@ -258,6 +175,19 @@ var AgentContext = class _AgentContext {
258
175
  return nonce;
259
176
  }
260
177
  };
178
+ async function writeFileAtomic(path, content, mode = 384) {
179
+ const tmpPath = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
180
+ try {
181
+ await writeFile(tmpPath, content, { mode });
182
+ await rename(tmpPath, path);
183
+ } catch (err) {
184
+ try {
185
+ await unlink(tmpPath);
186
+ } catch {
187
+ }
188
+ throw err;
189
+ }
190
+ }
261
191
  var LAMPORTS_PER_SOL = 1000000000n;
262
192
  function readPackageVersion() {
263
193
  try {
@@ -606,6 +536,18 @@ async function installToConfig(path, entry) {
606
536
  await safeRewriteJson(path, raw, config);
607
537
  return true;
608
538
  }
539
+ function createLogger(destination) {
540
+ const opts = {
541
+ name: "elisym-mcp",
542
+ level: process.env.LOG_LEVEL ?? "info",
543
+ redact: {
544
+ paths: DEFAULT_REDACT_PATHS,
545
+ censor: makeCensor()
546
+ }
547
+ };
548
+ return pino(opts, pino.destination(2));
549
+ }
550
+ var logger = createLogger();
609
551
 
610
552
  // src/tools/types.ts
611
553
  function defineTool(def) {
@@ -675,7 +617,10 @@ async function buildAgentInstance(name, config) {
675
617
  secretKey: decoded
676
618
  };
677
619
  } catch {
678
- console.error(`[mcp:warn] Invalid Solana key for agent "${name}" - payments disabled`);
620
+ logger.warn(
621
+ { event: "invalid_solana_key", agent: name },
622
+ "invalid Solana key - payments disabled"
623
+ );
679
624
  }
680
625
  }
681
626
  return {
@@ -690,7 +635,7 @@ async function buildAgentInstance(name, config) {
690
635
  var agentTools = [
691
636
  defineTool({
692
637
  name: "create_agent",
693
- description: "Create a new agent identity. Generates Nostr keypair and Solana wallet, saves config to ~/.elisym/agents/<name>/. When activate=true (default), the current active agent must have `security.agent_switch_enabled` set to true, otherwise the new agent is created but NOT activated (pass activate=false or run `elisym-mcp enable-agent-switch <current-agent>`).",
638
+ description: "Create a new agent identity. Generates Nostr keypair and Solana wallet, saves config to ~/.elisym/<name>/. When activate=true (default), the current active agent must have `security.agent_switch_enabled` set to true, otherwise the new agent is created but NOT activated (pass activate=false or run `elisym-mcp enable-agent-switch <current-agent>`).",
694
639
  schema: CreateAgentSchema,
695
640
  async handler(ctx, input) {
696
641
  ctx.toolRateLimiter.check();
@@ -708,8 +653,9 @@ var agentTools = [
708
653
  if (input.activate) {
709
654
  const envOverride = process.env.ELISYM_ALLOW_AGENT_SWITCH === "1";
710
655
  if (envOverride) {
711
- console.error(
712
- "[mcp:security] ELISYM_ALLOW_AGENT_SWITCH override active - agent switch gate bypassed"
656
+ logger.warn(
657
+ { event: "agent_switch_gate_bypassed", context: "create_agent" },
658
+ "ELISYM_ALLOW_AGENT_SWITCH override active - agent switch gate bypassed"
713
659
  );
714
660
  }
715
661
  try {
@@ -760,8 +706,9 @@ Solana: ${solanaSigner.address}
760
706
  async handler(ctx, input) {
761
707
  const envOverride = process.env.ELISYM_ALLOW_AGENT_SWITCH === "1";
762
708
  if (envOverride) {
763
- console.error(
764
- "[mcp:security] ELISYM_ALLOW_AGENT_SWITCH override active - agent switch gate bypassed"
709
+ logger.warn(
710
+ { event: "agent_switch_gate_bypassed", context: "switch_agent" },
711
+ "ELISYM_ALLOW_AGENT_SWITCH override active - agent switch gate bypassed"
765
712
  );
766
713
  }
767
714
  try {
@@ -1177,7 +1124,11 @@ async function executePaymentFlow(agent, paymentRequest, jobId, providerPubkey,
1177
1124
  signature
1178
1125
  );
1179
1126
  } catch (e) {
1180
- console.error("[mcp] On-chain payment confirmed but Nostr confirmation failed:", e);
1127
+ const message = e instanceof Error ? e.message : String(e);
1128
+ logger.error(
1129
+ { event: "nostr_confirmation_failed", jobId, providerPubkey, err: message },
1130
+ "on-chain payment confirmed but Nostr confirmation failed"
1131
+ );
1181
1132
  }
1182
1133
  return signature;
1183
1134
  }
@@ -1198,8 +1149,13 @@ function makePaymentFeedbackHandler(opts) {
1198
1149
  return;
1199
1150
  }
1200
1151
  if (paying || paid) {
1201
- console.error(
1202
- `[mcp] ignoring duplicate payment-required for job ${opts.jobId} (state=${paying ? "in-flight" : "paid"})`
1152
+ logger.info(
1153
+ {
1154
+ event: "duplicate_payment_required",
1155
+ jobId: opts.jobId,
1156
+ state: paying ? "in-flight" : "paid"
1157
+ },
1158
+ "ignoring duplicate payment-required"
1203
1159
  );
1204
1160
  return;
1205
1161
  }
@@ -1421,7 +1377,11 @@ var customerTools = [
1421
1377
  ])
1422
1378
  );
1423
1379
  } catch (e) {
1424
- console.error("[mcp:list_my_jobs] queryJobResults failed:", e);
1380
+ const message = e instanceof Error ? e.message : String(e);
1381
+ logger.error(
1382
+ { event: "list_my_jobs_query_failed", err: message },
1383
+ "queryJobResults failed"
1384
+ );
1425
1385
  }
1426
1386
  }
1427
1387
  let freetextSuspicious = false;
@@ -2108,8 +2068,9 @@ Balance: ${formatSol(BigInt(balance))} (${balance} lamports)`
2108
2068
  }
2109
2069
  const envOverride = process.env.ELISYM_ALLOW_WITHDRAWAL === "1";
2110
2070
  if (envOverride) {
2111
- console.error(
2112
- "[mcp:security] ELISYM_ALLOW_WITHDRAWAL override active - withdrawal gate bypassed"
2071
+ logger.warn(
2072
+ { event: "withdrawal_gate_bypassed", agent: agent.name },
2073
+ "ELISYM_ALLOW_WITHDRAWAL override active - withdrawal gate bypassed"
2113
2074
  );
2114
2075
  }
2115
2076
  if (!envOverride && !agent.security.withdrawals_enabled) {
@@ -2237,7 +2198,9 @@ if (toolMap.size !== allTools.length) {
2237
2198
  );
2238
2199
  }
2239
2200
  function safeError(context, e) {
2240
- console.error(`[mcp:error][${context}]`, e);
2201
+ const message = e instanceof Error ? e.message : String(e);
2202
+ const stack = e instanceof Error ? e.stack : void 0;
2203
+ logger.error({ event: "tool_error", context, err: message, stack }, "tool call failed");
2241
2204
  let msg;
2242
2205
  if (e instanceof ZodError) {
2243
2206
  const parts = e.issues.map((i) => {
@@ -2373,12 +2336,16 @@ async function startServer(ctx) {
2373
2336
  return;
2374
2337
  }
2375
2338
  shuttingDown = true;
2376
- console.error(`[mcp] shutting down (${reason})`);
2339
+ logger.info({ event: "shutdown", reason }, "shutting down");
2377
2340
  for (const agent of ctx.registry.values()) {
2378
2341
  try {
2379
2342
  agent.client.close();
2380
2343
  } catch (e) {
2381
- console.error(`[mcp:close] ${agent.name}:`, e);
2344
+ const message = e instanceof Error ? e.message : String(e);
2345
+ logger.warn(
2346
+ { event: "close_failed", agent: agent.name, err: message },
2347
+ "agent client close failed"
2348
+ );
2382
2349
  }
2383
2350
  if (agent.solanaKeypair) {
2384
2351
  agent.solanaKeypair.secretKey.fill(0);
@@ -2390,10 +2357,14 @@ async function startServer(ctx) {
2390
2357
  process.on("SIGINT", () => void shutdown("SIGINT", 0));
2391
2358
  process.on("SIGTERM", () => void shutdown("SIGTERM", 0));
2392
2359
  process.on("unhandledRejection", (r) => {
2393
- console.error("[mcp:unhandledRejection]", r);
2360
+ const message = r instanceof Error ? r.message : String(r);
2361
+ logger.error({ event: "unhandled_rejection", err: message }, "unhandled rejection");
2394
2362
  });
2395
2363
  process.on("uncaughtException", (e) => {
2396
- console.error("[mcp:uncaughtException]", e);
2364
+ logger.error(
2365
+ { event: "uncaught_exception", err: e.message, stack: e.stack },
2366
+ "uncaught exception"
2367
+ );
2397
2368
  void shutdown("uncaughtException", 1);
2398
2369
  });
2399
2370
  const transport = new StdioServerTransport();
@@ -2408,150 +2379,170 @@ process.on("warning", (w) => {
2408
2379
  }
2409
2380
  console.warn(w);
2410
2381
  });
2411
- var program = new Command().name("elisym-mcp").description("MCP server for the elisym agent network").version(PACKAGE_VERSION);
2412
- program.action(async () => {
2413
- const ctx = new AgentContext();
2414
- const agentName = process.env.ELISYM_AGENT;
2415
- const nostrSecret = process.env.ELISYM_NOSTR_SECRET;
2416
- if (agentName) {
2382
+ function safe(fn) {
2383
+ return async (...args) => {
2417
2384
  try {
2418
- const config = await loadAgentConfig(agentName);
2419
- const instance = await buildAgentInstance(agentName, config);
2420
- ctx.register(instance);
2421
- console.error(`Loaded agent: ${agentName}`);
2385
+ await fn(...args);
2422
2386
  } catch (e) {
2423
- console.error(`Failed to load agent "${agentName}": ${e.message}`);
2387
+ console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
2424
2388
  process.exit(1);
2425
2389
  }
2426
- } else if (nostrSecret) {
2427
- let identity;
2428
- if (nostrSecret.startsWith("nsec")) {
2429
- const decoded = nip19.decode(nostrSecret);
2430
- if (decoded.type !== "nsec") {
2431
- console.error(`ELISYM_NOSTR_SECRET: expected nsec, got ${decoded.type}`);
2390
+ };
2391
+ }
2392
+ var program = new Command().name("elisym-mcp").description("MCP server for the elisym agent network").version(PACKAGE_VERSION);
2393
+ program.action(
2394
+ safe(async () => {
2395
+ const ctx = new AgentContext();
2396
+ const agentName = process.env.ELISYM_AGENT;
2397
+ const nostrSecret = process.env.ELISYM_NOSTR_SECRET;
2398
+ if (agentName) {
2399
+ try {
2400
+ const config = await loadAgentConfig(agentName);
2401
+ const instance = await buildAgentInstance(agentName, config);
2402
+ ctx.register(instance);
2403
+ console.error(`Loaded agent: ${agentName}`);
2404
+ } catch (e) {
2405
+ console.error(`Failed to load agent "${agentName}": ${e.message}`);
2432
2406
  process.exit(1);
2433
2407
  }
2434
- identity = ElisymIdentity.fromSecretKey(decoded.data);
2408
+ } else if (nostrSecret) {
2409
+ let identity;
2410
+ if (nostrSecret.startsWith("nsec")) {
2411
+ const decoded = nip19.decode(nostrSecret);
2412
+ if (decoded.type !== "nsec") {
2413
+ console.error(`ELISYM_NOSTR_SECRET: expected nsec, got ${decoded.type}`);
2414
+ process.exit(1);
2415
+ }
2416
+ identity = ElisymIdentity.fromSecretKey(decoded.data);
2417
+ } else {
2418
+ identity = ElisymIdentity.fromHex(nostrSecret);
2419
+ }
2420
+ const client = new ElisymClient({ relays: RELAYS });
2421
+ const name = process.env.ELISYM_AGENT_NAME ?? "mcp-agent";
2422
+ if (process.env.ELISYM_NETWORK && process.env.ELISYM_NETWORK !== "devnet") {
2423
+ console.error(
2424
+ `ELISYM_NETWORK="${process.env.ELISYM_NETWORK}" is not supported. Only "devnet" is available until the on-chain protocol program ships on mainnet.`
2425
+ );
2426
+ process.exit(1);
2427
+ }
2428
+ ctx.register({ client, identity, name, network: "devnet", security: {} });
2429
+ console.error(`Ephemeral agent: ${name} (devnet)`);
2435
2430
  } else {
2436
- identity = ElisymIdentity.fromHex(nostrSecret);
2431
+ const names = (await listAgentNames()).slice().sort();
2432
+ if (names.length > 0) {
2433
+ const name = names[0];
2434
+ try {
2435
+ const config = await loadAgentConfig(name);
2436
+ const instance = await buildAgentInstance(name, config);
2437
+ ctx.register(instance);
2438
+ console.error(`Loaded default agent: ${name} (${instance.network})`);
2439
+ } catch (e) {
2440
+ console.error(`Failed to load agent "${name}": ${e.message}`);
2441
+ process.exit(1);
2442
+ }
2443
+ } else {
2444
+ const identity = ElisymIdentity.generate();
2445
+ const client = new ElisymClient({ relays: RELAYS });
2446
+ ctx.register({ client, identity, name: "mcp-agent", network: "devnet", security: {} });
2447
+ console.error("Created ephemeral agent (no persistent identity, devnet).");
2448
+ }
2437
2449
  }
2438
- const client = new ElisymClient({ relays: RELAYS });
2439
- const name = process.env.ELISYM_AGENT_NAME ?? "mcp-agent";
2440
- if (process.env.ELISYM_NETWORK && process.env.ELISYM_NETWORK !== "devnet") {
2450
+ await startServer(ctx);
2451
+ })
2452
+ );
2453
+ program.command("init [name]").description("Create a new agent identity").option("-d, --description <desc>", "Agent description", "Elisym MCP agent").option("-n, --network <network>", "Solana network (devnet only)", "devnet").option("--install", "Also install into MCP clients").action(
2454
+ safe(async (name, options) => {
2455
+ const { default: inquirer } = await import('inquirer');
2456
+ if (!name) {
2457
+ const answers = await inquirer.prompt([
2458
+ {
2459
+ type: "input",
2460
+ name: "name",
2461
+ message: "Agent name:",
2462
+ validate: (v) => /^[a-zA-Z0-9_-]+$/.test(v) || "Alphanumeric, _, - only"
2463
+ },
2464
+ {
2465
+ type: "input",
2466
+ name: "description",
2467
+ message: "Description:",
2468
+ default: "Elisym MCP agent"
2469
+ },
2470
+ {
2471
+ type: "list",
2472
+ name: "network",
2473
+ message: "Solana network:",
2474
+ // Only devnet is supported until the elisym-config program ships on mainnet.
2475
+ choices: ["devnet"],
2476
+ default: "devnet"
2477
+ }
2478
+ ]);
2479
+ name = answers.name;
2480
+ options.description = answers.description;
2481
+ options.network = answers.network;
2482
+ }
2483
+ if (options.network !== "devnet") {
2441
2484
  console.error(
2442
- `ELISYM_NETWORK="${process.env.ELISYM_NETWORK}" is not supported. Only "devnet" is available until the on-chain protocol program ships on mainnet.`
2485
+ `Network must be "devnet", got "${options.network}". Mainnet is not supported until the on-chain protocol program is deployed.`
2443
2486
  );
2444
2487
  process.exit(1);
2445
2488
  }
2446
- ctx.register({ client, identity, name, network: "devnet", security: {} });
2447
- console.error(`Ephemeral agent: ${name} (devnet)`);
2448
- } else {
2449
- const names = (await listAgentNames()).slice().sort();
2450
- if (names.length > 0) {
2451
- const name = names[0];
2452
- try {
2453
- const config = await loadAgentConfig(name);
2454
- const instance = await buildAgentInstance(name, config);
2455
- ctx.register(instance);
2456
- console.error(`Loaded default agent: ${name} (${instance.network})`);
2457
- } catch (e) {
2458
- console.error(`Failed to load agent "${name}": ${e.message}`);
2459
- process.exit(1);
2460
- }
2461
- } else {
2462
- const identity = ElisymIdentity.generate();
2463
- const client = new ElisymClient({ relays: RELAYS });
2464
- ctx.register({ client, identity, name: "mcp-agent", network: "devnet", security: {} });
2465
- console.error("Created ephemeral agent (no persistent identity, devnet).");
2466
- }
2467
- }
2468
- await startServer(ctx);
2469
- });
2470
- program.command("init [name]").description("Create a new agent identity").option("-d, --description <desc>", "Agent description", "Elisym MCP agent").option("-n, --network <network>", "Solana network (devnet only)", "devnet").option("--install", "Also install into MCP clients").action(async (name, options) => {
2471
- const { default: inquirer } = await import('inquirer');
2472
- if (!name) {
2473
- const answers = await inquirer.prompt([
2489
+ const { passphrase } = await inquirer.prompt([
2474
2490
  {
2475
- type: "input",
2476
- name: "name",
2477
- message: "Agent name:",
2478
- validate: (v) => /^[a-zA-Z0-9_-]+$/.test(v) || "Alphanumeric, _, - only"
2479
- },
2480
- {
2481
- type: "input",
2482
- name: "description",
2483
- message: "Description:",
2484
- default: "Elisym MCP agent"
2485
- },
2486
- {
2487
- type: "list",
2488
- name: "network",
2489
- message: "Solana network:",
2490
- // Only devnet is supported until the elisym-config program ships on mainnet.
2491
- choices: ["devnet"],
2492
- default: "devnet"
2491
+ type: "password",
2492
+ name: "passphrase",
2493
+ message: "Passphrase to encrypt secret keys (leave blank for none):",
2494
+ mask: "*"
2493
2495
  }
2494
2496
  ]);
2495
- name = answers.name;
2496
- options.description = answers.description;
2497
- options.network = answers.network;
2498
- }
2499
- if (options.network !== "devnet") {
2500
- console.error(
2501
- `Network must be "devnet", got "${options.network}". Mainnet is not supported until the on-chain protocol program is deployed.`
2502
- );
2503
- process.exit(1);
2504
- }
2505
- const { passphrase } = await inquirer.prompt([
2506
- {
2507
- type: "password",
2508
- name: "passphrase",
2509
- message: "Passphrase to encrypt secret keys (leave blank for none):",
2510
- mask: "*"
2497
+ const nostrSecretKey = generateSecretKey();
2498
+ const nostrPubkey = getPublicKey(nostrSecretKey);
2499
+ const solanaSigner = await generateKeyPairSigner(true);
2500
+ const solanaSecretBytes = await exportKeyPairBytes(solanaSigner);
2501
+ await saveAgentConfig(name, {
2502
+ name,
2503
+ description: options.description,
2504
+ relays: [...RELAYS],
2505
+ nostrSecretKey: Buffer.from(nostrSecretKey).toString("hex"),
2506
+ solanaSecretKey: bs58.encode(solanaSecretBytes),
2507
+ solanaAddress: solanaSigner.address,
2508
+ network: "devnet",
2509
+ security: { withdrawals_enabled: false, agent_switch_enabled: false },
2510
+ passphrase: passphrase || void 0
2511
+ });
2512
+ const npub = nip19.npubEncode(nostrPubkey);
2513
+ console.log(`Agent "${name}" created.`);
2514
+ console.log(` Nostr: ${npub}`);
2515
+ console.log(` Solana: ${solanaSigner.address}`);
2516
+ console.log(` Network: ${options.network}`);
2517
+ console.log(` Encrypted: ${passphrase ? "yes" : "no"}`);
2518
+ console.log(` Config: ~/.elisym/${name}/elisym.yaml`);
2519
+ if (passphrase) {
2520
+ console.log(` Note: set ELISYM_PASSPHRASE before launching the MCP server.`);
2521
+ }
2522
+ if (options.install) {
2523
+ await runInstall({ agent: name });
2511
2524
  }
2512
- ]);
2513
- const nostrSecretKey = generateSecretKey();
2514
- const nostrPubkey = getPublicKey(nostrSecretKey);
2515
- const solanaSigner = await generateKeyPairSigner(true);
2516
- const solanaSecretBytes = await exportKeyPairBytes(solanaSigner);
2517
- await saveAgentConfig(name, {
2518
- name,
2519
- description: options.description,
2520
- relays: [...RELAYS],
2521
- nostrSecretKey: Buffer.from(nostrSecretKey).toString("hex"),
2522
- solanaSecretKey: bs58.encode(solanaSecretBytes),
2523
- solanaAddress: solanaSigner.address,
2524
- network: "devnet",
2525
- security: { withdrawals_enabled: false, agent_switch_enabled: false },
2526
- passphrase: passphrase || void 0
2527
- });
2528
- const npub = nip19.npubEncode(nostrPubkey);
2529
- console.log(`Agent "${name}" created.`);
2530
- console.log(` Nostr: ${npub}`);
2531
- console.log(` Solana: ${solanaSigner.address}`);
2532
- console.log(` Network: ${options.network}`);
2533
- console.log(` Encrypted: ${passphrase ? "yes" : "no"}`);
2534
- console.log(` Config: ~/.elisym/agents/${name}/config.json`);
2535
- if (passphrase) {
2536
- console.log(` Note: set ELISYM_PASSPHRASE before launching the MCP server.`);
2537
- }
2538
- if (options.install) {
2539
- await runInstall({ agent: name });
2540
- }
2541
- });
2542
- program.command("install").description("Install elisym MCP server into client configs").option("--client <name>", "Specific client (claude-desktop, claude-code, cursor, windsurf)").option("--agent <name>", "Bind to specific agent").option("--list", "List detected clients").action(async (options) => {
2543
- if (options.list) {
2544
- await runList();
2545
- } else {
2546
- await runInstall({ client: options.client, agent: options.agent });
2547
- }
2548
- });
2549
- program.command("update").description("Refresh the elisym MCP entry in installed client configs").option("--client <name>", "Specific client (claude-desktop, claude-code, cursor, windsurf)").option("--agent <name>", "Override the agent binding").action(async (options) => {
2550
- await runUpdate({ client: options.client, agent: options.agent });
2551
- });
2552
- program.command("uninstall").description("Remove elisym from MCP client configs").option("--client <name>", "Specific client").action(async (options) => {
2553
- await runUninstall({ client: options.client });
2554
- });
2525
+ })
2526
+ );
2527
+ program.command("install").description("Install elisym MCP server into client configs").option("--client <name>", "Specific client (claude-desktop, claude-code, cursor, windsurf)").option("--agent <name>", "Bind to specific agent").option("--list", "List detected clients").action(
2528
+ safe(async (options) => {
2529
+ if (options.list) {
2530
+ await runList();
2531
+ } else {
2532
+ await runInstall({ client: options.client, agent: options.agent });
2533
+ }
2534
+ })
2535
+ );
2536
+ program.command("update").description("Refresh the elisym MCP entry in installed client configs").option("--client <name>", "Specific client (claude-desktop, claude-code, cursor, windsurf)").option("--agent <name>", "Override the agent binding").action(
2537
+ safe(async (options) => {
2538
+ await runUpdate({ client: options.client, agent: options.agent });
2539
+ })
2540
+ );
2541
+ program.command("uninstall").description("Remove elisym from MCP client configs").option("--client <name>", "Specific client").action(
2542
+ safe(async (options) => {
2543
+ await runUninstall({ client: options.client });
2544
+ })
2545
+ );
2555
2546
  async function toggleFlag(agentName, field, enable) {
2556
2547
  const { default: inquirer } = await import('inquirer');
2557
2548
  if (enable) {
@@ -2572,10 +2563,10 @@ async function toggleFlag(agentName, field, enable) {
2572
2563
  console.log(`Agent "${agentName}" security:`, merged);
2573
2564
  console.log("Note: restart the MCP server for changes to take effect on a running session.");
2574
2565
  }
2575
- program.command("enable-withdrawals <agent>").description("Enable SOL withdrawals for a specific agent (interactive confirmation)").action((agent) => toggleFlag(agent, "withdrawals_enabled", true));
2576
- program.command("disable-withdrawals <agent>").description("Disable SOL withdrawals for a specific agent").action((agent) => toggleFlag(agent, "withdrawals_enabled", false));
2577
- program.command("enable-agent-switch <agent>").description("Allow the MCP server to switch away from this agent at runtime").action((agent) => toggleFlag(agent, "agent_switch_enabled", true));
2578
- program.command("disable-agent-switch <agent>").description("Forbid the MCP server from switching away from this agent at runtime").action((agent) => toggleFlag(agent, "agent_switch_enabled", false));
2566
+ program.command("enable-withdrawals <agent>").description("Enable SOL withdrawals for a specific agent (interactive confirmation)").action(safe((agent) => toggleFlag(agent, "withdrawals_enabled", true)));
2567
+ program.command("disable-withdrawals <agent>").description("Disable SOL withdrawals for a specific agent").action(safe((agent) => toggleFlag(agent, "withdrawals_enabled", false)));
2568
+ program.command("enable-agent-switch <agent>").description("Allow the MCP server to switch away from this agent at runtime").action(safe((agent) => toggleFlag(agent, "agent_switch_enabled", true)));
2569
+ program.command("disable-agent-switch <agent>").description("Forbid the MCP server from switching away from this agent at runtime").action(safe((agent) => toggleFlag(agent, "agent_switch_enabled", false)));
2579
2570
  program.parse();
2580
2571
  //# sourceMappingURL=index.js.map
2581
2572
  //# sourceMappingURL=index.js.map