apimail-mcp 1.0.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 (3) hide show
  1. package/README.md +52 -0
  2. package/package.json +34 -0
  3. package/server.js +185 -0
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # ApiMail MCP (`apimail-mcp`)
2
+
3
+ Stdio MCP server that proxies ApiMail HTTP API. No business logic duplication: pricing and purchases stay on the API.
4
+
5
+ ## End users (any machine, no local repo path)
6
+
7
+ Add to Cursor / Claude Desktop:
8
+
9
+ ```json
10
+ {
11
+ "mcpServers": {
12
+ "apimail": {
13
+ "command": "npx",
14
+ "args": ["-y", "apimail-mcp@latest"],
15
+ "env": {
16
+ "APIMAIL_TOKEN": "YOUR_API_TOKEN",
17
+ "APIMAIL_BASE_URL": "https://api.apimail.me"
18
+ }
19
+ }
20
+ }
21
+ }
22
+ ```
23
+
24
+ Same pattern as other MCP servers: `npx` downloads and runs the package; **no filesystem path** in the config.
25
+
26
+ ## Publish (maintainers only)
27
+
28
+ From this directory:
29
+
30
+ ```bash
31
+ npm login
32
+ npm publish
33
+ ```
34
+
35
+ Bump `version` in `package.json` for each release (`npm version patch`).
36
+
37
+ ## Environment variables
38
+
39
+ - `APIMAIL_TOKEN` — required for authenticated tools.
40
+ - `APIMAIL_BASE_URL` — default `https://api.apimail.me`.
41
+ - `APIMAIL_TIMEOUT_MS` — HTTP timeout ms, default `20000`.
42
+
43
+ ## Tools
44
+
45
+ - `apimail_user_balance`
46
+ - `apimail_domain_availability`
47
+ - `apimail_purchase_accounts`
48
+ - `apimail_purchased_emails`
49
+
50
+ ## Security
51
+
52
+ Do not commit real API tokens. Use env in the MCP client only.
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "apimail-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for ApiMail API (stdio, Model Context Protocol)",
5
+ "type": "module",
6
+ "main": "server.js",
7
+ "files": [
8
+ "server.js"
9
+ ],
10
+ "bin": {
11
+ "apimail-mcp": "./server.js"
12
+ },
13
+ "scripts": {
14
+ "start": "node server.js",
15
+ "prepublishOnly": "node --check server.js"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "model-context-protocol",
20
+ "apimail",
21
+ "stdio"
22
+ ],
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.17.5"
25
+ },
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "license": "UNLICENSED",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/apimail/mcp"
33
+ }
34
+ }
package/server.js ADDED
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5
+
6
+ const API_BASE_URL = (process.env.APIMAIL_BASE_URL || "https://api.apimail.me").replace(/\/+$/, "");
7
+ const APIMAIL_TOKEN = process.env.APIMAIL_TOKEN || "";
8
+ const REQUEST_TIMEOUT_MS = Number(process.env.APIMAIL_TIMEOUT_MS || 20000);
9
+
10
+ const TOOL_DEFS = [
11
+ {
12
+ name: "apimail_user_balance",
13
+ description: "Get current user balance from ApiMail.",
14
+ inputSchema: {
15
+ type: "object",
16
+ properties: {
17
+ token: { type: "string", description: "Optional API token override." }
18
+ },
19
+ additionalProperties: false
20
+ }
21
+ },
22
+ {
23
+ name: "apimail_domain_availability",
24
+ description: "Get public domain availability with optional filters.",
25
+ inputSchema: {
26
+ type: "object",
27
+ properties: {
28
+ domain: { type: "string", description: "Optional domain filter, e.g. outlook.com." },
29
+ age: { type: "string", enum: ["fresh", "medium", "aged", "old"], description: "Optional age filter." }
30
+ },
31
+ additionalProperties: false
32
+ }
33
+ },
34
+ {
35
+ name: "apimail_purchase_accounts",
36
+ description: "Purchase Outlook accounts via ApiMail getEmail endpoint.",
37
+ inputSchema: {
38
+ type: "object",
39
+ properties: {
40
+ token: { type: "string", description: "Optional API token override." },
41
+ count: { type: "integer", minimum: 1, maximum: 500, default: 1 },
42
+ domain: { type: "string", description: "Optional exact domain filter." },
43
+ age: { type: "string", enum: ["fresh", "medium", "aged", "old"], description: "Optional age tier." }
44
+ },
45
+ additionalProperties: false
46
+ }
47
+ },
48
+ {
49
+ name: "apimail_purchased_emails",
50
+ description: "Fetch purchased emails by payment_id within retention period.",
51
+ inputSchema: {
52
+ type: "object",
53
+ properties: {
54
+ token: { type: "string", description: "Optional API token override." },
55
+ payment_id: { type: "integer", minimum: 1 }
56
+ },
57
+ required: ["payment_id"],
58
+ additionalProperties: false
59
+ }
60
+ }
61
+ ];
62
+
63
+ function asText(text, isError = false) {
64
+ return {
65
+ content: [{ type: "text", text }],
66
+ isError
67
+ };
68
+ }
69
+
70
+ function getTokenFromArgs(args) {
71
+ const local = args && typeof args.token === "string" ? args.token.trim() : "";
72
+ const token = local || APIMAIL_TOKEN;
73
+ if (!token) {
74
+ throw new Error("ApiMail token is required. Set APIMAIL_TOKEN env or pass token argument.");
75
+ }
76
+ return token;
77
+ }
78
+
79
+ function toQuery(params) {
80
+ const query = new URLSearchParams();
81
+ for (const [key, val] of Object.entries(params)) {
82
+ if (val === null || val === undefined || val === "") continue;
83
+ query.set(key, String(val));
84
+ }
85
+ return query.toString();
86
+ }
87
+
88
+ async function callApi(endpoint, params) {
89
+ const query = toQuery(params);
90
+ const url = `${API_BASE_URL}/${endpoint}${query ? `?${query}` : ""}`;
91
+ const controller = new AbortController();
92
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
93
+
94
+ try {
95
+ const response = await fetch(url, {
96
+ method: "GET",
97
+ headers: { Accept: "application/json" },
98
+ signal: controller.signal
99
+ });
100
+ const text = await response.text();
101
+
102
+ let parsed;
103
+ try {
104
+ parsed = JSON.parse(text);
105
+ } catch {
106
+ parsed = { status: "ERROR", value: "INVALID_JSON_RESPONSE", raw: text };
107
+ }
108
+
109
+ return {
110
+ ok: response.ok,
111
+ statusCode: response.status,
112
+ url,
113
+ data: parsed
114
+ };
115
+ } finally {
116
+ clearTimeout(timer);
117
+ }
118
+ }
119
+
120
+ async function handleTool(name, args) {
121
+ if (name === "apimail_user_balance") {
122
+ const token = getTokenFromArgs(args || {});
123
+ const result = await callApi("userBalance", { token });
124
+ return asText(JSON.stringify(result, null, 2));
125
+ }
126
+
127
+ if (name === "apimail_domain_availability") {
128
+ const params = {};
129
+ if (args && typeof args.domain === "string") params.domain = args.domain.trim();
130
+ if (args && typeof args.age === "string") params.age = args.age.trim();
131
+ const result = await callApi("domainAvailability", params);
132
+ return asText(JSON.stringify(result, null, 2));
133
+ }
134
+
135
+ if (name === "apimail_purchase_accounts") {
136
+ const token = getTokenFromArgs(args || {});
137
+ const count = Number(args && args.count ? args.count : 1);
138
+ if (!Number.isInteger(count) || count < 1 || count > 500) {
139
+ throw new Error("count must be an integer from 1 to 500.");
140
+ }
141
+
142
+ const params = { token, count };
143
+ if (args && typeof args.domain === "string" && args.domain.trim()) {
144
+ params.domain = args.domain.trim().toLowerCase();
145
+ }
146
+ if (args && typeof args.age === "string" && args.age.trim()) {
147
+ params.age = args.age.trim().toLowerCase();
148
+ }
149
+
150
+ const result = await callApi("getEmail", params);
151
+ return asText(JSON.stringify(result, null, 2));
152
+ }
153
+
154
+ if (name === "apimail_purchased_emails") {
155
+ const token = getTokenFromArgs(args || {});
156
+ const paymentId = Number(args && args.payment_id);
157
+ if (!Number.isInteger(paymentId) || paymentId <= 0) {
158
+ throw new Error("payment_id must be a positive integer.");
159
+ }
160
+ const result = await callApi("purchasedEmails", { token, payment_id: paymentId });
161
+ return asText(JSON.stringify(result, null, 2));
162
+ }
163
+
164
+ throw new Error(`Unknown tool: ${name}`);
165
+ }
166
+
167
+ const server = new Server(
168
+ { name: "apimail-mcp", version: "1.1.0" },
169
+ { capabilities: { tools: { listChanged: false } } }
170
+ );
171
+
172
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOL_DEFS }));
173
+
174
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
175
+ const name = request.params?.name;
176
+ const args = request.params?.arguments || {};
177
+ try {
178
+ return await handleTool(name, args);
179
+ } catch (error) {
180
+ return asText(String(error?.message || error), true);
181
+ }
182
+ });
183
+
184
+ const transport = new StdioServerTransport();
185
+ await server.connect(transport);