easypaydirect-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 the easypaydirect-mcp contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # easypaydirect-mcp
2
+
3
+ > **Unofficial, read-only [Model Context Protocol](https://modelcontextprotocol.io) server for the [Easy Pay Direct](https://easypaydirect.com) (EPD) / NMI-family payment gateway.**
4
+
5
+ Give an AI assistant (Claude Desktop, Claude Code, or any MCP client) safe, **read-only** access to your gateway's transactions, subscriptions, recurring plans, and Customer Vault records — so you can ask questions like *"find the failed transactions for this customer last week"* or *"is this subscription still active?"* in plain language.
6
+
7
+ Easy Pay Direct is built on the **NMI / Network Merchants** gateway platform, so this server works with **any NMI white-label gateway** — just point it at your gateway's host. EPD is the headline example, not the limit.
8
+
9
+ > ⚠️ **Not affiliated with, endorsed by, or sponsored by Easy Pay Direct or NMI.** "Easy Pay Direct", "EPD", and "NMI" are trademarks of their respective owners. This is an independent open-source client for their public [Query API](https://docs.nmi.com/reference/query).
10
+
11
+ ---
12
+
13
+ ## Why read-only?
14
+
15
+ This server talks **only** to the gateway's [Query API](https://docs.nmi.com/reference/query) (`/api/query.php`) — the reporting endpoint. It has **no code path** to the transaction endpoint (`transact.php`), so it **cannot** charge a card, issue a refund, void a transaction, or modify the vault. An LLM connected to this server can look, but it cannot touch money. See [`docs/security.md`](docs/security.md).
16
+
17
+ Write operations may arrive in a future major version — always **opt-in, off by default, and loudly gated**.
18
+
19
+ ---
20
+
21
+ ## Install & run
22
+
23
+ Requires **Node.js 18+**.
24
+
25
+ ```bash
26
+ # no install needed — run straight from npm
27
+ npx easypaydirect-mcp
28
+ ```
29
+
30
+ The server speaks MCP over **stdio** and expects two environment variables:
31
+
32
+ | Variable | Required | Description |
33
+ |---|---|---|
34
+ | `NMI_SECURITY_KEY` | ✅ | Your gateway API **security key** (a **read-only** key is recommended). Merchant portal → Settings → Security Keys. |
35
+ | `NMI_API_URL` | — | Gateway API base URL. Defaults to `https://secure.nmi.com`. For EPD/white-labels, set this to your gateway's host. |
36
+
37
+ See [`docs/configuration.md`](docs/configuration.md) for how to find your key and host.
38
+
39
+ ## Connect it to Claude
40
+
41
+ **Claude Desktop** — add to `claude_desktop_config.json` (see [`examples/claude-desktop-config.json`](examples/claude-desktop-config.json)):
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "easypaydirect": {
47
+ "command": "npx",
48
+ "args": ["-y", "easypaydirect-mcp"],
49
+ "env": {
50
+ "NMI_SECURITY_KEY": "your_read_only_security_key",
51
+ "NMI_API_URL": "https://secure.nmi.com"
52
+ }
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ **Claude Code:**
59
+
60
+ ```bash
61
+ claude mcp add easypaydirect \
62
+ -e NMI_SECURITY_KEY=your_read_only_security_key \
63
+ -e NMI_API_URL=https://secure.nmi.com \
64
+ -- npx -y easypaydirect-mcp
65
+ ```
66
+
67
+ Full walkthrough: [`docs/getting-started.md`](docs/getting-started.md).
68
+
69
+ ## Tools
70
+
71
+ All tools are **read-only**. Full reference in [`docs/tools.md`](docs/tools.md).
72
+
73
+ | Tool | What it does |
74
+ |---|---|
75
+ | `get_transaction` | Fetch one transaction by gateway transaction ID. |
76
+ | `search_transactions` | Search transactions by date range + filters (condition, action type, payment type, source, email, order id). |
77
+ | `get_subscription` | Fetch one recurring subscription by ID. |
78
+ | `list_subscriptions` | List recurring subscriptions, optionally by created/updated date range. |
79
+ | `list_recurring_plans` | List recurring billing plans (or one by `plan_id`). |
80
+ | `get_customer_vault_record` | Fetch one stored Customer Vault record by ID. |
81
+ | `list_customer_vault` | List stored Customer Vault records, optionally by date range. |
82
+
83
+ ## Develop
84
+
85
+ ```bash
86
+ git clone https://github.com/praveendias1180/easypaydirect-mcp.git
87
+ cd easypaydirect-mcp
88
+ npm install
89
+ npm run build # compile TypeScript to dist/
90
+ npm run typecheck # type-check only
91
+ ```
92
+
93
+ Local run against the MCP Inspector:
94
+
95
+ ```bash
96
+ npx @modelcontextprotocol/inspector node dist/index.js
97
+ ```
98
+
99
+ Architecture and how tools map to the Query API: [`docs/nmi-api-mapping.md`](docs/nmi-api-mapping.md).
100
+
101
+ ## License
102
+
103
+ [MIT](LICENSE) © the easypaydirect-mcp contributors.
@@ -0,0 +1,20 @@
1
+ import type { Config } from "./config.js";
2
+ export type QueryParams = Record<string, string | number | undefined>;
3
+ export declare class NmiQueryError extends Error {
4
+ readonly status?: number | undefined;
5
+ readonly body?: string | undefined;
6
+ constructor(message: string, status?: number | undefined, body?: string | undefined);
7
+ }
8
+ /**
9
+ * Thin client over the EPD / NMI **Query API** (`POST /api/query.php`).
10
+ *
11
+ * The Query API is strictly READ-ONLY: it returns transaction, subscription,
12
+ * recurring-plan and customer-vault data. This client intentionally exposes no
13
+ * way to reach the transaction (`transact.php`) endpoint, so no charge, refund,
14
+ * void or vault-write can be issued through it.
15
+ */
16
+ export declare class NmiClient {
17
+ private readonly config;
18
+ constructor(config: Config);
19
+ query(params: QueryParams): Promise<unknown>;
20
+ }
package/dist/client.js ADDED
@@ -0,0 +1,66 @@
1
+ import { XMLParser } from "fast-xml-parser";
2
+ /**
3
+ * Values are kept as strings (parseTagValue: false) so that monetary amounts
4
+ * and long numeric IDs are never coerced to floats and lose precision.
5
+ */
6
+ const parser = new XMLParser({
7
+ ignoreAttributes: false,
8
+ parseTagValue: false,
9
+ trimValues: true,
10
+ });
11
+ export class NmiQueryError extends Error {
12
+ status;
13
+ body;
14
+ constructor(message, status, body) {
15
+ super(message);
16
+ this.status = status;
17
+ this.body = body;
18
+ this.name = "NmiQueryError";
19
+ }
20
+ }
21
+ /**
22
+ * Thin client over the EPD / NMI **Query API** (`POST /api/query.php`).
23
+ *
24
+ * The Query API is strictly READ-ONLY: it returns transaction, subscription,
25
+ * recurring-plan and customer-vault data. This client intentionally exposes no
26
+ * way to reach the transaction (`transact.php`) endpoint, so no charge, refund,
27
+ * void or vault-write can be issued through it.
28
+ */
29
+ export class NmiClient {
30
+ config;
31
+ constructor(config) {
32
+ this.config = config;
33
+ }
34
+ async query(params) {
35
+ const body = new URLSearchParams();
36
+ body.set("security_key", this.config.securityKey);
37
+ for (const [key, value] of Object.entries(params)) {
38
+ if (value !== undefined && value !== null && value !== "") {
39
+ body.set(key, String(value));
40
+ }
41
+ }
42
+ const url = `${this.config.apiUrl}/api/query.php`;
43
+ let res;
44
+ try {
45
+ res = await fetch(url, {
46
+ method: "POST",
47
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
48
+ body: body.toString(),
49
+ });
50
+ }
51
+ catch (err) {
52
+ throw new NmiQueryError(`Network error calling ${url}: ${err.message}`);
53
+ }
54
+ const text = await res.text();
55
+ if (!res.ok) {
56
+ throw new NmiQueryError(`Query API returned HTTP ${res.status}`, res.status, text.slice(0, 500));
57
+ }
58
+ // A well-formed response is XML. A plaintext body almost always means an
59
+ // authentication failure (bad/missing security key) or a wrong base URL.
60
+ if (!text.trim().startsWith("<")) {
61
+ throw new NmiQueryError(`Unexpected non-XML response (check NMI_SECURITY_KEY and NMI_API_URL): ${text.slice(0, 300)}`, res.status, text.slice(0, 500));
62
+ }
63
+ return parser.parse(text);
64
+ }
65
+ }
66
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C;;;GAGG;AACH,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,gBAAgB,EAAE,KAAK;IACvB,aAAa,EAAE,KAAK;IACpB,UAAU,EAAE,IAAI;CACjB,CAAC,CAAC;AAIH,MAAM,OAAO,aAAc,SAAQ,KAAK;IAG3B;IACA;IAHX,YACE,OAAe,EACN,MAAe,EACf,IAAa;QAEtB,KAAK,CAAC,OAAO,CAAC,CAAC;QAHN,WAAM,GAAN,MAAM,CAAS;QACf,SAAI,GAAJ,IAAI,CAAS;QAGtB,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,SAAS;IACS;IAA7B,YAA6B,MAAc;QAAd,WAAM,GAAN,MAAM,CAAQ;IAAG,CAAC;IAE/C,KAAK,CAAC,KAAK,CAAC,MAAmB;QAC7B,MAAM,IAAI,GAAG,IAAI,eAAe,EAAE,CAAC;QACnC,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAClD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAClD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;gBAC1D,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QAED,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,gBAAgB,CAAC;QAElD,IAAI,GAAa,CAAC;QAClB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBACrB,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;gBAChE,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE;aACtB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,aAAa,CAAC,yBAAyB,GAAG,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACrF,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAE9B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,aAAa,CAAC,2BAA2B,GAAG,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACnG,CAAC;QAED,yEAAyE;QACzE,yEAAyE;QACzE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,aAAa,CACrB,yEAAyE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAC7F,GAAG,CAAC,MAAM,EACV,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CACnB,CAAC;QACJ,CAAC;QAED,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;CACF"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Runtime configuration, loaded from environment variables.
3
+ *
4
+ * The gateway security key is read from the environment ONLY — it is never
5
+ * accepted as a tool argument, so a model driving this server can never see it
6
+ * or pass it around.
7
+ */
8
+ export interface Config {
9
+ /** Gateway API "security key" (read-only key recommended). */
10
+ securityKey: string;
11
+ /** Gateway API base URL, no trailing slash. */
12
+ apiUrl: string;
13
+ }
14
+ export declare function loadConfig(env?: NodeJS.ProcessEnv): Config;
package/dist/config.js ADDED
@@ -0,0 +1,11 @@
1
+ const DEFAULT_API_URL = "https://secure.nmi.com";
2
+ export function loadConfig(env = process.env) {
3
+ const securityKey = env.NMI_SECURITY_KEY?.trim();
4
+ if (!securityKey) {
5
+ throw new Error("NMI_SECURITY_KEY is required. Set it to your gateway API security key " +
6
+ "(EPD / NMI merchant portal -> Settings -> Security Keys). A read-only key is recommended.");
7
+ }
8
+ const apiUrl = (env.NMI_API_URL?.trim() || DEFAULT_API_URL).replace(/\/+$/, "");
9
+ return { securityKey, apiUrl };
10
+ }
11
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAcA,MAAM,eAAe,GAAG,wBAAwB,CAAC;AAEjD,MAAM,UAAU,UAAU,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC7D,MAAM,WAAW,GAAG,GAAG,CAAC,gBAAgB,EAAE,IAAI,EAAE,CAAC;IACjD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,wEAAwE;YACtE,2FAA2F,CAC9F,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,eAAe,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEhF,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;AACjC,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { loadConfig } from "./config.js";
5
+ import { NmiClient } from "./client.js";
6
+ import { registerTransactionTools } from "./tools/transactions.js";
7
+ import { registerSubscriptionTools } from "./tools/subscriptions.js";
8
+ import { registerPlanTools } from "./tools/plans.js";
9
+ import { registerVaultTools } from "./tools/vault.js";
10
+ async function main() {
11
+ const config = loadConfig();
12
+ const client = new NmiClient(config);
13
+ const server = new McpServer({
14
+ name: "easypaydirect-mcp",
15
+ version: "0.1.0",
16
+ });
17
+ registerTransactionTools(server, client);
18
+ registerSubscriptionTools(server, client);
19
+ registerPlanTools(server, client);
20
+ registerVaultTools(server, client);
21
+ const transport = new StdioServerTransport();
22
+ await server.connect(transport);
23
+ // stdout is reserved for the MCP protocol; log to stderr only.
24
+ console.error(`easypaydirect-mcp running on stdio (endpoint: ${config.apiUrl})`);
25
+ }
26
+ main().catch((err) => {
27
+ console.error("Fatal:", err instanceof Error ? err.message : err);
28
+ process.exit(1);
29
+ });
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,CAAC;IAErC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,wBAAwB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,yBAAyB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEnC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,+DAA+D;IAC/D,OAAO,CAAC,KAAK,CAAC,iDAAiD,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;AACnF,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,15 @@
1
+ /** Wrap parsed Query API data as an MCP text result (pretty-printed JSON). */
2
+ export declare function jsonResult(data: unknown): {
3
+ content: {
4
+ type: "text";
5
+ text: string;
6
+ }[];
7
+ };
8
+ /** Convert a thrown error into an MCP error result without leaking internals. */
9
+ export declare function toErrorResult(err: unknown): {
10
+ content: {
11
+ type: "text";
12
+ text: string;
13
+ }[];
14
+ isError: true;
15
+ };
@@ -0,0 +1,25 @@
1
+ import { NmiQueryError } from "../client.js";
2
+ /** Wrap parsed Query API data as an MCP text result (pretty-printed JSON). */
3
+ export function jsonResult(data) {
4
+ return {
5
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
6
+ };
7
+ }
8
+ /** Convert a thrown error into an MCP error result without leaking internals. */
9
+ export function toErrorResult(err) {
10
+ let message;
11
+ if (err instanceof NmiQueryError) {
12
+ message = err.status ? `${err.message} (HTTP ${err.status})` : err.message;
13
+ }
14
+ else if (err instanceof Error) {
15
+ message = err.message;
16
+ }
17
+ else {
18
+ message = String(err);
19
+ }
20
+ return {
21
+ content: [{ type: "text", text: `Error: ${message}` }],
22
+ isError: true,
23
+ };
24
+ }
25
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/tools/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,8EAA8E;AAC9E,MAAM,UAAU,UAAU,CAAC,IAAa;IACtC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;KAC1E,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,IAAI,OAAe,CAAC;IACpB,IAAI,GAAG,YAAY,aAAa,EAAE,CAAC;QACjC,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,UAAU,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC;IAC7E,CAAC;SAAM,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QAChC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;IACxB,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IACD,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAU,OAAO,EAAE,EAAE,CAAC;QAC/D,OAAO,EAAE,IAAa;KACvB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { NmiClient } from "../client.js";
3
+ export declare function registerPlanTools(server: McpServer, client: NmiClient): void;
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ import { jsonResult, toErrorResult } from "./helpers.js";
3
+ export function registerPlanTools(server, client) {
4
+ server.tool("list_recurring_plans", "List recurring billing plans, optionally filtered to a single plan (report_type=recurring_plans). Read-only.", {
5
+ plan_id: z.string().optional().describe("Optional plan ID to return a single plan instead of all plans."),
6
+ }, async ({ plan_id }) => {
7
+ try {
8
+ return jsonResult(await client.query({ report_type: "recurring_plans", plan_id }));
9
+ }
10
+ catch (err) {
11
+ return toErrorResult(err);
12
+ }
13
+ });
14
+ }
15
+ //# sourceMappingURL=plans.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plans.js","sourceRoot":"","sources":["../../src/tools/plans.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEzD,MAAM,UAAU,iBAAiB,CAAC,MAAiB,EAAE,MAAiB;IACpE,MAAM,CAAC,IAAI,CACT,sBAAsB,EACtB,8GAA8G,EAC9G;QACE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gEAAgE,CAAC;KAC1G,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACpB,IAAI,CAAC;YACH,OAAO,UAAU,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,iBAAiB,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;QACrF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { NmiClient } from "../client.js";
3
+ export declare function registerSubscriptionTools(server: McpServer, client: NmiClient): void;
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+ import { jsonResult, toErrorResult } from "./helpers.js";
3
+ export function registerSubscriptionTools(server, client) {
4
+ server.tool("get_subscription", "Fetch a single recurring subscription by its subscription ID (report_type=recurring). Read-only.", {
5
+ subscription_id: z.string().min(1).describe("The recurring subscription ID to look up."),
6
+ }, async ({ subscription_id }) => {
7
+ try {
8
+ return jsonResult(await client.query({ report_type: "recurring", subscription_id }));
9
+ }
10
+ catch (err) {
11
+ return toErrorResult(err);
12
+ }
13
+ });
14
+ server.tool("list_subscriptions", "List recurring subscriptions, optionally filtered by created/updated date range (report_type=recurring). Read-only.", {
15
+ date_search: z
16
+ .string()
17
+ .optional()
18
+ .describe("Which date(s) to filter on, e.g. 'created' or 'updated' or 'created,updated'."),
19
+ start_date: z.string().optional().describe("Start of range, NMI format YYYYMMDDhhmmss."),
20
+ end_date: z.string().optional().describe("End of range, NMI format YYYYMMDDhhmmss."),
21
+ }, async (args) => {
22
+ try {
23
+ return jsonResult(await client.query({ report_type: "recurring", ...args }));
24
+ }
25
+ catch (err) {
26
+ return toErrorResult(err);
27
+ }
28
+ });
29
+ }
30
+ //# sourceMappingURL=subscriptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscriptions.js","sourceRoot":"","sources":["../../src/tools/subscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEzD,MAAM,UAAU,yBAAyB,CAAC,MAAiB,EAAE,MAAiB;IAC5E,MAAM,CAAC,IAAI,CACT,kBAAkB,EAClB,kGAAkG,EAClG;QACE,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,2CAA2C,CAAC;KACzF,EACD,KAAK,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE;QAC5B,IAAI,CAAC;YACH,OAAO,UAAU,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC;QACvF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,oBAAoB,EACpB,qHAAqH,EACrH;QACE,WAAW,EAAE,CAAC;aACX,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,+EAA+E,CAAC;QAC5F,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;QACxF,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0CAA0C,CAAC;KACrF,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,OAAO,UAAU,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC;QAC/E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { NmiClient } from "../client.js";
3
+ export declare function registerTransactionTools(server: McpServer, client: NmiClient): void;
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+ import { jsonResult, toErrorResult } from "./helpers.js";
3
+ const NMI_DATE_HINT = "NMI datetime format YYYYMMDDhhmmss (e.g. 20260101000000 for 2026-01-01 00:00:00).";
4
+ export function registerTransactionTools(server, client) {
5
+ server.tool("get_transaction", "Fetch a single transaction by its gateway transaction ID. Read-only.", {
6
+ transaction_id: z.string().min(1).describe("The gateway transaction ID to look up."),
7
+ }, async ({ transaction_id }) => {
8
+ try {
9
+ return jsonResult(await client.query({ transaction_id }));
10
+ }
11
+ catch (err) {
12
+ return toErrorResult(err);
13
+ }
14
+ });
15
+ server.tool("search_transactions", "Search transactions by date range and optional filters (condition, action type, payment type, source, email, order id). Read-only.", {
16
+ start_date: z.string().optional().describe(`Start of range. ${NMI_DATE_HINT}`),
17
+ end_date: z.string().optional().describe(`End of range. ${NMI_DATE_HINT}`),
18
+ condition: z
19
+ .string()
20
+ .optional()
21
+ .describe("Comma-separated transaction conditions: pending, pendingsettlement, in_progress, abandoned, failed, canceled, complete, unknown."),
22
+ action_type: z
23
+ .string()
24
+ .optional()
25
+ .describe("Comma-separated action types: sale, refund, credit, auth, capture, void, return."),
26
+ transaction_type: z.string().optional().describe("Payment type filter: 'cc' (card) or 'ck' (ACH/check)."),
27
+ source: z.string().optional().describe("Filter by transaction source."),
28
+ email: z.string().optional().describe("Filter by customer email address."),
29
+ order_id: z.string().optional().describe("Filter by merchant-supplied order id."),
30
+ }, async (args) => {
31
+ try {
32
+ return jsonResult(await client.query(args));
33
+ }
34
+ catch (err) {
35
+ return toErrorResult(err);
36
+ }
37
+ });
38
+ }
39
+ //# sourceMappingURL=transactions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactions.js","sourceRoot":"","sources":["../../src/tools/transactions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEzD,MAAM,aAAa,GAAG,mFAAmF,CAAC;AAE1G,MAAM,UAAU,wBAAwB,CAAC,MAAiB,EAAE,MAAiB;IAC3E,MAAM,CAAC,IAAI,CACT,iBAAiB,EACjB,sEAAsE,EACtE;QACE,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,wCAAwC,CAAC;KACrF,EACD,KAAK,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE;QAC3B,IAAI,CAAC;YACH,OAAO,UAAU,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;QAC5D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,oIAAoI,EACpI;QACE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mBAAmB,aAAa,EAAE,CAAC;QAC9E,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iBAAiB,aAAa,EAAE,CAAC;QAC1E,SAAS,EAAE,CAAC;aACT,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,kIAAkI,CAAC;QAC/I,WAAW,EAAE,CAAC;aACX,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,kFAAkF,CAAC;QAC/F,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uDAAuD,CAAC;QACzG,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC;QACvE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;QAC1E,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;KAClF,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,OAAO,UAAU,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { NmiClient } from "../client.js";
3
+ export declare function registerVaultTools(server: McpServer, client: NmiClient): void;
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+ import { jsonResult, toErrorResult } from "./helpers.js";
3
+ export function registerVaultTools(server, client) {
4
+ server.tool("get_customer_vault_record", "Fetch a single stored Customer Vault record by its customer_vault_id (report_type=customer_vault). Returns stored profile data only — never full card numbers. Read-only.", {
5
+ customer_vault_id: z.string().min(1).describe("The Customer Vault ID to look up."),
6
+ }, async ({ customer_vault_id }) => {
7
+ try {
8
+ return jsonResult(await client.query({ report_type: "customer_vault", customer_vault_id }));
9
+ }
10
+ catch (err) {
11
+ return toErrorResult(err);
12
+ }
13
+ });
14
+ server.tool("list_customer_vault", "List stored Customer Vault records, optionally filtered by created/updated date range (report_type=customer_vault). Read-only.", {
15
+ date_search: z
16
+ .string()
17
+ .optional()
18
+ .describe("Which date(s) to filter on, e.g. 'created', 'updated', or 'created,updated'."),
19
+ start_date: z.string().optional().describe("Start of range, NMI format YYYYMMDDhhmmss."),
20
+ end_date: z.string().optional().describe("End of range, NMI format YYYYMMDDhhmmss."),
21
+ }, async (args) => {
22
+ try {
23
+ return jsonResult(await client.query({ report_type: "customer_vault", ...args }));
24
+ }
25
+ catch (err) {
26
+ return toErrorResult(err);
27
+ }
28
+ });
29
+ }
30
+ //# sourceMappingURL=vault.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.js","sourceRoot":"","sources":["../../src/tools/vault.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEzD,MAAM,UAAU,kBAAkB,CAAC,MAAiB,EAAE,MAAiB;IACrE,MAAM,CAAC,IAAI,CACT,2BAA2B,EAC3B,2KAA2K,EAC3K;QACE,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,mCAAmC,CAAC;KACnF,EACD,KAAK,EAAE,EAAE,iBAAiB,EAAE,EAAE,EAAE;QAC9B,IAAI,CAAC;YACH,OAAO,UAAU,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC;QAC9F,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,gIAAgI,EAChI;QACE,WAAW,EAAE,CAAC;aACX,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,8EAA8E,CAAC;QAC3F,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;QACxF,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0CAA0C,CAAC;KACrF,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,OAAO,UAAU,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,gBAAgB,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC;QACpF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "easypaydirect-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Unofficial read-only Model Context Protocol (MCP) server for the Easy Pay Direct (EPD) / NMI-family payment gateway Query API.",
5
+ "type": "module",
6
+ "bin": {
7
+ "easypaydirect-mcp": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc && node -e \"require('fs').chmodSync('dist/index.js', 0o755)\"",
16
+ "typecheck": "tsc --noEmit",
17
+ "start": "node dist/index.js",
18
+ "prepublishOnly": "npm run build"
19
+ },
20
+ "keywords": [
21
+ "mcp",
22
+ "model-context-protocol",
23
+ "nmi",
24
+ "easypaydirect",
25
+ "epd",
26
+ "payments",
27
+ "payment-gateway",
28
+ "claude",
29
+ "ai"
30
+ ],
31
+ "author": "Praveen Dias (https://github.com/praveendias1180)",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/praveendias1180/easypaydirect-mcp.git"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/praveendias1180/easypaydirect-mcp/issues"
39
+ },
40
+ "homepage": "https://github.com/praveendias1180/easypaydirect-mcp#readme",
41
+ "engines": {
42
+ "node": ">=18"
43
+ },
44
+ "dependencies": {
45
+ "@modelcontextprotocol/sdk": "^1.12.0",
46
+ "fast-xml-parser": "^5.9.3",
47
+ "zod": "^3.23.8"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^22.0.0",
51
+ "typescript": "^5.6.0"
52
+ }
53
+ }