mxprobe 0.1.0 → 0.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.
package/README.md CHANGED
@@ -45,10 +45,13 @@ environment.
45
45
 
46
46
  ## From Node
47
47
 
48
- ```js
48
+ ```ts
49
49
  import { createClient, checkEmails } from "mxprobe";
50
50
  const client = createClient({ apiKey: process.env.MXPROBE_API_KEY });
51
51
  const { results, summary } = await checkEmails(["a@b.com"], { hosted: true, client });
52
52
  ```
53
53
 
54
+ Typed: `Client`, `CheckOptions`, `CheckOutput` and the response types come
55
+ with the package; `results` is `VerifyResult[]` from `mxprobe-core`.
56
+
54
57
  Docs: https://mxprobe.dev. Source: https://github.com/andrewchmr/mxprobe. MIT.
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/dist/bin.js ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "./cli.js";
3
+ // No forced exit on success: the MCP server may still be answering a call
4
+ // when stdin closes, and `check` has nothing left to wait for anyway.
5
+ main(process.argv.slice(2)).then((code) => {
6
+ process.exitCode = code;
7
+ }, (err) => {
8
+ console.error(err instanceof Error ? err.message : err);
9
+ process.exit(1);
10
+ });
11
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAEhC,0EAA0E;AAC1E,sEAAsE;AACtE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC9B,CAAC,IAAI,EAAE,EAAE;IACP,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC,EACD,CAAC,GAAY,EAAE,EAAE;IACf,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CACF,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { type Env } from "./config.ts";
2
+ import { type FetchLike } from "./client.ts";
3
+ import type { VerifierOptions } from "mxprobe-core";
4
+ /** Anything with a write(string): process.stdout, or a buffer in tests. */
5
+ export interface Sink {
6
+ write(chunk: string): unknown;
7
+ }
8
+ export interface CliIo {
9
+ stdout?: Sink;
10
+ stderr?: Sink;
11
+ env?: Env;
12
+ /** For tests: the fetch the hosted client uses. */
13
+ fetchImpl?: FetchLike;
14
+ /** For tests: a fake resolver for the local DNS tier. */
15
+ verifierOptions?: VerifierOptions;
16
+ }
17
+ /** Exit codes: 0 ok, 1 a kill (single address) or an API error, 2 usage. */
18
+ export declare function main(argv: readonly string[], { stdout, stderr, env, fetchImpl, verifierOptions }?: CliIo): Promise<number>;
19
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAuC,KAAK,GAAG,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAClF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAsBpD,2EAA2E;AAC3E,MAAM,WAAW,IAAI;IACnB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,KAAK;IACpB,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,mDAAmD;IACnD,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,yDAAyD;IACzD,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC;AAED,4EAA4E;AAC5E,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,MAAuB,EAAE,MAAuB,EAAE,GAAiB,EAAE,SAAS,EAAE,eAAe,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkGpL"}
package/dist/cli.js ADDED
@@ -0,0 +1,127 @@
1
+ import { parseArgs } from "node:util";
2
+ import { readFileSync } from "node:fs";
3
+ import { readConfig, writeConfig, configPath } from "./config.js";
4
+ import { createClient, checkEmails, ApiError } from "./client.js";
5
+ import { version } from "./version.js";
6
+ const HELP = `mxprobe ${version}: email verification for AI agents. Verdicts: send, hold, kill.
7
+
8
+ Usage
9
+ mxprobe check <email>... DNS tier, local, free, no key
10
+ mxprobe check --hosted <email>... send the survivors to the hosted SMTP probe (1 credit each)
11
+ mxprobe check --file list.txt one address per line
12
+ mxprobe check --json ... print the verdict objects as JSON
13
+ mxprobe signup <email> get an API key by mail, with 100 free checks
14
+ mxprobe balance credits left on the key
15
+ mxprobe buy [--packs N] a Stripe Checkout link: 9 USD per 10,000 checks
16
+ mxprobe mcp start the MCP server on stdio
17
+
18
+ Options
19
+ --smtp run the SMTP probe locally too (needs outbound port 25; most laptops and clouds block it)
20
+ --api-url hosted API base (default https://api.mxprobe.dev)
21
+
22
+ Key: MXPROBE_API_KEY in the environment, else ${configPath()}
23
+ Docs: https://mxprobe.dev`;
24
+ /** Exit codes: 0 ok, 1 a kill (single address) or an API error, 2 usage. */
25
+ export async function main(argv, { stdout = process.stdout, stderr = process.stderr, env = process.env, fetchImpl, verifierOptions } = {}) {
26
+ const { values, positionals } = parseArgs({
27
+ args: [...argv],
28
+ allowPositionals: true,
29
+ options: {
30
+ hosted: { type: "boolean", default: false },
31
+ smtp: { type: "boolean", default: false },
32
+ json: { type: "boolean", default: false },
33
+ file: { type: "string" },
34
+ packs: { type: "string", default: "1" },
35
+ "api-url": { type: "string" },
36
+ help: { type: "boolean", short: "h", default: false },
37
+ version: { type: "boolean", short: "v", default: false },
38
+ },
39
+ });
40
+ if (values.version) {
41
+ stdout.write(`${version}\n`);
42
+ return 0;
43
+ }
44
+ const [command, ...rest] = positionals;
45
+ if (values.help || !command || command === "help") {
46
+ stdout.write(HELP + "\n");
47
+ return 0;
48
+ }
49
+ const cfg = readConfig(env);
50
+ if (values["api-url"])
51
+ cfg.apiUrl = values["api-url"].replace(/\/$/, "");
52
+ const client = createClient({ apiUrl: cfg.apiUrl, apiKey: cfg.apiKey, fetchImpl });
53
+ try {
54
+ switch (command) {
55
+ case "check": {
56
+ const emails = [...rest];
57
+ if (values.file) {
58
+ emails.push(...readFileSync(values.file, "utf8")
59
+ .split("\n")
60
+ .map((l) => l.trim())
61
+ .filter((l) => l && !l.startsWith("#")));
62
+ }
63
+ if (emails.length === 0) {
64
+ stderr.write("usage: mxprobe check [--hosted] [--json] <email>... | --file <list>\n");
65
+ return 2;
66
+ }
67
+ const out = await checkEmails(emails, { hosted: values.hosted, client, smtp: values.smtp, verifierOptions });
68
+ if (values.json) {
69
+ stdout.write(JSON.stringify(out, null, 2) + "\n");
70
+ }
71
+ else {
72
+ const width = Math.max(...out.results.map((r) => r.email.length));
73
+ for (const r of out.results)
74
+ stdout.write(`${r.action.padEnd(4)} ${r.verdict.padEnd(4)} ${r.email.padEnd(width)} ${r.reason}\n`);
75
+ const s = out.summary;
76
+ stderr.write(`\n${s.total} checked: ${s.send} send, ${s.hold} hold, ${s.kill} kill`);
77
+ if (values.hosted)
78
+ stderr.write(`; ${out.hosted} probed on the hosted tier, ${out.credits_left ?? "?"} credits left`);
79
+ else if (!values.smtp)
80
+ stderr.write(`. DNS tier only: a send means the domain takes mail, not that the mailbox exists. Add --hosted to probe the mailbox.`);
81
+ stderr.write("\n");
82
+ }
83
+ return out.summary.kill > 0 && emails.length === 1 ? 1 : 0;
84
+ }
85
+ case "signup": {
86
+ const email = rest[0];
87
+ if (!email) {
88
+ stderr.write("usage: mxprobe signup <email>\n");
89
+ return 2;
90
+ }
91
+ const res = await client.signup(email);
92
+ const path = writeConfig({ api_key: res.api_key, api_url: cfg.apiUrl, email: res.email }, env);
93
+ stdout.write(`Key saved to ${path}. ${res.credits} free checks on it. The key was also mailed to ${res.email}.\n`);
94
+ return 0;
95
+ }
96
+ case "balance": {
97
+ const res = await client.balance();
98
+ stdout.write(values.json ? JSON.stringify(res) + "\n" : `${res.credits} credits left on the key for ${res.email} (${res.checks_total} checks so far)\n`);
99
+ return 0;
100
+ }
101
+ case "buy": {
102
+ const packs = Math.max(1, Number.parseInt(values.packs, 10) || 1);
103
+ const res = await client.checkout(packs);
104
+ stdout.write(values.json ? JSON.stringify(res) + "\n" : `Pay ${res.amount_usd} USD for ${res.credits} checks here:\n${res.url}\nCredits land on the key as soon as Stripe confirms the payment.\n`);
105
+ return 0;
106
+ }
107
+ case "mcp": {
108
+ const { startMcpServer } = await import("./mcp.js");
109
+ await startMcpServer({ env });
110
+ return 0;
111
+ }
112
+ default:
113
+ stderr.write(`unknown command "${command}"\n\n${HELP}\n`);
114
+ return 2;
115
+ }
116
+ }
117
+ catch (err) {
118
+ if (err instanceof ApiError) {
119
+ stderr.write(`${err.status}: ${err.message}\n`);
120
+ if (err.status === 402 && err.body.checkout_hint)
121
+ stderr.write(`${err.body.checkout_hint}\n`);
122
+ return 1;
123
+ }
124
+ throw err;
125
+ }
126
+ }
127
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { type ApiErrorBody, type BalanceResponse, type CheckoutResponse, type SignupResponse, type Summary, type VerifierOptions, type VerifyResponse, type VerifyResult } from "mxprobe-core";
2
+ export type { ApiErrorBody, BalanceResponse, CheckoutResponse, SignupResponse, VerifyResponse } from "mxprobe-core";
3
+ export declare class ApiError extends Error {
4
+ readonly status: number;
5
+ readonly body: Partial<ApiErrorBody>;
6
+ constructor(status: number, body?: Partial<ApiErrorBody>);
7
+ }
8
+ /** The part of `fetch` the client uses; tests pass a fake. */
9
+ export type FetchLike = (url: string, init: {
10
+ method: string;
11
+ headers: Record<string, string>;
12
+ body?: string;
13
+ }) => Promise<{
14
+ ok: boolean;
15
+ status: number;
16
+ text(): Promise<string>;
17
+ }>;
18
+ export interface ClientOptions {
19
+ apiUrl?: string;
20
+ apiKey?: string | null;
21
+ fetchImpl?: FetchLike;
22
+ }
23
+ export interface Client {
24
+ signup(email: string): Promise<SignupResponse>;
25
+ verify(emails: readonly string[]): Promise<VerifyResponse>;
26
+ balance(): Promise<BalanceResponse>;
27
+ checkout(packs?: number): Promise<CheckoutResponse>;
28
+ }
29
+ export declare function createClient({ apiUrl, apiKey, fetchImpl }?: ClientOptions): Client;
30
+ export interface CheckOptions {
31
+ /** Send the DNS survivors to the hosted SMTP probe, one credit each. */
32
+ hosted?: boolean;
33
+ /** Needed when `hosted` is on. Only `verify` is called. */
34
+ client?: Pick<Client, "verify"> | null;
35
+ /** Run the SMTP probe locally (needs outbound port 25). */
36
+ smtp?: boolean;
37
+ verifierOptions?: VerifierOptions;
38
+ }
39
+ export interface CheckOutput {
40
+ results: VerifyResult[];
41
+ summary: Summary;
42
+ /** How many results came from the hosted tier. */
43
+ hosted: number;
44
+ /** Credits left on the key after the hosted calls, when any were made. */
45
+ credits_left: number | null;
46
+ }
47
+ /** The hosted API takes at most this many addresses per call. */
48
+ export declare const HOSTED_BATCH = 100;
49
+ /**
50
+ * The two-tier check the CLI and the MCP server share. The DNS tier runs
51
+ * locally and is free; with `hosted` on, the survivors (send and hold) go to
52
+ * the hosted SMTP probe, one credit each. A DNS kill never costs a credit.
53
+ */
54
+ export declare function checkEmails(emails: readonly string[], { hosted, client, smtp, verifierOptions }?: CheckOptions): Promise<CheckOutput>;
55
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,OAAO,EACZ,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AAItB,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEpH,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAErC,YAAY,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,OAAO,CAAC,YAAY,CAAM,EAK3D;CACF;AAED,8DAA8D;AAC9D,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CAAC;AAErL,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAED,MAAM,WAAW,MAAM;IACrB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAC/C,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAC3D,OAAO,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACrD;AAED,wBAAgB,YAAY,CAAC,EAAE,MAAwB,EAAE,MAAa,EAAE,SAAiB,EAAE,GAAE,aAAkB,GAAG,MAAM,CA2BvH;AAED,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC;IACvC,2DAA2D;IAC3D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,iEAAiE;AACjE,eAAO,MAAM,YAAY,MAAM,CAAC;AAEhC;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,MAAc,EAAE,MAAa,EAAE,IAAY,EAAE,eAAoB,EAAE,GAAE,YAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAyB3K"}
package/dist/client.js ADDED
@@ -0,0 +1,79 @@
1
+ // The hosted API client. Also the package's main export, so a Node agent can
2
+ // `import { createClient } from "mxprobe"` without the CLI.
3
+ import { createVerifier, summarize, } from "mxprobe-core";
4
+ import { DEFAULT_API_URL } from "./config.js";
5
+ export class ApiError extends Error {
6
+ status;
7
+ body;
8
+ constructor(status, body = {}) {
9
+ super(body.message || body.error || `HTTP ${status}`);
10
+ this.name = "ApiError";
11
+ this.status = status;
12
+ this.body = body;
13
+ }
14
+ }
15
+ export function createClient({ apiUrl = DEFAULT_API_URL, apiKey = null, fetchImpl = fetch } = {}) {
16
+ const base = apiUrl.replace(/\/$/, "");
17
+ async function call(method, path, body, { auth = true } = {}) {
18
+ const headers = { "content-type": "application/json", "user-agent": "mxprobe-cli" };
19
+ if (auth) {
20
+ if (!apiKey)
21
+ throw new ApiError(401, { error: "no_api_key", message: "No API key. Run `mxprobe signup you@company.com` or set MXPROBE_API_KEY." });
22
+ headers["authorization"] = `Bearer ${apiKey}`;
23
+ }
24
+ const res = await fetchImpl(`${base}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined });
25
+ const text = await res.text();
26
+ let json;
27
+ try {
28
+ json = text ? JSON.parse(text) : {};
29
+ }
30
+ catch {
31
+ json = { error: "bad_response", message: text.slice(0, 200) };
32
+ }
33
+ if (!res.ok)
34
+ throw new ApiError(res.status, typeof json === "object" && json !== null ? json : {});
35
+ return json;
36
+ }
37
+ return {
38
+ signup: (email) => call("POST", "/v1/signup", { email }, { auth: false }),
39
+ verify: (emails) => call("POST", "/v1/verify", { emails }),
40
+ balance: () => call("GET", "/v1/balance"),
41
+ checkout: (packs = 1) => call("POST", "/v1/credits/checkout", { packs }),
42
+ };
43
+ }
44
+ /** The hosted API takes at most this many addresses per call. */
45
+ export const HOSTED_BATCH = 100;
46
+ /**
47
+ * The two-tier check the CLI and the MCP server share. The DNS tier runs
48
+ * locally and is free; with `hosted` on, the survivors (send and hold) go to
49
+ * the hosted SMTP probe, one credit each. A DNS kill never costs a credit.
50
+ */
51
+ export async function checkEmails(emails, { hosted = false, client = null, smtp = false, verifierOptions = {} } = {}) {
52
+ const local = createVerifier({ ...verifierOptions, smtp });
53
+ const results = await local.verifyBatch(emails);
54
+ let hostedCount = 0;
55
+ let creditsLeft = null;
56
+ if (hosted) {
57
+ if (!client)
58
+ throw new Error("hosted check needs an API client");
59
+ const survivors = results.filter((r) => r.action !== "kill").map((r) => r.email);
60
+ if (survivors.length) {
61
+ const byEmail = new Map();
62
+ for (let i = 0; i < survivors.length; i += HOSTED_BATCH) {
63
+ const res = await client.verify(survivors.slice(i, i + HOSTED_BATCH));
64
+ for (const r of res.results)
65
+ byEmail.set(r.email, r);
66
+ creditsLeft = res.credits_left ?? creditsLeft;
67
+ }
68
+ for (const [i, r] of results.entries()) {
69
+ const h = byEmail.get(r.email);
70
+ if (h) {
71
+ results[i] = h;
72
+ hostedCount++;
73
+ }
74
+ }
75
+ }
76
+ }
77
+ return { results, summary: summarize(results), hosted: hostedCount, credits_left: creditsLeft };
78
+ }
79
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export declare const DEFAULT_API_URL = "https://api.mxprobe.dev";
2
+ /** What the file holds. Every field is optional; unknown fields are kept. */
3
+ export interface ConfigFile {
4
+ api_key?: string;
5
+ api_url?: string;
6
+ email?: string;
7
+ }
8
+ /** The effective config: environment over file over defaults. */
9
+ export interface Config {
10
+ apiKey: string | null;
11
+ apiUrl: string;
12
+ email: string | null;
13
+ }
14
+ export type Env = NodeJS.ProcessEnv;
15
+ export declare function configPath(env?: Env): string;
16
+ export declare function readConfig(env?: Env): Config;
17
+ /** Merge `patch` into the file (mode 0600) and return its path. */
18
+ export declare function writeConfig(patch: ConfigFile, env?: Env): string;
19
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,eAAe,4BAA4B,CAAC;AAEzD,6EAA6E;AAC7E,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,iEAAiE;AACjE,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC;AAEpC,wBAAgB,UAAU,CAAC,GAAG,GAAE,GAAiB,GAAG,MAAM,CAGzD;AAWD,wBAAgB,UAAU,CAAC,GAAG,GAAE,GAAiB,GAAG,MAAM,CAOzD;AAED,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,GAAE,GAAiB,GAAG,MAAM,CAM7E"}
package/dist/config.js ADDED
@@ -0,0 +1,36 @@
1
+ // Where the API key lives: MXPROBE_API_KEY in the environment wins, then
2
+ // ~/.config/mxprobe/config.json (written by `mxprobe signup`).
3
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
6
+ export const DEFAULT_API_URL = "https://api.mxprobe.dev";
7
+ export function configPath(env = process.env) {
8
+ const base = env["XDG_CONFIG_HOME"] || join(env["HOME"] || homedir(), ".config");
9
+ return join(base, "mxprobe", "config.json");
10
+ }
11
+ function readFile(path) {
12
+ try {
13
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
14
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
15
+ }
16
+ catch {
17
+ return {}; // no file yet, or not JSON
18
+ }
19
+ }
20
+ export function readConfig(env = process.env) {
21
+ const file = readFile(configPath(env));
22
+ return {
23
+ apiKey: env["MXPROBE_API_KEY"] || file.api_key || null,
24
+ apiUrl: (env["MXPROBE_API_URL"] || file.api_url || DEFAULT_API_URL).replace(/\/$/, ""),
25
+ email: file.email || null,
26
+ };
27
+ }
28
+ /** Merge `patch` into the file (mode 0600) and return its path. */
29
+ export function writeConfig(patch, env = process.env) {
30
+ const path = configPath(env);
31
+ const next = { ...readFile(path), ...patch };
32
+ mkdirSync(join(path, ".."), { recursive: true });
33
+ writeFileSync(path, JSON.stringify(next, null, 2) + "\n", { mode: 0o600 });
34
+ return path;
35
+ }
36
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,+DAA+D;AAC/D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,eAAe,GAAG,yBAAyB,CAAC;AAkBzD,MAAM,UAAU,UAAU,CAAC,GAAG,GAAQ,OAAO,CAAC,GAAG;IAC/C,MAAM,IAAI,GAAG,GAAG,CAAC,iBAAiB,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IACjF,OAAO,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QAC/D,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,MAAqB,CAAC,CAAC,CAAC,EAAE,CAAC;IACtG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC,CAAC,2BAA2B;IACxC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAG,GAAQ,OAAO,CAAC,GAAG;IAC/C,MAAM,IAAI,GAAG,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IACvC,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,iBAAiB,CAAC,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI;QACtD,MAAM,EAAE,CAAC,GAAG,CAAC,iBAAiB,CAAC,IAAI,IAAI,CAAC,OAAO,IAAI,eAAe,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;QACtF,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI;KAC1B,CAAC;AACJ,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,WAAW,CAAC,KAAiB,EAAE,GAAG,GAAQ,OAAO,CAAC,GAAG;IACnE,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAe,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC;IACzD,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3E,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { ApiError, HOSTED_BATCH, checkEmails, createClient } from "./client.ts";
2
+ export type { ApiErrorBody, BalanceResponse, CheckOptions, CheckOutput, CheckoutResponse, Client, ClientOptions, FetchLike, SignupResponse, VerifyResponse } from "./client.ts";
3
+ export { DEFAULT_API_URL, configPath, readConfig, writeConfig } from "./config.ts";
4
+ export type { Config, ConfigFile } from "./config.ts";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChF,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAChL,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACnF,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // The package's Node API: the hosted client and the two-tier check.
2
+ export { ApiError, HOSTED_BATCH, checkEmails, createClient } from "./client.js";
3
+ export { DEFAULT_API_URL, configPath, readConfig, writeConfig } from "./config.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhF,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { type Env } from "./config.ts";
3
+ import { type FetchLike } from "./client.ts";
4
+ import { type VerifierOptions } from "mxprobe-core";
5
+ export interface McpOptions {
6
+ env?: Env;
7
+ /** For tests: the fetch the hosted client uses. */
8
+ fetchImpl?: FetchLike;
9
+ /** For tests: a fake resolver for the local DNS tier. */
10
+ verifierOptions?: VerifierOptions;
11
+ }
12
+ export declare function buildMcpServer({ env, fetchImpl, verifierOptions }?: McpOptions): McpServer;
13
+ export declare function startMcpServer(opts?: McpOptions): Promise<McpServer>;
14
+ //# sourceMappingURL=mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,OAAO,EAA2B,KAAK,GAAG,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAElF,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAiBlE,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,mDAAmD;IACnD,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,yDAAyD;IACzD,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC;AAED,wBAAgB,cAAc,CAAC,EAAE,GAAiB,EAAE,SAAS,EAAE,eAAe,EAAE,GAAE,UAAe,GAAG,SAAS,CA4F5G;AAED,wBAAsB,cAAc,CAAC,IAAI,GAAE,UAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAM9E"}
package/dist/mcp.js ADDED
@@ -0,0 +1,103 @@
1
+ // The MCP server: the same five things the API does, as tools an agent can
2
+ // call. verify_email and verify_batch run the free DNS tier locally and, when
3
+ // a key is configured, send the survivors to the hosted SMTP probe.
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z } from "zod";
7
+ import { readConfig, writeConfig } from "./config.js";
8
+ import { createClient, checkEmails, ApiError } from "./client.js";
9
+ import { version } from "./version.js";
10
+ import { errorMessage } from "mxprobe-core";
11
+ const VERDICT_DOC = "Each result is { email, action, verdict, reason, checks }. action is send | hold | kill: send means go ahead, hold means send only with a fallback in hand (catch-all, forwarder, greylisted or refused probe), kill means never send (no mail server, or the mailbox does not exist). checks.smtp is skipped on the free DNS tier; the hosted tier probes the mailbox.";
12
+ function text(obj) {
13
+ return { content: [{ type: "text", text: JSON.stringify(obj, null, 2) }], structuredContent: obj };
14
+ }
15
+ function failure(err) {
16
+ const body = err instanceof ApiError
17
+ ? { error: err.body.error ?? "api_error", status: err.status, message: err.message, ...(err.body.checkout_hint ? { checkout_hint: err.body.checkout_hint } : {}) }
18
+ : { error: "failed", message: errorMessage(err) };
19
+ return { ...text(body), isError: true };
20
+ }
21
+ export function buildMcpServer({ env = process.env, fetchImpl, verifierOptions } = {}) {
22
+ const server = new McpServer({ name: "mxprobe", version });
23
+ // Re-read the config on every call so a signup made through the server is
24
+ // picked up without a restart.
25
+ const state = () => {
26
+ const cfg = readConfig(env);
27
+ return { cfg, client: createClient({ apiUrl: cfg.apiUrl, apiKey: cfg.apiKey, fetchImpl }) };
28
+ };
29
+ server.registerTool("verify_email", {
30
+ title: "Verify one email address",
31
+ description: `Check whether an email address can receive mail before sending to it. ${VERDICT_DOC} Uses the hosted SMTP probe (1 credit) when an API key is configured and hosted is not false; otherwise the free local DNS tier.`,
32
+ inputSchema: { email: z.string().describe("The address to check"), hosted: z.boolean().optional().describe("Probe the mailbox on the hosted tier (default: yes when a key is configured)") },
33
+ }, async ({ email, hosted }) => {
34
+ try {
35
+ const { cfg, client } = state();
36
+ const useHosted = hosted ?? !!cfg.apiKey;
37
+ const out = await checkEmails([email], { hosted: useHosted, client, verifierOptions });
38
+ return text({ ...out.results[0], hosted: useHosted, credits_left: out.credits_left });
39
+ }
40
+ catch (err) {
41
+ return failure(err);
42
+ }
43
+ });
44
+ server.registerTool("verify_batch", {
45
+ title: "Verify a list of email addresses",
46
+ description: `Check up to 500 addresses in one call. ${VERDICT_DOC} DNS kills cost nothing; with a key the survivors go to the hosted probe at 1 credit each.`,
47
+ inputSchema: { emails: z.array(z.string()).min(1).max(500).describe("The addresses to check"), hosted: z.boolean().optional().describe("Probe mailboxes on the hosted tier (default: yes when a key is configured)") },
48
+ }, async ({ emails, hosted }) => {
49
+ try {
50
+ const { cfg, client } = state();
51
+ const useHosted = hosted ?? !!cfg.apiKey;
52
+ const out = await checkEmails(emails, { hosted: useHosted, client, verifierOptions });
53
+ return text({ results: out.results, summary: out.summary, hosted: useHosted, credits_left: out.credits_left });
54
+ }
55
+ catch (err) {
56
+ return failure(err);
57
+ }
58
+ });
59
+ server.registerTool("signup", {
60
+ title: "Create an MX Probe API key",
61
+ description: "Sign up with an email address. Returns an API key with 100 free checks and saves it locally for the other tools. The key is also mailed to the address. One key per address.",
62
+ inputSchema: { email: z.string().describe("The operator's email address, where the key is mailed") },
63
+ }, async ({ email }) => {
64
+ try {
65
+ const { cfg, client } = state();
66
+ const res = await client.signup(email);
67
+ const path = writeConfig({ api_key: res.api_key, api_url: cfg.apiUrl, email: res.email }, env);
68
+ return text({ ok: true, email: res.email, credits: res.credits, api_key: res.api_key, saved_to: path, message: res.message });
69
+ }
70
+ catch (err) {
71
+ return failure(err);
72
+ }
73
+ });
74
+ server.registerTool("balance", { title: "Credits left", description: "How many hosted checks are left on the configured API key.", inputSchema: {} }, async () => {
75
+ try {
76
+ return text(await state().client.balance());
77
+ }
78
+ catch (err) {
79
+ return failure(err);
80
+ }
81
+ });
82
+ server.registerTool("buy_credits", {
83
+ title: "Buy credits",
84
+ description: "Get a Stripe Checkout link for more hosted checks: 9 USD per 10,000, one payment, credits never expire. Open the link to pay; credits land on the key when Stripe confirms.",
85
+ inputSchema: { packs: z.number().int().min(1).max(100).optional().describe("How many packs of 10,000 (default 1)") },
86
+ }, async ({ packs }) => {
87
+ try {
88
+ return text(await state().client.checkout(packs ?? 1));
89
+ }
90
+ catch (err) {
91
+ return failure(err);
92
+ }
93
+ });
94
+ return server;
95
+ }
96
+ export async function startMcpServer(opts = {}) {
97
+ const server = buildMcpServer(opts);
98
+ await server.connect(new StdioServerTransport());
99
+ // The transport keeps the process alive while stdin is open; when the
100
+ // client closes it, in-flight calls finish and the loop drains on its own.
101
+ return server;
102
+ }
103
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** The package version, read from package.json at runtime. */
2
+ export declare const version: string;
3
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAIA,8DAA8D;AAC9D,eAAO,MAAM,OAAO,EAAE,MAAoB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { createRequire } from "node:module";
2
+ const pkg = createRequire(import.meta.url)("../package.json");
3
+ /** The package version, read from package.json at runtime. */
4
+ export const version = pkg.version;
5
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,GAAG,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAwB,CAAC;AAErF,8DAA8D;AAC9D,MAAM,CAAC,MAAM,OAAO,GAAW,GAAG,CAAC,OAAO,CAAC"}
package/package.json CHANGED
@@ -1,18 +1,23 @@
1
1
  {
2
2
  "name": "mxprobe",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Email verification for AI agents. One call returns send, hold or kill with the reason. Free DNS tier runs locally; the hosted SMTP probe is 9 USD per 10,000 checks. CLI + MCP server.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
8
- "mxprobe": "./bin/mxprobe.mjs"
8
+ "mxprobe": "./dist/bin.js"
9
9
  },
10
- "main": "./src/client.mjs",
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
11
12
  "exports": {
12
- ".": "./src/client.mjs"
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ }
13
17
  },
14
18
  "files": [
15
- "bin",
19
+ "dist",
20
+ "!dist/.tsbuildinfo",
16
21
  "src",
17
22
  "README.md"
18
23
  ],
@@ -40,9 +45,15 @@
40
45
  "dependencies": {
41
46
  "@modelcontextprotocol/sdk": "^1.30.0",
42
47
  "zod": "^4.5.4",
43
- "mxprobe-core": "^0.1.0"
48
+ "mxprobe-core": "^0.2.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^24",
52
+ "typescript": "^7.0.2"
44
53
  },
45
54
  "scripts": {
46
- "test": "node --test test/*.test.mjs"
55
+ "build": "tsc -b",
56
+ "typecheck": "tsc -p tsconfig.test.json",
57
+ "test": "node --test test/*.test.ts"
47
58
  }
48
59
  }
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import { main } from "../src/cli.mjs";
2
+ import { main } from "./cli.ts";
3
3
 
4
4
  // No forced exit on success: the MCP server may still be answering a call
5
5
  // when stdin closes, and `check` has nothing left to wait for anyway.
6
6
  main(process.argv.slice(2)).then(
7
7
  (code) => {
8
- process.exitCode = code ?? 0;
8
+ process.exitCode = code;
9
9
  },
10
- (err) => {
11
- console.error(err?.message ?? err);
10
+ (err: unknown) => {
11
+ console.error(err instanceof Error ? err.message : err);
12
12
  process.exit(1);
13
13
  },
14
14
  );
@@ -1,11 +1,9 @@
1
1
  import { parseArgs } from "node:util";
2
2
  import { readFileSync } from "node:fs";
3
- import { createRequire } from "node:module";
4
- import { readConfig, writeConfig, configPath } from "./config.mjs";
5
- import { createClient, checkEmails, ApiError } from "./client.mjs";
6
-
7
- const require = createRequire(import.meta.url);
8
- const { version } = require("../package.json");
3
+ import { readConfig, writeConfig, configPath, type Env } from "./config.ts";
4
+ import { createClient, checkEmails, ApiError, type FetchLike } from "./client.ts";
5
+ import type { VerifierOptions } from "mxprobe-core";
6
+ import { version } from "./version.ts";
9
7
 
10
8
  const HELP = `mxprobe ${version}: email verification for AI agents. Verdicts: send, hold, kill.
11
9
 
@@ -26,9 +24,25 @@ Options
26
24
  Key: MXPROBE_API_KEY in the environment, else ${configPath()}
27
25
  Docs: https://mxprobe.dev`;
28
26
 
29
- export async function main(argv, { stdout = process.stdout, stderr = process.stderr, env = process.env } = {}) {
27
+ /** Anything with a write(string): process.stdout, or a buffer in tests. */
28
+ export interface Sink {
29
+ write(chunk: string): unknown;
30
+ }
31
+
32
+ export interface CliIo {
33
+ stdout?: Sink;
34
+ stderr?: Sink;
35
+ env?: Env;
36
+ /** For tests: the fetch the hosted client uses. */
37
+ fetchImpl?: FetchLike;
38
+ /** For tests: a fake resolver for the local DNS tier. */
39
+ verifierOptions?: VerifierOptions;
40
+ }
41
+
42
+ /** Exit codes: 0 ok, 1 a kill (single address) or an API error, 2 usage. */
43
+ export async function main(argv: readonly string[], { stdout = process.stdout, stderr = process.stderr, env = process.env, fetchImpl, verifierOptions }: CliIo = {}): Promise<number> {
30
44
  const { values, positionals } = parseArgs({
31
- args: argv,
45
+ args: [...argv],
32
46
  allowPositionals: true,
33
47
  options: {
34
48
  hosted: { type: "boolean", default: false },
@@ -41,13 +55,19 @@ export async function main(argv, { stdout = process.stdout, stderr = process.std
41
55
  version: { type: "boolean", short: "v", default: false },
42
56
  },
43
57
  });
44
- if (values.version) return void stdout.write(`${version}\n`);
58
+ if (values.version) {
59
+ stdout.write(`${version}\n`);
60
+ return 0;
61
+ }
45
62
  const [command, ...rest] = positionals;
46
- if (values.help || !command || command === "help") return void stdout.write(HELP + "\n");
63
+ if (values.help || !command || command === "help") {
64
+ stdout.write(HELP + "\n");
65
+ return 0;
66
+ }
47
67
 
48
68
  const cfg = readConfig(env);
49
69
  if (values["api-url"]) cfg.apiUrl = values["api-url"].replace(/\/$/, "");
50
- const client = createClient({ apiUrl: cfg.apiUrl, apiKey: cfg.apiKey });
70
+ const client = createClient({ apiUrl: cfg.apiUrl, apiKey: cfg.apiKey, fetchImpl });
51
71
 
52
72
  try {
53
73
  switch (command) {
@@ -65,7 +85,7 @@ export async function main(argv, { stdout = process.stdout, stderr = process.std
65
85
  stderr.write("usage: mxprobe check [--hosted] [--json] <email>... | --file <list>\n");
66
86
  return 2;
67
87
  }
68
- const out = await checkEmails(emails, { hosted: values.hosted, client, smtp: values.smtp });
88
+ const out = await checkEmails(emails, { hosted: values.hosted, client, smtp: values.smtp, verifierOptions });
69
89
  if (values.json) {
70
90
  stdout.write(JSON.stringify(out, null, 2) + "\n");
71
91
  } else {
@@ -102,8 +122,8 @@ export async function main(argv, { stdout = process.stdout, stderr = process.std
102
122
  return 0;
103
123
  }
104
124
  case "mcp": {
105
- const { startMcpServer } = await import("./mcp.mjs");
106
- await startMcpServer({ client, config: cfg, env });
125
+ const { startMcpServer } = await import("./mcp.ts");
126
+ await startMcpServer({ env });
107
127
  return 0;
108
128
  }
109
129
  default:
@@ -113,7 +133,7 @@ export async function main(argv, { stdout = process.stdout, stderr = process.std
113
133
  } catch (err) {
114
134
  if (err instanceof ApiError) {
115
135
  stderr.write(`${err.status}: ${err.message}\n`);
116
- if (err.status === 402 && err.body?.checkout_hint) stderr.write(`${err.body.checkout_hint}\n`);
136
+ if (err.status === 402 && err.body.checkout_hint) stderr.write(`${err.body.checkout_hint}\n`);
117
137
  return 1;
118
138
  }
119
139
  throw err;
package/src/client.ts ADDED
@@ -0,0 +1,129 @@
1
+ // The hosted API client. Also the package's main export, so a Node agent can
2
+ // `import { createClient } from "mxprobe"` without the CLI.
3
+ import {
4
+ createVerifier,
5
+ summarize,
6
+ type ApiErrorBody,
7
+ type BalanceResponse,
8
+ type CheckoutResponse,
9
+ type SignupResponse,
10
+ type Summary,
11
+ type VerifierOptions,
12
+ type VerifyResponse,
13
+ type VerifyResult,
14
+ } from "mxprobe-core";
15
+
16
+ import { DEFAULT_API_URL } from "./config.ts";
17
+
18
+ export type { ApiErrorBody, BalanceResponse, CheckoutResponse, SignupResponse, VerifyResponse } from "mxprobe-core";
19
+
20
+ export class ApiError extends Error {
21
+ readonly status: number;
22
+ readonly body: Partial<ApiErrorBody>;
23
+
24
+ constructor(status: number, body: Partial<ApiErrorBody> = {}) {
25
+ super(body.message || body.error || `HTTP ${status}`);
26
+ this.name = "ApiError";
27
+ this.status = status;
28
+ this.body = body;
29
+ }
30
+ }
31
+
32
+ /** The part of `fetch` the client uses; tests pass a fake. */
33
+ export type FetchLike = (url: string, init: { method: string; headers: Record<string, string>; body?: string }) => Promise<{ ok: boolean; status: number; text(): Promise<string> }>;
34
+
35
+ export interface ClientOptions {
36
+ apiUrl?: string;
37
+ apiKey?: string | null;
38
+ fetchImpl?: FetchLike;
39
+ }
40
+
41
+ export interface Client {
42
+ signup(email: string): Promise<SignupResponse>;
43
+ verify(emails: readonly string[]): Promise<VerifyResponse>;
44
+ balance(): Promise<BalanceResponse>;
45
+ checkout(packs?: number): Promise<CheckoutResponse>;
46
+ }
47
+
48
+ export function createClient({ apiUrl = DEFAULT_API_URL, apiKey = null, fetchImpl = fetch }: ClientOptions = {}): Client {
49
+ const base = apiUrl.replace(/\/$/, "");
50
+
51
+ async function call<T>(method: string, path: string, body?: object, { auth = true } = {}): Promise<T> {
52
+ const headers: Record<string, string> = { "content-type": "application/json", "user-agent": "mxprobe-cli" };
53
+ if (auth) {
54
+ if (!apiKey) throw new ApiError(401, { error: "no_api_key", message: "No API key. Run `mxprobe signup you@company.com` or set MXPROBE_API_KEY." });
55
+ headers["authorization"] = `Bearer ${apiKey}`;
56
+ }
57
+ const res = await fetchImpl(`${base}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined });
58
+ const text = await res.text();
59
+ let json: unknown;
60
+ try {
61
+ json = text ? JSON.parse(text) : {};
62
+ } catch {
63
+ json = { error: "bad_response", message: text.slice(0, 200) };
64
+ }
65
+ if (!res.ok) throw new ApiError(res.status, typeof json === "object" && json !== null ? (json as Partial<ApiErrorBody>) : {});
66
+ return json as T;
67
+ }
68
+
69
+ return {
70
+ signup: (email) => call<SignupResponse>("POST", "/v1/signup", { email }, { auth: false }),
71
+ verify: (emails) => call<VerifyResponse>("POST", "/v1/verify", { emails }),
72
+ balance: () => call<BalanceResponse>("GET", "/v1/balance"),
73
+ checkout: (packs = 1) => call<CheckoutResponse>("POST", "/v1/credits/checkout", { packs }),
74
+ };
75
+ }
76
+
77
+ export interface CheckOptions {
78
+ /** Send the DNS survivors to the hosted SMTP probe, one credit each. */
79
+ hosted?: boolean;
80
+ /** Needed when `hosted` is on. Only `verify` is called. */
81
+ client?: Pick<Client, "verify"> | null;
82
+ /** Run the SMTP probe locally (needs outbound port 25). */
83
+ smtp?: boolean;
84
+ verifierOptions?: VerifierOptions;
85
+ }
86
+
87
+ export interface CheckOutput {
88
+ results: VerifyResult[];
89
+ summary: Summary;
90
+ /** How many results came from the hosted tier. */
91
+ hosted: number;
92
+ /** Credits left on the key after the hosted calls, when any were made. */
93
+ credits_left: number | null;
94
+ }
95
+
96
+ /** The hosted API takes at most this many addresses per call. */
97
+ export const HOSTED_BATCH = 100;
98
+
99
+ /**
100
+ * The two-tier check the CLI and the MCP server share. The DNS tier runs
101
+ * locally and is free; with `hosted` on, the survivors (send and hold) go to
102
+ * the hosted SMTP probe, one credit each. A DNS kill never costs a credit.
103
+ */
104
+ export async function checkEmails(emails: readonly string[], { hosted = false, client = null, smtp = false, verifierOptions = {} }: CheckOptions = {}): Promise<CheckOutput> {
105
+ const local = createVerifier({ ...verifierOptions, smtp });
106
+ const results = await local.verifyBatch(emails);
107
+ let hostedCount = 0;
108
+ let creditsLeft: number | null = null;
109
+ if (hosted) {
110
+ if (!client) throw new Error("hosted check needs an API client");
111
+ const survivors = results.filter((r) => r.action !== "kill").map((r) => r.email);
112
+ if (survivors.length) {
113
+ const byEmail = new Map<string, VerifyResult>();
114
+ for (let i = 0; i < survivors.length; i += HOSTED_BATCH) {
115
+ const res = await client.verify(survivors.slice(i, i + HOSTED_BATCH));
116
+ for (const r of res.results) byEmail.set(r.email, r);
117
+ creditsLeft = res.credits_left ?? creditsLeft;
118
+ }
119
+ for (const [i, r] of results.entries()) {
120
+ const h = byEmail.get(r.email);
121
+ if (h) {
122
+ results[i] = h;
123
+ hostedCount++;
124
+ }
125
+ }
126
+ }
127
+ }
128
+ return { results, summary: summarize(results), hosted: hostedCount, credits_left: creditsLeft };
129
+ }
package/src/config.ts ADDED
@@ -0,0 +1,55 @@
1
+ // Where the API key lives: MXPROBE_API_KEY in the environment wins, then
2
+ // ~/.config/mxprobe/config.json (written by `mxprobe signup`).
3
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
6
+
7
+ export const DEFAULT_API_URL = "https://api.mxprobe.dev";
8
+
9
+ /** What the file holds. Every field is optional; unknown fields are kept. */
10
+ export interface ConfigFile {
11
+ api_key?: string;
12
+ api_url?: string;
13
+ email?: string;
14
+ }
15
+
16
+ /** The effective config: environment over file over defaults. */
17
+ export interface Config {
18
+ apiKey: string | null;
19
+ apiUrl: string;
20
+ email: string | null;
21
+ }
22
+
23
+ export type Env = NodeJS.ProcessEnv;
24
+
25
+ export function configPath(env: Env = process.env): string {
26
+ const base = env["XDG_CONFIG_HOME"] || join(env["HOME"] || homedir(), ".config");
27
+ return join(base, "mxprobe", "config.json");
28
+ }
29
+
30
+ function readFile(path: string): ConfigFile {
31
+ try {
32
+ const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
33
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as ConfigFile) : {};
34
+ } catch {
35
+ return {}; // no file yet, or not JSON
36
+ }
37
+ }
38
+
39
+ export function readConfig(env: Env = process.env): Config {
40
+ const file = readFile(configPath(env));
41
+ return {
42
+ apiKey: env["MXPROBE_API_KEY"] || file.api_key || null,
43
+ apiUrl: (env["MXPROBE_API_URL"] || file.api_url || DEFAULT_API_URL).replace(/\/$/, ""),
44
+ email: file.email || null,
45
+ };
46
+ }
47
+
48
+ /** Merge `patch` into the file (mode 0600) and return its path. */
49
+ export function writeConfig(patch: ConfigFile, env: Env = process.env): string {
50
+ const path = configPath(env);
51
+ const next: ConfigFile = { ...readFile(path), ...patch };
52
+ mkdirSync(join(path, ".."), { recursive: true });
53
+ writeFileSync(path, JSON.stringify(next, null, 2) + "\n", { mode: 0o600 });
54
+ return path;
55
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ // The package's Node API: the hosted client and the two-tier check.
2
+ export { ApiError, HOSTED_BATCH, checkEmails, createClient } from "./client.ts";
3
+ export type { ApiErrorBody, BalanceResponse, CheckOptions, CheckOutput, CheckoutResponse, Client, ClientOptions, FetchLike, SignupResponse, VerifyResponse } from "./client.ts";
4
+ export { DEFAULT_API_URL, configPath, readConfig, writeConfig } from "./config.ts";
5
+ export type { Config, ConfigFile } from "./config.ts";
@@ -3,34 +3,44 @@
3
3
  // a key is configured, send the survivors to the hosted SMTP probe.
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
6
7
  import { z } from "zod";
7
- import { createRequire } from "node:module";
8
- import { readConfig, writeConfig } from "./config.mjs";
9
- import { createClient, checkEmails, ApiError } from "./client.mjs";
10
-
11
- const require = createRequire(import.meta.url);
12
- const { version } = require("../package.json");
8
+ import { readConfig, writeConfig, type Env } from "./config.ts";
9
+ import { createClient, checkEmails, ApiError, type FetchLike } from "./client.ts";
10
+ import { version } from "./version.ts";
11
+ import { errorMessage, type VerifierOptions } from "mxprobe-core";
13
12
 
14
13
  const VERDICT_DOC =
15
14
  "Each result is { email, action, verdict, reason, checks }. action is send | hold | kill: send means go ahead, hold means send only with a fallback in hand (catch-all, forwarder, greylisted or refused probe), kill means never send (no mail server, or the mailbox does not exist). checks.smtp is skipped on the free DNS tier; the hosted tier probes the mailbox.";
16
15
 
17
- function text(obj) {
18
- return { content: [{ type: "text", text: JSON.stringify(obj, null, 2) }], structuredContent: obj };
16
+ function text(obj: object): CallToolResult {
17
+ return { content: [{ type: "text", text: JSON.stringify(obj, null, 2) }], structuredContent: obj as Record<string, unknown> };
19
18
  }
20
19
 
21
- function failure(err) {
22
- const body = err instanceof ApiError ? { error: err.body?.error ?? "api_error", status: err.status, message: err.message, ...(err.body?.checkout_hint ? { checkout_hint: err.body.checkout_hint } : {}) } : { error: "failed", message: err.message };
20
+ function failure(err: unknown): CallToolResult {
21
+ const body =
22
+ err instanceof ApiError
23
+ ? { error: err.body.error ?? "api_error", status: err.status, message: err.message, ...(err.body.checkout_hint ? { checkout_hint: err.body.checkout_hint } : {}) }
24
+ : { error: "failed", message: errorMessage(err) };
23
25
  return { ...text(body), isError: true };
24
26
  }
25
27
 
26
- export function buildMcpServer({ env = process.env } = {}) {
28
+ export interface McpOptions {
29
+ env?: Env;
30
+ /** For tests: the fetch the hosted client uses. */
31
+ fetchImpl?: FetchLike;
32
+ /** For tests: a fake resolver for the local DNS tier. */
33
+ verifierOptions?: VerifierOptions;
34
+ }
35
+
36
+ export function buildMcpServer({ env = process.env, fetchImpl, verifierOptions }: McpOptions = {}): McpServer {
27
37
  const server = new McpServer({ name: "mxprobe", version });
28
38
 
29
39
  // Re-read the config on every call so a signup made through the server is
30
40
  // picked up without a restart.
31
41
  const state = () => {
32
42
  const cfg = readConfig(env);
33
- return { cfg, client: createClient({ apiUrl: cfg.apiUrl, apiKey: cfg.apiKey }) };
43
+ return { cfg, client: createClient({ apiUrl: cfg.apiUrl, apiKey: cfg.apiKey, fetchImpl }) };
34
44
  };
35
45
 
36
46
  server.registerTool(
@@ -44,7 +54,7 @@ export function buildMcpServer({ env = process.env } = {}) {
44
54
  try {
45
55
  const { cfg, client } = state();
46
56
  const useHosted = hosted ?? !!cfg.apiKey;
47
- const out = await checkEmails([email], { hosted: useHosted, client });
57
+ const out = await checkEmails([email], { hosted: useHosted, client, verifierOptions });
48
58
  return text({ ...out.results[0], hosted: useHosted, credits_left: out.credits_left });
49
59
  } catch (err) {
50
60
  return failure(err);
@@ -63,7 +73,7 @@ export function buildMcpServer({ env = process.env } = {}) {
63
73
  try {
64
74
  const { cfg, client } = state();
65
75
  const useHosted = hosted ?? !!cfg.apiKey;
66
- const out = await checkEmails(emails, { hosted: useHosted, client });
76
+ const out = await checkEmails(emails, { hosted: useHosted, client, verifierOptions });
67
77
  return text({ results: out.results, summary: out.summary, hosted: useHosted, credits_left: out.credits_left });
68
78
  } catch (err) {
69
79
  return failure(err);
@@ -90,17 +100,13 @@ export function buildMcpServer({ env = process.env } = {}) {
90
100
  },
91
101
  );
92
102
 
93
- server.registerTool(
94
- "balance",
95
- { title: "Credits left", description: "How many hosted checks are left on the configured API key.", inputSchema: {} },
96
- async () => {
97
- try {
98
- return text(await state().client.balance());
99
- } catch (err) {
100
- return failure(err);
101
- }
102
- },
103
- );
103
+ server.registerTool("balance", { title: "Credits left", description: "How many hosted checks are left on the configured API key.", inputSchema: {} }, async () => {
104
+ try {
105
+ return text(await state().client.balance());
106
+ } catch (err) {
107
+ return failure(err);
108
+ }
109
+ });
104
110
 
105
111
  server.registerTool(
106
112
  "buy_credits",
@@ -121,7 +127,7 @@ export function buildMcpServer({ env = process.env } = {}) {
121
127
  return server;
122
128
  }
123
129
 
124
- export async function startMcpServer(opts = {}) {
130
+ export async function startMcpServer(opts: McpOptions = {}): Promise<McpServer> {
125
131
  const server = buildMcpServer(opts);
126
132
  await server.connect(new StdioServerTransport());
127
133
  // The transport keeps the process alive while stdin is open; when the
package/src/version.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ const pkg = createRequire(import.meta.url)("../package.json") as { version: string };
4
+
5
+ /** The package version, read from package.json at runtime. */
6
+ export const version: string = pkg.version;
package/src/client.mjs DELETED
@@ -1,73 +0,0 @@
1
- // The hosted API client. Also the package's main export, so a Node agent can
2
- // `import { createClient } from "mxprobe"` without the CLI.
3
- import { createVerifier, summarize } from "mxprobe-core";
4
-
5
- export class ApiError extends Error {
6
- constructor(status, body) {
7
- super(body?.message || body?.error || `HTTP ${status}`);
8
- this.status = status;
9
- this.body = body;
10
- }
11
- }
12
-
13
- export function createClient({ apiUrl = "https://api.mxprobe.dev", apiKey = null, fetchImpl = fetch } = {}) {
14
- const base = apiUrl.replace(/\/$/, "");
15
-
16
- async function call(method, path, body, { auth = true } = {}) {
17
- const headers = { "content-type": "application/json", "user-agent": "mxprobe-cli" };
18
- if (auth) {
19
- if (!apiKey) throw new ApiError(401, { error: "no_api_key", message: "No API key. Run `mxprobe signup you@company.com` or set MXPROBE_API_KEY." });
20
- headers.authorization = `Bearer ${apiKey}`;
21
- }
22
- const res = await fetchImpl(`${base}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined });
23
- const text = await res.text();
24
- let json;
25
- try {
26
- json = text ? JSON.parse(text) : {};
27
- } catch {
28
- json = { error: "bad_response", message: text.slice(0, 200) };
29
- }
30
- if (!res.ok) throw new ApiError(res.status, json);
31
- return json;
32
- }
33
-
34
- return {
35
- signup: (email) => call("POST", "/v1/signup", { email }, { auth: false }),
36
- verify: (emails) => call("POST", "/v1/verify", { emails }),
37
- balance: () => call("GET", "/v1/balance"),
38
- checkout: (packs = 1) => call("POST", "/v1/credits/checkout", { packs }),
39
- };
40
- }
41
-
42
- /**
43
- * The two-tier check the CLI and the MCP server share. The DNS tier runs
44
- * locally and is free; with `hosted` on, the survivors (send and hold) go to
45
- * the hosted SMTP probe, one credit each. A DNS kill never costs a credit.
46
- */
47
- export async function checkEmails(emails, { hosted = false, client = null, smtp = false, verifierOptions = {} } = {}) {
48
- const local = createVerifier({ ...verifierOptions, smtp });
49
- const results = await local.verifyBatch(emails);
50
- let hostedCount = 0;
51
- let creditsLeft = null;
52
- if (hosted) {
53
- if (!client) throw new Error("hosted check needs an API client");
54
- const survivors = results.filter((r) => r.action !== "kill").map((r) => r.email);
55
- if (survivors.length) {
56
- const byEmail = new Map();
57
- for (let i = 0; i < survivors.length; i += 100) {
58
- const chunk = survivors.slice(i, i + 100);
59
- const res = await client.verify(chunk);
60
- for (const r of res.results) byEmail.set(r.email, r);
61
- creditsLeft = res.credits_left ?? creditsLeft;
62
- }
63
- for (let i = 0; i < results.length; i++) {
64
- const h = byEmail.get(results[i].email);
65
- if (h) {
66
- results[i] = h;
67
- hostedCount++;
68
- }
69
- }
70
- }
71
- }
72
- return { results, summary: summarize(results), hosted: hostedCount, credits_left: creditsLeft };
73
- }
package/src/config.mjs DELETED
@@ -1,40 +0,0 @@
1
- // Where the API key lives: MXPROBE_API_KEY in the environment wins, then
2
- // ~/.config/mxprobe/config.json (written by `mxprobe signup`).
3
- import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
4
- import { homedir } from "node:os";
5
- import { join } from "node:path";
6
-
7
- export const DEFAULT_API_URL = "https://api.mxprobe.dev";
8
-
9
- export function configPath(env = process.env) {
10
- const base = env.XDG_CONFIG_HOME || join(env.HOME || homedir(), ".config");
11
- return join(base, "mxprobe", "config.json");
12
- }
13
-
14
- export function readConfig(env = process.env) {
15
- let file = {};
16
- try {
17
- file = JSON.parse(readFileSync(configPath(env), "utf8"));
18
- } catch {
19
- /* no file yet */
20
- }
21
- return {
22
- apiKey: env.MXPROBE_API_KEY || file.api_key || null,
23
- apiUrl: (env.MXPROBE_API_URL || file.api_url || DEFAULT_API_URL).replace(/\/$/, ""),
24
- email: file.email || null,
25
- };
26
- }
27
-
28
- export function writeConfig(patch, env = process.env) {
29
- const path = configPath(env);
30
- let file = {};
31
- try {
32
- file = JSON.parse(readFileSync(path, "utf8"));
33
- } catch {
34
- /* new file */
35
- }
36
- const next = { ...file, ...patch };
37
- mkdirSync(join(path, ".."), { recursive: true });
38
- writeFileSync(path, JSON.stringify(next, null, 2) + "\n", { mode: 0o600 });
39
- return path;
40
- }