@xlmtools/cli 0.1.1 → 0.2.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 +18 -12
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +6 -1
- package/dist/lib/api-fetch.d.ts +19 -0
- package/dist/lib/api-fetch.js +20 -0
- package/dist/lib/budget.d.ts +14 -0
- package/dist/lib/cache.d.ts +7 -0
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/format.d.ts +15 -0
- package/dist/lib/logger.d.ts +2 -0
- package/dist/lib/wallet.d.ts +9 -0
- package/dist/server.d.ts +22 -0
- package/dist/server.js +89 -0
- package/dist/tools/budget.d.ts +2 -0
- package/dist/tools/crypto.d.ts +2 -0
- package/dist/tools/crypto.js +2 -1
- package/dist/tools/dex-candles.d.ts +2 -0
- package/dist/tools/dex-candles.js +2 -1
- package/dist/tools/dex-orderbook.d.ts +2 -0
- package/dist/tools/dex-orderbook.js +2 -1
- package/dist/tools/dex-trades.d.ts +2 -0
- package/dist/tools/dex-trades.js +2 -1
- package/dist/tools/domain.d.ts +2 -0
- package/dist/tools/domain.js +2 -1
- package/dist/tools/image.d.ts +2 -0
- package/dist/tools/image.js +2 -1
- package/dist/tools/oracle-price.d.ts +2 -0
- package/dist/tools/oracle-price.js +2 -1
- package/dist/tools/research.d.ts +2 -0
- package/dist/tools/research.js +2 -1
- package/dist/tools/scrape.d.ts +2 -0
- package/dist/tools/scrape.js +2 -1
- package/dist/tools/screenshot.d.ts +2 -0
- package/dist/tools/screenshot.js +2 -1
- package/dist/tools/search.d.ts +2 -0
- package/dist/tools/search.js +2 -1
- package/dist/tools/stellar-account.d.ts +2 -0
- package/dist/tools/stellar-account.js +2 -1
- package/dist/tools/stellar-asset.d.ts +2 -0
- package/dist/tools/stellar-asset.js +2 -1
- package/dist/tools/stellar-pools.d.ts +2 -0
- package/dist/tools/stellar-pools.js +2 -1
- package/dist/tools/stocks.d.ts +2 -0
- package/dist/tools/stocks.js +2 -1
- package/dist/tools/swap-quote.d.ts +2 -0
- package/dist/tools/swap-quote.js +2 -1
- package/dist/tools/tools-list.d.ts +2 -0
- package/dist/tools/wallet-tool.d.ts +2 -0
- package/dist/tools/weather.d.ts +2 -0
- package/dist/tools/weather.js +2 -1
- package/dist/tools/youtube.d.ts +2 -0
- package/dist/tools/youtube.js +2 -1
- package/package.json +27 -3
- package/dist/index.js +0 -77
- package/dist/tools/card.js +0 -51
- package/dist/tools/reddit.js +0 -40
- package/src/cli.ts +0 -240
- package/src/index.ts +0 -90
- package/src/lib/budget.ts +0 -78
- package/src/lib/cache.ts +0 -66
- package/src/lib/config.ts +0 -18
- package/src/lib/format.ts +0 -51
- package/src/lib/logger.ts +0 -6
- package/src/lib/wallet.ts +0 -143
- package/src/tools/budget.ts +0 -67
- package/src/tools/crypto.ts +0 -25
- package/src/tools/dex-candles.ts +0 -52
- package/src/tools/dex-orderbook.ts +0 -45
- package/src/tools/dex-trades.ts +0 -51
- package/src/tools/domain.ts +0 -24
- package/src/tools/image.ts +0 -49
- package/src/tools/oracle-price.ts +0 -42
- package/src/tools/research.ts +0 -47
- package/src/tools/scrape.ts +0 -41
- package/src/tools/screenshot.ts +0 -46
- package/src/tools/search.ts +0 -47
- package/src/tools/stellar-account.ts +0 -38
- package/src/tools/stellar-asset.ts +0 -39
- package/src/tools/stellar-pools.ts +0 -45
- package/src/tools/stocks.ts +0 -43
- package/src/tools/swap-quote.ts +0 -43
- package/src/tools/tools-list.ts +0 -22
- package/src/tools/wallet-tool.ts +0 -51
- package/src/tools/weather.ts +0 -24
- package/src/tools/youtube.ts +0 -49
- package/tsconfig.json +0 -13
package/dist/tools/weather.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { loadOrCreateWallet } from "../lib/wallet.js";
|
|
3
|
+
import { apiFetch } from "../lib/api-fetch.js";
|
|
3
4
|
import { ok, err } from "../lib/format.js";
|
|
4
5
|
export function registerWeatherTool(server) {
|
|
5
6
|
server.registerTool("weather", {
|
|
@@ -10,7 +11,7 @@ export function registerWeatherTool(server) {
|
|
|
10
11
|
}, async ({ location }) => {
|
|
11
12
|
try {
|
|
12
13
|
const config = loadOrCreateWallet();
|
|
13
|
-
const res = await
|
|
14
|
+
const res = await apiFetch(config, `/weather?location=${encodeURIComponent(location)}`);
|
|
14
15
|
if (!res.ok)
|
|
15
16
|
return err(`Weather API error: ${res.status}`);
|
|
16
17
|
return ok(await res.json());
|
package/dist/tools/youtube.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { loadOrCreateWallet } from "../lib/wallet.js";
|
|
3
|
+
import { apiFetch } from "../lib/api-fetch.js";
|
|
3
4
|
import { okPaid, err } from "../lib/format.js";
|
|
4
5
|
import { logger } from "../lib/logger.js";
|
|
5
6
|
import { TOOL_PRICES } from "../lib/config.js";
|
|
@@ -26,7 +27,7 @@ export function registerYoutubeTool(server) {
|
|
|
26
27
|
qs.set("q", query);
|
|
27
28
|
if (id)
|
|
28
29
|
qs.set("id", id);
|
|
29
|
-
const res = await
|
|
30
|
+
const res = await apiFetch(config, `/youtube?${qs.toString()}`);
|
|
30
31
|
if (!res.ok) {
|
|
31
32
|
const body = await res.text();
|
|
32
33
|
return err(`YouTube API error ${res.status}: ${body}`);
|
package/package.json
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xlmtools/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "XLMTools standalone CLI — the `xlm` command plus the `createMcpServer` factory used by the @xlmtools/mcp wrapper package.",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"bin": {
|
|
6
|
-
"xlmtools": "dist/index.js",
|
|
7
7
|
"xlm": "dist/cli.js"
|
|
8
8
|
},
|
|
9
|
+
"main": "./dist/server.js",
|
|
10
|
+
"types": "./dist/server.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
9
15
|
"scripts": {
|
|
10
|
-
"dev": "tsx watch src/
|
|
16
|
+
"dev": "tsx watch src/cli.ts",
|
|
11
17
|
"build": "tsc",
|
|
12
18
|
"test": "vitest run"
|
|
13
19
|
},
|
|
@@ -24,5 +30,23 @@
|
|
|
24
30
|
"tsx": "^4.21.0",
|
|
25
31
|
"typescript": "^6.0.2",
|
|
26
32
|
"vitest": "^4.1.2"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"stellar",
|
|
36
|
+
"xlm",
|
|
37
|
+
"cli",
|
|
38
|
+
"soroban",
|
|
39
|
+
"mpp",
|
|
40
|
+
"x402",
|
|
41
|
+
"agent-tools"
|
|
42
|
+
],
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/Blockchain-Oracle/xlmtools.git",
|
|
46
|
+
"directory": "packages/cli"
|
|
47
|
+
},
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=22"
|
|
27
51
|
}
|
|
28
52
|
}
|
package/dist/index.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import { Mppx } from "mppx/client";
|
|
5
|
-
import { stellar } from "@stellar/mpp/charge/client";
|
|
6
|
-
import { loadOrCreateWallet, getKeypair } from "./lib/wallet.js";
|
|
7
|
-
import { logger } from "./lib/logger.js";
|
|
8
|
-
import { registerCryptoTool } from "./tools/crypto.js";
|
|
9
|
-
import { registerWeatherTool } from "./tools/weather.js";
|
|
10
|
-
import { registerDomainTool } from "./tools/domain.js";
|
|
11
|
-
import { registerToolsListTool } from "./tools/tools-list.js";
|
|
12
|
-
import { registerWalletTool } from "./tools/wallet-tool.js";
|
|
13
|
-
import { registerSearchTool } from "./tools/search.js";
|
|
14
|
-
import { registerResearchTool } from "./tools/research.js";
|
|
15
|
-
import { registerYoutubeTool } from "./tools/youtube.js";
|
|
16
|
-
import { registerScreenshotTool } from "./tools/screenshot.js";
|
|
17
|
-
import { registerScrapeTool } from "./tools/scrape.js";
|
|
18
|
-
import { registerImageTool } from "./tools/image.js";
|
|
19
|
-
import { registerStocksTool } from "./tools/stocks.js";
|
|
20
|
-
import { registerDexOrderbookTool } from "./tools/dex-orderbook.js";
|
|
21
|
-
import { registerDexCandlesTool } from "./tools/dex-candles.js";
|
|
22
|
-
import { registerDexTradesTool } from "./tools/dex-trades.js";
|
|
23
|
-
import { registerSwapQuoteTool } from "./tools/swap-quote.js";
|
|
24
|
-
import { registerStellarAssetTool } from "./tools/stellar-asset.js";
|
|
25
|
-
import { registerStellarAccountTool } from "./tools/stellar-account.js";
|
|
26
|
-
import { registerStellarPoolsTool } from "./tools/stellar-pools.js";
|
|
27
|
-
import { registerOraclePriceTool } from "./tools/oracle-price.js";
|
|
28
|
-
import { registerBudgetTool } from "./tools/budget.js";
|
|
29
|
-
const config = loadOrCreateWallet();
|
|
30
|
-
const keypair = getKeypair(config);
|
|
31
|
-
// Mppx polyfills global fetch to auto-handle 402 payments
|
|
32
|
-
Mppx.create({
|
|
33
|
-
methods: [
|
|
34
|
-
stellar.charge({
|
|
35
|
-
keypair,
|
|
36
|
-
mode: "pull",
|
|
37
|
-
onProgress(event) {
|
|
38
|
-
logger.debug({ eventType: event.type }, "MPP payment event");
|
|
39
|
-
},
|
|
40
|
-
}),
|
|
41
|
-
],
|
|
42
|
-
});
|
|
43
|
-
// { logging: {} } enables ctx.mcpReq.log() in tool handlers
|
|
44
|
-
const server = new McpServer({ name: "xlmtools", version: "0.1.0" }, { capabilities: { tools: {}, logging: {} } });
|
|
45
|
-
// Free tools
|
|
46
|
-
registerCryptoTool(server);
|
|
47
|
-
registerWeatherTool(server);
|
|
48
|
-
registerDomainTool(server);
|
|
49
|
-
registerToolsListTool(server);
|
|
50
|
-
registerWalletTool(server);
|
|
51
|
-
registerBudgetTool(server);
|
|
52
|
-
// Paid tools
|
|
53
|
-
registerSearchTool(server);
|
|
54
|
-
registerResearchTool(server);
|
|
55
|
-
registerYoutubeTool(server);
|
|
56
|
-
registerScreenshotTool(server);
|
|
57
|
-
registerScrapeTool(server);
|
|
58
|
-
registerImageTool(server);
|
|
59
|
-
registerStocksTool(server);
|
|
60
|
-
// Stellar-native tools (free)
|
|
61
|
-
registerDexOrderbookTool(server);
|
|
62
|
-
registerDexCandlesTool(server);
|
|
63
|
-
registerDexTradesTool(server);
|
|
64
|
-
registerSwapQuoteTool(server);
|
|
65
|
-
registerStellarAssetTool(server);
|
|
66
|
-
registerStellarAccountTool(server);
|
|
67
|
-
registerStellarPoolsTool(server);
|
|
68
|
-
registerOraclePriceTool(server);
|
|
69
|
-
async function main() {
|
|
70
|
-
const transport = new StdioServerTransport();
|
|
71
|
-
await server.connect(transport);
|
|
72
|
-
logger.info("XLMTools MCP server running");
|
|
73
|
-
}
|
|
74
|
-
main().catch((error) => {
|
|
75
|
-
logger.error({ err: error }, "Fatal error");
|
|
76
|
-
process.exit(1);
|
|
77
|
-
});
|
package/dist/tools/card.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { loadOrCreateWallet } from "../lib/wallet.js";
|
|
3
|
-
import { ok, err } from "../lib/format.js";
|
|
4
|
-
import { logger } from "../lib/logger.js";
|
|
5
|
-
export function registerCardTool(server) {
|
|
6
|
-
server.registerTool("card", {
|
|
7
|
-
title: "Virtual Card",
|
|
8
|
-
description: `Issue a virtual Mastercard for agent spending. Card is funded from your XLMTools USDC balance.
|
|
9
|
-
Cost: $10 flat card creation fee + load amount + 3.5% processing.
|
|
10
|
-
IMPORTANT: Card details are shown ONCE and not stored.
|
|
11
|
-
MVP: ASGCard API integration pending — add @asgcard/sdk and ASGCARD_STELLAR_KEY to enable real cards.`,
|
|
12
|
-
inputSchema: z.object({
|
|
13
|
-
amount: z
|
|
14
|
-
.number()
|
|
15
|
-
.min(5)
|
|
16
|
-
.max(1000)
|
|
17
|
-
.default(20)
|
|
18
|
-
.describe("Amount to load in USD ($5-$1000)"),
|
|
19
|
-
name: z
|
|
20
|
-
.string()
|
|
21
|
-
.default("AI AGENT")
|
|
22
|
-
.describe("Name on card"),
|
|
23
|
-
email: z
|
|
24
|
-
.string()
|
|
25
|
-
.email()
|
|
26
|
-
.default("agent@xlmtools.dev")
|
|
27
|
-
.describe("Email for registration"),
|
|
28
|
-
}),
|
|
29
|
-
}, async ({ amount, name, email }) => {
|
|
30
|
-
logger.debug({ amount, name, email }, "card tool invoked");
|
|
31
|
-
try {
|
|
32
|
-
const config = loadOrCreateWallet();
|
|
33
|
-
const res = await fetch(`${config.apiUrl}/card`, {
|
|
34
|
-
method: "POST",
|
|
35
|
-
headers: { "Content-Type": "application/json" },
|
|
36
|
-
body: JSON.stringify({ amount, nameOnCard: name, email }),
|
|
37
|
-
});
|
|
38
|
-
if (!res.ok) {
|
|
39
|
-
const body = await res.text();
|
|
40
|
-
return err(`Card API error ${res.status}: ${body}`);
|
|
41
|
-
}
|
|
42
|
-
const data = await res.json();
|
|
43
|
-
const totalCharge = (10 + amount + amount * 0.035).toFixed(3);
|
|
44
|
-
return ok({ ...data, total_charged_usdc: totalCharge });
|
|
45
|
-
}
|
|
46
|
-
catch (e) {
|
|
47
|
-
logger.error({ err: e }, "card tool error");
|
|
48
|
-
return err(String(e));
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|
package/dist/tools/reddit.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { loadOrCreateWallet } from "../lib/wallet.js";
|
|
3
|
-
import { ok, err } from "../lib/format.js";
|
|
4
|
-
import { logger } from "../lib/logger.js";
|
|
5
|
-
import { TOOL_PRICES } from "../lib/config.js";
|
|
6
|
-
export function registerRedditTool(server) {
|
|
7
|
-
server.registerTool("reddit", {
|
|
8
|
-
title: "Reddit Search",
|
|
9
|
-
description: `Search Reddit posts and comments in real-time.\nCost: $${TOOL_PRICES.reddit} USDC per query (paid via Stellar MPP).`,
|
|
10
|
-
inputSchema: z.object({
|
|
11
|
-
query: z.string().describe("Search query"),
|
|
12
|
-
subreddit: z
|
|
13
|
-
.string()
|
|
14
|
-
.optional()
|
|
15
|
-
.describe("Limit search to a specific subreddit (without r/ prefix)"),
|
|
16
|
-
sort: z
|
|
17
|
-
.enum(["relevance", "hot", "new", "top"])
|
|
18
|
-
.default("relevance")
|
|
19
|
-
.describe("Sort order for results"),
|
|
20
|
-
}),
|
|
21
|
-
}, async ({ query, subreddit, sort }) => {
|
|
22
|
-
logger.debug({ query, subreddit, sort }, "reddit tool invoked");
|
|
23
|
-
try {
|
|
24
|
-
const config = loadOrCreateWallet();
|
|
25
|
-
const params = new URLSearchParams({ q: query, sort });
|
|
26
|
-
if (subreddit)
|
|
27
|
-
params.set("subreddit", subreddit);
|
|
28
|
-
const res = await fetch(`${config.apiUrl}/reddit?${params.toString()}`);
|
|
29
|
-
if (!res.ok) {
|
|
30
|
-
const body = await res.text();
|
|
31
|
-
return err(`Reddit API error ${res.status}: ${body}`);
|
|
32
|
-
}
|
|
33
|
-
return ok(await res.json());
|
|
34
|
-
}
|
|
35
|
-
catch (e) {
|
|
36
|
-
logger.error({ err: e }, "reddit tool error");
|
|
37
|
-
return err(`Reddit search failed: ${String(e)}`);
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
}
|
package/src/cli.ts
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Mppx } from "mppx/client";
|
|
4
|
-
import { stellar } from "@stellar/mpp/charge/client";
|
|
5
|
-
import { Horizon } from "@stellar/stellar-sdk";
|
|
6
|
-
import { loadOrCreateWallet, getKeypair } from "./lib/wallet.js";
|
|
7
|
-
import { TOOL_PRICES } from "./lib/config.js";
|
|
8
|
-
import { logger } from "./lib/logger.js";
|
|
9
|
-
|
|
10
|
-
// ── Arg parsing ──────────────────────────────────────────
|
|
11
|
-
|
|
12
|
-
function parseArgs(argv: string[]) {
|
|
13
|
-
const [tool, ...rest] = argv;
|
|
14
|
-
const positional: string[] = [];
|
|
15
|
-
const flags: Record<string, string> = {};
|
|
16
|
-
|
|
17
|
-
for (let i = 0; i < rest.length; i++) {
|
|
18
|
-
if (rest[i].startsWith("--") && i + 1 < rest.length) {
|
|
19
|
-
const key = rest[i].slice(2).replace(/-/g, "_");
|
|
20
|
-
flags[key] = rest[i + 1];
|
|
21
|
-
i++;
|
|
22
|
-
} else {
|
|
23
|
-
positional.push(rest[i]);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return { tool: tool ?? "", positional, flags };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// ── URL builder ──────────────────────────────────────────
|
|
31
|
-
|
|
32
|
-
function buildRequest(
|
|
33
|
-
base: string,
|
|
34
|
-
tool: string,
|
|
35
|
-
pos: string[],
|
|
36
|
-
flags: Record<string, string>,
|
|
37
|
-
): { url: string; init: RequestInit } {
|
|
38
|
-
const p = new URLSearchParams();
|
|
39
|
-
|
|
40
|
-
// Map each tool to its API endpoint + params
|
|
41
|
-
const toolMap: Record<string, () => { path: string; method?: string; body?: string }> = {
|
|
42
|
-
search: () => { p.set("q", pos[0] ?? ""); if (flags.count) p.set("count", flags.count); return { path: "/search" }; },
|
|
43
|
-
research: () => { p.set("q", pos[0] ?? ""); if (flags.num_results) p.set("num_results", flags.num_results); return { path: "/research" }; },
|
|
44
|
-
youtube: () => { if (flags.id) p.set("id", flags.id); else p.set("q", pos[0] ?? flags.query ?? ""); return { path: "/youtube" }; },
|
|
45
|
-
screenshot: () => { p.set("url", pos[0] ?? ""); if (flags.format) p.set("format", flags.format); return { path: "/screenshot" }; },
|
|
46
|
-
scrape: () => { p.set("url", pos[0] ?? ""); return { path: "/scrape" }; },
|
|
47
|
-
image: () => ({ path: "/image", method: "POST", body: JSON.stringify({ prompt: pos[0] ?? "", size: flags.size ?? "1024x1024" }) }),
|
|
48
|
-
stocks: () => { p.set("symbol", pos[0] ?? ""); return { path: "/stocks" }; },
|
|
49
|
-
crypto: () => { p.set("ids", pos[0] ?? ""); if (flags.vs_currency) p.set("vs_currency", flags.vs_currency); return { path: "/crypto" }; },
|
|
50
|
-
weather: () => { p.set("location", pos[0] ?? ""); return { path: "/weather" }; },
|
|
51
|
-
domain: () => { p.set("name", pos[0] ?? ""); return { path: "/domain" }; },
|
|
52
|
-
"dex-orderbook": () => { p.set("pair", pos[0] ?? ""); if (flags.limit) p.set("limit", flags.limit); return { path: "/dex-orderbook" }; },
|
|
53
|
-
"dex-candles": () => { p.set("pair", pos[0] ?? ""); if (flags.resolution) p.set("resolution", flags.resolution); if (flags.limit) p.set("limit", flags.limit); return { path: "/dex-candles" }; },
|
|
54
|
-
"dex-trades": () => { p.set("pair", pos[0] ?? ""); if (flags.limit) p.set("limit", flags.limit); if (flags.trade_type) p.set("trade_type", flags.trade_type); return { path: "/dex-trades" }; },
|
|
55
|
-
"swap-quote": () => { p.set("from", pos[0] ?? flags.from ?? ""); p.set("to", pos[1] ?? flags.to ?? ""); p.set("amount", pos[2] ?? flags.amount ?? ""); if (flags.mode) p.set("mode", flags.mode); return { path: "/swap-quote" }; },
|
|
56
|
-
"stellar-asset": () => { p.set("asset", pos[0] ?? ""); return { path: "/stellar-asset" }; },
|
|
57
|
-
"stellar-account": () => { p.set("address", pos[0] ?? ""); return { path: "/stellar-account" }; },
|
|
58
|
-
"stellar-pools": () => { if (pos[0] || flags.asset) p.set("asset", pos[0] ?? flags.asset ?? ""); if (flags.limit) p.set("limit", flags.limit); return { path: "/stellar-pools" }; },
|
|
59
|
-
"oracle-price": () => { p.set("asset", pos[0] ?? ""); if (flags.feed) p.set("feed", flags.feed); return { path: "/oracle-price" }; },
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const builder = toolMap[tool];
|
|
63
|
-
if (!builder) {
|
|
64
|
-
process.stderr.write(`Unknown tool: ${tool}\nRun xlm --help for available tools.\n`);
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const { path, method, body } = builder();
|
|
69
|
-
const qs = p.toString();
|
|
70
|
-
const url = qs ? `${base}${path}?${qs}` : `${base}${path}`;
|
|
71
|
-
const init: RequestInit = { method: method ?? "GET" };
|
|
72
|
-
if (body) {
|
|
73
|
-
init.headers = { "Content-Type": "application/json" };
|
|
74
|
-
init.body = body;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return { url, init };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ── Local tool handlers ──────────────────────────────────
|
|
81
|
-
|
|
82
|
-
async function handleWallet(publicKey: string) {
|
|
83
|
-
const server = new Horizon.Server("https://horizon-testnet.stellar.org");
|
|
84
|
-
try {
|
|
85
|
-
const account = await server.loadAccount(publicKey);
|
|
86
|
-
const xlm = account.balances.find(
|
|
87
|
-
(b: Horizon.HorizonApi.BalanceLine) => b.asset_type === "native",
|
|
88
|
-
);
|
|
89
|
-
const usdc = account.balances.find(
|
|
90
|
-
(b: Horizon.HorizonApi.BalanceLine) =>
|
|
91
|
-
"asset_code" in b && b.asset_code === "USDC",
|
|
92
|
-
);
|
|
93
|
-
return {
|
|
94
|
-
address: publicKey,
|
|
95
|
-
network: "stellar:testnet",
|
|
96
|
-
xlm_balance: xlm?.balance ?? "0",
|
|
97
|
-
usdc_balance: usdc && "balance" in usdc ? usdc.balance : "0",
|
|
98
|
-
};
|
|
99
|
-
} catch {
|
|
100
|
-
return {
|
|
101
|
-
address: publicKey,
|
|
102
|
-
network: "stellar:testnet",
|
|
103
|
-
xlm_balance: "0",
|
|
104
|
-
usdc_balance: "0",
|
|
105
|
-
note: "Account not funded yet",
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function handleTools() {
|
|
111
|
-
const paid = Object.entries(TOOL_PRICES).map(([name, price]) => ({
|
|
112
|
-
name,
|
|
113
|
-
price: `$${price}`,
|
|
114
|
-
}));
|
|
115
|
-
// Note: `budget` is MCP-only — the CLI is a fresh process per invocation
|
|
116
|
-
// so a session-scoped cap is meaningless. Excluded from the CLI's free list.
|
|
117
|
-
const free = [
|
|
118
|
-
"crypto", "weather", "domain", "wallet", "tools",
|
|
119
|
-
"dex-orderbook", "dex-candles", "dex-trades", "swap-quote",
|
|
120
|
-
"stellar-asset", "stellar-account", "stellar-pools", "oracle-price",
|
|
121
|
-
];
|
|
122
|
-
return { paid, free, network: "stellar:testnet", payment: "MPP / USDC" };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// ── Help text ────────────────────────────────────────────
|
|
126
|
-
|
|
127
|
-
const HELP = `XLMTools CLI — Stellar-native tools with pay-per-call
|
|
128
|
-
|
|
129
|
-
Usage: xlm <tool> [args] [--flag value]
|
|
130
|
-
|
|
131
|
-
Paid tools ($0.001-$0.04 USDC via Stellar MPP):
|
|
132
|
-
search <query> [--count N] Web search
|
|
133
|
-
research <query> [--num-results N] Deep research
|
|
134
|
-
youtube <query> | --id <id> YouTube search/lookup
|
|
135
|
-
screenshot <url> [--format png] Capture URL screenshot
|
|
136
|
-
scrape <url> Extract text from URL
|
|
137
|
-
image <prompt> [--size 1024x1024] AI image generation
|
|
138
|
-
stocks <symbol> Stock quotes
|
|
139
|
-
|
|
140
|
-
Free tools:
|
|
141
|
-
crypto <ids> [--vs-currency usd] Crypto prices
|
|
142
|
-
weather <location> Current weather
|
|
143
|
-
domain <name> Domain availability
|
|
144
|
-
dex-orderbook <pair> [--limit N] Stellar DEX orderbook
|
|
145
|
-
dex-candles <pair> OHLCV candlesticks
|
|
146
|
-
dex-trades <pair> Recent DEX trades
|
|
147
|
-
swap-quote <from> <to> <amount> DEX swap pathfinding
|
|
148
|
-
stellar-asset <asset> Asset info
|
|
149
|
-
stellar-account <address> Account lookup
|
|
150
|
-
stellar-pools [--asset X] Liquidity pools
|
|
151
|
-
oracle-price <asset> [--feed crypto] Oracle prices
|
|
152
|
-
wallet Your Stellar wallet
|
|
153
|
-
tools List all tools
|
|
154
|
-
|
|
155
|
-
Examples:
|
|
156
|
-
xlm search "Stellar MPP micropayments"
|
|
157
|
-
xlm crypto bitcoin,ethereum,stellar
|
|
158
|
-
xlm weather Lagos
|
|
159
|
-
xlm stocks AAPL
|
|
160
|
-
xlm dex-orderbook XLM/USDC --limit 5
|
|
161
|
-
xlm wallet
|
|
162
|
-
`;
|
|
163
|
-
|
|
164
|
-
// ── Main ─────────────────────────────────────────────────
|
|
165
|
-
|
|
166
|
-
async function main() {
|
|
167
|
-
const args = process.argv.slice(2);
|
|
168
|
-
|
|
169
|
-
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
170
|
-
process.stdout.write(HELP);
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const { tool, positional, flags } = parseArgs(args);
|
|
175
|
-
|
|
176
|
-
// Init wallet + mppx (payment handling)
|
|
177
|
-
const config = loadOrCreateWallet();
|
|
178
|
-
const keypair = getKeypair(config);
|
|
179
|
-
|
|
180
|
-
Mppx.create({
|
|
181
|
-
methods: [
|
|
182
|
-
stellar.charge({
|
|
183
|
-
keypair,
|
|
184
|
-
mode: "pull",
|
|
185
|
-
onProgress(event) {
|
|
186
|
-
logger.debug({ eventType: event.type }, "MPP payment event");
|
|
187
|
-
},
|
|
188
|
-
}),
|
|
189
|
-
],
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
// ── Local tools (no API call needed) ──
|
|
193
|
-
|
|
194
|
-
if (tool === "wallet") {
|
|
195
|
-
const data = await handleWallet(config.stellarPublicKey);
|
|
196
|
-
console.log(JSON.stringify(data, null, 2));
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (tool === "tools") {
|
|
201
|
-
console.log(JSON.stringify(handleTools(), null, 2));
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// ── API tools ──
|
|
206
|
-
|
|
207
|
-
const isPaid = tool in TOOL_PRICES;
|
|
208
|
-
const { url, init } = buildRequest(config.apiUrl, tool, positional, flags);
|
|
209
|
-
|
|
210
|
-
if (isPaid) {
|
|
211
|
-
process.stderr.write(` Tool: ${tool} · Cost: $${TOOL_PRICES[tool]} USDC\n`);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const res = await fetch(url, init);
|
|
215
|
-
|
|
216
|
-
if (!res.ok) {
|
|
217
|
-
const text = await res.text();
|
|
218
|
-
process.stderr.write(`Error ${res.status}: ${text}\n`);
|
|
219
|
-
process.exit(1);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const data = (await res.json()) as Record<string, unknown>;
|
|
223
|
-
|
|
224
|
-
// Print receipt footer for paid tools
|
|
225
|
-
if (isPaid && data.receipt) {
|
|
226
|
-
const { receipt, ...rest } = data;
|
|
227
|
-
const r = receipt as { tx_hash: string; amount: string; currency: string; network: string };
|
|
228
|
-
console.log(JSON.stringify(rest, null, 2));
|
|
229
|
-
const hash = r.tx_hash.length > 16 ? r.tx_hash.slice(0, 16) + "..." : r.tx_hash;
|
|
230
|
-
const network = r.network === "stellar:testnet" ? "stellar testnet" : r.network;
|
|
231
|
-
process.stderr.write(`\n Payment: $${r.amount} ${r.currency} · tx/${hash} · ${network}\n`);
|
|
232
|
-
} else {
|
|
233
|
-
console.log(JSON.stringify(data, null, 2));
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
main().catch((e) => {
|
|
238
|
-
logger.error({ err: e }, "CLI fatal error");
|
|
239
|
-
process.exit(1);
|
|
240
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import { Mppx } from "mppx/client";
|
|
5
|
-
import { stellar } from "@stellar/mpp/charge/client";
|
|
6
|
-
import { loadOrCreateWallet, getKeypair } from "./lib/wallet.js";
|
|
7
|
-
import { logger } from "./lib/logger.js";
|
|
8
|
-
import { registerCryptoTool } from "./tools/crypto.js";
|
|
9
|
-
import { registerWeatherTool } from "./tools/weather.js";
|
|
10
|
-
import { registerDomainTool } from "./tools/domain.js";
|
|
11
|
-
import { registerToolsListTool } from "./tools/tools-list.js";
|
|
12
|
-
import { registerWalletTool } from "./tools/wallet-tool.js";
|
|
13
|
-
import { registerSearchTool } from "./tools/search.js";
|
|
14
|
-
import { registerResearchTool } from "./tools/research.js";
|
|
15
|
-
|
|
16
|
-
import { registerYoutubeTool } from "./tools/youtube.js";
|
|
17
|
-
import { registerScreenshotTool } from "./tools/screenshot.js";
|
|
18
|
-
import { registerScrapeTool } from "./tools/scrape.js";
|
|
19
|
-
import { registerImageTool } from "./tools/image.js";
|
|
20
|
-
import { registerStocksTool } from "./tools/stocks.js";
|
|
21
|
-
import { registerDexOrderbookTool } from "./tools/dex-orderbook.js";
|
|
22
|
-
import { registerDexCandlesTool } from "./tools/dex-candles.js";
|
|
23
|
-
import { registerDexTradesTool } from "./tools/dex-trades.js";
|
|
24
|
-
import { registerSwapQuoteTool } from "./tools/swap-quote.js";
|
|
25
|
-
import { registerStellarAssetTool } from "./tools/stellar-asset.js";
|
|
26
|
-
import { registerStellarAccountTool } from "./tools/stellar-account.js";
|
|
27
|
-
import { registerStellarPoolsTool } from "./tools/stellar-pools.js";
|
|
28
|
-
import { registerOraclePriceTool } from "./tools/oracle-price.js";
|
|
29
|
-
import { registerBudgetTool } from "./tools/budget.js";
|
|
30
|
-
|
|
31
|
-
const config = loadOrCreateWallet();
|
|
32
|
-
const keypair = getKeypair(config);
|
|
33
|
-
|
|
34
|
-
// Mppx polyfills global fetch to auto-handle 402 payments
|
|
35
|
-
Mppx.create({
|
|
36
|
-
methods: [
|
|
37
|
-
stellar.charge({
|
|
38
|
-
keypair,
|
|
39
|
-
mode: "pull",
|
|
40
|
-
onProgress(event) {
|
|
41
|
-
logger.debug({ eventType: event.type }, "MPP payment event");
|
|
42
|
-
},
|
|
43
|
-
}),
|
|
44
|
-
],
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// { logging: {} } enables ctx.mcpReq.log() in tool handlers
|
|
48
|
-
const server = new McpServer(
|
|
49
|
-
{ name: "xlmtools", version: "0.1.0" },
|
|
50
|
-
{ capabilities: { tools: {}, logging: {} } }
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
// Free tools
|
|
54
|
-
registerCryptoTool(server);
|
|
55
|
-
registerWeatherTool(server);
|
|
56
|
-
registerDomainTool(server);
|
|
57
|
-
registerToolsListTool(server);
|
|
58
|
-
registerWalletTool(server);
|
|
59
|
-
registerBudgetTool(server);
|
|
60
|
-
|
|
61
|
-
// Paid tools
|
|
62
|
-
registerSearchTool(server);
|
|
63
|
-
registerResearchTool(server);
|
|
64
|
-
|
|
65
|
-
registerYoutubeTool(server);
|
|
66
|
-
registerScreenshotTool(server);
|
|
67
|
-
registerScrapeTool(server);
|
|
68
|
-
registerImageTool(server);
|
|
69
|
-
registerStocksTool(server);
|
|
70
|
-
|
|
71
|
-
// Stellar-native tools (free)
|
|
72
|
-
registerDexOrderbookTool(server);
|
|
73
|
-
registerDexCandlesTool(server);
|
|
74
|
-
registerDexTradesTool(server);
|
|
75
|
-
registerSwapQuoteTool(server);
|
|
76
|
-
registerStellarAssetTool(server);
|
|
77
|
-
registerStellarAccountTool(server);
|
|
78
|
-
registerStellarPoolsTool(server);
|
|
79
|
-
registerOraclePriceTool(server);
|
|
80
|
-
|
|
81
|
-
async function main() {
|
|
82
|
-
const transport = new StdioServerTransport();
|
|
83
|
-
await server.connect(transport);
|
|
84
|
-
logger.info("XLMTools MCP server running");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
main().catch((error) => {
|
|
88
|
-
logger.error({ err: error }, "Fatal error");
|
|
89
|
-
process.exit(1);
|
|
90
|
-
});
|
package/src/lib/budget.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
-
import { err } from "./format.js";
|
|
3
|
-
import { TOOL_PRICES } from "./config.js";
|
|
4
|
-
import { logger } from "./logger.js";
|
|
5
|
-
|
|
6
|
-
// Session-scoped budget state (resets on MCP server restart)
|
|
7
|
-
let maxBudget: number | null = null;
|
|
8
|
-
let totalSpent = 0;
|
|
9
|
-
|
|
10
|
-
export function setBudget(max: number): void {
|
|
11
|
-
maxBudget = max;
|
|
12
|
-
// Reset spent counter so the new cap starts fresh. Users expect
|
|
13
|
-
// "I just set a $1.00 budget" to mean they have $1.00 to spend
|
|
14
|
-
// from now — not "$1.00 minus whatever I spent before setting it".
|
|
15
|
-
totalSpent = 0;
|
|
16
|
-
logger.info({ max }, "session budget set");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function clearBudget(): void {
|
|
20
|
-
maxBudget = null;
|
|
21
|
-
totalSpent = 0;
|
|
22
|
-
logger.info("session budget cleared");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function getStatus(): {
|
|
26
|
-
max: number | null;
|
|
27
|
-
spent: number;
|
|
28
|
-
remaining: number | null;
|
|
29
|
-
} {
|
|
30
|
-
return {
|
|
31
|
-
max: maxBudget,
|
|
32
|
-
spent: Math.round(totalSpent * 1000) / 1000,
|
|
33
|
-
remaining:
|
|
34
|
-
maxBudget !== null
|
|
35
|
-
? Math.round((maxBudget - totalSpent) * 1000) / 1000
|
|
36
|
-
: null,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function canSpend(amount: number): boolean {
|
|
41
|
-
if (maxBudget === null) return true;
|
|
42
|
-
// Round to 3 decimal places to avoid floating-point drift
|
|
43
|
-
const spent = Math.round(totalSpent * 1000) / 1000;
|
|
44
|
-
return spent + amount <= maxBudget;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function recordSpend(amount: number): void {
|
|
48
|
-
totalSpent += amount;
|
|
49
|
-
logger.debug({ amount, totalSpent }, "spend recorded");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Wrap a paid tool call with budget checking.
|
|
54
|
-
* If budget would be exceeded, returns an error without calling the API.
|
|
55
|
-
* On success, records the spend.
|
|
56
|
-
*/
|
|
57
|
-
export async function withBudget(
|
|
58
|
-
toolName: string,
|
|
59
|
-
fn: () => Promise<CallToolResult>,
|
|
60
|
-
): Promise<CallToolResult> {
|
|
61
|
-
const price = parseFloat(TOOL_PRICES[toolName] ?? "0");
|
|
62
|
-
if (!canSpend(price)) {
|
|
63
|
-
const status = getStatus();
|
|
64
|
-
return err(
|
|
65
|
-
`Budget limit reached. This call costs $${TOOL_PRICES[toolName]} ` +
|
|
66
|
-
`but only $${status.remaining?.toFixed(3)} remains. ` +
|
|
67
|
-
`Use the budget tool to check or adjust your limit.`,
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const result = await fn();
|
|
72
|
-
|
|
73
|
-
if (!result.isError) {
|
|
74
|
-
recordSpend(price);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return result;
|
|
78
|
-
}
|