stables-mcp-server 2.0.0 → 2.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.
Files changed (49) hide show
  1. package/README.md +74 -16
  2. package/build/http/auth.d.ts +26 -0
  3. package/build/http/auth.d.ts.map +1 -0
  4. package/build/http/auth.js +47 -0
  5. package/build/http/auth.js.map +1 -0
  6. package/build/http/handler.d.ts +13 -0
  7. package/build/http/handler.d.ts.map +1 -0
  8. package/build/http/handler.js +102 -0
  9. package/build/http/handler.js.map +1 -0
  10. package/build/index.d.ts +5 -18
  11. package/build/index.d.ts.map +1 -1
  12. package/build/index.js +7 -43
  13. package/build/index.js.map +1 -1
  14. package/build/lib/stables-client.d.ts +98 -9
  15. package/build/lib/stables-client.d.ts.map +1 -1
  16. package/build/lib/stables-client.js +53 -0
  17. package/build/lib/stables-client.js.map +1 -1
  18. package/build/serve.d.ts +11 -0
  19. package/build/serve.d.ts.map +1 -0
  20. package/build/serve.js +71 -0
  21. package/build/serve.js.map +1 -0
  22. package/build/server.d.ts +12 -0
  23. package/build/server.d.ts.map +1 -0
  24. package/build/server.js +32 -0
  25. package/build/server.js.map +1 -0
  26. package/build/tools/quotes.js +1 -1
  27. package/build/tools/quotes.js.map +1 -1
  28. package/build/tools/sandbox.d.ts +11 -0
  29. package/build/tools/sandbox.d.ts.map +1 -0
  30. package/build/tools/sandbox.js +78 -0
  31. package/build/tools/sandbox.js.map +1 -0
  32. package/build/tools/transfers.d.ts.map +1 -1
  33. package/build/tools/transfers.js +1 -2
  34. package/build/tools/transfers.js.map +1 -1
  35. package/build/tools/virtual-accounts.d.ts.map +1 -1
  36. package/build/tools/virtual-accounts.js +95 -38
  37. package/build/tools/virtual-accounts.js.map +1 -1
  38. package/build/tools/webhooks.d.ts.map +1 -1
  39. package/build/tools/webhooks.js +61 -5
  40. package/build/tools/webhooks.js.map +1 -1
  41. package/build/worker.d.ts +11 -0
  42. package/build/worker.d.ts.map +1 -0
  43. package/build/worker.js +13 -0
  44. package/build/worker.js.map +1 -0
  45. package/package.json +12 -5
  46. package/build/tools/payment-methods.d.ts +0 -12
  47. package/build/tools/payment-methods.d.ts.map +0 -1
  48. package/build/tools/payment-methods.js +0 -119
  49. package/build/tools/payment-methods.js.map +0 -1
package/README.md CHANGED
@@ -10,7 +10,7 @@ MCP (Model Context Protocol) is an open standard that provides a standardized wa
10
10
 
11
11
  ## Features
12
12
 
13
- This MCP server provides 23 tools across 7 categories:
13
+ This MCP server provides 26 tools across 7 categories:
14
14
 
15
15
  ### Customer Management
16
16
  - `create_customer` - Create individual or business customers
@@ -33,10 +33,12 @@ This MCP server provides 23 tools across 7 categories:
33
33
  - `create_virtual_account` - Create virtual bank accounts for fiat deposits
34
34
  - `list_virtual_accounts` - List virtual accounts for a customer
35
35
  - `update_virtual_account` - Update virtual account settings
36
- - `get_virtual_account_history` - Get activity history for a virtual account
36
+ - `get_virtual_account_history` - Get deposits and their payouts for a payment route
37
+ - `update_route_destination` - Change the payout wallet on an existing route
37
38
 
38
- ### Payment Methods
39
- - `validate_payment_method` - Check payout details against a currency's rules before creating a quote or transfer
39
+ ### Sandbox
40
+ - `simulate_route_deposit` - Simulate a fiat deposit into a payment route (sandbox only)
41
+ - `simulate_transfer_deposit` - Simulate the inbound crypto an off-ramp transfer awaits (sandbox only)
40
42
 
