@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/README.md +4 -4
- package/dist/index.js +273 -282
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { LIMITS, DEFAULT_KIND_OFFSET, SolanaPaymentStrategy,
|
|
3
|
-
import { generateKeyPairSigner,
|
|
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 {
|
|
7
|
-
import {
|
|
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
|
-
|
|
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 "${
|
|
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 "${
|
|
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
|
|
59
|
-
const
|
|
60
|
-
const
|
|
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:
|
|
79
|
-
solanaSecretKey:
|
|
80
|
-
relays:
|
|
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:
|
|
83
|
-
|
|
84
|
-
|
|
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,
|
|
55
|
+
async function saveAgentConfig(name, input) {
|
|
88
56
|
validateAgentName(name);
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
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
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
712
|
-
|
|
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
|
-
|
|
764
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1202
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2412
|
-
|
|
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
|
-
|
|
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(`
|
|
2387
|
+
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
2424
2388
|
process.exit(1);
|
|
2425
2389
|
}
|
|
2426
|
-
}
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
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: "
|
|
2476
|
-
name: "
|
|
2477
|
-
message: "
|
|
2478
|
-
|
|
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
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
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
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
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
|