stables-mcp-server 2.3.0 → 2.5.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 (54) hide show
  1. package/README.md +46 -5
  2. package/build/http/handler.d.ts +9 -0
  3. package/build/http/handler.d.ts.map +1 -1
  4. package/build/http/handler.js +22 -8
  5. package/build/http/handler.js.map +1 -1
  6. package/build/lib/stables-client.d.ts +161 -9
  7. package/build/lib/stables-client.d.ts.map +1 -1
  8. package/build/lib/stables-client.js +85 -5
  9. package/build/lib/stables-client.js.map +1 -1
  10. package/build/oauth/auth0.d.ts +76 -0
  11. package/build/oauth/auth0.d.ts.map +1 -0
  12. package/build/oauth/auth0.js +163 -0
  13. package/build/oauth/auth0.js.map +1 -0
  14. package/build/oauth/handlers.d.ts +11 -0
  15. package/build/oauth/handlers.d.ts.map +1 -0
  16. package/build/oauth/handlers.js +195 -0
  17. package/build/oauth/handlers.js.map +1 -0
  18. package/build/oauth/pages.d.ts +10 -0
  19. package/build/oauth/pages.d.ts.map +1 -0
  20. package/build/oauth/pages.js +80 -0
  21. package/build/oauth/pages.js.map +1 -0
  22. package/build/oauth/provider.d.ts +21 -0
  23. package/build/oauth/provider.d.ts.map +1 -0
  24. package/build/oauth/provider.js +95 -0
  25. package/build/oauth/provider.js.map +1 -0
  26. package/build/oauth/types.d.ts +41 -0
  27. package/build/oauth/types.d.ts.map +1 -0
  28. package/build/oauth/types.js +14 -0
  29. package/build/oauth/types.js.map +1 -0
  30. package/build/server.d.ts.map +1 -1
  31. package/build/server.js +4 -0
  32. package/build/server.js.map +1 -1
  33. package/build/tools/compliance.d.ts +13 -0
  34. package/build/tools/compliance.d.ts.map +1 -0
  35. package/build/tools/compliance.js +170 -0
  36. package/build/tools/compliance.js.map +1 -0
  37. package/build/tools/documents.d.ts +12 -0
  38. package/build/tools/documents.d.ts.map +1 -0
  39. package/build/tools/documents.js +94 -0
  40. package/build/tools/documents.js.map +1 -0
  41. package/build/tools/virtual-accounts.d.ts.map +1 -1
  42. package/build/tools/virtual-accounts.js +32 -0
  43. package/build/tools/virtual-accounts.js.map +1 -1
  44. package/build/tools/webhooks.d.ts +8 -0
  45. package/build/tools/webhooks.d.ts.map +1 -1
  46. package/build/tools/webhooks.js +15 -2
  47. package/build/tools/webhooks.js.map +1 -1
  48. package/build/version.d.ts +1 -1
  49. package/build/version.js +1 -1
  50. package/build/worker.d.ts +20 -7
  51. package/build/worker.d.ts.map +1 -1
  52. package/build/worker.js +35 -7
  53. package/build/worker.js.map +1 -1
  54. package/package.json +4 -2
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The two pages the OAuth flow shows in a browser: consent and error.
3
+ * Everything that came from the client (its name, domain, scopes) is
4
+ * escaped, since a dynamically registered client chooses those strings.
5
+ */
6
+ export function escapeHtml(value) {
7
+ return value.replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`);
8
+ }
9
+ const STYLE = `
10
+ :root { color-scheme: light; }
11
+ body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: #f4f3f8;
12
+ font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, sans-serif; color: #17151f; }
13
+ main { width: min(440px, calc(100vw - 32px)); background: #fff; border-radius: 16px;
14
+ padding: 32px 28px; box-shadow: 0 8px 32px rgba(23, 21, 31, 0.08); }
15
+ h1 { font-size: 22px; margin: 0 0 8px; }
16
+ p { margin: 0 0 14px; color: #4a4759; }
17
+ .brand { font-weight: 700; letter-spacing: 0.02em; color: #5b3df5; margin-bottom: 18px; font-size: 14px; text-transform: uppercase; }
18
+ .warn { background: #fff7e6; border: 1px solid #f5d58b; border-radius: 10px; padding: 10px 12px; color: #6b4b00; }
19
+ fieldset { border: 1px solid #e3e1ec; border-radius: 12px; padding: 12px 14px; margin: 18px 0; }
20
+ legend { padding: 0 6px; font-weight: 600; font-size: 14px; }
21
+ label.env { display: flex; gap: 10px; align-items: flex-start; padding: 8px 0; cursor: pointer; }
22
+ label.env input { margin-top: 5px; }
23
+ label.env small { display: block; color: #6c6880; }
24
+ .actions { display: flex; gap: 10px; margin-top: 8px; }
25
+ button { flex: 1; padding: 12px 16px; border-radius: 10px; border: 1px solid #d9d6e6; font: inherit; font-weight: 600; cursor: pointer; background: #fff; }
26
+ button.primary { background: #5b3df5; border-color: #5b3df5; color: #fff; }
27
+ code { background: #f1f0f7; padding: 1px 6px; border-radius: 6px; font-size: 14px; }
28
+ `;
29
+ function layout(title, body) {
30
+ return `<!doctype html>
31
+ <html lang="en">
32
+ <head>
33
+ <meta charset="utf-8">
34
+ <meta name="viewport" content="width=device-width, initial-scale=1">
35
+ <meta name="robots" content="noindex">
36
+ <title>${escapeHtml(title)}</title>
37
+ <style>${STYLE}</style>
38
+ </head>
39
+ <body>
40
+ <main>
41
+ <div class="brand">Stables</div>
42
+ ${body}
43
+ </main>
44
+ </body>
45
+ </html>`;
46
+ }
47
+ export function consentPage(details, handle) {
48
+ const name = escapeHtml(details.clientName);
49
+ const origin = details.clientDomain
50
+ ? `Published by <strong>${escapeHtml(details.clientDomain)}</strong>.`
51
+ : "This app registered itself, so its name is not verified.";
52
+ const loopback = details.redirectIsLoopback
53
+ ? `<p class="warn"><strong>This sends access to an app on your computer.</strong> Continue only if you just started connecting from it.</p>`
54
+ : "";
55
+ return layout(`Connect ${details.clientName} to Stables`, `<h1>Connect ${name} to Stables?</h1>
56
+ <p>${origin} Access will be sent to <strong>${escapeHtml(details.redirectHost)}</strong>.</p>
57
+ ${loopback}
58
+ <p>You'll sign in with your Stables dashboard account. Allowing creates an API key named after ${name} in the organisation you choose; revoke it any time under Developer → API keys.</p>
59
+ <form method="post">
60
+ <input type="hidden" name="handle" value="${escapeHtml(handle)}">
61
+ <fieldset>
62
+ <legend>Environment</legend>
63
+ <label class="env"><input type="radio" name="environment" value="sandbox" checked>
64
+ <span>Sandbox<small>Test data, no real money. Keys start with <code>sti_test_</code>.</small></span></label>
65
+ <label class="env"><input type="radio" name="environment" value="production">
66
+ <span>Production<small>Live organisation. The agent can move real money.</small></span></label>
67
+ </fieldset>
68
+ <div class="actions">
69
+ <button type="submit" name="decision" value="deny">Cancel</button>
70
+ <button type="submit" name="decision" value="approve" class="primary">Allow and sign in</button>
71
+ </div>
72
+ </form>`);
73
+ }
74
+ export function errorPage(title, message, hint) {
75
+ return layout(title, `<h1>${escapeHtml(title)}</h1>
76
+ <p>${escapeHtml(message)}</p>
77
+ ${hint ? `<p>${escapeHtml(hint)}</p>` : ""}
78
+ <p>Go back to the app that sent you here and try connecting again.</p>`);
79
+ }
80
+ //# sourceMappingURL=pages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.js","sourceRoot":"","sources":["../../src/oauth/pages.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;CAmBb,CAAC;AAEF,SAAS,MAAM,CAAC,KAAa,EAAE,IAAY;IACzC,OAAO;;;;;;SAMA,UAAU,CAAC,KAAK,CAAC;SACjB,KAAK;;;;;EAKZ,IAAI;;;QAGE,CAAC;AACT,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAA2B,EAAE,MAAc;IACrE,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY;QACjC,CAAC,CAAC,wBAAwB,UAAU,CAAC,OAAO,CAAC,YAAY,CAAC,YAAY;QACtE,CAAC,CAAC,0DAA0D,CAAC;IAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,kBAAkB;QACzC,CAAC,CAAC,0IAA0I;QAC5I,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,MAAM,CACX,WAAW,OAAO,CAAC,UAAU,aAAa,EAC1C,eAAe,IAAI;KAClB,MAAM,mCAAmC,UAAU,CAAC,OAAO,CAAC,YAAY,CAAC;EAC5E,QAAQ;iGACuF,IAAI;;8CAEvD,UAAU,CAAC,MAAM,CAAC;;;;;;;;;;;;QAYxD,CACL,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa,EAAE,OAAe,EAAE,IAAa;IACrE,OAAO,MAAM,CACX,KAAK,EACL,OAAO,UAAU,CAAC,KAAK,CAAC;KACvB,UAAU,CAAC,OAAO,CAAC;EACtB,IAAI,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;uEAC6B,CACpE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The OAuth provider the Worker runs: authorization server and protected
3
+ * resource in one, as @cloudflare/workers-oauth-provider's OAuthProvider.
4
+ *
5
+ * - /mcp is the protected resource. A grant's props carry the Stables API key
6
+ * minted at sign-in, and the request is served with it.
7
+ * - A plain Stables API key sent as a bearer token keeps working through
8
+ * resolveExternalToken, so coding agents configured with a key need no
9
+ * OAuth.
10
+ * - /.well-known/oauth-protected-resource/mcp, /.well-known/oauth-authorization-server,
11
+ * /oauth/token and /oauth/register are the provider's; /authorize and
12
+ * /callback are ours (handlers.ts).
13
+ */
14
+ import { OAuthProvider } from "@cloudflare/workers-oauth-provider";
15
+ import { StablesApiClient } from "../lib/stables-client.js";
16
+ import { type OAuthEnv } from "./types.js";
17
+ export declare function resourceFor(origin: string): string;
18
+ export declare function apiClientFor(apiKey: string, env: OAuthEnv): StablesApiClient | null;
19
+ /** One provider per public origin; metadata and token audiences are bound to it. */
20
+ export declare function providerFor(origin: string): OAuthProvider<OAuthEnv>;
21
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/oauth/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAsB,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAGvF,OAAO,EAAE,gBAAgB,EAAgB,MAAM,0BAA0B,CAAC;AAG1E,OAAO,EAA4C,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAErF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,gBAAgB,GAAG,IAAI,CAOnF;AAyBD,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,CAiDnE"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The OAuth provider the Worker runs: authorization server and protected
3
+ * resource in one, as @cloudflare/workers-oauth-provider's OAuthProvider.
4
+ *
5
+ * - /mcp is the protected resource. A grant's props carry the Stables API key
6
+ * minted at sign-in, and the request is served with it.
7
+ * - A plain Stables API key sent as a bearer token keeps working through
8
+ * resolveExternalToken, so coding agents configured with a key need no
9
+ * OAuth.
10
+ * - /.well-known/oauth-protected-resource/mcp, /.well-known/oauth-authorization-server,
11
+ * /oauth/token and /oauth/register are the provider's; /authorize and
12
+ * /callback are ours (handlers.ts).
13
+ */
14
+ import { ExternalTokenError, OAuthProvider } from "@cloudflare/workers-oauth-provider";
15
+ import { SERVER_VERSION } from "../version.js";
16
+ import { serveMcp, withCors, json } from "../http/handler.js";
17
+ import { StablesApiClient, apiUrlForKey } from "../lib/stables-client.js";
18
+ import { environmentForKey } from "./auth0.js";
19
+ import { handleDefault } from "./handlers.js";
20
+ import { SCOPE, SUPPORTED_SCOPES } from "./types.js";
21
+ export function resourceFor(origin) {
22
+ return `${origin}/mcp`;
23
+ }
24
+ export function apiClientFor(apiKey, env) {
25
+ const baseUrl = apiUrlForKey(apiKey);
26
+ if (!baseUrl)
27
+ return null;
28
+ return new StablesApiClient(apiKey, baseUrl, {
29
+ gatewaySigningSecret: env.MCP_GATEWAY_SIGNING_SECRET,
30
+ userAgent: `stables-mcp-server/${SERVER_VERSION} (hosted)`,
31
+ });
32
+ }
33
+ async function serveAuthorizedMcp(request, env, props) {
34
+ const client = props?.apiKey ? apiClientFor(props.apiKey, env) : null;
35
+ if (!client) {
36
+ return withCors(json({
37
+ error: "invalid_grant",
38
+ message: "This connection carries no usable Stables API key. Disconnect and connect again.",
39
+ }, 401));
40
+ }
41
+ return withCors(await serveMcp(request, client));
42
+ }
43
+ const providers = new Map();
44
+ /** One provider per public origin; metadata and token audiences are bound to it. */
45
+ export function providerFor(origin) {
46
+ const cached = providers.get(origin);
47
+ if (cached)
48
+ return cached;
49
+ const resource = resourceFor(origin);
50
+ const provider = new OAuthProvider({
51
+ apiRoute: "/mcp",
52
+ apiHandler: {
53
+ fetch(request, env, ctx) {
54
+ const props = ctx.props;
55
+ return serveAuthorizedMcp(request, env, props);
56
+ },
57
+ },
58
+ defaultHandler: {
59
+ fetch(request, env) {
60
+ return handleDefault(request, env);
61
+ },
62
+ },
63
+ authorizeEndpoint: "/authorize",
64
+ tokenEndpoint: "/oauth/token",
65
+ // DCR stays on for clients without CIMD support; MCP prefers CIMD.
66
+ clientRegistrationEndpoint: "/oauth/register",
67
+ clientIdMetadataDocumentEnabled: true,
68
+ scopesSupported: SUPPORTED_SCOPES,
69
+ requiredScopes: [SCOPE],
70
+ resourceMetadata: {
71
+ resource,
72
+ authorization_servers: [origin],
73
+ resource_name: "Stables MCP server",
74
+ },
75
+ accessTokenTTL: 60 * 60,
76
+ refreshTokenTTL: 30 * 24 * 60 * 60,
77
+ // A Stables API key presented directly. Sandbox keys go to sandbox and
78
+ // live keys to production, as the stdio server does.
79
+ resolveExternalToken: async ({ token }) => {
80
+ if (!token.startsWith("sti_"))
81
+ return null;
82
+ if (!apiUrlForKey(token)) {
83
+ throw new ExternalTokenError("invalid_token", {
84
+ description: "Unrecognised API key. Send a sti_test_ (sandbox) or sti_live_ (production) key, or connect with OAuth.",
85
+ statusCode: 401,
86
+ });
87
+ }
88
+ const props = { apiKey: token, environment: environmentForKey(token) };
89
+ return { props, audience: resource };
90
+ },
91
+ });
92
+ providers.set(origin, provider);
93
+ return provider;
94
+ }
95
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/oauth/provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Shared shapes for the hosted server's OAuth support.
3
+ *
4
+ * The Worker is the OAuth authorization server MCP clients talk to (Claude,
5
+ * ChatGPT, Cursor, ...). Behind it, Auth0 signs the user in exactly as the
6
+ * dashboard does, and the Worker then mints a Stables API key for the
7
+ * organisation the user picked. That key lives in the grant's encrypted
8
+ * props, so every later MCP request is served with it and the Auth0 token
9
+ * is never kept.
10
+ */
11
+ import type { OAuthHelpers } from "@cloudflare/workers-oauth-provider";
12
+ /** The one scope the server grants; tool-level scopes can come later. */
13
+ export declare const SCOPE = "stables";
14
+ export declare const SUPPORTED_SCOPES: string[];
15
+ export type StablesEnvironment = "sandbox" | "production";
16
+ /** What a grant carries; handlers read it as ctx.props. */
17
+ export interface GrantProps {
18
+ apiKey: string;
19
+ environment: StablesEnvironment;
20
+ apiKeyId?: string;
21
+ email?: string;
22
+ orgId?: string;
23
+ clientName?: string;
24
+ }
25
+ /** Bindings and settings the OAuth path needs. See wrangler.toml. */
26
+ export interface OAuthEnv {
27
+ /** KV namespace the provider stores clients, grants and tokens in. */
28
+ OAUTH_KV?: unknown;
29
+ /** Injected by the provider for the default handler. */
30
+ OAUTH_PROVIDER?: OAuthHelpers;
31
+ /** Canonical public origin; defaults to the request origin. */
32
+ MCP_PUBLIC_URL?: string;
33
+ /** Auth0 custom domain, e.g. login.stables.money. */
34
+ AUTH0_DOMAIN?: string;
35
+ AUTH0_CLIENT_ID?: string;
36
+ /** Worker secret. */
37
+ AUTH0_CLIENT_SECRET?: string;
38
+ /** Worker secret shared with api-lambda-authoriser; unset = unsigned. */
39
+ MCP_GATEWAY_SIGNING_SECRET?: string;
40
+ }
41
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/oauth/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAEvE,yEAAyE;AACzE,eAAO,MAAM,KAAK,YAAY,CAAC;AAC/B,eAAO,MAAM,gBAAgB,UAA4B,CAAC;AAE1D,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,YAAY,CAAC;AAE1D,2DAA2D;AAC3D,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,kBAAkB,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qEAAqE;AACrE,MAAM,WAAW,QAAQ;IACvB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,cAAc,CAAC,EAAE,YAAY,CAAC;IAC9B,+DAA+D;IAC/D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qBAAqB;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,yEAAyE;IACzE,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shared shapes for the hosted server's OAuth support.
3
+ *
4
+ * The Worker is the OAuth authorization server MCP clients talk to (Claude,
5
+ * ChatGPT, Cursor, ...). Behind it, Auth0 signs the user in exactly as the
6
+ * dashboard does, and the Worker then mints a Stables API key for the
7
+ * organisation the user picked. That key lives in the grant's encrypted
8
+ * props, so every later MCP request is served with it and the Auth0 token
9
+ * is never kept.
10
+ */
11
+ /** The one scope the server grants; tool-level scopes can come later. */
12
+ export const SCOPE = "stables";
13
+ export const SUPPORTED_SCOPES = [SCOPE, "offline_access"];
14
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/oauth/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,yEAAyE;AACzE,MAAM,CAAC,MAAM,KAAK,GAAG,SAAS,CAAC;AAC/B,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAWhE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,eAAO,MAAM,WAAW,uBAAuB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAmB1E"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAahE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,eAAO,MAAM,WAAW,uBAAuB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAqB1E"}
package/build/server.js CHANGED
@@ -14,6 +14,8 @@ import { registerWebhookTools } from "./tools/webhooks.js";
14
14
  import { registerSandboxTools } from "./tools/sandbox.js";
15
15
  import { registerBalanceTools } from "./tools/balances.js";
16
16
  import { registerEntitlementTools } from "./tools/entitlements.js";
17
+ import { registerComplianceTools } from "./tools/compliance.js";
18
+ import { registerDocumentTools } from "./tools/documents.js";
17
19
  import { SERVER_VERSION } from "./version.js";
18
20
  export const SERVER_NAME = "stables-mcp-server";
19
21
  export { SERVER_VERSION };
@@ -31,6 +33,8 @@ export function createStablesMcpServer(client) {
31
33
  registerWebhookTools(server, client);
32
34
  registerBalanceTools(server, client);
33
35
  registerEntitlementTools(server, client);
36
+ registerComplianceTools(server, client);
37
+ registerDocumentTools(server, client);
34
38
  registerSandboxTools(server, client);
35
39
  return server;
36
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,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,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAEnE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,CAAC,MAAM,WAAW,GAAG,oBAAoB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B,MAAM,UAAU,sBAAsB,CAAC,MAAwB;IAC7D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,cAAc;QACvB,WAAW,EACT,sGAAsG;KACzG,CAAC,CAAC;IAEH,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,2BAA2B,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,wBAAwB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAErC,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,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,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,CAAC,MAAM,WAAW,GAAG,oBAAoB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B,MAAM,UAAU,sBAAsB,CAAC,MAAwB;IAC7D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,cAAc;QACvB,WAAW,EACT,sGAAsG;KACzG,CAAC,CAAC;IAEH,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,2BAA2B,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,wBAAwB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAErC,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Compliance Tools for Stables MCP Server
3
+ *
4
+ * Mirrors the Compliance requests (RFIs) reference on docs.stables.money. An RFI
5
+ * is what Stables compliance needs before it releases a held payment or a held
6
+ * verification level. Each requirement on it says how to answer: `evidence`
7
+ * with submit_rfi_evidence, `kyc_level` / `kyc_capability` with a verification
8
+ * link (get_verification_link), and `manual_review` needs nothing.
9
+ */
10
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ import { StablesApiClient } from "../lib/stables-client.js";
12
+ export declare function registerComplianceTools(server: McpServer, client: StablesApiClient): void;
13
+ //# sourceMappingURL=compliance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compliance.d.ts","sourceRoot":"","sources":["../../src/tools/compliance.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,gBAAgB,EAAwC,MAAM,0BAA0B,CAAC;AA6DlG,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,gBAAgB,QA6IlF"}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Compliance Tools for Stables MCP Server
3
+ *
4
+ * Mirrors the Compliance requests (RFIs) reference on docs.stables.money. An RFI
5
+ * is what Stables compliance needs before it releases a held payment or a held
6
+ * verification level. Each requirement on it says how to answer: `evidence`
7
+ * with submit_rfi_evidence, `kyc_level` / `kyc_capability` with a verification
8
+ * link (get_verification_link), and `manual_review` needs nothing.
9
+ */
10
+ import { z } from "zod";
11
+ import { StablesApiError } from "../lib/stables-client.js";
12
+ const RFI_STATUSES = ["open", "submitted", "satisfied", "expired", "waived", "rejected"];
13
+ const RFI_TYPES = ["verification", "transaction", "manual"];
14
+ function formatError(error) {
15
+ if (error instanceof StablesApiError)
16
+ return `${error.message} (HTTP ${error.statusCode})`;
17
+ return error instanceof Error ? error.message : "Unknown error";
18
+ }
19
+ function text(value, isError = false) {
20
+ return { content: [{ type: "text", text: value }], ...(isError && { isError }) };
21
+ }
22
+ /** What the integrator should do next for one requirement. */
23
+ function nextStep(rfi, r) {
24
+ if (r.status === "satisfied" || r.status === "waived")
25
+ return "nothing to do";
26
+ if (r.status === "submitted")
27
+ return "submitted, waiting for Stables compliance";
28
+ switch (r.type) {
29
+ case "evidence":
30
+ return `answer with get_rfi_evidence_form, then submit_rfi_evidence (customerId ${rfi.customer_id}, rfiId ${rfi.rfi_id}, requirementId ${r.requirement_id})`;
31
+ case "kyc_level":
32
+ case "kyc_capability":
33
+ return r.action?.verification_type
34
+ ? `send the customer a verification link: get_verification_link with verificationType '${r.action.verification_type}'`
35
+ : "the customer must complete verification";
36
+ case "manual_review":
37
+ return "Stables compliance is reviewing, nothing to submit";
38
+ default:
39
+ return "see the RFI in the dashboard";
40
+ }
41
+ }
42
+ function describeRequirement(rfi, r) {
43
+ const what = r.required_level ?? r.required_capability ?? r.label ?? r.required_evidence ?? r.type;
44
+ const sentBack = r.rejection_reason ? ` (sent back: ${r.rejection_reason})` : "";
45
+ return ` - ${r.type} ${what}: ${r.status}${sentBack}. Next: ${nextStep(rfi, r)}`;
46
+ }
47
+ function describeRfi(rfi) {
48
+ const about = rfi.subject === "payment"
49
+ ? `payment ${rfi.transfer_id ?? rfi.transaction_reference_id ?? "(unknown)"}${rfi.transaction_type ? ` (${rfi.transaction_type})` : ""}`
50
+ : `customer ${rfi.customer_id}`;
51
+ const reasons = (rfi.reasons ?? []).map((r) => r.description).join("; ");
52
+ const lines = [
53
+ `RFI ${rfi.rfi_id}: ${rfi.type}, ${rfi.status}, about ${about}`,
54
+ reasons ? ` Why: ${reasons}` : "",
55
+ rfi.message ? ` Message: ${rfi.message}` : "",
56
+ rfi.rejection_reason ? ` Rejected: ${rfi.rejection_reason}` : "",
57
+ ...(rfi.requirements ?? []).map((r) => describeRequirement(rfi, r)),
58
+ ];
59
+ return lines.filter(Boolean).join("\n");
60
+ }
61
+ function describeList(rfis, empty) {
62
+ if (rfis.length === 0)
63
+ return empty;
64
+ return rfis.map(describeRfi).join("\n\n");
65
+ }
66
+ export function registerComplianceTools(server, client) {
67
+ server.tool("list_rfis", "List the organisation's compliance requests for information (RFIs), newest first: what Stables compliance needs before it releases a held payment or verification. Open and submitted ones by default. Check this when a transfer or deposit is held, or subscribe to the rfi.* webhooks.", {
68
+ status: z
69
+ .array(z.enum(RFI_STATUSES))
70
+ .optional()
71
+ .describe("Only these statuses (default: open and submitted)"),
72
+ type: z.array(z.enum(RFI_TYPES)).optional().describe("Only these RFI types"),
73
+ customerId: z.string().optional().describe("Only this customer's RFIs"),
74
+ transactionReferenceId: z
75
+ .string()
76
+ .optional()
77
+ .describe("Only RFIs about this transfer or deposit"),
78
+ limit: z.number().int().min(1).max(100).optional().describe("Results per page"),
79
+ cursor: z.string().optional().describe("next_cursor from the previous page"),
80
+ }, async (params) => {
81
+ try {
82
+ const { rfis, next_cursor } = await client.listRfis(params);
83
+ const more = next_cursor ? `\n\nMore results: pass cursor "${next_cursor}".` : "";
84
+ return text(describeList(rfis ?? [], "No RFIs match.") + more);
85
+ }
86
+ catch (error) {
87
+ return text(`Failed to list RFIs: ${formatError(error)}`, true);
88
+ }
89
+ });
90
+ server.tool("list_customer_rfis", "List one customer's compliance requests for information (RFIs), with what each requirement needs next.", {
91
+ customerId: z.string().describe("The customer ID"),
92
+ status: z.enum(RFI_STATUSES).optional().describe("Only this status"),
93
+ }, async ({ customerId, status }) => {
94
+ try {
95
+ const { rfis } = await client.listCustomerRfis(customerId, status);
96
+ return text(describeList(rfis ?? [], `Customer ${customerId} has no RFIs.`));
97
+ }
98
+ catch (error) {
99
+ return text(`Failed to list the customer's RFIs: ${formatError(error)}`, true);
100
+ }
101
+ });
102
+ server.tool("get_rfi", "Get one compliance request for information (RFI): why it was raised, its requirements and how to answer each.", {
103
+ rfiId: z.string().describe("The RFI ID"),
104
+ }, async ({ rfiId }) => {
105
+ try {
106
+ return text(describeRfi(await client.getRfi(rfiId)));
107
+ }
108
+ catch (error) {
109
+ return text(`Failed to get RFI: ${formatError(error)}`, true);
110
+ }
111
+ });
112
+ server.tool("get_rfi_evidence_form", "Get the form an `evidence` requirement on an RFI asks for (for example the source of funds behind one held payment), with the current submission if one was sent. Build the submit_rfi_evidence answers from its fields and documents.", {
113
+ customerId: z.string().describe("The customer ID"),
114
+ rfiId: z.string().describe("The RFI ID"),
115
+ requirementId: z.string().describe("The requirement ID (type 'evidence')"),
116
+ }, async ({ customerId, rfiId, requirementId }) => {
117
+ try {
118
+ const evidence = await client.getRfiEvidence(customerId, rfiId, requirementId);
119
+ const form = evidence.form ?? {};
120
+ const fields = (form.fields ?? []).map((f) => ` - ${f.key} (${f.type}${f.required ? ", required" : ""}): ${f.label}${f.description ? `. ${f.description}` : ""}`);
121
+ const documents = (form.documents ?? []).map((d) => ` - ${d.wire_field} (files as base64 data URLs${d.required ? ", required" : ""}): ${d.label}${d.description ? `. ${d.description}` : ""}`);
122
+ const lines = [
123
+ `Evidence requirement ${evidence.requirement_id} (${evidence.required_evidence}): ${evidence.status}`,
124
+ evidence.rejection_reason ? `Sent back: ${evidence.rejection_reason}` : "",
125
+ evidence.answerable
126
+ ? "It can be answered now."
127
+ : "It cannot be answered now (already approved or the RFI is resolved).",
128
+ form.title ? `\n${form.title}${form.description ? `: ${form.description}` : ""}` : "",
129
+ fields.length ? `\nFields:\n${fields.join("\n")}` : "",
130
+ documents.length ? `\nDocuments:\n${documents.join("\n")}` : "",
131
+ evidence.submission ? "\nA submission is already on file; a new one replaces it." : "",
132
+ ];
133
+ return text(lines.filter(Boolean).join("\n"));
134
+ }
135
+ catch (error) {
136
+ return text(`Failed to get the evidence form: ${formatError(error)}`, true);
137
+ }
138
+ });
139
+ server.tool("submit_rfi_evidence", "Submit the answers and documents an `evidence` requirement asks for, in one request, keyed as get_rfi_evidence_form lists them. Files are base64 data URLs ('data:application/pdf;base64,...'). The requirement moves to 'submitted' for Stables compliance to review; approving it releases the held payment once nothing else is outstanding. Confirm the content with a human before sending: it goes to compliance as the customer's answer.", {
140
+ customerId: z.string().describe("The customer ID"),
141
+ rfiId: z.string().describe("The RFI ID"),
142
+ requirementId: z.string().describe("The requirement ID (type 'evidence')"),
143
+ answers: z
144
+ .record(z.unknown())
145
+ .describe("Answers keyed by the form's field keys and document wire_field names, e.g. { funds_origin, funds_origin_explanation, supporting_documents: ['data:application/pdf;base64,...'] }"),
146
+ }, async ({ customerId, rfiId, requirementId, answers }) => {
147
+ try {
148
+ const { rfi } = await client.submitRfiEvidence(customerId, rfiId, requirementId, answers);
149
+ return text(`Evidence submitted.\n\n${describeRfi(rfi)}`);
150
+ }
151
+ catch (error) {
152
+ return text(`Failed to submit evidence: ${formatError(error)}`, true);
153
+ }
154
+ });
155
+ server.tool("get_kyc_capabilities", "List the verification capabilities a customer holds (for example proof_of_address), the level that granted each, and when. A `kyc_capability` requirement on an RFI is satisfied once the customer holds that capability.", {
156
+ customerId: z.string().describe("The customer ID"),
157
+ }, async ({ customerId }) => {
158
+ try {
159
+ const { capabilities } = await client.getKycCapabilities(customerId);
160
+ if (!capabilities?.length)
161
+ return text(`Customer ${customerId} holds no capabilities yet.`);
162
+ const lines = capabilities.map((c) => `- ${c.capability}: ${c.status} (from ${c.kyc_level}, granted ${c.granted_at})`);
163
+ return text(`Capabilities held by ${customerId}:\n${lines.join("\n")}`);
164
+ }
165
+ catch (error) {
166
+ return text(`Failed to get KYC capabilities: ${formatError(error)}`, true);
167
+ }
168
+ });
169
+ }
170
+ //# sourceMappingURL=compliance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compliance.js","sourceRoot":"","sources":["../../src/tools/compliance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Document Tools for Stables MCP Server
3
+ *
4
+ * Mirrors the Transfer Documents and Deposit Documents references on
5
+ * docs.stables.money: attach an invoice, contract or other supporting document
6
+ * to a transfer or to a payment route deposit, for example when Stables
7
+ * compliance asks for one, then list, download or remove it.
8
+ */
9
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
+ import { StablesApiClient } from "../lib/stables-client.js";
11
+ export declare function registerDocumentTools(server: McpServer, client: StablesApiClient): void;
12
+ //# sourceMappingURL=documents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../src/tools/documents.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,gBAAgB,EAAwC,MAAM,0BAA0B,CAAC;AAsBlG,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,gBAAgB,QAyFhF"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Document Tools for Stables MCP Server
3
+ *
4
+ * Mirrors the Transfer Documents and Deposit Documents references on
5
+ * docs.stables.money: attach an invoice, contract or other supporting document
6
+ * to a transfer or to a payment route deposit, for example when Stables
7
+ * compliance asks for one, then list, download or remove it.
8
+ */
9
+ import { z } from "zod";
10
+ import { StablesApiError } from "../lib/stables-client.js";
11
+ const OWNER = z
12
+ .enum(["transfer", "deposit"])
13
+ .describe("What the document belongs to: a transfer, or a deposit on a payment route");
14
+ const TRANSACTION_ID = z
15
+ .string()
16
+ .describe("The transfer ID, or the deposit ID from get_virtual_account_history");
17
+ function formatError(error) {
18
+ if (error instanceof StablesApiError)
19
+ return `${error.message} (HTTP ${error.statusCode})`;
20
+ return error instanceof Error ? error.message : "Unknown error";
21
+ }
22
+ function text(value, isError = false) {
23
+ return { content: [{ type: "text", text: value }], ...(isError && { isError }) };
24
+ }
25
+ function describeDocument(d) {
26
+ return `- ${d.document_id}: ${d.file_name} (${d.type}, ${d.content_type}, ${d.size_bytes} bytes, added ${d.created_at})`;
27
+ }
28
+ export function registerDocumentTools(server, client) {
29
+ server.tool("add_document", "Attach a supporting document (invoice, contract or other) to a transfer or a payment route deposit, for example when Stables compliance asks for one. The file goes as a base64 data URL ('data:application/pdf;base64,...').", {
30
+ on: OWNER,
31
+ transactionId: TRANSACTION_ID,
32
+ fileName: z.string().describe("File name shown to Stables, e.g. 'invoice-2041.pdf'"),
33
+ base64Url: z.string().describe("The file as a data URL: 'data:<mime>;base64,<data>'"),
34
+ type: z
35
+ .enum(["invoice", "contract", "other"])
36
+ .optional()
37
+ .describe("What the document is (default other)"),
38
+ }, async ({ on, transactionId, fileName, base64Url, type }) => {
39
+ try {
40
+ const { document } = await client.addDocument(on, transactionId, {
41
+ file_name: fileName,
42
+ base64_url: base64Url,
43
+ ...(type && { type }),
44
+ });
45
+ return text(`Document attached to ${on} ${transactionId}:\n${describeDocument(document)}`);
46
+ }
47
+ catch (error) {
48
+ return text(`Failed to attach the document: ${formatError(error)}`, true);
49
+ }
50
+ });
51
+ server.tool("list_documents", "List the documents attached to a transfer or a payment route deposit, newest first.", {
52
+ on: OWNER,
53
+ transactionId: TRANSACTION_ID,
54
+ }, async ({ on, transactionId }) => {
55
+ try {
56
+ const { documents } = await client.listDocuments(on, transactionId);
57
+ if (!documents?.length)
58
+ return text(`No documents on ${on} ${transactionId}.`);
59
+ return text(`Documents on ${on} ${transactionId}:\n${documents.map(describeDocument).join("\n")}`);
60
+ }
61
+ catch (error) {
62
+ return text(`Failed to list documents: ${formatError(error)}`, true);
63
+ }
64
+ });
65
+ server.tool("get_document_download_url", "Get a short-lived link to download a document attached to a transfer or a payment route deposit.", {
66
+ on: OWNER,
67
+ transactionId: TRANSACTION_ID,
68
+ documentId: z.string().describe("The document ID from list_documents"),
69
+ }, async ({ on, transactionId, documentId }) => {
70
+ try {
71
+ const link = await client.getDocumentDownloadUrl(on, transactionId, documentId);
72
+ return text(`Download ${link.file_name} (${link.content_type}), valid for ${link.expires_in} seconds:\n${link.url}`);
73
+ }
74
+ catch (error) {
75
+ return text(`Failed to get the download link: ${formatError(error)}`, true);
76
+ }
77
+ });
78
+ server.tool("delete_document", "Remove a document from a transfer or a payment route deposit. This cannot be undone, so confirm with a human first.", {
79
+ on: OWNER,
80
+ transactionId: TRANSACTION_ID,
81
+ documentId: z.string().describe("The document ID from list_documents"),
82
+ }, async ({ on, transactionId, documentId }) => {
83
+ try {
84
+ const { deleted } = await client.deleteDocument(on, transactionId, documentId);
85
+ return text(deleted
86
+ ? `Document ${documentId} removed from ${on} ${transactionId}.`
87
+ : `Document ${documentId} was not removed.`);
88
+ }
89
+ catch (error) {
90
+ return text(`Failed to remove the document: ${formatError(error)}`, true);
91
+ }
92
+ });
93
+ }
94
+ //# sourceMappingURL=documents.js.map