@routedock/mcp-server 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RouteDock
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # @routedock/mcp-server
2
+
3
+ Model Context Protocol (MCP) server for RouteDock Stellar payments. Exposes RouteDock's payment functionality as standardized tools that LLM agents can use directly.
4
+
5
+ ## Overview
6
+
7
+ This MCP server implements the thesis that base payment infrastructure should be built for machines, not humans. Instead of requiring every LLM agent to implement the full RouteDock SDK, agents can simply connect to this MCP server and use standardized tools:
8
+
9
+ - `pay_for_data(url, max_amount)` - Pay for a single data request
10
+ - `open_session(url)` - Open a sustained MPP session for streaming
11
+ - `stream_session(channel_id)` - Pull streamed data from an open session
12
+ - `close_session(channel_id)` - Settle and close an open session
13
+ - `check_balance()` - Check wallet balance
14
+ - `list_providers(tags)` - Discover available providers
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install -g @routedock/mcp-server
20
+ ```
21
+
22
+ Or build from source:
23
+
24
+ ```bash
25
+ cd packages/mcp-server
26
+ pnpm install
27
+ pnpm build
28
+ ```
29
+
30
+ ## Configuration
31
+
32
+ For security, it is highly recommended to store your secrets in an external `.env` file rather than inline in the Claude Desktop configuration.
33
+
34
+ Create a `.env` file (e.g., `~/.routedock/.env`):
35
+
36
+ ```bash
37
+ # Required
38
+ STELLAR_SECRET="SDU5..." # Use a dedicated low-balance testnet key for safety
39
+ STELLAR_NETWORK="testnet" # or "mainnet"
40
+ ROUTEDOCK_DAILY_CAP="1.00" # Required: Maximum daily spend in USDC
41
+
42
+ # Optional (for session mode)
43
+ COMMITMENT_SECRET="S..." # Ed25519 secret for channel commitments
44
+
45
+ # Optional (for provider registry)
46
+ SUPABASE_URL="https://..."
47
+ # Note: Use the anon key (anon + public_read_providers RLS is sufficient). Do NOT use the service-role key.
48
+ SUPABASE_KEY="..."
49
+ ```
50
+
51
+ ## Available Tools
52
+
53
+ ### pay_for_data
54
+
55
+ Pay for a single data request from a RouteDock provider. Automatically selects the best payment mode.
56
+
57
+ **Parameters:**
58
+ - `url` (required): Full URL of the provider endpoint
59
+ - `max_amount` (required): Maximum USDC amount to pay
60
+ - `preferred_mode` (optional): Preferred payment mode (`x402`, `mpp-charge`, `mpp-session`)
61
+
62
+ **Returns:** Payment result with mode, amount, transaction hash, and response data
63
+
64
+ ### open_session
65
+
66
+ Open a sustained MPP session for streaming data. Requires `COMMITMENT_SECRET`. RouteDock channels
67
+ are pre-deployed and funded out-of-band before the agent runs, so `initial_deposit` is a safety
68
+ check against the provider's `min_deposit`, not a fund transfer. Pass the returned `channel_id` to
69
+ `stream_session` and `close_session` — the channel's collateral cannot be settled otherwise.
70
+
71
+ **Parameters:**
72
+ - `url` (required): Base URL of the provider
73
+ - `initial_deposit` (optional): Amount in USDC you intend the channel to be funded with
74
+
75
+ **Returns:** `channel_id` and open transaction hash
76
+
77
+ ### stream_session
78
+
79
+ Pull the next batch of streamed responses from a session opened with `open_session`. Each message
80
+ sends a voucher and waits for the provider's response.
81
+
82
+ **Parameters:**
83
+ - `channel_id` (required): The `channel_id` returned by `open_session`
84
+ - `max_messages` (optional): Maximum number of messages to pull in this call (1 to 50, default 1)
85
+
86
+ **Returns:** The pulled messages. If a voucher fails partway through the batch (local spend cap
87
+ reached, provider 4xx/5xx), the call still returns `isError: true` with the messages that were
88
+ already paid for, plus `count` and `stats`, instead of discarding the batch.
89
+
90
+ ### close_session
91
+
92
+ Close a session opened with `open_session`, settling the channel on-chain with the highest signed
93
+ voucher. Required to release the session's locked collateral.
94
+
95
+ **Parameters:**
96
+ - `channel_id` (required): The `channel_id` returned by `open_session`
97
+
98
+ **Returns:** Close transaction hash, total amount paid, and vouchers issued
99
+
100
+ ### check_balance
101
+
102
+ Check the Stellar wallet balance for the configured account.
103
+
104
+ **Parameters:**
105
+ - `asset_code` (optional): Asset code to check (e.g., "USDC")
106
+ - `asset_issuer` (optional): Asset issuer address for non-native assets
107
+
108
+ **Returns:** Balance information for the specified asset
109
+
110
+ ### list_providers
111
+
112
+ List available RouteDock providers from the registry. Requires Supabase credentials.
113
+
114
+ **Parameters:**
115
+ - `tags` (optional): Comma-separated tags for filtering (e.g., "price,stellar,dex")
116
+ - `network` (optional): Filter by network (`testnet` or `mainnet`)
117
+
118
+ **Returns:** List of matching providers with their capabilities
119
+
120
+ ## Claude Desktop Configuration
121
+
122
+ Add this to your Claude Desktop config file:
123
+
124
+ **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
125
+ **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
126
+
127
+ ```json
128
+ {
129
+ "mcpServers": {
130
+ "routedock": {
131
+ "command": "node",
132
+ "args": ["/path/to/@routedock/mcp-server/dist/index.js"],
133
+ "env": {
134
+ "ROUTEDOCK_ENV_FILE": "/absolute/path/to/your/.env"
135
+ }
136
+ }
137
+ }
138
+ }
139
+ ```
140
+
141
+ ## Example Usage
142
+
143
+ Once configured, you can ask Claude to:
144
+
145
+ > "Check my Stellar balance"
146
+ > "List providers that offer price data"
147
+ > "Pay for data from https://api-a.routedock.xyz/price with a max of 0.01 USDC"
148
+ > "Open a session with https://api-b.routedock.xyz for streaming orderbook data"
149
+ > "Pull the next few messages from that session"
150
+ > "Close the session and settle the channel"
151
+
152
+ ## Development
153
+
154
+ ```bash
155
+ # Install dependencies
156
+ pnpm install
157
+
158
+ # Build
159
+ pnpm build
160
+
161
+ # Watch mode
162
+ pnpm dev
163
+
164
+ # Type check
165
+ pnpm typecheck
166
+
167
+ # Run directly
168
+ pnpm start
169
+ ```
170
+
171
+ ## Architecture
172
+
173
+ The MCP server wraps the existing `@routedock/routedock` SDK and exposes its functionality through the Model Context Protocol. This allows any MCP-compatible LLM client (Claude Desktop, etc.) to interact with Stellar payments without implementing the SDK directly.
174
+
175
+ ```
176
+ LLM Agent → MCP Protocol → @routedock/mcp-server → @routedock/routedock SDK → Stellar Network
177
+ ```
178
+
179
+ ## License
180
+
181
+ MIT
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,454 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { config as loadDotenv } from "dotenv";
5
+ import path from "path";
6
+ import os from "os";
7
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
+ import {
10
+ CallToolRequestSchema,
11
+ ListToolsRequestSchema
12
+ } from "@modelcontextprotocol/sdk/types.js";
13
+ import { RouteDockClient, FileSpendStore } from "@routedock/routedock";
14
+ import { createClient } from "@supabase/supabase-js";
15
+
16
+ // src/tools.ts
17
+ var MAX_STREAM_MESSAGES = 50;
18
+ var TOOLS = [
19
+ {
20
+ name: "pay_for_data",
21
+ description: "Pay for a single data request from a RouteDock provider. Automatically selects the best payment mode (x402, mpp-charge, mpp-session, or mpp-session-ws) based on the provider's manifest. Use mpp-session-ws for WebSocket-transport streaming providers (e.g. provider-b).",
22
+ inputSchema: {
23
+ type: "object",
24
+ properties: {
25
+ url: {
26
+ type: "string",
27
+ description: "The full URL of the provider endpoint to pay for (e.g., https://api-a.routedock.xyz/price)"
28
+ },
29
+ max_amount: {
30
+ type: "string",
31
+ description: 'Maximum amount in USDC to pay for this request (e.g., "0.01")'
32
+ },
33
+ preferred_mode: {
34
+ type: "string",
35
+ enum: ["x402", "mpp-charge", "mpp-session", "mpp-session-ws"],
36
+ description: "Optional preferred payment mode. If not specified, the best mode is selected automatically. mpp-session-ws opens a WebSocket-transport MPP session instead of the default HTTP/SSE variant."
37
+ }
38
+ },
39
+ required: ["url", "max_amount"]
40
+ }
41
+ },
42
+ {
43
+ name: "open_session",
44
+ description: "Open a sustained MPP session with a provider for streaming data. Requires COMMITMENT_SECRET to be configured. Returns a channel_id \u2014 pass it to stream_session to pull data and to close_session when done, or the channel collateral can never be settled.",
45
+ inputSchema: {
46
+ type: "object",
47
+ properties: {
48
+ url: {
49
+ type: "string",
50
+ description: "The base URL of the provider (e.g., https://api-b.routedock.xyz)"
51
+ },
52
+ initial_deposit: {
53
+ type: "string",
54
+ description: `Amount in USDC you intend the channel to be funded with (e.g., "1.0"). RouteDock channels are pre-deployed and funded out-of-band before the agent runs \u2014 this is checked against the provider's advertised min_deposit as a safety guard, it does not itself move funds.`
55
+ }
56
+ },
57
+ required: ["url"]
58
+ }
59
+ },
60
+ {
61
+ name: "stream_session",
62
+ description: "Pull the next batch of streamed responses from a session opened with open_session. Each message sends a voucher and waits for the provider to acknowledge it. Call repeatedly to keep streaming, then call close_session to settle and release the channel.",
63
+ inputSchema: {
64
+ type: "object",
65
+ properties: {
66
+ channel_id: {
67
+ type: "string",
68
+ description: "The channel_id returned by open_session"
69
+ },
70
+ max_messages: {
71
+ type: "integer",
72
+ minimum: 1,
73
+ maximum: MAX_STREAM_MESSAGES,
74
+ description: `Maximum number of messages to pull in this call (1 to ${MAX_STREAM_MESSAGES}, default 1)`
75
+ }
76
+ },
77
+ required: ["channel_id"]
78
+ }
79
+ },
80
+ {
81
+ name: "close_session",
82
+ description: "Close an MPP session opened with open_session, settling the channel on-chain with the highest signed voucher. This is required to release the session's locked collateral \u2014 an open session left unclosed keeps funds locked until the SDK's wall-clock auto-close guard fires.",
83
+ inputSchema: {
84
+ type: "object",
85
+ properties: {
86
+ channel_id: {
87
+ type: "string",
88
+ description: "The channel_id returned by open_session"
89
+ }
90
+ },
91
+ required: ["channel_id"]
92
+ }
93
+ },
94
+ {
95
+ name: "check_balance",
96
+ description: "Check the Stellar wallet balance for the configured account",
97
+ inputSchema: {
98
+ type: "object",
99
+ properties: {
100
+ asset_code: {
101
+ type: "string",
102
+ description: 'Optional asset code to check (e.g., "USDC"). If not specified, returns native XLM balance.'
103
+ },
104
+ asset_issuer: {
105
+ type: "string",
106
+ description: "Optional asset issuer address for non-native assets"
107
+ }
108
+ },
109
+ required: []
110
+ }
111
+ },
112
+ {
113
+ name: "list_providers",
114
+ description: "List available RouteDock providers from the registry. Can filter by capability tags (returns providers matching any of the given tags) and by network.",
115
+ inputSchema: {
116
+ type: "object",
117
+ properties: {
118
+ tags: {
119
+ type: "string",
120
+ description: 'Comma-separated tags to filter providers (e.g., "price,stellar,dex")'
121
+ },
122
+ network: {
123
+ type: "string",
124
+ enum: ["testnet", "mainnet"],
125
+ description: "Filter by Stellar network"
126
+ }
127
+ },
128
+ required: []
129
+ }
130
+ }
131
+ ];
132
+
133
+ // src/handlers.ts
134
+ import { Keypair, Horizon } from "@stellar/stellar-sdk";
135
+ function ok(payload) {
136
+ return {
137
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
138
+ };
139
+ }
140
+ function err(message) {
141
+ return {
142
+ content: [{ type: "text", text: JSON.stringify({ success: false, error: message }, null, 2) }],
143
+ isError: true
144
+ };
145
+ }
146
+ async function handlePayForData(args, deps2) {
147
+ const { url, max_amount, preferred_mode } = args;
148
+ const { client: client2 } = deps2;
149
+ const modeOptions = preferred_mode ? { forceMode: preferred_mode } : void 0;
150
+ const estimate = await client2.estimateCost(url, modeOptions);
151
+ if (estimate.amount === void 0 || isNaN(parseFloat(estimate.amount))) {
152
+ return err("Provider returned an undefined or invalid price");
153
+ }
154
+ if (parseFloat(estimate.amount) > parseFloat(max_amount)) {
155
+ return err(
156
+ `Provider cost ${estimate.amount} ${estimate.asset} exceeds max_amount ${max_amount} USDC`
157
+ );
158
+ }
159
+ const result = await client2.pay(url, modeOptions);
160
+ return ok({
161
+ success: true,
162
+ mode: result.mode,
163
+ amount: result.amount,
164
+ tx_hash: result.txHash,
165
+ timestamp: result.timestamp,
166
+ data: result.data
167
+ });
168
+ }
169
+ async function handleOpenSession(args, deps2, commitmentSecret) {
170
+ const { url, initial_deposit } = args;
171
+ const { client: client2, openSessions: openSessions2, fetchManifest } = deps2;
172
+ if (!commitmentSecret) {
173
+ return err("COMMITMENT_SECRET environment variable is required for session mode");
174
+ }
175
+ if (initial_deposit) {
176
+ const baseUrl = new URL(url).origin;
177
+ const manifestUrl = `${baseUrl}/.well-known/routedock.json`;
178
+ const fetcher = fetchManifest ?? ((u) => fetch(u));
179
+ const manifestResponse = await fetcher(manifestUrl);
180
+ const manifest = await manifestResponse.json();
181
+ const minDeposit = manifest?.pricing?.["mpp-session"]?.min_deposit ?? manifest?.pricing?.["mpp-session-ws"]?.min_deposit;
182
+ if (minDeposit && parseFloat(initial_deposit) < parseFloat(minDeposit)) {
183
+ return err(
184
+ `initial_deposit ${initial_deposit} is below this provider's min_deposit ${minDeposit}. RouteDock channels are pre-deployed and funded out-of-band before the agent runs \u2014 make sure the channel is funded with at least min_deposit before opening a session.`
185
+ );
186
+ }
187
+ }
188
+ const session = await client2.openSession(url);
189
+ openSessions2.set(session.channelId, session);
190
+ return ok({
191
+ success: true,
192
+ channel_id: session.channelId,
193
+ open_tx_hash: session.openTxHash,
194
+ message: "Session opened successfully. Use stream_session to pull data and close_session to settle and release the channel collateral."
195
+ });
196
+ }
197
+ async function handleStreamSession(args, deps2) {
198
+ const { channel_id, max_messages } = args;
199
+ const { openSessions: openSessions2 } = deps2;
200
+ const session = openSessions2.get(channel_id);
201
+ if (!session) {
202
+ return err(
203
+ `No open session found for channel_id ${channel_id}. It may have already been closed, auto-closed after its wall-clock lifetime guard, or opened by a different server process.`
204
+ );
205
+ }
206
+ if (max_messages !== void 0 && (!Number.isInteger(max_messages) || max_messages < 1 || max_messages > MAX_STREAM_MESSAGES)) {
207
+ return err(
208
+ `max_messages must be an integer between 1 and ${MAX_STREAM_MESSAGES} (received ${max_messages})`
209
+ );
210
+ }
211
+ const limit = max_messages ?? 1;
212
+ const messages = [];
213
+ const iterator = session.stream()[Symbol.asyncIterator]();
214
+ try {
215
+ for (let i = 0; i < limit; i++) {
216
+ const { value, done } = await iterator.next();
217
+ if (done) break;
218
+ messages.push(value);
219
+ }
220
+ } catch (error) {
221
+ return {
222
+ content: [
223
+ {
224
+ type: "text",
225
+ text: JSON.stringify(
226
+ {
227
+ success: false,
228
+ error: error instanceof Error ? error.message : String(error),
229
+ channel_id,
230
+ count: messages.length,
231
+ messages,
232
+ stats: session.stats()
233
+ },
234
+ null,
235
+ 2
236
+ )
237
+ }
238
+ ],
239
+ isError: true
240
+ };
241
+ } finally {
242
+ await iterator.return?.();
243
+ }
244
+ return ok({
245
+ success: true,
246
+ channel_id,
247
+ count: messages.length,
248
+ messages
249
+ });
250
+ }
251
+ async function handleCloseSession(args, deps2) {
252
+ const { channel_id } = args;
253
+ const { openSessions: openSessions2 } = deps2;
254
+ const session = openSessions2.get(channel_id);
255
+ if (!session) {
256
+ return err(
257
+ `No open session found for channel_id ${channel_id}. It may have already been closed or auto-closed after its wall-clock lifetime guard.`
258
+ );
259
+ }
260
+ const result = await session.close();
261
+ openSessions2.delete(channel_id);
262
+ return ok({
263
+ success: true,
264
+ channel_id,
265
+ close_tx_hash: result.closeTxHash,
266
+ total_paid: result.totalPaid,
267
+ vouchers_issued: result.vouchersIssued
268
+ });
269
+ }
270
+ var HORIZON_URLS = {
271
+ mainnet: "https://horizon.stellar.org",
272
+ testnet: "https://horizon-testnet.stellar.org"
273
+ };
274
+ function normalizeBalances(balances) {
275
+ return balances.map((b) => ({
276
+ asset_type: b.asset_type,
277
+ asset_code: "asset_code" in b ? b.asset_code : void 0,
278
+ asset_issuer: "asset_issuer" in b ? b.asset_issuer : void 0,
279
+ balance: b.balance
280
+ }));
281
+ }
282
+ async function handleCheckBalance(args, deps2) {
283
+ const { asset_code, asset_issuer } = args;
284
+ const { stellarSecret, stellarNetwork, createHorizonServer } = deps2;
285
+ const keypair = Keypair.fromSecret(stellarSecret);
286
+ const horizonUrl = HORIZON_URLS[stellarNetwork];
287
+ const horizonServer = createHorizonServer ? createHorizonServer(horizonUrl) : new Horizon.Server(horizonUrl);
288
+ const account = await horizonServer.loadAccount(keypair.publicKey());
289
+ const balances = normalizeBalances(account.balances);
290
+ if (asset_code && asset_issuer) {
291
+ const balance = balances.find(
292
+ (b) => b.asset_code === asset_code && b.asset_issuer === asset_issuer
293
+ );
294
+ return ok({
295
+ asset: asset_code,
296
+ issuer: asset_issuer,
297
+ balance: balance ? balance.balance : "0",
298
+ account: keypair.publicKey()
299
+ });
300
+ }
301
+ if (asset_code) {
302
+ const balance = balances.find((b) => b.asset_code === asset_code);
303
+ return ok({
304
+ asset: asset_code,
305
+ balance: balance ? balance.balance : "0",
306
+ account: keypair.publicKey()
307
+ });
308
+ }
309
+ const nativeBalance = balances.find((b) => b.asset_type === "native");
310
+ return ok({
311
+ asset: "XLM",
312
+ balance: nativeBalance ? nativeBalance.balance : "0",
313
+ account: keypair.publicKey()
314
+ });
315
+ }
316
+ async function handleListProviders(args, deps2) {
317
+ const { tags, network } = args;
318
+ const { supabase: supabase2 } = deps2;
319
+ if (!supabase2) {
320
+ return err(
321
+ "SUPABASE_URL and SUPABASE_KEY environment variables are required for provider registry access"
322
+ );
323
+ }
324
+ let query = supabase2.from("providers").select("*");
325
+ if (network && query.eq) {
326
+ query = query.eq("network", network);
327
+ }
328
+ if (tags) {
329
+ const tagList = tags.split(",").map((t) => t.trim()).filter(Boolean);
330
+ if (tagList.length > 0 && query.overlaps) {
331
+ query = query.overlaps("tags", tagList);
332
+ }
333
+ }
334
+ const { data, error } = await query;
335
+ if (error) {
336
+ return err(`Failed to fetch providers: ${error.message}`);
337
+ }
338
+ const rows = data ?? [];
339
+ return ok({
340
+ success: true,
341
+ count: rows.length,
342
+ providers: rows.map((p) => ({
343
+ name: p.name,
344
+ description: p.description,
345
+ network: p.network,
346
+ asset: p.asset,
347
+ modes: p.modes,
348
+ tags: p.tags,
349
+ base_url: p.base_url
350
+ }))
351
+ });
352
+ }
353
+
354
+ // src/index.ts
355
+ var envPath = process.env.ROUTEDOCK_ENV_FILE;
356
+ if (envPath) {
357
+ loadDotenv({ path: envPath });
358
+ } else {
359
+ loadDotenv();
360
+ }
361
+ var STELLAR_SECRET = process.env.STELLAR_SECRET || process.env.ROUTEDOCK_WALLET_SECRET;
362
+ var STELLAR_NETWORK = process.env.STELLAR_NETWORK || "testnet";
363
+ var COMMITMENT_SECRET = process.env.COMMITMENT_SECRET;
364
+ var SUPABASE_URL = process.env.SUPABASE_URL;
365
+ var SUPABASE_KEY = process.env.SUPABASE_KEY || process.env.SUPABASE_SERVICE_KEY;
366
+ if (process.env.SUPABASE_SERVICE_KEY) {
367
+ console.warn("WARNING: Using SUPABASE_SERVICE_KEY bypasses RLS. Use an anon key for list_providers (anon + public_read_providers RLS is sufficient).");
368
+ }
369
+ if (!STELLAR_SECRET) {
370
+ console.error("Error: STELLAR_SECRET or ROUTEDOCK_WALLET_SECRET environment variable is required");
371
+ process.exit(1);
372
+ }
373
+ var ROUTEDOCK_DAILY_CAP = process.env.ROUTEDOCK_DAILY_CAP;
374
+ if (!ROUTEDOCK_DAILY_CAP) {
375
+ console.error("Error: ROUTEDOCK_DAILY_CAP environment variable is required to prevent unbounded spending");
376
+ process.exit(1);
377
+ }
378
+ var spendStorePath = process.env.ROUTEDOCK_SPEND_STORE_PATH || path.join(os.homedir(), ".routedock", "spend.json");
379
+ var client = new RouteDockClient({
380
+ wallet: STELLAR_SECRET,
381
+ network: STELLAR_NETWORK,
382
+ commitmentSecret: COMMITMENT_SECRET,
383
+ spendCap: { daily: ROUTEDOCK_DAILY_CAP, asset: "USDC" },
384
+ spendStore: new FileSpendStore(spendStorePath)
385
+ });
386
+ var supabase = null;
387
+ if (SUPABASE_URL && SUPABASE_KEY) {
388
+ supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
389
+ }
390
+ var openSessions = /* @__PURE__ */ new Map();
391
+ var deps = {
392
+ client,
393
+ openSessions,
394
+ supabase,
395
+ stellarSecret: STELLAR_SECRET,
396
+ stellarNetwork: STELLAR_NETWORK
397
+ };
398
+ var server = new Server(
399
+ {
400
+ name: "@routedock/mcp-server",
401
+ version: "0.1.0"
402
+ },
403
+ {
404
+ capabilities: {
405
+ tools: {}
406
+ }
407
+ }
408
+ );
409
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
410
+ return { tools: TOOLS };
411
+ });
412
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
413
+ const { name, arguments: args } = request.params;
414
+ try {
415
+ switch (name) {
416
+ case "pay_for_data":
417
+ return await handlePayForData(args, deps);
418
+ case "open_session":
419
+ return await handleOpenSession(args, deps, COMMITMENT_SECRET);
420
+ case "stream_session":
421
+ return await handleStreamSession(args, deps);
422
+ case "close_session":
423
+ return await handleCloseSession(args, deps);
424
+ case "check_balance":
425
+ return await handleCheckBalance(args, deps);
426
+ case "list_providers":
427
+ return await handleListProviders(args, deps);
428
+ default:
429
+ throw new Error(`Unknown tool: ${name}`);
430
+ }
431
+ } catch (error) {
432
+ return {
433
+ content: [
434
+ {
435
+ type: "text",
436
+ text: JSON.stringify({
437
+ success: false,
438
+ error: error instanceof Error ? error.message : String(error)
439
+ }, null, 2)
440
+ }
441
+ ],
442
+ isError: true
443
+ };
444
+ }
445
+ });
446
+ async function main() {
447
+ const transport = new StdioServerTransport();
448
+ await server.connect(transport);
449
+ console.error("@routedock/mcp-server running on stdio");
450
+ }
451
+ main().catch((error) => {
452
+ console.error("Server error:", error);
453
+ process.exit(1);
454
+ });
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@routedock/mcp-server",
3
+ "version": "0.2.0",
4
+ "description": "MCP server for RouteDock Stellar payments - exposes payment tools to LLM agents",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/winsznx/routedock.git",
9
+ "directory": "packages/mcp-server"
10
+ },
11
+ "type": "module",
12
+ "main": "dist/index.js",
13
+ "types": "dist/index.d.ts",
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "bin": {
19
+ "routedock-mcp-server": "dist/index.js"
20
+ },
21
+ "dependencies": {
22
+ "@modelcontextprotocol/sdk": "^1.0.4",
23
+ "@stellar/stellar-sdk": "^14.6.1",
24
+ "@supabase/supabase-js": "^2.47.0",
25
+ "dotenv": "^17.4.2",
26
+ "@routedock/routedock": "^0.2.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^20.0.0",
30
+ "tsup": "^8.0.0",
31
+ "tsx": "^4.22.3",
32
+ "typescript": "^5.4.5"
33
+ },
34
+ "scripts": {
35
+ "build": "tsup",
36
+ "dev": "tsup --watch",
37
+ "typecheck": "tsc --noEmit",
38
+ "test": "tsx --experimental-test-module-mocks --test \"src/**/*.test.ts\"",
39
+ "start": "node dist/index.js"
40
+ }
41
+ }