41
43
  ### API Keys
42
44
  - `create_api_key` - Create a new API key
@@ -48,8 +50,56 @@ This MCP server provides 23 tools across 7 categories:
48
50
  - `create_webhook` - Subscribe to events via webhook
49
51
  - `list_webhooks` - List all webhook subscriptions
50
52
  - `delete_webhook` - Delete a webhook subscription
53
+ - `list_webhook_deliveries` - Recent delivery attempts, status codes and retry state
51
54
 
52
- ## Installation
55
+ ## Hosted server (recommended)
56
+
57
+ The server runs as a hosted service, so there is nothing to install. Point your MCP client at:
58
+
59
+ ```
60
+ https://mcp.stables.money/mcp
61
+ ```
62
+
63
+ and send your Stables API key as a bearer token. The key you get during onboarding is all you need: a `sti_test_…` key reaches sandbox, a `sti_live_…` key reaches production and moves real money. The hosted server keeps no credentials of its own; every request carries yours.
64
+
65
+ ### Claude Code
66
+
67
+ ```bash
68
+ claude mcp add --transport http stables https://mcp.stables.money/mcp --header "Authorization: Bearer ${STABLES_API_KEY}"
69
+ ```
70
+
71
+ ### Cursor, VS Code, Windsurf and other clients with a `url` field
72
+
73
+ ```json
74
+ {
75
+ "mcpServers": {
76
+ "stables": {
77
+ "url": "https://mcp.stables.money/mcp",
78
+ "headers": {
79
+ "Authorization": "Bearer sti_test_..."
80
+ }
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ ### Codex
87
+
88
+ ```toml
89
+ [mcp_servers.stables]
90
+ url = "https://mcp.stables.money/mcp"
91
+ bearer_token_env_var = "STABLES_API_KEY"
92
+ ```
93
+
94
+ ### Claude.ai and ChatGPT
95
+
96
+ Claude.ai custom connectors and ChatGPT connectors sign in with OAuth rather than a fixed key. OAuth sign-in for the hosted server is in progress; until it ships, use one of the clients above, or run the server locally as described below.
97
+
98
+ ## Running locally
99
+
100
+ The same server also runs as a local process over stdio, which is what `npx stables-mcp-server` does. Use this for a `sti_local_…` key against your own deployment, or when your client cannot reach the internet.
101
+
102
+ ### Installation
53
103
 
54
104
  ```bash
55
105
  # Install from npm
@@ -62,7 +112,7 @@ npm install
62
112
  npm run build
