@sendinel/mcp-server 1.0.21 → 1.0.22
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/build/tools/contacts.js +51 -70
- package/package.json +1 -1
package/build/tools/contacts.js
CHANGED
|
@@ -2284,81 +2284,62 @@ Requirements: urgency without desperation, acknowledge the gap, one clear CTA, v
|
|
|
2284
2284
|
}, async ({ site_id }) => {
|
|
2285
2285
|
const projectId = getProjectId();
|
|
2286
2286
|
// Sample recent contacts to discover keys (LIMIT 500 is enough for schema discovery)
|
|
2287
|
-
const
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
const
|
|
2306
|
-
if (
|
|
2307
|
-
return txt(`Error: ${
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
const
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
if (!keyMap.has(k))
|
|
2324
|
-
keyMap.set(k, { values: new Set(), count: 0 });
|
|
2325
|
-
const entry = keyMap.get(k);
|
|
2326
|
-
entry.count++;
|
|
2327
|
-
if (v !== null && v !== undefined && entry.values.size < 3) {
|
|
2328
|
-
entry.values.add(String(v));
|
|
2329
|
-
}
|
|
2287
|
+
const SCHEMA_SAMPLE_LIMIT = 500;
|
|
2288
|
+
async function sampleSubscriberProperties(scopeToSite) {
|
|
2289
|
+
let query = db()
|
|
2290
|
+
.from('subscribers')
|
|
2291
|
+
.select('custom_properties')
|
|
2292
|
+
.eq('project_id', projectId)
|
|
2293
|
+
.not('custom_properties', 'is', null)
|
|
2294
|
+
.order('created_at', { ascending: false });
|
|
2295
|
+
if (scopeToSite)
|
|
2296
|
+
query = query.eq('source_site', site_id);
|
|
2297
|
+
return query.limit(SCHEMA_SAMPLE_LIMIT);
|
|
2298
|
+
}
|
|
2299
|
+
const siteSample = await sampleSubscriberProperties(true);
|
|
2300
|
+
if (siteSample.error)
|
|
2301
|
+
return txt(`Error: ${siteSample.error.message}`);
|
|
2302
|
+
let rows = siteSample.data ?? [];
|
|
2303
|
+
let scope = 'site';
|
|
2304
|
+
if (rows.length === 0) {
|
|
2305
|
+
const projectSample = await sampleSubscriberProperties(false);
|
|
2306
|
+
if (projectSample.error)
|
|
2307
|
+
return txt(`Error: ${projectSample.error.message}`);
|
|
2308
|
+
rows = projectSample.data ?? [];
|
|
2309
|
+
scope = 'project';
|
|
2310
|
+
}
|
|
2311
|
+
const keyMap = new Map();
|
|
2312
|
+
for (const row of rows ?? []) {
|
|
2313
|
+
const props = row.custom_properties;
|
|
2314
|
+
if (!props)
|
|
2315
|
+
continue;
|
|
2316
|
+
for (const [k, v] of Object.entries(props)) {
|
|
2317
|
+
if (!keyMap.has(k))
|
|
2318
|
+
keyMap.set(k, { values: new Set(), count: 0 });
|
|
2319
|
+
const entry = keyMap.get(k);
|
|
2320
|
+
entry.count++;
|
|
2321
|
+
if (v !== null && v !== undefined && entry.values.size < 3) {
|
|
2322
|
+
entry.values.add(String(v));
|
|
2330
2323
|
}
|
|
2331
2324
|
}
|
|
2332
|
-
const schema = Array.from(keyMap.entries())
|
|
2333
|
-
.sort((a, b) => b[1].count - a[1].count)
|
|
2334
|
-
.map(([key, { values, count }]) => ({
|
|
2335
|
-
key,
|
|
2336
|
-
merge_tag: `{{custom.${key}}}`,
|
|
2337
|
-
merge_tag_with_fallback: `{{custom.${key}|fallback}}`,
|
|
2338
|
-
example_values: Array.from(values),
|
|
2339
|
-
contacts_with_this_key: count,
|
|
2340
|
-
}));
|
|
2341
|
-
return j({
|
|
2342
|
-
site_id,
|
|
2343
|
-
scope,
|
|
2344
|
-
total_keys: schema.length,
|
|
2345
|
-
schema,
|
|
2346
|
-
note: scope === 'project'
|
|
2347
|
-
? `No contacts are tagged to site ${site_id} via source_site — sampled custom_properties across the whole project instead. Use merge_tag syntax in email body/subject. For sensitive data, add a fallback: {{custom.plan|subscriber}}. ⚠️ custom_properties data passes through the Anthropic API — avoid storing sensitive PII (SSNs, passwords, financial account numbers).`
|
|
2348
|
-
: 'Use merge_tag syntax in email body/subject. For sensitive data, add a fallback: {{custom.plan|subscriber}}. ⚠️ custom_properties data passes through the Anthropic API — avoid storing sensitive PII (SSNs, passwords, financial account numbers).',
|
|
2349
|
-
});
|
|
2350
2325
|
}
|
|
2326
|
+
const schema = Array.from(keyMap.entries())
|
|
2327
|
+
.sort((a, b) => b[1].count - a[1].count)
|
|
2328
|
+
.map(([key, { values, count }]) => ({
|
|
2329
|
+
key,
|
|
2330
|
+
merge_tag: `{{custom.${key}}}`,
|
|
2331
|
+
merge_tag_with_fallback: `{{custom.${key}|fallback}}`,
|
|
2332
|
+
example_values: Array.from(values),
|
|
2333
|
+
contacts_with_this_key: count,
|
|
2334
|
+
}));
|
|
2351
2335
|
return j({
|
|
2352
2336
|
site_id,
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
contacts_with_this_key: r.count,
|
|
2360
|
-
})),
|
|
2361
|
-
note: 'Use merge_tag syntax in email body/subject. For sensitive data, add a fallback: {{custom.plan|subscriber}}. ⚠️ custom_properties data passes through the Anthropic API — avoid storing sensitive PII (SSNs, passwords, financial account numbers).',
|
|
2337
|
+
scope,
|
|
2338
|
+
total_keys: schema.length,
|
|
2339
|
+
schema,
|
|
2340
|
+
note: scope === 'project'
|
|
2341
|
+
? `No contacts are tagged to site ${site_id} via source_site — sampled custom_properties across the whole project instead. Use merge_tag syntax in email body/subject. For sensitive data, add a fallback: {{custom.plan|subscriber}}. ⚠️ custom_properties data passes through the Anthropic API — avoid storing sensitive PII (SSNs, passwords, financial account numbers).`
|
|
2342
|
+
: 'Use merge_tag syntax in email body/subject. For sensitive data, add a fallback: {{custom.plan|subscriber}}. ⚠️ custom_properties data passes through the Anthropic API — avoid storing sensitive PII (SSNs, passwords, financial account numbers).',
|
|
2362
2343
|
});
|
|
2363
2344
|
});
|
|
2364
2345
|
server.registerTool('create_custom_field', {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendinel/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.22",
|
|
4
4
|
"description": "AI-controlled email operations control plane for campaigns, contacts, segments, analytics, and multi-provider sending via MCP.",
|
|
5
5
|
"mcpName": "ai.sendinel/mcp-server",
|
|
6
6
|
"type": "module",
|