stelagent 0.0.1 → 0.0.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.
Files changed (2) hide show
  1. package/dist/index.mjs +596 -227
  2. package/package.json +2 -1
package/dist/index.mjs CHANGED
@@ -2,13 +2,15 @@
2
2
 
3
3
  import { defineCommand, runMain } from "citty";
4
4
  import { Result, TaggedError, isTaggedError, matchError, matchErrorPartial } from "better-result";
5
- import { cancel, confirm, isCancel, log, text } from "@clack/prompts";
6
- import pc from "picocolors";
7
5
  import { z } from "zod";
8
6
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
7
  import { join } from "node:path";
10
8
  import { homedir } from "node:os";
9
+ import pc from "picocolors";
11
10
  import { Horizon } from "@stellar/stellar-sdk";
11
+ import { cancel, confirm, isCancel } from "@clack/prompts";
12
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
14
  //#region src/lib/audit.ts
13
15
  const STELAGENT_DIR$1 = join(homedir(), ".stelagent");
14
16
  const AUDIT_FILE = join(STELAGENT_DIR$1, "audit.jsonl");
@@ -68,16 +70,6 @@ function printResult(result, format) {
68
70
  if (Result.isOk(result)) printText(result.value);
69
71
  else console.error(pc.red(`Error: ${result.error}`));
70
72
  }
71
- function printData(data, format) {
72
- if (format === "json") {
73
- console.log(JSON.stringify({
74
- ok: true,
75
- data
76
- }, null, 2));
77
- return;
78
- }
79
- printText(data);
80
- }
81
73
  function formatSessionError(err) {
82
74
  return matchErrorPartial(err, {
83
75
  SessionNotFoundError: () => "No active session.",
@@ -293,6 +285,89 @@ function verifyOtp(email, otp) {
293
285
  });
294
286
  }
295
287
  //#endregion
288
+ //#region src/lib/args.ts
289
+ const networkArg = {
290
+ type: "string",
291
+ alias: ["n"],
292
+ description: "Network: testnet or pubnet",
293
+ default: "testnet"
294
+ };
295
+ const formatArg = {
296
+ type: "string",
297
+ alias: ["f"],
298
+ description: "Output format: json or text",
299
+ default: "json"
300
+ };
301
+ function parseNetwork$4(value) {
302
+ if (value === "testnet" || value === "pubnet") return Result.ok(value);
303
+ return Result.err(new InvalidNetworkError({ provided: value }));
304
+ }
305
+ function parseFormat(value) {
306
+ if (value === "json" || value === "text") return value;
307
+ return "json";
308
+ }
309
+ //#endregion
310
+ //#region src/domain/validators.ts
311
+ const stellarPublicKey = z.string().regex(/^G[A-Z2-7]{55}$/, "Invalid Stellar public key. Must start with 'G' followed by 55 alphanumeric characters.");
312
+ const emailSchema = z.string().email("Invalid email address.");
313
+ const amountSchema = z.string().regex(/^\d+(\.\d{1,7})?$/, "Invalid amount. Must be a number with up to 7 decimal places.");
314
+ const assetSchema = z.union([z.literal("native"), z.string().regex(/^[A-Za-z0-9]{1,12}:[A-Z2-7]{56}$/, "Invalid asset format. Use 'native' for XLM or 'CODE:ISSUER' for custom assets.")]);
315
+ const memoSchema = z.string().max(28, "Memo text must be 28 characters or fewer.");
316
+ z.string().min(1, "Cursor must be a non-empty string.");
317
+ const otpSchema = z.string().regex(/^\d{4,8}$/, "OTP must be 4-8 digits.");
318
+ const urlSchema = z.string().url("Invalid URL.");
319
+ z.enum(["testnet", "pubnet"]);
320
+ z.enum(["json", "text"]);
321
+ const limitSchema = z.coerce.number().int().min(1).max(200).default(10);
322
+ z.enum(["asc", "desc"]).default("desc");
323
+ z.coerce.number().int().min(6e4);
324
+ //#endregion
325
+ //#region src/commands/wallet-login.ts
326
+ function formatZodError$11(e) {
327
+ return e.issues.map((issue) => issue.message).join(", ");
328
+ }
329
+ const walletLogin = defineCommand({
330
+ meta: {
331
+ name: "login",
332
+ description: "Request an OTP code be sent to your email"
333
+ },
334
+ args: {
335
+ email: {
336
+ type: "string",
337
+ alias: ["e"],
338
+ description: "Your email address",
339
+ required: true
340
+ },
341
+ network: networkArg,
342
+ format: formatArg
343
+ },
344
+ async run({ args }) {
345
+ const email = String(args.email ?? "");
346
+ const networkResult = parseNetwork$4(String(args.network ?? "testnet"));
347
+ const format = parseFormat(String(args.format ?? "json"));
348
+ if (Result.isError(networkResult)) {
349
+ printResult(Result.err(networkResult.error._tag), "json");
350
+ return;
351
+ }
352
+ const validation = Result.try({
353
+ try: () => emailSchema.parse(email),
354
+ catch: (e) => e
355
+ });
356
+ if (Result.isError(validation)) {
357
+ const msg = validation.error instanceof z.ZodError ? formatZodError$11(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
358
+ printResult(Result.err(msg), format);
359
+ return;
360
+ }
361
+ await runApp("wallet login", async () => {
362
+ const otpResponseResult = await requestOtp(email);
363
+ if (Result.isError(otpResponseResult)) throw new Error(`Could not reach auth server: ${otpResponseResult.error.cause}`);
364
+ const otpResponse = otpResponseResult.value;
365
+ if (!otpResponse || !otpResponse.ok) throw new Error("Failed to send OTP email");
366
+ return { message: otpResponse.message };
367
+ }, format);
368
+ }
369
+ });
370
+ //#endregion
296
371
  //#region src/services/session.ts
297
372
  const STELAGENT_DIR = join(homedir(), ".stelagent");
298
373
  const SESSION_FILE = join(STELAGENT_DIR, "session.json");
@@ -390,132 +465,6 @@ async function createWallet(network) {
390
465
  });
391
466
  }
