@routedock/mcp-server 0.2.1 → 0.2.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 (3) hide show
  1. package/README.md +16 -5
  2. package/dist/index.js +344 -34
  3. package/package.json +23 -3
package/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
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
4
 
5
+ ## Requirements
6
+
7
+ Node.js **22 or newer**, matching the supported runtime of all RouteDock npm packages.
8
+
5
9
  ## Overview
6
10
 
7
11
  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:
@@ -36,7 +40,7 @@ Create a `.env` file (e.g., `~/.routedock/.env`):
36
40
  ```bash
37
41
  # Required
38
42
  STELLAR_SECRET="SDU5..." # Use a dedicated low-balance testnet key for safety
39
- STELLAR_NETWORK="testnet" # or "mainnet"
43
+ STELLAR_NETWORK="testnet" # "testnet" (default) or "mainnet" (aliases: "pubnet", "public")
40
44
  ROUTEDOCK_DAILY_CAP="1.00" # Required: Maximum daily spend in USDC
41
45
 
42
46
  # Optional (for session mode)
@@ -56,7 +60,9 @@ Pay for a single data request from a RouteDock provider. Automatically selects t
56
60
 
57
61
  **Parameters:**
58
62
  - `url` (required): Full URL of the provider endpoint
59
- - `max_amount` (required): Maximum USDC amount to pay
63
+ - `max_amount` (required): Maximum USDC amount to pay, as a decimal string such as `"0.01"`: a positive
64
+ number with at most 7 decimal places and no currency symbol. Anything else (missing, `"$0.01"`, `"0"`,
65
+ a JSON number) is rejected before the provider is contacted.
60
66
  - `preferred_mode` (optional): Preferred payment mode (`x402`, `mpp-charge`, `mpp-session`)
61
67
 
62
68
  **Returns:** Payment result with mode, amount, transaction hash, and response data
@@ -69,10 +75,15 @@ check against the provider's `min_deposit`, not a fund transfer. Pass the return
69
75
  `stream_session` and `close_session` — the channel's collateral cannot be settled otherwise.
70
76
 
71
77
  **Parameters:**
72
- - `url` (required): Base URL of the provider
78
+ - `url` (required): The provider's streaming endpoint, e.g. `https://api-b.routedock.xyz/stream/orderbook`.
79
+ Vouchers and the closing `DELETE` are sent to this exact URL. A bare origin such as
80
+ `https://api-b.routedock.xyz` is also accepted: the server reads the provider's
81
+ `/.well-known/routedock.json` and uses `endpoints.stream`, or the only endpoint if there is just one.
82
+ The endpoint must be `GET` and on the same origin. If none qualifies, the call fails without opening
83
+ a session and lists the manifest's endpoints so you can retry with a full URL.
73
84
  - `initial_deposit` (optional): Amount in USDC you intend the channel to be funded with
74
85
 
75
- **Returns:** `channel_id` and open transaction hash
86
+ **Returns:** `channel_id`, open transaction hash, and `endpoint_url` (the URL the session streams from)
76
87
 
77
88
  ### stream_session
78
89
 
@@ -145,7 +156,7 @@ Once configured, you can ask Claude to:
145
156
  > "Check my Stellar balance"
146
157
  > "List providers that offer price data"
147
158
  > "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"
159
+ > "Open a session with https://api-b.routedock.xyz/stream/orderbook for streaming orderbook data"
149
160
  > "Pull the next few messages from that session"
150
161
  > "Close the session and settle the channel"
151
162
 
package/dist/index.js CHANGED
@@ -2,15 +2,14 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { config as loadDotenv } from "dotenv";
5
- import path from "path";
6
- import os from "os";
7
5
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
7
  import {
10
8
  CallToolRequestSchema,
11
9
  ListToolsRequestSchema
12
10
  } from "@modelcontextprotocol/sdk/types.js";
13
- import { RouteDockClient, FileSpendStore } from "@routedock/routedock";
11
+ import { RouteDockClient } from "@routedock/routedock";
12
+ import { FileSpendStore } from "@routedock/routedock/node";
14
13
  import { createClient } from "@supabase/supabase-js";
15
14
 
16
15
  // src/tools.ts
