notifycx-mcp 1.1.0 → 1.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
@@ -6,6 +6,8 @@ Use this in Cursor, Claude Code, or other MCP hosts when a coding agent should c
6
6
 
7
7
  ## Install (Cursor)
8
8
 
9
+ **Already have a key:**
10
+
9
11
  ```json
10
12
  {
11
13
  "mcpServers": {
@@ -20,23 +22,27 @@ Use this in Cursor, Claude Code, or other MCP hosts when a coding agent should c
20
22
  }
21
23
  ```
22
24
 
23
- Get your API key from [Credentials](https://notify.cx/dashboard/credentials). Docs: [notify.cx/mcp](https://notify.cx/mcp).
25
+ **No key yet:** omit `NOTIFY_API_KEY`, call `bootstrap_start` → user pastes email OTP → `bootstrap_confirm` → put the returned `api_key` into MCP env. See [agent bootstrap docs](https://notify.cx/docs/agent-bootstrap-no-dashboard).
26
+
27
+ Never paste API key secrets into chat logs.
24
28
 
25
29
  ## Tools
26
30
 
27
31
  | Tool | When to use |
28
32
  |------|-------------|
33
+ | `bootstrap_start` / `bootstrap_confirm` | Sign up or recover without the dashboard (no API key required) |
29
34
  | `send_email` | Production transactional send (verified `from`) |
30
35
  | `send_test_email` | Non-delivering API rehearsal |
31
- | `add_domain` | Add domain + get DNS records (SPF/DKIM) |
36
+ | `add_domain` | Add domain + structured `dnsRecords` (type/name/value) for the user to publish at their host |
32
37
  | `check_domain` | Poll verification after DNS is configured |
33
- | `list_domains` / `get_domain` | Domain / SPF / DKIM status |
38
+ | `list_domains` / `get_domain` | Domain status + `dnsRecords` |
34
39
  | `list_webhooks` / `create_webhook` / `update_webhook` / `delete_webhook` / `get_webhook` / `test_webhook` | Webhook setup (Pro+) |
35
40
  | `list_email_logs` | Recent delivery logs |
36
41
  | `get_usage` | Quota / account_verified |
37
- | `list_api_keys` | Key metadata (prefix only — create keys in the dashboard) |
42
+ | `list_api_keys` / `create_api_key` / `regenerate_api_key` / `delete_api_key` | Manage keys |
43
+ | `create_billing_checkout` | Stripe Checkout URL for Pro/Scale |
38
44
 
39
- Typical setup path: `add_domain` → add DNS → `check_domain` → `send_email`. Optional: `create_webhook` after a verified domain.
45
+ Typical setup path: bootstrap → `add_domain` → user publishes `dnsRecords` → `check_domain` → `send_email`.
40
46
 
41
47
  ## Pricing
42
48
 
package/dist/index.d.ts CHANGED
@@ -1,2 +1 @@
1
-
2
- export { }
1
+ #!/usr/bin/env node
package/dist/index.js CHANGED
@@ -1,15 +1,22 @@
1
1
  #!/usr/bin/env node
2
+ #!/usr/bin/env node
2
3
 
3
4
  // src/index.ts
4
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
7
  import Notify, { NotifyError } from "notifycx";
7
8
  import { z } from "zod";
9
+ function baseUrl() {
10
+ return process.env.NOTIFY_BASE_URL;
11
+ }
12
+ function optionalApiKey() {
13
+ return process.env.NOTIFY_API_KEY;
14
+ }
8
15
  function requireApiKey() {
9
- const apiKey = process.env.NOTIFY_API_KEY;
16
+ const apiKey = optionalApiKey();
10
17
  if (!apiKey) {
11
18
  throw new Error(
12
- "Missing NOTIFY_API_KEY. Set it in your MCP server env (dashboard \u2192 Credentials)."
19
+ "Missing NOTIFY_API_KEY. Run bootstrap_start + bootstrap_confirm first, then set NOTIFY_API_KEY in MCP env to the returned api_key (never paste secrets into chat)."
13
20
  );
14
21
  }
15
22
  return apiKey;
@@ -17,7 +24,7 @@ function requireApiKey() {
17
24
  function client() {
18
25
  return new Notify({
19
26
  apiKey: requireApiKey(),
20
- baseUrl: process.env.NOTIFY_BASE_URL,
27
+ baseUrl: baseUrl(),
21
28
  client: "mcp"
22
29
  });
23
30
  }
@@ -77,8 +84,56 @@ var webhookWriteShape = {
77
84
  };
78
85
  var server = new McpServer({
79
86
  name: "notifycx-mcp",
80
- version: "1.1.0"
87
+ version: "1.2.0"
81
88
  });
89
+ server.tool(
90
+ "bootstrap_start",
91
+ "Start Notify agent signup or key recovery without the dashboard. Sends a 6-digit email OTP. No NOTIFY_API_KEY required. Ask the user to paste the code, then call bootstrap_confirm. Requires acceptTerms: true (https://notify.cx/terms).",
92
+ {
93
+ email: z.string().email().describe("User email for the Notify account"),
94
+ intent: z.enum(["signup", "recover"]).optional().describe("signup (default) or recover existing account"),
95
+ acceptTerms: z.boolean().describe("Must be true \u2014 user accepts Terms and AUP")
96
+ },
97
+ async (args) => {
98
+ try {
99
+ return ok(
100
+ await Notify.bootstrapStart({
101
+ email: args.email,
102
+ intent: args.intent,
103
+ acceptTerms: args.acceptTerms,
104
+ baseUrl: baseUrl()
105
+ })
106
+ );
107
+ } catch (err) {
108
+ return fail(err);
109
+ }
110
+ }
111
+ );
112
+ server.tool(
113
+ "bootstrap_confirm",
114
+ "Confirm the email OTP from bootstrap_start. Returns api_key once \u2014 instruct the user to put it in MCP env NOTIFY_API_KEY (do not leave the secret in chat). Then use add_domain / send_email. No NOTIFY_API_KEY required for this call.",
115
+ {
116
+ email: z.string().email(),
117
+ code: z.string().regex(/^\d{6}$/).describe("6-digit code from email"),
118
+ intent: z.enum(["signup", "recover"]).optional(),
119
+ clientHint: z.string().max(64).optional().describe("e.g. cursor, chatgpt")
120
+ },
121
+ async (args) => {
122
+ try {
123
+ return ok(
124
+ await Notify.bootstrapConfirm({
125
+ email: args.email,
126
+ code: args.code,
127
+ intent: args.intent,
128
+ clientHint: args.clientHint,
129
+ baseUrl: baseUrl()
130
+ })
131
+ );
132
+ } catch (err) {
133
+ return fail(err);
134
+ }
135
+ }
136
+ );
82
137
  server.tool(
83
138
  "send_email",
84
139
  "Send transactional email via Notify (password reset, receipt, magic link, invoice, etc.) with a verified from domain. Use when the user wants production delivery. Prefer html for the body. For non-delivering API rehearsal before domain verification, use send_test_email instead.",
@@ -105,7 +160,7 @@ server.tool(
105
160
  );
106
161
  server.tool(
107
162
  "list_domains",
108
- "List Notify sending domains and verification status (SPF/DKIM). Use when wiring a production from address or debugging domain auth. Includes DNS tokens when present.",
163
+ "List Notify sending domains and verification status (SPF/DKIM). Includes structured dnsRecords (type/name/value) for the user to publish at their DNS host.",
109
164
  {},
110
165
  async () => {
111
166
  try {
@@ -117,7 +172,7 @@ server.tool(
117
172
  );
118
173
  server.tool(
119
174
  "get_domain",
120
- "Get one Notify sending domain by hostname, including DNS records/tokens for pending verification.",
175
+ "Get one Notify sending domain by hostname, including dnsRecords (type/name/value) and raw DNS tokens for pending verification. Show dnsRecords to the user so they can add them at their hosting provider.",
121
176
  {
122
177
  domain: z.string().min(1).describe("Domain hostname, e.g. example.com")
123
178
  },
@@ -131,7 +186,7 @@ server.tool(
131
186
  );
132
187
  server.tool(
133
188
  "add_domain",
134
- "Add a sending domain and start SPF/DKIM verification. Returns DNS records the user must add. After DNS is configured, call check_domain. Then use send_email with a from address on that domain.",
189
+ "Add a sending domain and start SPF/DKIM verification. Returns dnsRecords (type/name/value) the user must add at their DNS host \u2014 Notify does not publish DNS. After DNS is configured, call check_domain. Then use send_email with a from address on that domain.",
135
190
  {
136
191
  domain: z.string().min(1).describe("Domain hostname to verify, e.g. example.com"),
137
192
  dnsProvider: z.enum([
@@ -142,7 +197,7 @@ server.tool(
142
197
  "godaddy",
143
198
  "route53",
144
199
  "other"
145
- ]).optional().describe("Optional DNS provider hint for dashboard UX")
200
+ ]).optional().describe("Optional DNS provider hint for host/name formatting")
146
201
  },
147
202
  async (args) => {
148
203
  try {
@@ -154,7 +209,7 @@ server.tool(
154
209
  );
155
210
  server.tool(
156
211
  "check_domain",
157
- "Poll SES for domain SPF/DKIM verification status after DNS records were added. Call after add_domain. When both succeed, production send_email with that from domain is unlocked.",
212
+ "Poll SES for domain SPF/DKIM verification status after DNS records were added. Call after add_domain. When both succeed, production send_email with that from domain is unlocked and the plan email limit lifts.",
158
213
  {
159
214
  domain: z.string().min(1).describe("Domain hostname to check")
160
215
  },
@@ -196,7 +251,7 @@ server.tool(
196
251
  );
197
252
  server.tool(
198
253
  "list_api_keys",
199
- "List API key metadata for the account (id, name, prefix only \u2014 never the full secret). Create or rotate keys in the Notify dashboard Credentials page.",
254
+ "List API key metadata for the account (id, name, prefix only \u2014 never the full secret).",
200
255
  {},
201
256
  async () => {
202
257
  try {
@@ -206,6 +261,64 @@ server.tool(
206
261
  }
207
262
  }
208
263
  );
264
+ server.tool(
265
+ "create_api_key",
266
+ "Create an API key (full_access required). Free plan allows one key. Prefer sending_access + domainId for app keys on Pro+.",
267
+ {
268
+ name: z.string().min(1),
269
+ permission: z.enum(["full_access", "sending_access"]).optional(),
270
+ domainId: z.string().uuid().optional()
271
+ },
272
+ async (args) => {
273
+ try {
274
+ return ok(await client().createApiKey(args));
275
+ } catch (err) {
276
+ return fail(err);
277
+ }
278
+ }
279
+ );
280
+ server.tool(
281
+ "regenerate_api_key",
282
+ "Rotate an API key secret by keyId. Returns the new api_key once \u2014 update NOTIFY_API_KEY / app secrets.",
283
+ {
284
+ keyId: z.string().uuid()
285
+ },
286
+ async (args) => {
287
+ try {
288
+ return ok(await client().regenerateApiKey(args.keyId));
289
+ } catch (err) {
290
+ return fail(err);
291
+ }
292
+ }
293
+ );
294
+ server.tool(
295
+ "delete_api_key",
296
+ "Deactivate an API key by keyId. Cannot delete the last full_access key.",
297
+ {
298
+ keyId: z.string().uuid()
299
+ },
300
+ async (args) => {
301
+ try {
302
+ return ok(await client().deleteApiKey(args.keyId));
303
+ } catch (err) {
304
+ return fail(err);
305
+ }
306
+ }
307
+ );
308
+ server.tool(
309
+ "create_billing_checkout",
310
+ "Get a Stripe Checkout URL for Pro or Scale (full_access key). User opens the URL in a browser to pay \u2014 not the Notify dashboard.",
311
+ {
312
+ plan: z.enum(["pro", "scale"]).optional()
313
+ },
314
+ async (args) => {
315
+ try {
316
+ return ok(await client().createBillingCheckout({ plan: args.plan }));
317
+ } catch (err) {
318
+ return fail(err);
319
+ }
320
+ }
321
+ );
209
322
  server.tool(
210
323
  "list_webhooks",
211
324
  "List Notify webhook subscriptions (URL, events, domain scope).",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notifycx-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Official MCP server for Notify — transactional email tools for coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,7 +27,7 @@
27
27
  "homepage": "https://notify.cx/mcp",
28
28
  "repository": {
29
29
  "type": "git",
30
- "url": "https://github.com/sunil-sandhu/notifycx.git",
30
+ "url": "https://github.com/notifycx/notifycx.git",
31
31
  "directory": "mcp"
32
32
  },
33
33
  "author": "notify.cx",
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@modelcontextprotocol/sdk": "^1.12.1",
40
- "notifycx": "^1.1.0",
40
+ "notifycx": "^1.2.0",
41
41
  "zod": "^3.24.2"
42
42
  },
43
43
  "devDependencies": {