392
467
  //#endregion
393
- //#region src/lib/args.ts
394
- const networkArg = {
395
- type: "string",
396
- alias: ["n"],
397
- description: "Network: testnet or pubnet",
398
- default: "testnet"
399
- };
400
- const formatArg = {
401
- type: "string",
402
- alias: ["f"],
403
- description: "Output format: json or text",
404
- default: "json"
405
- };
406
- function parseNetwork(value) {
407
- if (value === "testnet" || value === "pubnet") return Result.ok(value);
408
- return Result.err(new InvalidNetworkError({ provided: value }));
409
- }
410
- function parseFormat(value) {
411
- if (value === "json" || value === "text") return value;
412
- return "json";
413
- }
414
- //#endregion
415
- //#region src/domain/validators.ts
416
- const stellarPublicKey = z.string().regex(/^G[A-Z2-7]{55}$/, "Invalid Stellar public key. Must start with 'G' followed by 55 alphanumeric characters.");
417
- const emailSchema = z.string().email("Invalid email address.");
418
- const amountSchema = z.string().regex(/^\d+(\.\d{1,7})?$/, "Invalid amount. Must be a number with up to 7 decimal places.");
419
- const assetSchema = z.union([z.literal("native"), z.string().regex(/^[A-Za-z0-9]{1,12}:[A-Z2-7]{56}$/, "Invalid asset format. Use 'native' for XLM or 'CODE:ISSUER' for custom assets.")]);
420
- const memoSchema = z.string().max(28, "Memo text must be 28 characters or fewer.");
421
- z.string().min(1, "Cursor must be a non-empty string.");
422
- z.string().regex(/^\d{4,8}$/, "OTP must be 4-8 digits.");
423
- const urlSchema = z.string().url("Invalid URL.");
424
- z.enum(["testnet", "pubnet"]);
425
- z.enum(["json", "text"]);
426
- const limitSchema = z.coerce.number().int().min(1).max(200).default(10);
427
- z.enum(["asc", "desc"]).default("desc");
428
- z.coerce.number().int().min(6e4);
429
- //#endregion
430
- //#region src/commands/wallet-login.ts
431
- function formatZodError$11(e) {
432
- return e.issues.map((issue) => issue.message).join(", ");
433
- }
434
- const walletLogin = defineCommand({
435
- meta: {
436
- name: "login",
437
- description: "Sign in with email to create or recover your wallet"
438
- },
439
- args: {
440
- email: {
441
- type: "string",
442
- alias: ["e"],
443
- description: "Your email address",
444
- required: true
445
- },
446
- network: networkArg,
447
- format: formatArg
448
- },
449
- async run({ args }) {
450
- const email = String(args.email ?? "");
451
- const networkResult = parseNetwork(String(args.network ?? "testnet"));
452
- const format = parseFormat(String(args.format ?? "json"));
453
- if (Result.isError(networkResult)) {
454
- printResult(Result.err(networkResult.error._tag), "json");
455
- return;
456
- }
457
- const network = networkResult.value;
458
- const validation = Result.try({
459
- try: () => emailSchema.parse(email),
460
- catch: (e) => e
461
- });
462
- if (Result.isError(validation)) {
463
- const msg = validation.error instanceof z.ZodError ? formatZodError$11(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
464
- printResult(Result.err(msg), format);
465
- return;
466
- }
467
- log.message(pc.cyan(`Sending OTP to ${email}...`));
468
- const otpResponseResult = await requestOtp(email);
469
- if (Result.isError(otpResponseResult)) {
470
- const msg = `AuthRequestError: ${otpResponseResult.error.cause}`;
471
- printResult(Result.err(msg), format);
472
- return;
473
- }
474
- const otpResponse = otpResponseResult.value;
475
- if (!otpResponse || !otpResponse.ok) return;
476
- log.success(otpResponse.message);
477
- const otpInput = await text({
478
- message: `Enter the OTP sent to ${email}`,
479
- placeholder: "123456",
480
- validate: (value) => {
481
- if (!value || value.trim().length === 0) return "OTP is required";
482
- }
483
- });
484
- if (isCancel(otpInput)) {
485
- cancel("Login cancelled.");
486
- process.exit(0);
487
- }
488
- const otp = String(otpInput);
489
- await runApp("wallet login verify", async () => {
490
- const verifyResult = await verifyOtp(email, otp);
491
- if (Result.isError(verifyResult)) {
492
- printResult(Result.err(`OTP verification failed: ${verifyResult.error.cause}`), format);
493
- return;
494
- }
495
- const verifyResponse = verifyResult.value;
496
- if (!verifyResponse.verified) {
497
- printResult(Result.err("OTP verification failed."), format);
498
- return;
499
- }
500
- const sessionResult = saveSession(verifyResponse.token, verifyResponse.email);
501
- if (Result.isError(sessionResult)) {
502
- printResult(Result.err(formatSessionError(sessionResult.error)), format);
503
- return;
504
- }
505
- log.message(pc.cyan("Setting up your wallet..."));
506
- const walletResult = await createWallet(network);
507
- if (Result.isError(walletResult)) {
508
- printResult(Result.err(formatWalletError(walletResult.error)), format);
509
- return;
510
- }
511
- const wallet = walletResult.value;
512
- log.success(pc.green(wallet.publicKey ? `Wallet ready: ${wallet.publicKey.slice(0, 8)}...` : "Wallet recovered!"));
513
- printData({ wallet }, format);
514
- return wallet;
515
- }, format);
516
- }
517
- });
518
- //#endregion
519
468
  //#region src/commands/wallet-verify.ts
520
469
  const walletVerify = defineCommand({
521
470
  meta: {
@@ -541,7 +490,7 @@ const walletVerify = defineCommand({
541
490
  async run({ args }) {
542
491
  const email = String(args.email ?? "");
543
492
  const otp = String(args.otp ?? "");
544
- const networkResult = parseNetwork(String(args.network ?? "testnet"));
493
+ const networkResult = parseNetwork$4(String(args.network ?? "testnet"));
545
494
  const format = parseFormat(String(args.format ?? "json"));
546
495
  if (Result.isError(networkResult)) {
547
496
  printResult(Result.err(networkResult.error._tag), "json");
@@ -1182,7 +1131,7 @@ const accountDetails = defineCommand({
1182
1131
  format: formatArg
1183
1132
  },
1184
1133
  async run({ args }) {
1185
- const networkResult = parseNetwork(String(args.network ?? "testnet"));
1134
+ const networkResult = parseNetwork$4(String(args.network ?? "testnet"));
1186
1135
  const format = parseFormat(String(args.format ?? "json"));
1187
1136
  if (Result.isError(networkResult)) {
1188
1137
  printResult(Result.err(networkResult.error._tag), "json");
@@ -1243,7 +1192,7 @@ const accountTransactions = defineCommand({
1243
1192
  }
1244
1193
  },
1245
1194
  async run({ args }) {
1246
- const networkResult = parseNetwork(String(args.network ?? "testnet"));
1195
+ const networkResult = parseNetwork$4(String(args.network ?? "testnet"));
1247
1196
  const format = parseFormat(String(args.format ?? "json"));
1248
1197
  if (Result.isError(networkResult)) {
1249
1198
  printResult(Result.err(networkResult.error._tag), "json");
@@ -1312,7 +1261,7 @@ const accountPayments = defineCommand({
1312
1261
  }
1313
1262
  },
1314
1263
  async run({ args }) {
1315
- const networkResult = parseNetwork(String(args.network ?? "testnet"));
1264
+ const networkResult = parseNetwork$4(String(args.network ?? "testnet"));
1316
1265
  const format = parseFormat(String(args.format ?? "json"));
1317
1266
  if (Result.isError(networkResult)) {
1318
1267
  printResult(Result.err(networkResult.error._tag), "json");
@@ -1386,7 +1335,7 @@ const accountCommand = defineCommand({
1386
1335
  }
1387
1336
  },
1388
1337
  async run({ args }) {
1389
- const networkResult = parseNetwork(String(args.network ?? "testnet"));
1338
+ const networkResult = parseNetwork$4(String(args.network ?? "testnet"));
1390
1339
  const format = parseFormat(String(args.format ?? "json"));
1391
1340
  if (Result.isError(networkResult)) {
1392
1341
  printResult(Result.err(networkResult.error._tag), "json");
@@ -1458,7 +1407,7 @@ const assetsCommand = defineCommand({
1458
1407
  }
1459
1408
  },
1460
1409
  async run({ args }) {
1461
- const networkResult = parseNetwork(String(args.network ?? "testnet"));
1410
+ const networkResult = parseNetwork$4(String(args.network ?? "testnet"));
1462
1411
  const format = parseFormat(String(args.format ?? "json"));
1463
1412
  if (Result.isError(networkResult)) {
1464
1413
  printResult(Result.err(networkResult.error._tag), "json");
@@ -1516,7 +1465,7 @@ const assetsCommand = defineCommand({
1516
1465
  }
1517
1466
  },
1518
1467
  async run({ args }) {
1519
- const networkResult = parseNetwork(String(args.network ?? "testnet"));
1468
+ const networkResult = parseNetwork$4(String(args.network ?? "testnet"));
1520
1469
  const format = parseFormat(String(args.format ?? "json"));
1521
1470
  if (Result.isError(networkResult)) {
1522
1471
  printResult(Result.err(networkResult.error._tag), "json");
@@ -1591,7 +1540,7 @@ const sendCommand = defineCommand({
1591
1540
  format: formatArg
1592
1541
  },
1593
1542
  async run({ args }) {
1594
- const networkResult = parseNetwork(String(args.network ?? "testnet"));
1543
+ const networkResult = parseNetwork$4(String(args.network ?? "testnet"));
1595
1544
  const format = parseFormat(String(args.format ?? "json"));
1596
1545
  if (Result.isError(networkResult)) {
1597
1546
  printResult(Result.err(networkResult.error._tag), "json");
@@ -1644,7 +1593,7 @@ const feeCommand = defineCommand({
1644
1593
  format: formatArg
1645
1594
  },
1646
1595
  async run({ args }) {
1647
- const networkResult = parseNetwork(String(args.network ?? "testnet"));
1596
+ const networkResult = parseNetwork$4(String(args.network ?? "testnet"));
1648
1597
  const format = parseFormat(String(args.format ?? "json"));
1649
1598
  if (Result.isError(networkResult)) {
1650
1599
  printResult(Result.err(networkResult.error._tag), "json");
@@ -1684,7 +1633,7 @@ const monitorTransactions = defineCommand({
1684
1633
  }
1685
1634
  },
1686
1635
  async run({ args }) {
1687
- const networkResult = parseNetwork(String(args.network ?? "testnet"));
1636
+ const networkResult = parseNetwork$4(String(args.network ?? "testnet"));
1688
1637
  const format = parseFormat(String(args.format ?? "json"));
1689
1638
  if (Result.isError(networkResult)) {
1690
1639
  printResult(Result.err(networkResult.error._tag), "json");
@@ -1761,7 +1710,7 @@ const monitorPayments = defineCommand({
1761
1710
  }
1762
1711
  },
1763
1712
  async run({ args }) {
1764
- const networkResult = parseNetwork(String(args.network ?? "testnet"));
1713
+ const networkResult = parseNetwork$4(String(args.network ?? "testnet"));
1765
1714
  const format = parseFormat(String(args.format ?? "json"));
1766
1715
  if (Result.isError(networkResult)) {
1767
1716
  printResult(Result.err(networkResult.error._tag), "json");
@@ -1818,6 +1767,491 @@ function formatZodError(e) {
1818
1767
  return e.issues.map((issue) => issue.message).join(", ");
1819
1768
  }
1820
1769
  //#endregion
1770
+ //#region src/commands/monitor.ts
1771
+ const monitorCommand = defineCommand({
1772
+ meta: {
1773
+ name: "monitor",
1774
+ description: "Stream live data from Horizon via SSE"
1775
+ },
1776
+ subCommands: {
1777
+ transactions: monitorTransactions,
1778
+ payments: monitorPayments,
1779
+ effects: defineCommand({
1780
+ meta: {
1781
+ name: "effects",
1782
+ description: "Stream effects for an account in real-time"
1783
+ },
1784
+ args: {
1785
+ address: {
1786
+ type: "positional",
1787
+ description: "Stellar public key (G...)",
1788
+ required: true
1789
+ },
1790
+ network: networkArg,
1791
+ format: formatArg,
1792
+ cursor: {
1793
+ type: "string",
1794
+ alias: ["c"],
1795
+ description: "Start from this cursor (default: 'now' for new events)",
1796
+ default: "now"
1797
+ }
1798
+ },
1799
+ async run({ args }) {
1800
+ const networkResult = parseNetwork$4(String(args.network ?? "testnet"));
1801
+ const format = parseFormat(String(args.format ?? "json"));
1802
+ if (Result.isError(networkResult)) {
1803
+ printResult(Result.err(networkResult.error._tag), "json");
1804
+ return;
1805
+ }
1806
+ const network = networkResult.value;
1807
+ const address = String(args.address ?? "");
1808
+ const validation = Result.try({
1809
+ try: () => stellarPublicKey.parse(address),
1810
+ catch: (e) => e
1811
+ });
1812
+ if (Result.isError(validation)) {
1813
+ const msg = validation.error instanceof z.ZodError ? formatZodError(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1814
+ printResult(Result.err(msg), format);
1815
+ return;
1816
+ }
1817
+ const cursor = args.cursor === "now" ? void 0 : String(args.cursor);
1818
+ let close;
1819
+ try {
1820
+ close = await runApp("monitor effects", async () => {
1821
+ return streamEffects(address, network, { cursor }, (record) => {
1822
+ console.log(JSON.stringify({
1823
+ ok: true,
1824
+ data: record
1825
+ }));
1826
+ }, (error) => {
1827
+ const message = error instanceof Error ? error.message : String(error);
1828
+ console.log(JSON.stringify({
1829
+ ok: false,
1830
+ error: `Stream error: ${message}`
1831
+ }));
1832
+ });
1833
+ }, format);
1834
+ } catch (e) {
1835
+ const message = e instanceof Error ? e.message : String(e);
1836
+ printResult(Result.err(`Failed to stream effects: ${message}`), "json");
1837
+ return;
1838
+ }
1839
+ console.log(JSON.stringify({
1840
+ ok: true,
1841
+ data: { message: `Streaming effects for ${address} on ${network}... Press Ctrl+C to stop.` }
1842
+ }, null, 2));
1843
+ await new Promise((resolve) => {
1844
+ process.on("SIGINT", () => {
1845
+ close();
1846
+ resolve();
1847
+ });
1848
+ });
1849
+ }
1850
+ })
1851
+ }
1852
+ });
1853
+ //#endregion
1854
+ //#region src/mcp/wallet-tools.ts
1855
+ function ok$3(data) {
1856
+ return { content: [{
1857
+ type: "text",
1858
+ text: JSON.stringify({
1859
+ ok: true,
1860
+ data
1861
+ }, null, 2)
1862
+ }] };
1863
+ }
1864
+ function err$3(message) {
1865
+ return { content: [{
1866
+ type: "text",
1867
+ text: JSON.stringify({
1868
+ ok: false,
1869
+ error: message
1870
+ }, null, 2)
1871
+ }] };
1872
+ }
1873
+ function parseNetwork$3(value) {
1874
+ return value === "pubnet" ? "pubnet" : "testnet";
1875
+ }
1876
+ function formatZodIssues(issues) {
1877
+ return issues.map((i) => i.message).join(", ");
1878
+ }
1879
+ function registerWalletTools(server) {
1880
+ server.registerTool("wallet_login", {
1881
+ description: "Request an OTP code be sent to the user's email. After calling this, use wallet_verify with the code the user provides. Does NOT require an active session.",
1882
+ inputSchema: { email: z.string().describe("User's email address") }
1883
+ }, async ({ email }) => {
1884
+ const validation = emailSchema.safeParse(email);
1885
+ if (!validation.success) return err$3(`Invalid email: ${formatZodIssues(validation.error.issues)}`);
1886
+ const result = await requestOtp(email);
1887
+ if (Result.isError(result)) return err$3(`Could not reach auth server: ${result.error.cause}`);
1888
+ const response = result.value;
1889
+ if (!response || !response.ok) return err$3("Failed to send OTP email");
1890
+ return ok$3({ message: response.message });
1891
+ });
1892
+ server.registerTool("wallet_verify", {
1893
+ description: "Verify an OTP code and create or recover the wallet. The session is saved locally so subsequent tools (wallet_address, wallet_balance, wallet_transfer, etc.) work without re-authentication.",
1894
+ inputSchema: {
1895
+ email: z.string().describe("Must match the email used in wallet_login"),
1896
+ otp: z.string().describe("OTP code from email"),
1897
+ network: z.enum(["testnet", "pubnet"]).default("testnet").describe("Stellar network")
1898
+ }
1899
+ }, async ({ email, otp, network }) => {
1900
+ const emailValidation = emailSchema.safeParse(email);
1901
+ if (!emailValidation.success) return err$3(`Invalid email: ${formatZodIssues(emailValidation.error.issues)}`);
1902
+ const otpValidation = otpSchema.safeParse(otp);
1903
+ if (!otpValidation.success) return err$3(`Invalid OTP: ${formatZodIssues(otpValidation.error.issues)}`);
1904
+ const verifyResult = await verifyOtp(email, otp);
1905
+ if (Result.isError(verifyResult)) return err$3(`OTP verification failed: ${verifyResult.error.cause}`);
1906
+ const verifyResponse = verifyResult.value;
1907
+ if (!verifyResponse.verified) return err$3("OTP verification failed.");
1908
+ const sessionResult = saveSession(verifyResponse.token, verifyResponse.email);
1909
+ if (Result.isError(sessionResult)) return err$3(formatSessionError(sessionResult.error));
1910
+ const walletResult = await createWallet(parseNetwork$3(network));
1911
+ if (Result.isError(walletResult)) return err$3(formatWalletError(walletResult.error));
1912
+ const wallet = walletResult.value;
1913
+ return ok$3({ wallet: {
1914
+ email: wallet.email,
1915
+ publicKey: wallet.publicKey,
1916
+ network: wallet.network,
1917
+ createdAt: wallet.createdAt
1918
+ } });
1919
+ });
1920
+ server.registerTool("wallet_address", {
1921
+ description: "Get the logged-in wallet's public key, network, and email. Requires an active session.",
1922
+ inputSchema: {}
1923
+ }, async () => {
1924
+ const addressResult = await fetchAddress();
1925
+ if (Result.isError(addressResult)) return err$3(formatWalletError(addressResult.error));
1926
+ const addr = addressResult.value;
1927
+ return ok$3({
1928
+ publicKey: addr.publicKey,
1929
+ network: addr.network,
1930
+ email: addr.email
1931
+ });
1932
+ });
1933
+ server.registerTool("wallet_balance", {
1934
+ description: "Get all asset balances for the logged-in wallet. Requires an active session.",
1935
+ inputSchema: {}
1936
+ }, async () => {
1937
+ const walletResult = await fetchWallet();
1938
+ if (Result.isError(walletResult)) return err$3(formatWalletError(walletResult.error));
1939
+ const wallet = walletResult.value;
1940
+ const balancesResult = await getBalances(wallet.publicKey, wallet.network);
1941
+ if (Result.isError(balancesResult)) return err$3(formatStellarError(balancesResult.error));
1942
+ return ok$3({
1943
+ address: wallet.publicKey,
1944
+ email: wallet.email,
1945
+ balances: balancesResult.value
1946
+ });
1947
+ });
1948
+ server.registerTool("wallet_transfer", {
1949
+ description: "Send XLM from the logged-in wallet to another Stellar address. Requires an active session.",
1950
+ inputSchema: {
1951
+ destination: z.string().describe("Destination public key (G...)"),
1952
+ amount: z.string().describe("Amount in XLM (up to 7 decimal places)")
1953
+ }
1954
+ }, async ({ destination, amount }) => {
1955
+ const destValidation = stellarPublicKey.safeParse(destination);
1956
+ if (!destValidation.success) return err$3(`Invalid destination: ${formatZodIssues(destValidation.error.issues)}`);
1957
+ const amountValidation = amountSchema.safeParse(amount);
1958
+ if (!amountValidation.success) return err$3(`Invalid amount: ${formatZodIssues(amountValidation.error.issues)}`);
1959
+ const walletResult = await fetchWallet();
1960
+ if (Result.isError(walletResult)) return err$3(formatWalletError(walletResult.error));
1961
+ const result = await transferXlm(walletResult.value.secretKey, destination, amount, walletResult.value.network);
1962
+ if (Result.isError(result)) return err$3(formatStellarError(result.error));
1963
+ return ok$3(result.value);
1964
+ });
1965
+ server.registerTool("wallet_logout", {
1966
+ description: "Clear the local wallet session.",
1967
+ inputSchema: {}
1968
+ }, async () => {
1969
+ const sessionResult = loadSession();
1970
+ if (Result.isError(sessionResult)) return err$3(formatSessionError(sessionResult.error));
1971
+ const email = sessionResult.value.email;
1972
+ const clearResult = clearSession();
1973
+ if (Result.isError(clearResult)) return err$3(formatSessionError(clearResult.error));
1974
+ return ok$3({
1975
+ loggedOut: true,
1976
+ email
1977
+ });
1978
+ });
1979
+ }
1980
+ //#endregion
1981
+ //#region src/mcp/account-tools.ts
1982
+ function ok$2(data) {
1983
+ return { content: [{
1984
+ type: "text",
1985
+ text: JSON.stringify({
1986
+ ok: true,
1987
+ data
1988
+ }, null, 2)
1989
+ }] };
1990
+ }
1991
+ function err$2(message) {
1992
+ return { content: [{
1993
+ type: "text",
1994
+ text: JSON.stringify({
1995
+ ok: false,
1996
+ error: message
1997
+ }, null, 2)
1998
+ }] };
1999
+ }
2000
+ function parseNetwork$2(value) {
2001
+ return value === "pubnet" ? "pubnet" : "testnet";
2002
+ }
2003
+ const addressParam = z.string().describe("Stellar public key (G...)");
2004
+ const networkParam$1 = z.enum(["testnet", "pubnet"]).default("testnet").describe("Stellar network");
2005
+ const limitParam$1 = z.number().int().min(1).max(200).default(10).describe("Max records to return");
2006
+ const cursorParam = z.string().optional().describe("Pagination cursor");
2007
+ const orderParam = z.enum(["asc", "desc"]).default("desc").describe("Sort order");
2008
+ function registerAccountTools(server) {
2009
+ server.registerTool("account_details", {
2010
+ description: "Get detailed account information from Horizon: balances, signers, thresholds, flags, and more.",
2011
+ inputSchema: {
2012
+ address: addressParam,
2013
+ network: networkParam$1
2014
+ }
2015
+ }, async ({ address, network }) => {
2016
+ const validation = stellarPublicKey.safeParse(address);
2017
+ if (!validation.success) return err$2(`Invalid address: ${validation.error.issues.map((i) => i.message).join(", ")}`);
2018
+ const result = await getAccountDetails(address, parseNetwork$2(network));
2019
+ if (Result.isError(result)) return err$2(formatHorizonError(result.error));
2020
+ return ok$2(result.value);
2021
+ });
2022
+ server.registerTool("account_transactions", {
2023
+ description: "Get transaction history for a Stellar account from Horizon.",
2024
+ inputSchema: {
2025
+ address: addressParam,
2026
+ network: networkParam$1,
2027
+ limit: limitParam$1,
2028
+ cursor: cursorParam,
2029
+ order: orderParam
2030
+ }
2031
+ }, async ({ address, network, limit, cursor, order }) => {
2032
+ const validation = stellarPublicKey.safeParse(address);
2033
+ if (!validation.success) return err$2(`Invalid address: ${validation.error.issues.map((i) => i.message).join(", ")}`);
2034
+ const result = await getTransactions(address, parseNetwork$2(network), {
2035
+ limit,
2036
+ cursor,
2037
+ order
2038
+ });
2039
+ if (Result.isError(result)) return err$2(formatHorizonError(result.error));
2040
+ return ok$2({
2041
+ address,
2042
+ count: result.value.length,
2043
+ transactions: result.value
2044
+ });
2045
+ });
2046
+ server.registerTool("account_payments", {
2047
+ description: "Get payment history for a Stellar account from Horizon.",
2048
+ inputSchema: {
2049
+ address: addressParam,
2050
+ network: networkParam$1,
2051
+ limit: limitParam$1,
2052
+ cursor: cursorParam,
2053
+ order: orderParam
2054
+ }
2055
+ }, async ({ address, network, limit, cursor, order }) => {
2056
+ const validation = stellarPublicKey.safeParse(address);
2057
+ if (!validation.success) return err$2(`Invalid address: ${validation.error.issues.map((i) => i.message).join(", ")}`);
2058
+ const result = await getPayments(address, parseNetwork$2(network), {
2059
+ limit,
2060
+ cursor,
2061
+ order
2062
+ });
2063
+ if (Result.isError(result)) return err$2(formatHorizonError(result.error));
2064
+ return ok$2({
2065
+ address,
2066
+ count: result.value.length,
2067
+ payments: result.value
2068
+ });
2069
+ });
2070
+ server.registerTool("account_effects", {
2071
+ description: "Get effects history for a Stellar account from Horizon.",
2072
+ inputSchema: {
2073
+ address: addressParam,
2074
+ network: networkParam$1,
2075
+ limit: limitParam$1,
2076
+ cursor: cursorParam
2077
+ }
2078
+ }, async ({ address, network, limit, cursor }) => {
2079
+ const validation = stellarPublicKey.safeParse(address);
2080
+ if (!validation.success) return err$2(`Invalid address: ${validation.error.issues.map((i) => i.message).join(", ")}`);
2081
+ const result = await getEffects(address, parseNetwork$2(network), {
2082
+ limit,
2083
+ cursor
2084
+ });
2085
+ if (Result.isError(result)) return err$2(formatHorizonError(result.error));
2086
+ return ok$2({
2087
+ address,
2088
+ count: result.value.length,
2089
+ effects: result.value
2090
+ });
2091
+ });
2092
+ }
2093
+ //#endregion
2094
+ //#region src/mcp/asset-tools.ts
2095
+ function ok$1(data) {
2096
+ return { content: [{
2097
+ type: "text",
2098
+ text: JSON.stringify({
2099
+ ok: true,
2100
+ data
2101
+ }, null, 2)
2102
+ }] };
2103
+ }
2104
+ function err$1(message) {
2105
+ return { content: [{
2106
+ type: "text",
2107
+ text: JSON.stringify({
2108
+ ok: false,
2109
+ error: message
2110
+ }, null, 2)
2111
+ }] };
2112
+ }
2113
+ function parseNetwork$1(value) {
2114
+ return value === "pubnet" ? "pubnet" : "testnet";
2115
+ }
2116
+ const networkParam = z.enum(["testnet", "pubnet"]).default("testnet").describe("Stellar network");
2117
+ const limitParam = z.number().int().min(1).max(200).default(10).describe("Max records to return");
2118
+ function parseAssetInput(input) {
2119
+ if (input === "native" || input === "XLM") return { assetType: "native" };
2120
+ const parts = input.split(":");
2121
+ if (parts.length !== 2) throw new Error(`Invalid asset format: "${input}". Use "native" or "CODE:ISSUER".`);
2122
+ return {
2123
+ assetType: parts[0].length <= 4 ? "credit_alphanum4" : "credit_alphanum12",
2124
+ assetCode: parts[0],
2125
+ assetIssuer: parts[1]
2126
+ };
2127
+ }
2128
+ function registerAssetTools(server) {
2129
+ server.registerTool("assets_search", {
2130
+ description: "Search for assets on Stellar. Filter by asset code and/or issuer.",
2131
+ inputSchema: {
2132
+ network: networkParam,
2133
+ code: z.string().optional().describe("Filter by asset code (e.g. USDC)"),
2134
+ issuer: z.string().optional().describe("Filter by asset issuer (G...)"),
2135
+ limit: limitParam
2136
+ }
2137
+ }, async ({ network, code, issuer, limit }) => {
2138
+ const result = await getAssets(parseNetwork$1(network), {
2139
+ limit,
2140
+ code,
2141
+ issuer
2142
+ });
2143
+ if (Result.isError(result)) return err$1(formatHorizonError(result.error));
2144
+ return ok$1({
2145
+ count: result.value.length,
2146
+ assets: result.value
2147
+ });
2148
+ });
2149
+ server.registerTool("assets_orderbook", {
2150
+ description: "View the order book (bids and asks) for a pair of assets on Stellar.",
2151
+ inputSchema: {
2152
+ selling: z.string().describe("Selling asset: 'native' for XLM, or 'CODE:ISSUER'"),
2153
+ buying: z.string().describe("Buying asset: 'native' for XLM, or 'CODE:ISSUER'"),
2154
+ network: networkParam,
2155
+ limit: limitParam
2156
+ }
2157
+ }, async ({ selling, buying, network, limit }) => {
2158
+ try {
2159
+ const result = await getOrderbook(parseAssetInput(selling), parseAssetInput(buying), parseNetwork$1(network), { limit });
2160
+ if (Result.isError(result)) return err$1(formatHorizonError(result.error));
2161
+ return ok$1(result.value);
2162
+ } catch (e) {
2163
+ return err$1(e instanceof Error ? e.message : String(e));
2164
+ }
2165
+ });
2166
+ server.registerTool("fee_stats", {
2167
+ description: "Get current fee statistics from the Stellar network.",
2168
+ inputSchema: { network: networkParam }
2169
+ }, async ({ network }) => {
2170
+ const result = await getFeeStats(parseNetwork$1(network));
2171
+ if (Result.isError(result)) return err$1(formatHorizonError(result.error));
2172
+ return ok$1(result.value);
2173
+ });
2174
+ }
2175
+ //#endregion
2176
+ //#region src/mcp/payment-tools.ts
2177
+ function ok(data) {
2178
+ return { content: [{
2179
+ type: "text",
2180
+ text: JSON.stringify({
2181
+ ok: true,
2182
+ data
2183
+ }, null, 2)
2184
+ }] };
2185
+ }
2186
+ function err(message) {
2187
+ return { content: [{
2188
+ type: "text",
2189
+ text: JSON.stringify({
2190
+ ok: false,
2191
+ error: message
2192
+ }, null, 2)
2193
+ }] };
2194
+ }
2195
+ function parseNetwork(value) {
2196
+ return value === "pubnet" ? "pubnet" : "testnet";
2197
+ }
2198
+ function registerPaymentTools(server) {
2199
+ server.registerTool("send_payment", {
2200
+ description: "Send a payment (XLM or custom asset) from the logged-in wallet to another Stellar address. Requires an active session.",
2201
+ inputSchema: {
2202
+ destination: z.string().describe("Destination public key (G...)"),
2203
+ amount: z.string().describe("Amount to send (up to 7 decimal places)"),
2204
+ asset: z.string().default("native").describe("Asset: 'native' for XLM, or 'CODE:ISSUER' for custom assets"),
2205
+ memo: z.string().optional().describe("Transaction memo text (max 28 chars)"),
2206
+ network: z.enum(["testnet", "pubnet"]).default("testnet").describe("Stellar network")
2207
+ }
2208
+ }, async ({ destination, amount, asset, memo, network }) => {
2209
+ const destValidation = stellarPublicKey.safeParse(destination);
2210
+ if (!destValidation.success) return err(`Invalid destination: ${destValidation.error.issues.map((i) => i.message).join(", ")}`);
2211
+ const amountValidation = amountSchema.safeParse(amount);
2212
+ if (!amountValidation.success) return err(`Invalid amount: ${amountValidation.error.issues.map((i) => i.message).join(", ")}`);
2213
+ const assetValidation = assetSchema.safeParse(asset);
2214
+ if (!assetValidation.success) return err(`Invalid asset: ${assetValidation.error.issues.map((i) => i.message).join(", ")}`);
2215
+ if (memo !== void 0) {
2216
+ const memoValidation = memoSchema.safeParse(memo);
2217
+ if (!memoValidation.success) return err(`Invalid memo: ${memoValidation.error.issues.map((i) => i.message).join(", ")}`);
2218
+ }
2219
+ const walletResult = await fetchWallet();
2220
+ if (Result.isError(walletResult)) return err(formatWalletError(walletResult.error));
2221
+ const result = await sendPayment(walletResult.value.secretKey, destination, amount, asset, parseNetwork(network), memo);
2222
+ if (Result.isError(result)) return err(formatStellarError(result.error));
2223
+ return ok(result.value);
2224
+ });
2225
+ server.registerTool("pay_url", {
2226
+ description: "Make an X402 micropayment to a URL. The CLI detects 402 responses, signs auth entries, and returns the paid content. Requires an active session.",
2227
+ inputSchema: { url: z.string().describe("URL that requires X402 payment") }
2228
+ }, async ({ url }) => {
2229
+ const result = await pay(url);
2230
+ if (Result.isError(result)) return err(formatPaymentError(result.error));
2231
+ return ok(result.value);
2232
+ });
2233
+ }
2234
+ //#endregion
2235
+ //#region src/mcp/mod.ts
2236
+ async function startMcpServer() {
2237
+ const server = new McpServer({
2238
+ name: "stelagent",
2239
+ version: "0.1.0"
2240
+ }, { instructions: [
2241
+ "Stelagent MCP server for Stellar blockchain operations.",
2242
+ "Authentication flow: 1) wallet_login (sends OTP to email) → 2) wallet_verify (verifies OTP, creates session and wallet). After verify, all other wallet tools work.",
2243
+ "Account, asset, and fee tools are public and require no authentication — only a Stellar public key or network parameter.",
2244
+ "All tools return { ok: true, data } on success or { ok: false, error } on failure.",
2245
+ "Network defaults to 'testnet'. Use 'pubnet' for mainnet."
2246
+ ].join("\n") });
2247
+ registerWalletTools(server);
2248
+ registerAccountTools(server);
2249
+ registerAssetTools(server);
2250
+ registerPaymentTools(server);
2251
+ const transport = new StdioServerTransport();
2252
+ await server.connect(transport);
2253
+ }
2254
+ //#endregion
1821
2255
  //#region src/index.ts
1822
2256
  runMain(defineCommand({
1823
2257
  meta: {
@@ -1832,86 +2266,21 @@ runMain(defineCommand({
1832
2266
  assets: assetsCommand,
1833
2267
  send: sendCommand,
1834
2268
  fee: feeCommand,
1835
- monitor: defineCommand({
2269
+ monitor: monitorCommand,
2270
+ mcp: defineCommand({
1836
2271
  meta: {
1837
- name: "monitor",
1838
- description: "Stream live data from Horizon via SSE"
2272
+ name: "mcp",
2273
+ description: "Start the MCP server on stdio"
1839
2274
  },
1840
- subCommands: {
1841
- transactions: monitorTransactions,
1842
- payments: monitorPayments,
1843
- effects: defineCommand({
1844
- meta: {
1845
- name: "effects",
1846
- description: "Stream effects for an account in real-time"
1847
- },
1848
- args: {
1849
- address: {
1850
- type: "positional",
1851
- description: "Stellar public key (G...)",
1852
- required: true
1853
- },
1854
- network: networkArg,
1855
- format: formatArg,
1856
- cursor: {
1857
- type: "string",
1858
- alias: ["c"],
1859
- description: "Start from this cursor (default: 'now' for new events)",
1860
- default: "now"
1861
- }
1862
- },
1863
- async run({ args }) {
1864
- const networkResult = parseNetwork(String(args.network ?? "testnet"));
1865
- const format = parseFormat(String(args.format ?? "json"));
1866
- if (Result.isError(networkResult)) {
1867
- printResult(Result.err(networkResult.error._tag), "json");
1868
- return;
1869
- }
1870
- const network = networkResult.value;
1871
- const address = String(args.address ?? "");
1872
- const validation = Result.try({
1873
- try: () => stellarPublicKey.parse(address),
1874
- catch: (e) => e
1875
- });
1876
- if (Result.isError(validation)) {
1877
- const msg = validation.error instanceof z.ZodError ? formatZodError(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1878
- printResult(Result.err(msg), format);
1879
- return;
1880
- }
1881
- const cursor = args.cursor === "now" ? void 0 : String(args.cursor);
1882
- let close;
1883
- try {
1884
- close = await runApp("monitor effects", async () => {
1885
- return streamEffects(address, network, { cursor }, (record) => {
1886
- console.log(JSON.stringify({
1887
- ok: true,
1888
- data: record
1889
- }));
1890
- }, (error) => {
1891
- const message = error instanceof Error ? error.message : String(error);
1892
- console.log(JSON.stringify({
1893
- ok: false,
1894
- error: `Stream error: ${message}`
1895
- }));
1896
- });
1897
- }, format);
1898
- } catch (e) {
1899
- const message = e instanceof Error ? e.message : String(e);
1900
- printResult(Result.err(`Failed to stream effects: ${message}`), "json");
1901
- return;
1902
- }
1903
- console.log(JSON.stringify({
1904
- ok: true,
1905
- data: { message: `Streaming effects for ${address} on ${network}... Press Ctrl+C to stop.` }
1906
- }, null, 2));
1907
- await new Promise((resolve) => {
1908
- process.on("SIGINT", () => {
1909
- close();
1910
- resolve();
1911
- });
1912
- });
1913
- }
1914
- })
2275
+ args: {},
2276
+ async run() {
2277
+ try {
2278
+ await startMcpServer();
2279
+ } catch (e) {
2280
+ const message = e instanceof Error ? e.message : String(e);
2281
+ console.error(`Failed to start MCP server: ${message}`);
2282
+ process.exit(1);
2283
+ }
1915
2284
  }
1916
2285
  })
1917
2286
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stelagent",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Modular, agent-first CLI for Stellar — wallet, payments, markets, and DeFi",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -25,6 +25,7 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@clack/prompts": "^1.2.0",
28
+ "@modelcontextprotocol/sdk": "^1.29.0",
28
29
  "@stellar/stellar-sdk": "^14.6.1",
29
30
  "@x402/core": "^2.9.0",
30
31
  "@x402/stellar": "^2.9.0",