@@ -47,7 +46,7 @@ var TOOLS = [
47
46
  properties: {
48
47
  url: {
49
48
  type: "string",
50
- description: "The base URL of the provider (e.g., https://api-b.routedock.xyz)"
49
+ description: "The provider's streaming endpoint URL (e.g., https://api-b.routedock.xyz/stream/orderbook). Vouchers and the closing DELETE go to this exact URL. A bare origin (e.g., https://api-b.routedock.xyz) is also accepted and resolved to the provider's streaming endpoint from its /.well-known/routedock.json manifest."
51
50
  },
52
51
  initial_deposit: {
53
52
  type: "string",
@@ -131,6 +130,8 @@ var TOOLS = [
131
130
  ];
132
131
 
133
132
  // src/handlers.ts
133
+ import { usdcToStroops } from "@routedock/routedock";
134
+ import { HORIZON_URLS } from "@routedock/routedock/network";
134
135
  import { Keypair, Horizon } from "@stellar/stellar-sdk";
135
136
  function ok(payload) {
136
137
  return {
@@ -143,15 +144,30 @@ function err(message) {
143
144
  isError: true
144
145
  };
145
146
  }
147
+ function parseUsdc(amount) {
148
+ if (typeof amount !== "string") return null;
149
+ try {
150
+ return usdcToStroops(amount);
151
+ } catch {
152
+ return null;
153
+ }
154
+ }
146
155
  async function handlePayForData(args, deps2) {
147
156
  const { url, max_amount, preferred_mode } = args;
148
157
  const { client: client2 } = deps2;
158
+ const maxStroops = parseUsdc(max_amount);
159
+ if (maxStroops === null || maxStroops === 0n) {
160
+ return err(
161
+ `max_amount must be a positive USDC amount as a decimal string with at most 7 decimal places, e.g. "0.01" (received ${JSON.stringify(max_amount) ?? "nothing"})`
162
+ );
163
+ }
149
164
  const modeOptions = preferred_mode ? { forceMode: preferred_mode } : void 0;
150
165
  const estimate = await client2.estimateCost(url, modeOptions);
151
- if (estimate.amount === void 0 || isNaN(parseFloat(estimate.amount))) {
166
+ const priceStroops = parseUsdc(estimate.amount);
167
+ if (priceStroops === null) {
152
168
  return err("Provider returned an undefined or invalid price");
153
169
  }
154
- if (parseFloat(estimate.amount) > parseFloat(max_amount)) {
170
+ if (priceStroops > maxStroops) {
155
171
  return err(
156
172
  `Provider cost ${estimate.amount} ${estimate.asset} exceeds max_amount ${max_amount} USDC`
157
173
  );
@@ -166,18 +182,64 @@ async function handlePayForData(args, deps2) {
166
182
  data: result.data
167
183
  });
168
184
  }
185
+ function resolveSessionEndpoint(origin, manifest) {
186
+ const raw = manifest?.endpoints;
187
+ const endpoints = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
188
+ const entries = Object.entries(endpoints);
189
+ const listing = entries.length > 0 ? entries.map(([name2, e]) => `${name2}: ${String(e?.method)} ${String(e?.path)}`).join(", ") : "(none)";
190
+ const fail = (reason) => ({
191
+ error: `Could not resolve a streaming endpoint for ${origin}: ${reason}. Endpoints in its manifest: ${listing}. Call open_session again with the full endpoint URL (e.g. ${origin}/stream/orderbook).`
192
+ });
193
+ let chosen;
194
+ if (Object.hasOwn(endpoints, "stream")) {
195
+ chosen = ["stream", endpoints.stream];
196
+ } else if (entries.length === 1) {
197
+ chosen = entries[0];
198
+ }
199
+ if (!chosen) {
200
+ return fail(
201
+ entries.length === 0 ? "the manifest lists no endpoints" : `the manifest has no "stream" endpoint and ${entries.length} others to choose from`
202
+ );
203
+ }
204
+ const [name, endpoint] = chosen;
205
+ const method = typeof endpoint?.method === "string" ? endpoint.method.toUpperCase() : "";
206
+ if (method !== "GET") {
207
+ return fail(`endpoint "${name}" uses method ${String(endpoint?.method)}, but sessions stream with GET`);
208
+ }
209
+ if (typeof endpoint.path !== "string") {
210
+ return fail(`endpoint "${name}" has no path`);
211
+ }
212
+ let resolved;
213
+ try {
214
+ resolved = new URL(endpoint.path, origin);
215
+ } catch {
216
+ return fail(`endpoint "${name}" has an invalid path`);
217
+ }
218
+ if (resolved.origin !== origin) {
219
+ return fail(`endpoint "${name}" points outside ${origin}`);
220
+ }
221
+ return { url: resolved.href };
222
+ }
169
223
  async function handleOpenSession(args, deps2, commitmentSecret) {
170
224
  const { url, initial_deposit } = args;
171
225
  const { client: client2, openSessions: openSessions2, fetchManifest } = deps2;
172
226
  if (!commitmentSecret) {
173
227
  return err("COMMITMENT_SECRET environment variable is required for session mode");
174
228
  }
229
+ const parsed = new URL(url);
230
+ const origin = parsed.origin;
231
+ const isBareOrigin = parsed.pathname === "" || parsed.pathname === "/";
232
+ let cachedManifest;
233
+ const loadManifest = async () => {
234
+ if (cachedManifest === void 0) {
235
+ const fetcher = fetchManifest ?? ((u) => fetch(u));
236
+ const manifestResponse = await fetcher(`${origin}/.well-known/routedock.json`);
237
+ cachedManifest = await manifestResponse.json();
238
+ }
239
+ return cachedManifest;
240
+ };
175
241
  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();
242
+ const manifest = await loadManifest();
181
243
  const minDeposit = manifest?.pricing?.["mpp-session"]?.min_deposit ?? manifest?.pricing?.["mpp-session-ws"]?.min_deposit;
182
244
  if (minDeposit && parseFloat(initial_deposit) < parseFloat(minDeposit)) {
183
245
  return err(
@@ -185,12 +247,21 @@ async function handleOpenSession(args, deps2, commitmentSecret) {
185
247
  );
186
248
  }
187
249
  }
188
- const session = await client2.openSession(url);
250
+ let endpointUrl = url;
251
+ if (isBareOrigin) {
252
+ const resolution = resolveSessionEndpoint(origin, await loadManifest());
253
+ if ("error" in resolution) {
254
+ return err(resolution.error);
255
+ }
256
+ endpointUrl = resolution.url;
257
+ }
258
+ const session = await client2.openSession(endpointUrl);
189
259
  openSessions2.set(session.channelId, session);
190
260
  return ok({
191
261
  success: true,
192
262
  channel_id: session.channelId,
193
263
  open_tx_hash: session.openTxHash,
264
+ endpoint_url: endpointUrl,
194
265
  message: "Session opened successfully. Use stream_session to pull data and close_session to settle and release the channel collateral."
195
266
  });
196
267
  }
@@ -267,10 +338,6 @@ async function handleCloseSession(args, deps2) {
267
338
  vouchers_issued: result.vouchersIssued
268
339
  });
269
340
  }
270
- var HORIZON_URLS = {
271
- mainnet: "https://horizon.stellar.org",
272
- testnet: "https://horizon-testnet.stellar.org"
273
- };
274
341
  function normalizeBalances(balances) {
275
342
  return balances.map((b) => ({
276
343
  asset_type: b.asset_type,
@@ -351,6 +418,249 @@ async function handleListProviders(args, deps2) {
351
418
  });
352
419
  }
353
420
 
421
+ // src/env.ts
422
+ import path from "path";
423
+ import os from "os";
424
+
425
+ // package.json
426
+ var package_default = {
427
+ name: "@routedock/mcp-server",
428
+ version: "0.2.2",
429
+ description: "MCP server for RouteDock Stellar payments - exposes payment tools to LLM agents",
430
+ license: "MIT",
431
+ homepage: "https://github.com/winsznx/routedock/tree/main/packages/mcp-server#readme",
432
+ bugs: {
433
+ url: "https://github.com/winsznx/routedock/issues"
434
+ },
435
+ publishConfig: {
436
+ provenance: true
437
+ },
438
+ repository: {
439
+ type: "git",
440
+ url: "https://github.com/winsznx/routedock.git",
441
+ directory: "packages/mcp-server"
442
+ },
443
+ type: "module",
444
+ main: "dist/index.js",
445
+ types: "dist/index.d.ts",
446
+ files: [
447
+ "dist",
448
+ "README.md"
449
+ ],
450
+ bin: {
451
+ "routedock-mcp-server": "dist/index.js"
452
+ },
453
+ scripts: {
454
+ build: "tsup",
455
+ dev: "tsup --watch",
456
+ typecheck: "tsc --noEmit",
457
+ test: 'tsx --experimental-test-module-mocks --test "src/**/*.test.ts"',
458
+ start: "node dist/index.js"
459
+ },
460
+ dependencies: {
461
+ "@modelcontextprotocol/sdk": "^1.32.1",
462
+ "@routedock/routedock": "workspace:^",
463
+ "@stellar/stellar-sdk": "^14.6.1",
464
+ "@supabase/supabase-js": "^2.47.0",
465
+ dotenv: "^17.4.2"
466
+ },
467
+ devDependencies: {
468
+ "@types/node": "^20.0.0",
469
+ tsup: "^8.0.0",
470
+ tsx: "^4.22.3",
471
+ typescript: "^5.4.5"
472
+ },
473
+ engines: {
474
+ node: ">=22"
475
+ },
476
+ keywords: [
477
+ "stellar",
478
+ "soroban",
479
+ "usdc",
480
+ "x402",
481
+ "mpp",
482
+ "payments",
483
+ "ai-agents",
484
+ "mcp"
485
+ ]
486
+ };
487
+
488
+ // src/env.ts
489
+ var SERVER_NAME = "@routedock/mcp-server";
490
+ var SERVER_VERSION = package_default.version;
491
+ function validateStellarNetwork(networkInput, warn = console.warn) {
492
+ if (!networkInput || networkInput.trim() === "") {
493
+ return "testnet";
494
+ }
495
+ const normalized = networkInput.trim().toLowerCase();
496
+ if (normalized === "testnet") {
497
+ return "testnet";
498
+ }
499
+ if (normalized === "mainnet") {
500
+ return "mainnet";
501
+ }
502
+ if (normalized === "pubnet" || normalized === "public") {
503
+ warn(`WARNING: STELLAR_NETWORK='${networkInput}' is an alias for 'mainnet'. Using 'mainnet'.`);
504
+ return "mainnet";
505
+ }
506
+ throw new Error(
507
+ `Invalid STELLAR_NETWORK "${networkInput}". Allowed values: testnet, mainnet (or pubnet/public as alias for mainnet).`
508
+ );
509
+ }
510
+ function parseEnv(env = process.env, options) {
511
+ const warn = options?.warn ?? console.warn;
512
+ if (env.SUPABASE_SERVICE_KEY) {
513
+ warn(
514
+ "WARNING: Using SUPABASE_SERVICE_KEY bypasses RLS. Use an anon key for list_providers (anon + public_read_providers RLS is sufficient)."
515
+ );
516
+ }
517
+ const stellarSecret = env.STELLAR_SECRET || env.ROUTEDOCK_WALLET_SECRET;
518
+ if (!stellarSecret) {
519
+ throw new Error("STELLAR_SECRET or ROUTEDOCK_WALLET_SECRET environment variable is required");
520
+ }
521
+ const routedockDailyCap = env.ROUTEDOCK_DAILY_CAP;
522
+ if (!routedockDailyCap) {
523
+ throw new Error("ROUTEDOCK_DAILY_CAP environment variable is required to prevent unbounded spending");
524
+ }
525
+ const stellarNetwork = validateStellarNetwork(env.STELLAR_NETWORK, warn);
526
+ const commitmentSecret = env.COMMITMENT_SECRET;
527
+ const supabaseUrl = env.SUPABASE_URL;
528
+ const supabaseKey = env.SUPABASE_KEY || env.SUPABASE_SERVICE_KEY;
529
+ const spendStorePath2 = env.ROUTEDOCK_SPEND_STORE_PATH || path.join(os.homedir(), ".routedock", "spend.json");
530
+ return {
531
+ stellarSecret,
532
+ stellarNetwork,
533
+ commitmentSecret,
534
+ supabaseUrl,
535
+ supabaseKey,
536
+ routedockDailyCap,
537
+ spendStorePath: spendStorePath2
538
+ };
539
+ }
540
+
541
+ // src/shutdown.ts
542
+ var DEFAULT_SHUTDOWN_TIMEOUT_MS = 1e4;
543
+ async function closeAllSessions(openSessions2, options = {}) {
544
+ const timeoutMs = options.timeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
545
+ const logError = options.logError ?? console.error;
546
+ const logInfo = options.logInfo ?? console.error;
547
+ const entries = Array.from(openSessions2.entries());
548
+ if (entries.length === 0) {
549
+ return { closed: 0, failed: 0, timedOut: 0, results: [] };
550
+ }
551
+ logInfo(`[mcp-server] Closing ${entries.length} active session(s)...`);
552
+ const trackers = entries.map(([channelId, session]) => ({
553
+ channelId,
554
+ session,
555
+ status: "pending",
556
+ error: void 0
557
+ }));
558
+ const closePromises = trackers.map(async (tracker) => {
559
+ try {
560
+ await tracker.session.close();
561
+ tracker.status = "closed";
562
+ openSessions2.delete(tracker.channelId);
563
+ logInfo(`[mcp-server] Successfully closed session ${tracker.channelId}`);
564
+ } catch (err2) {
565
+ tracker.status = "error";
566
+ tracker.error = err2;
567
+ openSessions2.delete(tracker.channelId);
568
+ logError(`[mcp-server] Failed to close session ${tracker.channelId}:`, err2);
569
+ }
570
+ });
571
+ let timer;
572
+ const timeoutPromise = new Promise((resolve) => {
573
+ timer = setTimeout(() => resolve("TIMEOUT"), timeoutMs);
574
+ });
575
+ const allFinishedPromise = Promise.all(closePromises).then(() => "DONE");
576
+ const outcome = await Promise.race([allFinishedPromise, timeoutPromise]);
577
+ if (timer) {
578
+ clearTimeout(timer);
579
+ }
580
+ if (outcome === "TIMEOUT") {
581
+ for (const tracker of trackers) {
582
+ if (tracker.status === "pending") {
583
+ tracker.status = "timeout";
584
+ openSessions2.delete(tracker.channelId);
585
+ logError(
586
+ `[mcp-server] Timed out waiting to close session ${tracker.channelId} after ${timeoutMs}ms`
587
+ );
588
+ }
589
+ }
590
+ }
591
+ const results = trackers.map((t) => {
592
+ const status = t.status === "pending" ? "timeout" : t.status;
593
+ return {
594
+ channelId: t.channelId,
595
+ status,
596
+ error: t.error
597
+ };
598
+ });
599
+ const closed = results.filter((r) => r.status === "closed").length;
600
+ const failed = results.filter((r) => r.status === "error").length;
601
+ const timedOut = results.filter((r) => r.status === "timeout").length;
602
+ return { closed, failed, timedOut, results };
603
+ }
604
+ function setupShutdownHandlers(options) {
605
+ const {
606
+ openSessions: openSessions2,
607
+ transport,
608
+ stdin = process.stdin,
609
+ processObj = process,
610
+ timeoutMs = DEFAULT_SHUTDOWN_TIMEOUT_MS,
611
+ logError = console.error,
612
+ logInfo = console.error,
613
+ onExit = (code) => {
614
+ process.exit(code);
615
+ }
616
+ } = options;
617
+ let shutdownPromise = null;
618
+ const handleShutdown = (reason) => {
619
+ if (shutdownPromise) {
620
+ return shutdownPromise;
621
+ }
622
+ logInfo(`[mcp-server] Graceful shutdown initiated (${reason})`);
623
+ shutdownPromise = (async () => {
624
+ try {
625
+ const summary = await closeAllSessions(openSessions2, {
626
+ timeoutMs,
627
+ logError,
628
+ logInfo
629
+ });
630
+ const exitCode = summary.failed > 0 || summary.timedOut > 0 ? 1 : 0;
631
+ onExit(exitCode);
632
+ return summary;
633
+ } catch (err2) {
634
+ logError("[mcp-server] Unexpected error during shutdown:", err2);
635
+ onExit(1);
636
+ throw err2;
637
+ }
638
+ })();
639
+ return shutdownPromise;
640
+ };
641
+ processObj.once("SIGINT", () => {
642
+ void handleShutdown("SIGINT");
643
+ });
644
+ processObj.once("SIGTERM", () => {
645
+ void handleShutdown("SIGTERM");
646
+ });
647
+ if (stdin && typeof stdin.once === "function") {
648
+ stdin.once("end", () => {
649
+ void handleShutdown("stdin end");
650
+ });
651
+ }
652
+ if (transport) {
653
+ const prevOnClose = transport.onclose;
654
+ transport.onclose = () => {
655
+ if (typeof prevOnClose === "function") {
656
+ prevOnClose();
657
+ }
658
+ void handleShutdown("transport onclose");
659
+ };
660
+ }
661
+ return (reason = "manual") => handleShutdown(reason);
662
+ }
663
+
354
664
  // src/index.ts
355
665
  var envPath = process.env.ROUTEDOCK_ENV_FILE;
356
666
  if (envPath) {
@@ -358,24 +668,23 @@ if (envPath) {
358
668
  } else {
359
669
  loadDotenv();
360
670
  }
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");
671
+ var config;
672
+ try {
673
+ config = parseEnv(process.env);
674
+ } catch (error) {
675
+ const message = error instanceof Error ? error.message : String(error);
676
+ console.error(`Error: ${message}`);
371
677
  process.exit(1);
372
678
  }
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");
679
+ var {
680
+ stellarSecret: STELLAR_SECRET,
681
+ stellarNetwork: STELLAR_NETWORK,
682
+ commitmentSecret: COMMITMENT_SECRET,
683
+ supabaseUrl: SUPABASE_URL,
684
+ supabaseKey: SUPABASE_KEY,
685
+ routedockDailyCap: ROUTEDOCK_DAILY_CAP,
686
+ spendStorePath
687
+ } = config;
379
688
  var client = new RouteDockClient({
380
689
  wallet: STELLAR_SECRET,
381
690
  network: STELLAR_NETWORK,
@@ -397,8 +706,8 @@ var deps = {
397
706
  };
398
707
  var server = new Server(
399
708
  {
400
- name: "@routedock/mcp-server",
401
- version: "0.1.0"
709
+ name: SERVER_NAME,
710
+ version: SERVER_VERSION
402
711
  },
403
712
  {
404
713
  capabilities: {
@@ -445,6 +754,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
445
754
  });
446
755
  async function main() {
447
756
  const transport = new StdioServerTransport();
757
+ setupShutdownHandlers({ openSessions, transport });
448
758
  await server.connect(transport);
449
759
  console.error("@routedock/mcp-server running on stdio");
450
760
  }
package/package.json CHANGED
@@ -1,8 +1,15 @@
1
1
  {
2
2
  "name": "@routedock/mcp-server",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "MCP server for RouteDock Stellar payments - exposes payment tools to LLM agents",
5
5
  "license": "MIT",
6
+ "homepage": "https://github.com/winsznx/routedock/tree/main/packages/mcp-server#readme",
7
+ "bugs": {
8
+ "url": "https://github.com/winsznx/routedock/issues"
9
+ },
10
+ "publishConfig": {
11
+ "provenance": true
12
+ },
6
13
  "repository": {
7
14
  "type": "git",
8
15
  "url": "https://github.com/winsznx/routedock.git",
@@ -19,11 +26,11 @@
19
26
  "routedock-mcp-server": "dist/index.js"
20
27
  },
21
28
  "dependencies": {
22
- "@modelcontextprotocol/sdk": "^1.0.4",
29
+ "@modelcontextprotocol/sdk": "^1.32.1",
23
30
  "@stellar/stellar-sdk": "^14.6.1",
24
31
  "@supabase/supabase-js": "^2.47.0",
25
32
  "dotenv": "^17.4.2",
26
- "@routedock/routedock": "^0.3.0"
33
+ "@routedock/routedock": "^0.4.0"
27
34
  },
28
35
  "devDependencies": {
29
36
  "@types/node": "^20.0.0",
@@ -31,6 +38,19 @@
31
38
  "tsx": "^4.22.3",
32
39
  "typescript": "^5.4.5"
33
40
  },
41
+ "engines": {
42
+ "node": ">=22"
43
+ },
44
+ "keywords": [
45
+ "stellar",
46
+ "soroban",
47
+ "usdc",
48
+ "x402",
49
+ "mpp",
50
+ "payments",
51
+ "ai-agents",
52
+ "mcp"
53
+ ],
34
54
  "scripts": {
35
55
  "build": "tsup",
36
56
  "dev": "tsup --watch",