stormgtm-mcp 0.1.0 → 0.1.1
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 +3 -1
- package/dist/index.js +39 -21
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -37,7 +37,9 @@ Then call `whoami`. It returns the account email and credit balance.
|
|
|
37
37
|
|
|
38
38
|
## Authentication
|
|
39
39
|
|
|
40
|
-
The server reads `STORMGTM_API_KEY` first, then `~/.stormgtm/config.json` (the same file `stormgtm login` writes). `STORMGTM_API_URL` overrides the API URL (default `https://stormgtm.com`).
|
|
40
|
+
The server reads `STORMGTM_API_KEY` first, then `~/.stormgtm/config.json` (the same file `stormgtm login` writes), on every tool call. `STORMGTM_API_URL` overrides the API URL (default `https://stormgtm.com`).
|
|
41
|
+
|
|
42
|
+
The server starts without a key. Until you sign in, every tool returns a short message saying how to; after `stormgtm login` the next call works without restarting the server.
|
|
41
43
|
|
|
42
44
|
```bash
|
|
43
45
|
npm i -g stormgtm
|
package/dist/index.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
-
import { clientFromEnv } from "stormgtm";
|
|
5
|
+
import { clientFromEnv, resolveApiKey } from "stormgtm";
|
|
6
6
|
|
|
7
7
|
// src/server.ts
|
|
8
8
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
-
import { StormGTMError, summarize } from "stormgtm";
|
|
9
|
+
import { describeError, MissingApiKeyError, StormGTMError, summarize } from "stormgtm";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
12
|
// src/version.ts
|
|
@@ -59,11 +59,26 @@ var policyShape = z.object({
|
|
|
59
59
|
function ok(summary, data) {
|
|
60
60
|
return { content: [{ type: "text", text: summary }], structuredContent: data };
|
|
61
61
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
var NO_KEY_MESSAGE = "StormGTM is not signed in. Run `npx stormgtm login` (or `stormgtm login` if installed globally), or set STORMGTM_API_KEY in this MCP server's environment, then call the tool again.";
|
|
63
|
+
function failureMessage(error, apiUrl) {
|
|
64
|
+
if (error instanceof MissingApiKeyError) return NO_KEY_MESSAGE;
|
|
65
|
+
if (error instanceof StormGTMError) {
|
|
66
|
+
const detail = error.status === 401 || error.status === 402 || error.status === 429 ? describeError(error, apiUrl).message : error.message;
|
|
67
|
+
return `StormGTM request failed (${error.code}): ${detail}`;
|
|
68
|
+
}
|
|
69
|
+
return describeError(error, apiUrl).message;
|
|
65
70
|
}
|
|
66
|
-
function createServer(
|
|
71
|
+
function createServer(source) {
|
|
72
|
+
const api = typeof source === "function" ? source : () => source;
|
|
73
|
+
const fail = (error) => {
|
|
74
|
+
let apiUrl;
|
|
75
|
+
try {
|
|
76
|
+
apiUrl = api().baseUrl;
|
|
77
|
+
} catch {
|
|
78
|
+
apiUrl = void 0;
|
|
79
|
+
}
|
|
80
|
+
return { content: [{ type: "text", text: failureMessage(error, apiUrl) }], isError: true };
|
|
81
|
+
};
|
|
67
82
|
const server2 = new McpServer({ name: "stormgtm", version: MCP_VERSION }, { instructions: INSTRUCTIONS });
|
|
68
83
|
server2.registerTool(
|
|
69
84
|
"check_lead",
|
|
@@ -79,7 +94,7 @@ function createServer(client) {
|
|
|
79
94
|
},
|
|
80
95
|
async ({ email, context, tier, policy }) => {
|
|
81
96
|
try {
|
|
82
|
-
const result = await
|
|
97
|
+
const result = await api().check({ email, context, tier, policy });
|
|
83
98
|
return ok(summarize(result), result);
|
|
84
99
|
} catch (error) {
|
|
85
100
|
return fail(error);
|
|
@@ -99,7 +114,7 @@ function createServer(client) {
|
|
|
99
114
|
},
|
|
100
115
|
async ({ leads, tier, policy }) => {
|
|
101
116
|
try {
|
|
102
|
-
const created = await
|
|
117
|
+
const created = await api().createBatch({ leads, tier, policy });
|
|
103
118
|
return ok(`Batch ${created.id} queued with ${created.total} leads (up to ${created.maxCredits} credits). Call batch_status with this id.`, created);
|
|
104
119
|
} catch (error) {
|
|
105
120
|
return fail(error);
|
|
@@ -119,7 +134,7 @@ function createServer(client) {
|
|
|
119
134
|
},
|
|
120
135
|
async ({ id, offset, limit }) => {
|
|
121
136
|
try {
|
|
122
|
-
const status = await
|
|
137
|
+
const status = await api().batch(id, { offset, limit: limit ?? 200 });
|
|
123
138
|
const counts = {};
|
|
124
139
|
for (const row of status.results) {
|
|
125
140
|
const key = row.result?.verdict ?? row.status;
|
|
@@ -146,7 +161,7 @@ function createServer(client) {
|
|
|
146
161
|
},
|
|
147
162
|
async ({ email, kind, detail }) => {
|
|
148
163
|
try {
|
|
149
|
-
const result = await
|
|
164
|
+
const result = await api().reportOutcome({ email, kind, detail });
|
|
150
165
|
return ok(result.recorded ? `Recorded ${kind} for ${email}.` : `Not recorded: ${result.rejected.map((r) => r.reason).join(", ")}`, result);
|
|
151
166
|
} catch (error) {
|
|
152
167
|
return fail(error);
|
|
@@ -162,6 +177,7 @@ function createServer(client) {
|
|
|
162
177
|
},
|
|
163
178
|
async () => {
|
|
164
179
|
try {
|
|
180
|
+
const client = api();
|
|
165
181
|
const me = await client.me();
|
|
166
182
|
return ok(`${me.email}: ${me.credits} credits (${client.baseUrl})`, { id: me.id, email: me.email, credits: me.credits, apiUrl: client.baseUrl });
|
|
167
183
|
} catch (error) {
|
|
@@ -178,7 +194,7 @@ function createServer(client) {
|
|
|
178
194
|
},
|
|
179
195
|
async () => {
|
|
180
196
|
try {
|
|
181
|
-
const me = await
|
|
197
|
+
const me = await api().me();
|
|
182
198
|
return ok(`${me.credits} credits. fast=${me.pricing.fast}, deep=${me.pricing.deep}, unknown=free. 30d: ${me.usage30d.total} checks.`, me);
|
|
183
199
|
} catch (error) {
|
|
184
200
|
return fail(error);
|
|
@@ -206,7 +222,7 @@ function createServer(client) {
|
|
|
206
222
|
},
|
|
207
223
|
async ({ messages }) => {
|
|
208
224
|
try {
|
|
209
|
-
const result = await
|
|
225
|
+
const result = await api().send(messages);
|
|
210
226
|
const lines = [`${result.accepted.length} queued, ${result.rejected.length} rejected.`];
|
|
211
227
|
for (const entry of result.accepted) lines.push(`queued ${entry.id} \u2192 ${entry.to}${entry.duplicate ? " (already queued)" : ""}`);
|
|
212
228
|
for (const entry of result.rejected) lines.push(`rejected #${entry.index}: ${entry.code} \u2014 ${entry.message}`);
|
|
@@ -225,7 +241,7 @@ function createServer(client) {
|
|
|
225
241
|
},
|
|
226
242
|
async () => {
|
|
227
243
|
try {
|
|
228
|
-
const domains = await
|
|
244
|
+
const domains = await api().domains();
|
|
229
245
|
const lines = domains.length ? domains.map((domain) => `${domain.name} (${domain.id}): ${domain.status}, ${domain.warmup.paused ? "paused" : `${domain.warmup.dailyCap}/day`}`) : ["No sending domains yet. Add one in the StormGTM dashboard."];
|
|
230
246
|
return ok(lines.join("\n"), { domains });
|
|
231
247
|
} catch (error) {
|
|
@@ -242,7 +258,7 @@ function createServer(client) {
|
|
|
242
258
|
},
|
|
243
259
|
async ({ id }) => {
|
|
244
260
|
try {
|
|
245
|
-
const health = await
|
|
261
|
+
const health = await api().domainHealth(id);
|
|
246
262
|
const pct = (value) => `${(value * 100).toFixed(1)}%`;
|
|
247
263
|
const state = health.warmup.paused ? `paused (${health.warmup.pausedReason ?? "manual"})` : `${health.warmup.remainingToday}/${health.warmup.dailyCap} left today`;
|
|
248
264
|
return ok(`${health.name}: ${state}. 7d: ${health.last7Days.sent} sent, ${pct(health.last7Days.bounceRate)} bounced, ${pct(health.last7Days.complaintRate)} complaints.`, health);
|
|
@@ -260,7 +276,7 @@ function createServer(client) {
|
|
|
260
276
|
},
|
|
261
277
|
async ({ id }) => {
|
|
262
278
|
try {
|
|
263
|
-
const email = await
|
|
279
|
+
const email = await api().email(id);
|
|
264
280
|
return ok(`${email.id} \u2192 ${email.to}: ${email.status}, delivery ${email.delivery}${email.error ? ` (${email.error})` : ""}`, email);
|
|
265
281
|
} catch (error) {
|
|
266
282
|
return fail(error);
|
|
@@ -281,7 +297,7 @@ function createServer(client) {
|
|
|
281
297
|
},
|
|
282
298
|
async ({ name, from, replyTo, steps }) => {
|
|
283
299
|
try {
|
|
284
|
-
const sequence = await
|
|
300
|
+
const sequence = await api().createSequence({ name, from, replyTo, steps });
|
|
285
301
|
const variables = sequence.variables.length ? ` Leads need: ${sequence.variables.join(", ")}.` : "";
|
|
286
302
|
return ok(`Created sequence ${sequence.id} "${sequence.name}" with ${sequence.steps.length} steps.${variables} Enroll leads with enroll_leads.`, sequence);
|
|
287
303
|
} catch (error) {
|
|
@@ -301,7 +317,7 @@ function createServer(client) {
|
|
|
301
317
|
},
|
|
302
318
|
async ({ sequenceId, leads }) => {
|
|
303
319
|
try {
|
|
304
|
-
const result = await
|
|
320
|
+
const result = await api().enroll(sequenceId, leads);
|
|
305
321
|
const fresh = result.accepted.filter((entry) => !entry.duplicate).length;
|
|
306
322
|
const lines = [`${fresh} enrolled, ${result.accepted.length - fresh} already enrolled, ${result.rejected.length} rejected. Up to ${result.maxCredits} credits.`];
|
|
307
323
|
for (const entry of result.rejected.slice(0, 50)) lines.push(`rejected #${entry.index}: ${entry.code} \u2014 ${entry.message}`);
|
|
@@ -321,11 +337,11 @@ function createServer(client) {
|
|
|
321
337
|
async ({ id }) => {
|
|
322
338
|
try {
|
|
323
339
|
if (!id) {
|
|
324
|
-
const sequences = await
|
|
340
|
+
const sequences = await api().sequences();
|
|
325
341
|
const lines2 = sequences.length ? sequences.map((entry) => `${entry.id} "${entry.name}"${entry.archivedAt ? " (archived)" : ""}: ${entry.steps} steps, ${entry.counts.active} active, ${entry.counts.completed} completed, ${entry.counts.stopped} stopped`) : ["No sequences yet. Create one with create_sequence."];
|
|
326
342
|
return ok(lines2.join("\n"), { sequences });
|
|
327
343
|
}
|
|
328
|
-
const [sequence, enrollments] = await Promise.all([
|
|
344
|
+
const [sequence, enrollments] = await Promise.all([api().sequence(id), api().enrollments(id, 100)]);
|
|
329
345
|
const lines = [`${sequence.id} "${sequence.name}" from ${sequence.from}: ${sequence.steps.length} steps`];
|
|
330
346
|
for (const entry of enrollments.slice(0, 50)) lines.push(`${entry.email}: ${entry.status}${entry.stopReason ? ` (${entry.stopReason})` : entry.nextStep !== null ? `, step ${entry.nextStep + 1} next` : ""}`);
|
|
331
347
|
return ok(lines.join("\n"), { sequence, enrollments });
|
|
@@ -343,7 +359,7 @@ function createServer(client) {
|
|
|
343
359
|
},
|
|
344
360
|
async ({ sequenceId, enrollmentId }) => {
|
|
345
361
|
try {
|
|
346
|
-
const result = await
|
|
362
|
+
const result = await api().stopEnrollment(sequenceId, enrollmentId);
|
|
347
363
|
return ok(`Stopped ${result.id}.`, result);
|
|
348
364
|
} catch (error) {
|
|
349
365
|
return fail(error);
|
|
@@ -354,5 +370,7 @@ function createServer(client) {
|
|
|
354
370
|
}
|
|
355
371
|
|
|
356
372
|
// src/index.ts
|
|
357
|
-
|
|
373
|
+
if (!resolveApiKey()) process.stderr.write(`stormgtm-mcp: ${NO_KEY_MESSAGE}
|
|
374
|
+
`);
|
|
375
|
+
var server = createServer(() => clientFromEnv());
|
|
358
376
|
await server.connect(new StdioServerTransport());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stormgtm-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "StormGTM MCP server — qualify leads with Barometer and send through your own domains, over stdio",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"lead-qualification"
|
|
14
14
|
],
|
|
15
15
|
"bin": {
|
|
16
|
-
"stormgtm-mcp": "
|
|
16
|
+
"stormgtm-mcp": "dist/index.js"
|
|
17
17
|
},
|
|
18
18
|
"homepage": "https://github.com/marginsystems/stormgtm-mcp",
|
|
19
19
|
"repository": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
42
|
-
"stormgtm": "0.1.
|
|
42
|
+
"stormgtm": "0.1.1",
|
|
43
43
|
"zod": "^4.6.5"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|