@neschadin/sendgrid-mcp 0.0.0-stage → 3.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.
- package/LICENSE +21 -0
- package/MCP_TOOLS.md +457 -0
- package/README.md +175 -2
- package/bin/sendgrid-mcp +2 -0
- package/mcp.json.example +9 -0
- package/package.json +63 -4
- package/src/client.ts +1585 -0
- package/src/config.ts +197 -0
- package/src/http.ts +132 -0
- package/src/index.ts +147 -0
- package/src/logger.ts +43 -0
- package/src/redact.ts +91 -0
- package/src/tool_signal.ts +50 -0
- package/src/tools/account.ts +566 -0
- package/src/tools/classify_error.ts +133 -0
- package/src/tools/console_settings.ts +329 -0
- package/src/tools/delivery_trace.ts +291 -0
- package/src/tools/diagnostics.ts +1557 -0
- package/src/tools/email.ts +486 -0
- package/src/tools/output_schemas.ts +488 -0
- package/src/tools/preflight.ts +768 -0
- package/src/tools/sync.ts +139 -0
- package/src/tools/templates.ts +602 -0
- package/src/tools/tool_utils.ts +341 -0
- package/src/webhook_receiver.ts +467 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { SendGridClient } from '../client';
|
|
4
|
+
import {
|
|
5
|
+
jsonReadResult,
|
|
6
|
+
SyncTemplateIdsOutputSchema,
|
|
7
|
+
} from './output_schemas';
|
|
8
|
+
import { ensureSafeToolRegistration } from './tool_utils';
|
|
9
|
+
|
|
10
|
+
const ConstantsPathSchema = z
|
|
11
|
+
.string()
|
|
12
|
+
.min(1)
|
|
13
|
+
.refine(
|
|
14
|
+
(value) => /\.(ts|js|mjs|cjs)$/iu.test(value),
|
|
15
|
+
'constantsPath must be a .ts, .js, .mjs, or .cjs file',
|
|
16
|
+
)
|
|
17
|
+
.describe(
|
|
18
|
+
'Absolute path to a local file that exports or defines SENDGRID_TEMPLATES.',
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse SENDGRID_TEMPLATES object from the source file.
|
|
23
|
+
* Returns a map of eventKey → templateId.
|
|
24
|
+
*/
|
|
25
|
+
function parseTemplatesFromSource(source: string): Record<string, string> {
|
|
26
|
+
const result: Record<string, string> = {};
|
|
27
|
+
|
|
28
|
+
const blockMatch = source.match(/SENDGRID_TEMPLATES\s*=\s*\{([^}]+)\}/s);
|
|
29
|
+
if (!blockMatch) return result;
|
|
30
|
+
|
|
31
|
+
const block = blockMatch[1];
|
|
32
|
+
if (!block) return result;
|
|
33
|
+
const lineRe = /['"]?([\w.]+)['"]?\s*:\s*['"]([^'"]+)['"]/g;
|
|
34
|
+
for (const match of block.matchAll(lineRe)) {
|
|
35
|
+
const key = match[1];
|
|
36
|
+
const templateId = match[2];
|
|
37
|
+
if (!key || !templateId) continue;
|
|
38
|
+
result[key] = templateId;
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function registerSyncTools(server: McpServer, client: SendGridClient) {
|
|
44
|
+
ensureSafeToolRegistration(server);
|
|
45
|
+
server.registerTool(
|
|
46
|
+
'sync_template_ids',
|
|
47
|
+
{
|
|
48
|
+
description: [
|
|
49
|
+
'Read a TypeScript/JavaScript file containing a SENDGRID_TEMPLATES object and compare it with real templates in SendGrid.',
|
|
50
|
+
'Reports: which IDs are placeholder (d-xxx), which do not exist in SendGrid, and which are real.',
|
|
51
|
+
'This is an opt-in local sync helper; provide constantsPath explicitly.',
|
|
52
|
+
].join(' '),
|
|
53
|
+
inputSchema: z.object({
|
|
54
|
+
constantsPath: ConstantsPathSchema,
|
|
55
|
+
}),
|
|
56
|
+
outputSchema: SyncTemplateIdsOutputSchema,
|
|
57
|
+
},
|
|
58
|
+
async ({ constantsPath }) => {
|
|
59
|
+
const filePath = constantsPath;
|
|
60
|
+
const file = Bun.file(filePath);
|
|
61
|
+
|
|
62
|
+
if (!(await file.exists())) {
|
|
63
|
+
throw new Error(`File not found: ${filePath}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const source = await file.text();
|
|
67
|
+
const localMap = parseTemplatesFromSource(source);
|
|
68
|
+
|
|
69
|
+
if (Object.keys(localMap).length === 0) {
|
|
70
|
+
throw new Error(`Could not parse SENDGRID_TEMPLATES from ${filePath}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const sgTemplates = await client.listAllDynamicTemplates(200);
|
|
74
|
+
const sgById = new Map(sgTemplates.map((t) => [t.id, t]));
|
|
75
|
+
|
|
76
|
+
const placeholderPattern = /^d-[a-z]+-?[a-z]*$/;
|
|
77
|
+
const isPlaceholder = (id: string) =>
|
|
78
|
+
placeholderPattern.test(id) || id.length < 36;
|
|
79
|
+
|
|
80
|
+
const rows: string[] = [];
|
|
81
|
+
let missingCount = 0;
|
|
82
|
+
let placeholderCount = 0;
|
|
83
|
+
let okCount = 0;
|
|
84
|
+
|
|
85
|
+
for (const [key, id] of Object.entries(localMap)) {
|
|
86
|
+
if (isPlaceholder(id)) {
|
|
87
|
+
rows.push(`PLACEHOLDER '${key}': '${id}'`);
|
|
88
|
+
placeholderCount++;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const sgTemplate = sgById.get(id);
|
|
93
|
+
if (!sgTemplate) {
|
|
94
|
+
rows.push(
|
|
95
|
+
`NOT IN SENDGRID '${key}': '${id}' (ID not found in your SendGrid account)`,
|
|
96
|
+
);
|
|
97
|
+
missingCount++;
|
|
98
|
+
} else {
|
|
99
|
+
const active = sgTemplate.versions.find((v) => v.active === 1);
|
|
100
|
+
rows.push(
|
|
101
|
+
`OK '${key}': '${id}' -> "${sgTemplate.name}" (active: "${active?.subject ?? 'none'}")`,
|
|
102
|
+
);
|
|
103
|
+
okCount++;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const localIds = new Set(Object.values(localMap));
|
|
108
|
+
const unreferencedTemplateIds = sgTemplates
|
|
109
|
+
.filter((t) => !localIds.has(t.id))
|
|
110
|
+
.map((t) => `${t.id} "${t.name}"`);
|
|
111
|
+
|
|
112
|
+
const structured = {
|
|
113
|
+
filePath,
|
|
114
|
+
okCount,
|
|
115
|
+
placeholderCount,
|
|
116
|
+
missingCount,
|
|
117
|
+
rows,
|
|
118
|
+
unreferencedTemplateIds,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const summary = [
|
|
122
|
+
`File: ${filePath}`,
|
|
123
|
+
``,
|
|
124
|
+
`Summary: ${okCount} ok | ${placeholderCount} placeholder | ${missingCount} id-not-found`,
|
|
125
|
+
``,
|
|
126
|
+
...rows,
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
if (unreferencedTemplateIds.length > 0) {
|
|
130
|
+
summary.push(``, `Templates in SendGrid not referenced in constants`);
|
|
131
|
+
for (const entry of unreferencedTemplateIds) {
|
|
132
|
+
summary.push(` [${entry}]`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return jsonReadResult(structured, summary.join('\n'));
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
}
|