63
113
  ```
64
114
 
65
- ## Configuration
115
+ ### Configuration
66
116
 
67
117
  The server requires the following environment variables:
68
118
 
@@ -90,9 +140,9 @@ local deployment.
90
140
  **Start with a sandbox key.** An agent holding a live key can move real money on
91
141
  your behalf; see [agent safety](https://docs.stables.money/get-started/getting-started/quickstart/building-with-ai/agent-safety).
92
142
 
93
- ## Usage
143
+ ### Usage
94
144
 
95
- ### With Claude Desktop
145
+ #### With Claude Desktop
96
146
 
97
147
  Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
98
148
 
@@ -113,7 +163,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
113
163
 
114
164
  Then restart Claude Desktop.
115
165
 
116
- ### With Cursor, Codex, ChatGPT, or another MCP client
166
+ #### With Cursor, Codex, ChatGPT, or another MCP client
117
167
 
118
168
  Use the same command and environment variables in any MCP-compatible client:
119
169
 
@@ -161,7 +211,7 @@ STABLES_API_KEY=your-api-key node build/index.js
161
211
 
162
212
  **AI (using MCP tools):**
163
213
  1. Calls `create_customer` with email and type
164
- 2. Calls `create_quote` with USDC, amount, EUR destination, network, country, and payment method
214
+ 2. Calls `create_quote` with source USDT + network, destination EUR + country, and `destinationNetwork` (`swift` or `bank`)
165
215
  3. Returns customer details and quote information
166
216
 
167
217
  ### Checking transfer status
@@ -169,16 +219,24 @@ STABLES_API_KEY=your-api-key node build/index.js
169
219
  **User:** "What's the status of all my pending transfers?"
170
220
 
171
221
  **AI (using MCP tools):**
172
- 1. Calls `list_transfers` with `status=PENDING`
173
- 2. Returns formatted list of pending transfers
222
+ 1. Calls `list_transfers` with `status=created` or `status=in_progress` (statuses are lowercase)
223
+ 2. Returns a formatted list of in-flight transfers
174
224
 
175
225
  ### Setting up auto-payout
176
226
 
177
- **User:** "Create a virtual USD account for customer abc123 that auto-pays to my Polygon USDC wallet 0x..."
227
+ **User:** "Create a payment route for customer abc123 that pays AUD deposits out to my Polygon USDT wallet 0x..."
228
+
229
+ **AI (using MCP tools):**
230
+ 1. Calls `create_virtual_account` with the customer ID, AUD source currency, and the Polygon destination (the payout address is mandatory)
231
+ 2. Returns the deposit instructions to share with the customer
232
+
233
+ ### Paying out to a European beneficiary
234
+
235
+ **User:** "Pay 500 EUR to this German bank account"
178
236
 
179
237
  **AI (using MCP tools):**
180
- 1. Calls `create_virtual_account` with customer ID, USD currency, and Polygon destination
181
- 2. Returns virtual account details with deposit instructions
238
+ 1. Collects the extra beneficiary details EUR requires — `recipientType`, a full address, and `dateOfBirth` for individuals — before doing anything else
239
+ 2. Calls `create_quote`, then `create_transfer` once a human approves
182
240
 
183
241
  ## Development
184
242
 
@@ -218,7 +276,7 @@ stables-mcp-server/
218
276
  │ ├── virtual-accounts.ts # Virtual account tools (6)
219
277
  │ ├── api-keys.ts # API key tools (4)
220
278
  │ ├── webhooks.ts # Webhook tools (3)
221
- │ └── payment-methods.ts # Payment method validation (1)
279
+ │ └── sandbox.ts # Sandbox deposit simulation (2)
222
280
  ├── package.json
223
281
  ├── tsconfig.json
224
282
  ├── vitest.config.ts
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Per-request authentication for the hosted server.
3
+ *
4
+ * The hosted service holds no credentials of its own. Each request carries the
5
+ * caller's Stables API key, and the key alone decides which environment the
6
+ * request reaches: `sti_test_…` goes to sandbox, `sti_live_…` to production.
7
+ * That is the same rule the stdio server applies to STABLES_API_KEY, so a key
8
+ * issued during onboarding works here with no further setup.
9
+ */
10
+ import { StablesApiClient } from "../lib/stables-client.js";
11
+ export declare const BEARER_CHALLENGE: string;
12
+ export type AuthResult = {
13
+ ok: true;
14
+ client: StablesApiClient;
15
+ } | {
16
+ ok: false;
17
+ reason: string;
18
+ };
19
+ /**
20
+ * Read the API key off a request. The Authorization header is the standard
21
+ * place; X-Api-Key is accepted because the Stables API itself accepts it and
22
+ * some MCP clients only let users set that header.
23
+ */
24
+ export declare function extractApiKey(headers: Headers): string | null;
25
+ export declare function authenticate(headers: Headers): AuthResult;
26
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/http/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,gBAAgB,EAAgB,MAAM,0BAA0B,CAAC;AAE1E,eAAO,MAAM,gBAAgB,QAEiH,CAAC;AAE/I,MAAM,MAAM,UAAU,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhG;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAS7D;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAiBzD"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Per-request authentication for the hosted server.
3
+ *
4
+ * The hosted service holds no credentials of its own. Each request carries the
5
+ * caller's Stables API key, and the key alone decides which environment the
6
+ * request reaches: `sti_test_…` goes to sandbox, `sti_live_…` to production.
7
+ * That is the same rule the stdio server applies to STABLES_API_KEY, so a key
8
+ * issued during onboarding works here with no further setup.
9
+ */
10
+ import { StablesApiClient, apiUrlForKey } from "../lib/stables-client.js";
11
+ export const BEARER_CHALLENGE = 'Bearer realm="stables", error="invalid_token", ' +
12
+ 'error_description="Send your Stables API key as a bearer token: Authorization: Bearer sti_test_... (sandbox) or sti_live_... (production)"';
13
+ /**
14
+ * Read the API key off a request. The Authorization header is the standard
15
+ * place; X-Api-Key is accepted because the Stables API itself accepts it and
16
+ * some MCP clients only let users set that header.
17
+ */
18
+ export function extractApiKey(headers) {
19
+ const authorization = headers.get("authorization");
20
+ if (authorization) {
21
+ const match = authorization.match(/^Bearer\s+(.+)$/i);
22
+ if (match)
23
+ return match[1].trim();
24
+ }
25
+ const apiKeyHeader = headers.get("x-api-key");
26
+ if (apiKeyHeader)
27
+ return apiKeyHeader.trim();
28
+ return null;
29
+ }
30
+ export function authenticate(headers) {
31
+ const apiKey = extractApiKey(headers);
32
+ if (!apiKey) {
33
+ return { ok: false, reason: "Missing API key" };
34
+ }
35
+ const baseUrl = apiUrlForKey(apiKey);
36
+ if (!baseUrl) {
37
+ // A `sti_local_…` key or an unrecognised shape has no environment we can
38
+ // route to. The stdio server lets STABLES_API_URL resolve that; the hosted
39
+ // one has no such channel, and guessing production would be wrong.
40
+ return {
41
+ ok: false,
42
+ reason: "Unrecognised API key. The hosted server accepts sti_test_… (sandbox) and sti_live_… (production) keys; for a local deployment run the server locally with STABLES_API_URL",
43
+ };
44
+ }
45
+ return { ok: true, client: new StablesApiClient(apiKey, baseUrl) };
46
+ }
47
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/http/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE1E,MAAM,CAAC,MAAM,gBAAgB,GAC3B,iDAAiD;IACjD,4IAA4I,CAAC;AAI/I;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACnD,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;QACtD,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,CAAC;IACD,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC9C,IAAI,YAAY;QAAE,OAAO,YAAY,CAAC,IAAI,EAAE,CAAC;IAC7C,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAClD,CAAC;IACD,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,yEAAyE;QACzE,2EAA2E;QAC3E,mEAAmE;QACnE,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EACJ,2KAA2K;SAC9K,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;AACrE,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Web-standard HTTP handler for the hosted Stables MCP server.
3
+ *
4
+ * Works anywhere a `fetch`-style `Request` → `Response` function runs: a
5
+ * Cloudflare Worker, a Node process (see serve.ts), or any other runtime with
6
+ * the web platform APIs. The server is stateless: each request authenticates
7
+ * from its own headers and gets a fresh MCP server bound to that key, so
8
+ * nothing about one caller survives to the next and no session store is
9
+ * needed.
10
+ */
11
+ export declare const MCP_PATH = "/mcp";
12
+ export declare function handleRequest(request: Request): Promise<Response>;
13
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/http/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,eAAO,MAAM,QAAQ,SAAS,CAAC;AAgF/B,wBAAsB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyBvE"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Web-standard HTTP handler for the hosted Stables MCP server.
3
+ *
4
+ * Works anywhere a `fetch`-style `Request` → `Response` function runs: a
5
+ * Cloudflare Worker, a Node process (see serve.ts), or any other runtime with
6
+ * the web platform APIs. The server is stateless: each request authenticates
7
+ * from its own headers and gets a fresh MCP server bound to that key, so
8
+ * nothing about one caller survives to the next and no session store is
9
+ * needed.
10
+ */
11
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
12
+ import { SERVER_NAME, SERVER_VERSION, createStablesMcpServer } from "../server.js";
13
+ import { BEARER_CHALLENGE, authenticate } from "./auth.js";
14
+ export const MCP_PATH = "/mcp";
15
+ const CORS_HEADERS = {
16
+ "access-control-allow-origin": "*",
17
+ "access-control-allow-methods": "GET, POST, DELETE, OPTIONS",
18
+ "access-control-allow-headers": "authorization, x-api-key, content-type, accept, mcp-session-id, mcp-protocol-version, last-event-id",
19
+ "access-control-expose-headers": "mcp-session-id, mcp-protocol-version",
20
+ "access-control-max-age": "86400",
21
+ };
22
+ function withCors(response) {
23
+ const headers = new Headers(response.headers);
24
+ for (const [name, value] of Object.entries(CORS_HEADERS))
25
+ headers.set(name, value);
26
+ return new Response(response.body, { status: response.status, headers });
27
+ }
28
+ function json(body, status = 200, extraHeaders = {}) {
29
+ return new Response(JSON.stringify(body, null, 2), {
30
+ status,
31
+ headers: { "content-type": "application/json", ...extraHeaders },
32
+ });
33
+ }
34
+ function unauthorized(reason) {
35
+ return json({
36
+ error: "unauthorized",
37
+ message: reason,
38
+ hint: "Authorization: Bearer <your Stables API key>. A sti_test_ key reaches sandbox, a sti_live_ key reaches production and moves real money.",
39
+ docs: "https://docs.stables.money/get-started/getting-started/quickstart/building-with-ai",
40
+ }, 401, { "www-authenticate": BEARER_CHALLENGE });
41
+ }
42
+ function describe(origin) {
43
+ return json({
44
+ name: SERVER_NAME,
45
+ version: SERVER_VERSION,
46
+ description: "Hosted MCP server for the Stables fiat-to-crypto API. Connect any MCP client to the endpoint below with your Stables API key as a bearer token.",
47
+ endpoint: `${origin}${MCP_PATH}`,
48
+ transport: "streamable-http",
49
+ authentication: {
50
+ type: "bearer",
51
+ header: "Authorization: Bearer <api key>",
52
+ environments: {
53
+ "sti_test_…": "sandbox (https://api.sandbox.stables.money)",
54
+ "sti_live_…": "production (https://api.stables.money)",
55
+ },
56
+ },
57
+ docs: "https://docs.stables.money/get-started/getting-started/quickstart/building-with-ai",
58
+ source: "https://github.com/stables-money/mcp-server",
59
+ });
60
+ }
61
+ async function handleMcp(request) {
62
+ const auth = authenticate(request.headers);
63
+ if (!auth.ok)
64
+ return unauthorized(auth.reason);
65
+ const server = createStablesMcpServer(auth.client);
66
+ const transport = new WebStandardStreamableHTTPServerTransport({
67
+ // Stateless: no session ids, so any instance (or any edge location) can
68
+ // answer any request, and a client never has to re-initialise after a
69
+ // deploy. Each tool call is one round trip to the Stables API anyway.
70
+ sessionIdGenerator: undefined,
71
+ // Plain JSON bodies rather than SSE frames: nothing here streams partial
72
+ // results, and JSON is the easier contract for clients and for debugging.
73
+ enableJsonResponse: true,
74
+ });
75
+ await server.connect(transport);
76
+ try {
77
+ return await transport.handleRequest(request);
78
+ }
79
+ finally {
80
+ await transport.close();
81
+ }
82
+ }
83
+ export async function handleRequest(request) {
84
+ const url = new URL(request.url);
85
+ if (request.method === "OPTIONS") {
86
+ return withCors(new Response(null, { status: 204 }));
87
+ }
88
+ if (url.pathname === "/" || url.pathname === "") {
89
+ return withCors(describe(url.origin));
90
+ }
91
+ if (url.pathname === "/health") {
92
+ return withCors(json({ status: "ok", version: SERVER_VERSION }));
93
+ }
94
+ if (url.pathname === MCP_PATH || url.pathname === `${MCP_PATH}/`) {
95
+ return withCors(await handleMcp(request));
96
+ }
97
+ return withCors(json({
98
+ error: "not_found",
99
+ message: `Nothing at ${url.pathname}. The MCP endpoint is ${MCP_PATH}.`,
100
+ }, 404));
101
+ }
102
+ //# sourceMappingURL=handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/http/handler.ts"],"names":[],"mappings":"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"}
package/build/index.d.ts CHANGED
@@ -1,27 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Stables MCP Server
3
+ * Stables MCP Server — stdio entry point.
4
4
  *
5
- * This server exposes the Stables fiat-to-crypto API to AI agents via the
6
- * Model Context Protocol (MCP). It enables AI assistants to manage customers,
7
- * create quotes, execute transfers, and handle virtual accounts.
5
+ * This is what `npx stables-mcp-server` runs: a local process that speaks MCP
6
+ * over stdin/stdout and authenticates to the Stables API with the key in
7
+ * STABLES_API_KEY. The hosted service at https://mcp.stables.money/mcp exposes
8
+ * the same tools over HTTP without installing anything; see README.md.
8
9
  *
9
10
  * Usage:
10
11
  * STABLES_API_KEY=your-key node build/index.js
11
- *
12
- * For Claude Desktop, add to ~/Library/Application Support/Claude/claude_desktop_config.json:
13
- * {
14
- * "mcpServers": {
15
- * "stables": {
16
- * "command": "node",
17
- * "args": ["/path/to/stables-mcp-server/build/index.js"],
18
- * "env": {
19
- * "STABLES_API_KEY": "your-api-key",
20
- * "STABLES_API_URL": "https://api.stables.money"
21
- * }
22
- * }
23
- * }
24
- * }
25
12
  */
26
13
  export {};
27
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;GAuBG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG"}
package/build/index.js CHANGED
@@ -1,38 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Stables MCP Server
3
+ * Stables MCP Server — stdio entry point.
4
4
  *
5
- * This server exposes the Stables fiat-to-crypto API to AI agents via the
6
- * Model Context Protocol (MCP). It enables AI assistants to manage customers,
7
- * create quotes, execute transfers, and handle virtual accounts.
5
+ * This is what `npx stables-mcp-server` runs: a local process that speaks MCP
6
+ * over stdin/stdout and authenticates to the Stables API with the key in
7
+ * STABLES_API_KEY. The hosted service at https://mcp.stables.money/mcp exposes
8
+ * the same tools over HTTP without installing anything; see README.md.
8
9
  *
9
10
  * Usage:
10
11
  * STABLES_API_KEY=your-key node build/index.js
11
- *
12
- * For Claude Desktop, add to ~/Library/Application Support/Claude/claude_desktop_config.json:
13
- * {
14
- * "mcpServers": {
15
- * "stables": {
16
- * "command": "node",
17
- * "args": ["/path/to/stables-mcp-server/build/index.js"],
18
- * "env": {
19
- * "STABLES_API_KEY": "your-api-key",
20
- * "STABLES_API_URL": "https://api.stables.money"
21
- * }
22
- * }
23
- * }
24
- * }
25
12
  */
26
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
27
13
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
28
14
  import { createStablesClient } from "./lib/stables-client.js";
29
- import { registerCustomerTools } from "./tools/customers.js";
30
- import { registerQuoteTools } from "./tools/quotes.js";
31
- import { registerTransferTools } from "./tools/transfers.js";
32
- import { registerVirtualAccountTools } from "./tools/virtual-accounts.js";
33
- import { registerApiKeyTools } from "./tools/api-keys.js";
34
- import { registerWebhookTools } from "./tools/webhooks.js";
35
- import { registerPaymentMethodTools } from "./tools/payment-methods.js";
15
+ import { createStablesMcpServer } from "./server.js";
36
16
  // Validate environment
37
17
  const apiKey = process.env.STABLES_API_KEY;
38
18
  if (!apiKey) {
@@ -40,24 +20,8 @@ if (!apiKey) {
40
20
  console.error("Error: STABLES_API_KEY environment variable is required");
41
21
  process.exit(1);
42
22
  }
43
- // Create the MCP server
44
- const server = new McpServer({
45
- name: "stables-mcp-server",
46
- version: "2.0.0",
47
- description: "Stables fiat-to-crypto API for AI agents - manage customers, quotes, transfers, and virtual accounts",
48
- });
49
- // Create the Stables API client
50
- const stablesClient = createStablesClient();
51
- // Register all tools
52
- registerCustomerTools(server, stablesClient);
53
- registerQuoteTools(server, stablesClient);
54
- registerTransferTools(server, stablesClient);
55
- registerVirtualAccountTools(server, stablesClient);
56
- registerApiKeyTools(server, stablesClient);
57
- registerWebhookTools(server, stablesClient);
58
- registerPaymentMethodTools(server, stablesClient);
59
- // Start the server with STDIO transport
60
23
  async function main() {
24
+ const server = createStablesMcpServer(createStablesClient());
61
25
  const transport = new StdioServerTransport();
62
26
  await server.connect(transport);
63
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAExE,uBAAuB;AACvB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;AAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;IACZ,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,yDAAyD,CAAC,CAAC;IACzE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,wBAAwB;AACxB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,oBAAoB;IAC1B,OAAO,EAAE,OAAO;IAChB,WAAW,EACT,sGAAsG;CACzG,CAAC,CAAC;AAEH,gCAAgC;AAChC,MAAM,aAAa,GAAG,mBAAmB,EAAE,CAAC;AAE5C,qBAAqB;AACrB,qBAAqB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAC7C,kBAAkB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAC1C,qBAAqB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAC7C,2BAA2B,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AACnD,mBAAmB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAC3C,oBAAoB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAC5C,0BAA0B,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAElD,wCAAwC;AACxC,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;IAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAErD,uBAAuB;AACvB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;AAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;IACZ,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,yDAAyD,CAAC,CAAC;IACzE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,sBAAsB,CAAC,mBAAmB,EAAE,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;IAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -279,27 +279,94 @@ export interface VirtualAccount {
279
279
  last_deposit_at: string | null;
280
280
  };
281
281
  }
282
+ /**
283
+ * `deposit_handling_mode` is deliberately absent — it is not part of the create
284
+ * schema (the server sets it, defaulting to auto_payout) and was being silently
285
+ * ignored. Change it afterwards with updateVirtualAccount.
286
+ */
282
287
  export interface CreateVirtualAccountRequest {
283
288
  source: {
284
289
  currency: string;
285
290
  };
286
- deposit_handling_mode?: DepositHandlingMode;
291
+ /** Required for fiat_to_crypto, which is the default. */
287
292
  destination?: VirtualAccountDestination;
293
+ workflow_type?: "fiat_to_crypto" | "fiat_to_fiat";
294
+ developer_fee_percent?: string;
288
295
  metadata?: Record<string, string>;
289
296
  }
290
297
  export interface ListVirtualAccountsResponse {
291
298
  count: number;
292
299
  data: VirtualAccount[];
293
300
  }
301
+ /**
302
+ * One deposit and its payout. The client previously described these as
303
+ * `{type, amount, currency}`, none of which exist, so every row rendered as
304
+ * "undefined: undefined undefined".
305
+ */
294
306
  export interface VirtualAccountHistoryEvent {
295
307
  id: string;
296
- type: string;
297
- customer_id: string;
298
308
  virtual_account_id: string;
299
- amount: string;
300
- currency: string;
309
+ deposit_amount?: string;
310
+ deposit_currency?: string;
301
311
  deposit_id?: string;
312
+ sender_name?: string;
313
+ sender_reference?: string;
314
+ handling_mode?: string;
315
+ payout_status?: string;
316
+ payout_amount?: string;
317
+ payout_currency?: string;
318
+ payout_network?: string;
319
+ payout_address?: string;
320
+ payout_transaction_hash?: string;
321
+ payout_completed_at?: string;
322
+ platform_fee_amount?: string;
323
+ platform_fee_currency?: string;
324
+ deposited_at?: string;
325
+ created_at?: string;
326
+ }
327
+ /** The envelope is `{data, has_more}` — there is no `count`. */
328
+ export interface VirtualAccountHistoryResponse {
329
+ data: VirtualAccountHistoryEvent[];
330
+ has_more?: boolean;
331
+ }
332
+ /**
333
+ * A request for information: what a customer must supply before a held
334
+ * verification or payout can proceed. Compliance can raise one at any time, so
335
+ * an integration that never reads them will stall without knowing why.
336
+ */
337
+ export interface Rfi {
338
+ rfi_id: string;
339
+ type: string;
340
+ status: string;
341
+ customer_id: string;
342
+ kyc_level?: string;
343
+ reasons?: unknown[];
344
+ requirements?: unknown[];
302
345
  created_at: string;
346
+ expires_at?: string;
347
+ resolved_at?: string;
348
+ }
349
+ export interface WebhookDelivery {
350
+ deliveryId: string;
351
+ subscriptionId?: string | null;
352
+ subscriptionName?: string | null;
353
+ subscriptionUrl?: string | null;
354
+ eventType: string;
355
+ status: "PENDING" | "SUCCESS" | "FAILED" | "RETRYING";
356
+ attemptCount: number;
357
+ lastAttemptAt?: string | null;
358
+ nextRetryAt?: string | null;
359
+ responseCode?: number | null;
360
+ createdAt?: string | null;
361
+ }
362
+ export interface SandboxDepositRequest {
363
+ amount: string;
364
+ scenario?: "create_only" | "completed" | "failed";
365
+ external_deposit_id?: string;
366
+ sender_name?: string;
367
+ sender_reference?: string;
368
+ failure_code?: string;
369
+ failure_message?: string;
303
370
  }
304
371
  export type QuoteStatus = "active" | "expired" | "used" | "cancelled" | "preview";
305
372
  /**
@@ -423,10 +490,7 @@ export declare class StablesApiClient {
423
490
  depositId?: string;
424
491
  startingAfter?: string;
425
492
  endingBefore?: string;
426
- }): Promise<{
427
- count: number;
428
- data: VirtualAccountHistoryEvent[];
429
- }>;
493
+ }): Promise<VirtualAccountHistoryResponse>;
430
494
  listTransfers(params?: {
431
495
  status?: string;
432
496
  type?: string;
@@ -455,6 +519,31 @@ export declare class StablesApiClient {
455
519
  * discover what a corridor demands before spending a short-lived quote.
456
520
  */
457
521
  validatePaymentMethod(network: string, destination: BankTransferDestination): Promise<ValidatePaymentMethodResponse>;
522
+ /** Replace the payout wallet on an existing payment route. */
523
+ updateVirtualAccountDestination(customerId: string, virtualAccountId: string, destination: VirtualAccountDestination): Promise<VirtualAccount>;
524
+ /** Compliance requests for information against a customer. */
525
+ listCustomerRfis(customerId: string): Promise<{
526
+ rfis: Rfi[];
527
+ }>;
528
+ getRfi(rfiId: string): Promise<Rfi>;
529
+ /** What verification the customer still owes. */
530
+ getKycCapabilities(customerId: string): Promise<Record<string, unknown>>;
531
+ listAvailableEntitlements(customerId: string): Promise<Record<string, unknown>>;
532
+ /**
533
+ * Sandbox only. Drives a deposit through the route or transfer so an agent can
534
+ * reach a terminal state in test, which is otherwise impossible without a real
535
+ * bank payment.
536
+ */
537
+ simulateVirtualAccountDeposit(customerId: string, virtualAccountId: string, data: SandboxDepositRequest): Promise<Record<string, unknown>>;
538
+ simulateTransferDeposit(transferId: string, data?: Record<string, unknown>): Promise<Record<string, unknown>>;
539
+ /** Recent webhook delivery attempts — the first place to look when events go missing. */
540
+ listWebhookDeliveries(params?: {
541
+ pageSize?: number;
542
+ status?: string;
543
+ eventType?: string;
544
+ }): Promise<{
545
+ deliveries: WebhookDelivery[];
546
+ }>;
458
547
  listWebhooks(): Promise<{
459
548
  subscriptions: WebhookSubscription[];
460
549
  }>;