@mgsoftwarebv/mg-dashboard-mcp 2.4.3 → 2.4.4
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/dist/index.js +231 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -413,6 +413,9 @@ var VALID_REVIEW_STATUSES = /* @__PURE__ */ new Set([
|
|
|
413
413
|
"agent_flagged",
|
|
414
414
|
"human_approved"
|
|
415
415
|
]);
|
|
416
|
+
function normalizeCompanyName(name) {
|
|
417
|
+
return name.toLowerCase().replace(/\b(b\.?v\.?|n\.?v\.?|v\.?o\.?f\.?|c\.?v\.?|holding|groep|group|nederland|netherlands)\b/gi, "").replace(/[^a-z0-9\s]/g, "").replace(/\s+/g, " ").trim();
|
|
418
|
+
}
|
|
416
419
|
var AGENT_TOOLS = [
|
|
417
420
|
{
|
|
418
421
|
name: "agent-report-coverage",
|
|
@@ -632,6 +635,79 @@ var AGENT_TOOLS = [
|
|
|
632
635
|
},
|
|
633
636
|
required: ["repo_slug", "results"]
|
|
634
637
|
}
|
|
638
|
+
},
|
|
639
|
+
// -- Lead Generation tools --------------------------------------------------
|
|
640
|
+
{
|
|
641
|
+
name: "agent-check-lead-exists",
|
|
642
|
+
description: "Check if a lead already exists by website URL or company name. Call this BEFORE visiting a website to avoid wasting time on duplicates.",
|
|
643
|
+
inputSchema: {
|
|
644
|
+
type: "object",
|
|
645
|
+
properties: {
|
|
646
|
+
website_url: {
|
|
647
|
+
type: "string",
|
|
648
|
+
description: 'Website URL to check (e.g. "https://example.nl")'
|
|
649
|
+
},
|
|
650
|
+
company_name: {
|
|
651
|
+
type: "string",
|
|
652
|
+
description: "Company name to fuzzy-match against existing leads"
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
required: []
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
name: "agent-save-lead",
|
|
660
|
+
description: "Save a discovered lead (company) to the database. Handles dedup on website_url + fuzzy match on company name. Returns the lead ID and whether it already existed.",
|
|
661
|
+
inputSchema: {
|
|
662
|
+
type: "object",
|
|
663
|
+
properties: {
|
|
664
|
+
company_name: { type: "string", description: "Company name" },
|
|
665
|
+
website_url: { type: "string", description: "Company website URL" },
|
|
666
|
+
industry: { type: "string", description: "Industry sector" },
|
|
667
|
+
region: { type: "string", description: 'Geographic region (e.g. "amsterdam", "rotterdam")' },
|
|
668
|
+
description: { type: "string", description: "AI-generated summary of what the company does (max 2000 chars)" },
|
|
669
|
+
potential_fit: { type: "string", description: "Why MG Software could help this company (max 2000 chars)" },
|
|
670
|
+
fit_score: { type: "number", description: "Fit score 1-10 for MG Software partnership" },
|
|
671
|
+
estimated_company_size: { type: "string", description: 'Estimated employee count range (e.g. "1-10", "10-50", "50-200")' },
|
|
672
|
+
kvk_number: { type: "string", description: "KvK (Chamber of Commerce) number if found" },
|
|
673
|
+
contact_name: { type: "string", description: "Primary contact person name" },
|
|
674
|
+
contact_role: { type: "string", description: "Contact person role/title" },
|
|
675
|
+
contact_email: { type: "string", description: "Contact person email" },
|
|
676
|
+
contact_phone: { type: "string", description: "Contact person phone" },
|
|
677
|
+
contact_linkedin: { type: "string", description: "Contact person LinkedIn URL" },
|
|
678
|
+
general_email: { type: "string", description: "General company email (info@...)" },
|
|
679
|
+
general_phone: { type: "string", description: "General company phone number" },
|
|
680
|
+
source_url: { type: "string", description: "URL where this lead was found (Google result, directory, etc.)" },
|
|
681
|
+
target_id: { type: "string", description: "UUID of the lead_generation_target this lead was found for" }
|
|
682
|
+
},
|
|
683
|
+
required: ["company_name", "website_url"]
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
name: "agent-save-email-draft",
|
|
688
|
+
description: "Save a cold email draft for a lead. The email will be shown in the backoffice for manual review and copy. Do NOT use em-dashes or en-dashes in the email.",
|
|
689
|
+
inputSchema: {
|
|
690
|
+
type: "object",
|
|
691
|
+
properties: {
|
|
692
|
+
lead_id: { type: "string", description: "UUID of the lead this email is for" },
|
|
693
|
+
subject: { type: "string", description: "Email subject line (Dutch, max 200 chars)" },
|
|
694
|
+
body: { type: "string", description: "Email body text (Dutch, max 5000 chars, NO em-dashes or en-dashes)" },
|
|
695
|
+
tone: { type: "string", description: "Tone of the email (default: professional)" }
|
|
696
|
+
},
|
|
697
|
+
required: ["lead_id", "subject", "body"]
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
name: "agent-complete-target",
|
|
702
|
+
description: "Mark a lead_generation_target as completed with the number of leads found.",
|
|
703
|
+
inputSchema: {
|
|
704
|
+
type: "object",
|
|
705
|
+
properties: {
|
|
706
|
+
target_id: { type: "string", description: "UUID of the lead_generation_target" },
|
|
707
|
+
results_count: { type: "number", description: "Number of new leads saved for this target" }
|
|
708
|
+
},
|
|
709
|
+
required: ["target_id", "results_count"]
|
|
710
|
+
}
|
|
635
711
|
}
|
|
636
712
|
];
|
|
637
713
|
var AGENT_TOOL_NAMES = new Set(AGENT_TOOLS.map((t) => t.name));
|
|
@@ -641,7 +717,11 @@ var AGENT_TOOL_MODULE_MAP = {
|
|
|
641
717
|
"agent-save-documentation": "agent_reporting",
|
|
642
718
|
"agent-list-findings": "agent_reporting",
|
|
643
719
|
"agent-get-documentation": "agent_reporting",
|
|
644
|
-
"agent-validate-suggestions": "agent_reporting"
|
|
720
|
+
"agent-validate-suggestions": "agent_reporting",
|
|
721
|
+
"agent-check-lead-exists": "agent_reporting",
|
|
722
|
+
"agent-save-lead": "agent_reporting",
|
|
723
|
+
"agent-save-email-draft": "agent_reporting",
|
|
724
|
+
"agent-complete-target": "agent_reporting"
|
|
645
725
|
};
|
|
646
726
|
function clamp(val, min, max) {
|
|
647
727
|
return Math.max(min, Math.min(max, val));
|
|
@@ -996,6 +1076,151 @@ ${output}` }]
|
|
|
996
1076
|
};
|
|
997
1077
|
}
|
|
998
1078
|
// -----------------------------------------------------------------
|
|
1079
|
+
// Lead Generation tools
|
|
1080
|
+
// -----------------------------------------------------------------
|
|
1081
|
+
case "agent-check-lead-exists": {
|
|
1082
|
+
const websiteUrl = sanitizeString(args2.website_url, 500).replace(/\/+$/, "");
|
|
1083
|
+
const companyName = sanitizeString(args2.company_name, 500);
|
|
1084
|
+
if (!websiteUrl && !companyName) {
|
|
1085
|
+
throw new Error("At least one of website_url or company_name is required");
|
|
1086
|
+
}
|
|
1087
|
+
const checks = [];
|
|
1088
|
+
if (websiteUrl) {
|
|
1089
|
+
const { data: urlMatch } = await supabase2.from("lead").select("id, company_name, website_url").eq("website_url", websiteUrl).maybeSingle();
|
|
1090
|
+
if (urlMatch) {
|
|
1091
|
+
return {
|
|
1092
|
+
content: [{
|
|
1093
|
+
type: "text",
|
|
1094
|
+
text: `DUPLICATE: Lead already exists (URL match). ID: ${urlMatch.id}, Company: ${urlMatch.company_name}. SKIP this company.`
|
|
1095
|
+
}]
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
checks.push(`URL "${websiteUrl}" not found`);
|
|
1099
|
+
}
|
|
1100
|
+
if (companyName) {
|
|
1101
|
+
const normalized = normalizeCompanyName(companyName);
|
|
1102
|
+
if (normalized.length >= 3) {
|
|
1103
|
+
const { data: nameMatches } = await supabase2.from("lead").select("id, company_name, website_url").ilike("company_name", `%${normalized.split(" ")[0]}%`).limit(20);
|
|
1104
|
+
if (nameMatches) {
|
|
1105
|
+
for (const match of nameMatches) {
|
|
1106
|
+
const matchNorm = normalizeCompanyName(match.company_name);
|
|
1107
|
+
if (textSimilarity(normalized, matchNorm) > 0.6) {
|
|
1108
|
+
return {
|
|
1109
|
+
content: [{
|
|
1110
|
+
type: "text",
|
|
1111
|
+
text: `DUPLICATE: Similar company found. ID: ${match.id}, Name: "${match.company_name}" (${match.website_url}). SKIP this company.`
|
|
1112
|
+
}]
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
checks.push(`Name "${companyName}" has no similar matches`);
|
|
1119
|
+
}
|
|
1120
|
+
return {
|
|
1121
|
+
content: [{
|
|
1122
|
+
type: "text",
|
|
1123
|
+
text: `NOT FOUND: No duplicate detected. ${checks.join(". ")}. Safe to proceed.`
|
|
1124
|
+
}]
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
case "agent-save-lead": {
|
|
1128
|
+
const companyName = sanitizeString(args2.company_name, 500);
|
|
1129
|
+
const websiteUrl = sanitizeString(args2.website_url, 500).replace(/\/+$/, "");
|
|
1130
|
+
if (!companyName) throw new Error("company_name is required");
|
|
1131
|
+
if (!websiteUrl) throw new Error("website_url is required");
|
|
1132
|
+
const { data: existing } = await supabase2.from("lead").select("id, company_name").eq("website_url", websiteUrl).maybeSingle();
|
|
1133
|
+
if (existing) {
|
|
1134
|
+
return {
|
|
1135
|
+
content: [{
|
|
1136
|
+
type: "text",
|
|
1137
|
+
text: `Lead already exists (URL match). ID: ${existing.id}, Company: "${existing.company_name}". Skipped.`
|
|
1138
|
+
}]
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
const normalized = normalizeCompanyName(companyName);
|
|
1142
|
+
if (normalized.length >= 3) {
|
|
1143
|
+
const { data: nameMatches } = await supabase2.from("lead").select("id, company_name").ilike("company_name", `%${normalized.split(" ")[0]}%`).limit(20);
|
|
1144
|
+
if (nameMatches) {
|
|
1145
|
+
for (const match of nameMatches) {
|
|
1146
|
+
if (textSimilarity(normalized, normalizeCompanyName(match.company_name)) > 0.6) {
|
|
1147
|
+
return {
|
|
1148
|
+
content: [{
|
|
1149
|
+
type: "text",
|
|
1150
|
+
text: `Lead already exists (name match). ID: ${match.id}, Name: "${match.company_name}". Skipped.`
|
|
1151
|
+
}]
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
const fitScore = args2.fit_score ? clamp(Number(args2.fit_score), 1, 10) : null;
|
|
1158
|
+
const { data: inserted, error } = await supabase2.from("lead").insert({
|
|
1159
|
+
company_name: companyName,
|
|
1160
|
+
website_url: websiteUrl,
|
|
1161
|
+
industry: args2.industry ? sanitizeString(args2.industry, 200) : null,
|
|
1162
|
+
region: args2.region ? sanitizeString(args2.region, 200) : null,
|
|
1163
|
+
description: args2.description ? sanitizeString(args2.description, 2e3) : null,
|
|
1164
|
+
potential_fit: args2.potential_fit ? sanitizeString(args2.potential_fit, 2e3) : null,
|
|
1165
|
+
fit_score: fitScore,
|
|
1166
|
+
estimated_company_size: args2.estimated_company_size ? sanitizeString(args2.estimated_company_size, 50) : null,
|
|
1167
|
+
kvk_number: args2.kvk_number ? sanitizeString(args2.kvk_number, 20) : null,
|
|
1168
|
+
contact_name: args2.contact_name ? sanitizeString(args2.contact_name, 200) : null,
|
|
1169
|
+
contact_role: args2.contact_role ? sanitizeString(args2.contact_role, 200) : null,
|
|
1170
|
+
contact_email: args2.contact_email ? sanitizeString(args2.contact_email, 200) : null,
|
|
1171
|
+
contact_phone: args2.contact_phone ? sanitizeString(args2.contact_phone, 50) : null,
|
|
1172
|
+
contact_linkedin: args2.contact_linkedin ? sanitizeString(args2.contact_linkedin, 500) : null,
|
|
1173
|
+
general_email: args2.general_email ? sanitizeString(args2.general_email, 200) : null,
|
|
1174
|
+
general_phone: args2.general_phone ? sanitizeString(args2.general_phone, 50) : null,
|
|
1175
|
+
source_url: args2.source_url ? sanitizeString(args2.source_url, 500) : null,
|
|
1176
|
+
target_id: args2.target_id ? sanitizeString(args2.target_id, 100) : null,
|
|
1177
|
+
status: "new"
|
|
1178
|
+
}).select("id").single();
|
|
1179
|
+
if (error) throw new Error(`Failed to save lead: ${error.message}`);
|
|
1180
|
+
return {
|
|
1181
|
+
content: [{
|
|
1182
|
+
type: "text",
|
|
1183
|
+
text: `Lead saved: "${companyName}" (${websiteUrl}). ID: ${inserted.id}. Fit score: ${fitScore ?? "N/A"}.`
|
|
1184
|
+
}]
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
case "agent-save-email-draft": {
|
|
1188
|
+
const leadId = sanitizeString(args2.lead_id, 100);
|
|
1189
|
+
const subject = sanitizeString(args2.subject, 200);
|
|
1190
|
+
let body = sanitizeString(args2.body, 5e3);
|
|
1191
|
+
const tone = sanitizeString(args2.tone, 50) || "professional";
|
|
1192
|
+
if (!leadId) throw new Error("lead_id is required");
|
|
1193
|
+
if (!subject) throw new Error("subject is required");
|
|
1194
|
+
if (!body) throw new Error("body is required");
|
|
1195
|
+
body = body.replace(/[\u2013\u2014]/g, ",");
|
|
1196
|
+
const { data: inserted, error } = await supabase2.from("lead_email_draft").insert({ lead_id: leadId, subject, body, tone, status: "draft" }).select("id").single();
|
|
1197
|
+
if (error) throw new Error(`Failed to save email draft: ${error.message}`);
|
|
1198
|
+
await supabase2.from("lead").update({ status: "email_drafted", updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", leadId).eq("status", "new");
|
|
1199
|
+
return {
|
|
1200
|
+
content: [{
|
|
1201
|
+
type: "text",
|
|
1202
|
+
text: `Email draft saved for lead ${leadId}. Draft ID: ${inserted.id}. Subject: "${subject}".`
|
|
1203
|
+
}]
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
case "agent-complete-target": {
|
|
1207
|
+
const targetId = sanitizeString(args2.target_id, 100);
|
|
1208
|
+
const resultsCount = clamp(Number(args2.results_count) || 0, 0, 9999);
|
|
1209
|
+
if (!targetId) throw new Error("target_id is required");
|
|
1210
|
+
const { error } = await supabase2.from("lead_generation_target").update({
|
|
1211
|
+
status: "completed",
|
|
1212
|
+
results_count: resultsCount,
|
|
1213
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1214
|
+
}).eq("id", targetId);
|
|
1215
|
+
if (error) throw new Error(`Failed to complete target: ${error.message}`);
|
|
1216
|
+
return {
|
|
1217
|
+
content: [{
|
|
1218
|
+
type: "text",
|
|
1219
|
+
text: `Target ${targetId} marked completed. ${resultsCount} leads found.`
|
|
1220
|
+
}]
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
// -----------------------------------------------------------------
|
|
999
1224
|
default:
|
|
1000
1225
|
return { content: [{ type: "text", text: `Unknown agent tool: ${name}` }] };
|
|
1001
1226
|
}
|
|
@@ -2787,7 +3012,11 @@ async function main() {
|
|
|
2787
3012
|
"/api/save-documentation": "agent-save-documentation",
|
|
2788
3013
|
"/api/list-findings": "agent-list-findings",
|
|
2789
3014
|
"/api/get-documentation": "agent-get-documentation",
|
|
2790
|
-
"/api/validate-suggestions": "agent-validate-suggestions"
|
|
3015
|
+
"/api/validate-suggestions": "agent-validate-suggestions",
|
|
3016
|
+
"/api/check-lead-exists": "agent-check-lead-exists",
|
|
3017
|
+
"/api/save-lead": "agent-save-lead",
|
|
3018
|
+
"/api/save-email-draft": "agent-save-email-draft",
|
|
3019
|
+
"/api/complete-target": "agent-complete-target"
|
|
2791
3020
|
};
|
|
2792
3021
|
const httpServer = createServer(async (req, res) => {
|
|
2793
3022
|
const url = new URL(req.url ?? "/", `http://localhost:${httpPort}`);
|