@hasna/contacts 0.2.9 → 0.3.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/dist/cli/index.js +902 -25
- package/dist/db/applications.d.ts +10 -0
- package/dist/db/applications.d.ts.map +1 -0
- package/dist/db/companies.d.ts +4 -0
- package/dist/db/companies.d.ts.map +1 -1
- package/dist/db/contact-tasks.d.ts +19 -0
- package/dist/db/contact-tasks.d.ts.map +1 -0
- package/dist/db/contacts.d.ts.map +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/notes.d.ts +2 -1
- package/dist/db/notes.d.ts.map +1 -1
- package/dist/db/org-members.d.ts +9 -0
- package/dist/db/org-members.d.ts.map +1 -0
- package/dist/db/relationships.d.ts +11 -0
- package/dist/db/relationships.d.ts.map +1 -1
- package/dist/db/vendor-comms.d.ts +15 -0
- package/dist/db/vendor-comms.d.ts.map +1 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +771 -4
- package/dist/mcp/index.js +1104 -18
- package/dist/server/index.js +102 -4
- package/dist/types/index.d.ts +204 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -236,6 +236,85 @@ var MIGRATIONS = [
|
|
|
236
236
|
);
|
|
237
237
|
|
|
238
238
|
CREATE INDEX IF NOT EXISTS idx_contact_notes_contact ON contact_notes(contact_id);
|
|
239
|
+
`,
|
|
240
|
+
`
|
|
241
|
+
ALTER TABLE companies ADD COLUMN is_owned_entity INTEGER NOT NULL DEFAULT 0;
|
|
242
|
+
ALTER TABLE companies ADD COLUMN entity_type TEXT CHECK(entity_type IN ('operating','holding','dissolved','nonprofit','trust','branch','other'));
|
|
243
|
+
|
|
244
|
+
ALTER TABLE company_relationships ADD COLUMN start_date TEXT;
|
|
245
|
+
ALTER TABLE company_relationships ADD COLUMN end_date TEXT;
|
|
246
|
+
ALTER TABLE company_relationships ADD COLUMN is_primary INTEGER NOT NULL DEFAULT 0;
|
|
247
|
+
ALTER TABLE company_relationships ADD COLUMN status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active','inactive','ended'));
|
|
248
|
+
|
|
249
|
+
CREATE TABLE IF NOT EXISTS org_members (
|
|
250
|
+
id TEXT PRIMARY KEY,
|
|
251
|
+
company_id TEXT NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
|
252
|
+
contact_id TEXT NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
|
|
253
|
+
title TEXT,
|
|
254
|
+
specialization TEXT,
|
|
255
|
+
office_phone TEXT,
|
|
256
|
+
response_sla_hours INTEGER,
|
|
257
|
+
notes TEXT,
|
|
258
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
259
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
260
|
+
UNIQUE(company_id, contact_id)
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
CREATE TABLE IF NOT EXISTS vendor_communications (
|
|
264
|
+
id TEXT PRIMARY KEY,
|
|
265
|
+
company_id TEXT NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
|
266
|
+
contact_id TEXT REFERENCES contacts(id) ON DELETE SET NULL,
|
|
267
|
+
comm_date TEXT NOT NULL,
|
|
268
|
+
type TEXT NOT NULL DEFAULT 'email' CHECK(type IN ('email','call','meeting','invoice_request','invoice_received','payment','dispute','other')),
|
|
269
|
+
direction TEXT NOT NULL DEFAULT 'outbound' CHECK(direction IN ('inbound','outbound')),
|
|
270
|
+
subject TEXT,
|
|
271
|
+
body TEXT,
|
|
272
|
+
status TEXT NOT NULL DEFAULT 'sent' CHECK(status IN ('sent','awaiting_response','responded','no_response','resolved')),
|
|
273
|
+
invoice_amount REAL,
|
|
274
|
+
invoice_currency TEXT,
|
|
275
|
+
invoice_ref TEXT,
|
|
276
|
+
follow_up_date TEXT,
|
|
277
|
+
follow_up_done INTEGER NOT NULL DEFAULT 0,
|
|
278
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
CREATE TABLE IF NOT EXISTS contact_tasks (
|
|
282
|
+
id TEXT PRIMARY KEY,
|
|
283
|
+
title TEXT NOT NULL,
|
|
284
|
+
description TEXT,
|
|
285
|
+
contact_id TEXT NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
|
|
286
|
+
assigned_by TEXT,
|
|
287
|
+
deadline TEXT,
|
|
288
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','awaiting_response','in_progress','completed','cancelled','escalated')),
|
|
289
|
+
priority TEXT NOT NULL DEFAULT 'medium' CHECK(priority IN ('low','medium','high','critical')),
|
|
290
|
+
entity_id TEXT REFERENCES companies(id) ON DELETE SET NULL,
|
|
291
|
+
linked_todos_task_id TEXT,
|
|
292
|
+
escalation_rules TEXT NOT NULL DEFAULT '[]',
|
|
293
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
294
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
CREATE TABLE IF NOT EXISTS applications (
|
|
298
|
+
id TEXT PRIMARY KEY,
|
|
299
|
+
program_name TEXT NOT NULL,
|
|
300
|
+
provider_company_id TEXT REFERENCES companies(id) ON DELETE SET NULL,
|
|
301
|
+
type TEXT NOT NULL DEFAULT 'other' CHECK(type IN ('ai_credits','grant','startup_program','visa','trademark','tax_filing','loan','other')),
|
|
302
|
+
value_usd REAL,
|
|
303
|
+
applicant_contact_id TEXT REFERENCES contacts(id) ON DELETE SET NULL,
|
|
304
|
+
primary_contact_id TEXT REFERENCES contacts(id) ON DELETE SET NULL,
|
|
305
|
+
status TEXT NOT NULL DEFAULT 'draft' CHECK(status IN ('draft','submitted','pending','approved','rejected','follow_up_needed','expired','cancelled')),
|
|
306
|
+
submitted_date TEXT,
|
|
307
|
+
decision_date TEXT,
|
|
308
|
+
follow_up_date TEXT,
|
|
309
|
+
notes TEXT,
|
|
310
|
+
method TEXT CHECK(method IN ('email','form','typeform','hubspot','manual','browser','feathery','other')),
|
|
311
|
+
form_url TEXT,
|
|
312
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
313
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
314
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
ALTER TABLE contact_notes ADD COLUMN company_id TEXT REFERENCES companies(id) ON DELETE SET NULL;
|
|
239
318
|
`
|
|
240
319
|
];
|
|
241
320
|
var _db = null;
|
|
@@ -383,7 +462,9 @@ function rowToCompany(row) {
|
|
|
383
462
|
...row,
|
|
384
463
|
custom_fields: JSON.parse(row.custom_fields || "{}"),
|
|
385
464
|
archived: !!row.archived,
|
|
386
|
-
project_id: row.project_id ?? null
|
|
465
|
+
project_id: row.project_id ?? null,
|
|
466
|
+
is_owned_entity: !!row.is_owned_entity,
|
|
467
|
+
entity_type: row.entity_type ?? null
|
|
387
468
|
};
|
|
388
469
|
}
|
|
389
470
|
function insertEmails(db, contactId, companyId, emails) {
|
|
@@ -963,7 +1044,9 @@ function rowToCompany2(row) {
|
|
|
963
1044
|
...row,
|
|
964
1045
|
custom_fields: JSON.parse(row.custom_fields || "{}"),
|
|
965
1046
|
archived: !!row.archived,
|
|
966
|
-
project_id: row.project_id ?? null
|
|
1047
|
+
project_id: row.project_id ?? null,
|
|
1048
|
+
is_owned_entity: !!row.is_owned_entity,
|
|
1049
|
+
entity_type: row.entity_type ?? null
|
|
967
1050
|
};
|
|
968
1051
|
}
|
|
969
1052
|
function insertEmails2(db, companyId, emails) {
|
|
@@ -1027,8 +1110,8 @@ function createCompany(input, db) {
|
|
|
1027
1110
|
const d = db || getDatabase();
|
|
1028
1111
|
const id = uuid();
|
|
1029
1112
|
const timestamp = now();
|
|
1030
|
-
d.run(`INSERT INTO companies (id, name, domain, logo_url, description, industry, size, founded_year, notes, custom_fields, created_at, updated_at)
|
|
1031
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1113
|
+
d.run(`INSERT INTO companies (id, name, domain, logo_url, description, industry, size, founded_year, notes, custom_fields, is_owned_entity, entity_type, created_at, updated_at)
|
|
1114
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1032
1115
|
id,
|
|
1033
1116
|
input.name,
|
|
1034
1117
|
input.domain ?? null,
|
|
@@ -1039,6 +1122,8 @@ function createCompany(input, db) {
|
|
|
1039
1122
|
input.founded_year ?? null,
|
|
1040
1123
|
input.notes ?? null,
|
|
1041
1124
|
JSON.stringify(input.custom_fields ?? {}),
|
|
1125
|
+
input.is_owned_entity ? 1 : 0,
|
|
1126
|
+
input.entity_type ?? null,
|
|
1042
1127
|
timestamp,
|
|
1043
1128
|
timestamp
|
|
1044
1129
|
]);
|
|
@@ -1075,6 +1160,7 @@ function listCompanies(opts = {}, db) {
|
|
|
1075
1160
|
tag_id,
|
|
1076
1161
|
project_id,
|
|
1077
1162
|
archived = false,
|
|
1163
|
+
is_owned_entity,
|
|
1078
1164
|
order_by = "name",
|
|
1079
1165
|
order_dir = "asc"
|
|
1080
1166
|
} = opts;
|
|
@@ -1094,6 +1180,10 @@ function listCompanies(opts = {}, db) {
|
|
|
1094
1180
|
conditions.push("EXISTS (SELECT 1 FROM company_tags ct WHERE ct.company_id = co.id AND ct.tag_id = ?)");
|
|
1095
1181
|
params.push(tag_id);
|
|
1096
1182
|
}
|
|
1183
|
+
if (is_owned_entity !== undefined) {
|
|
1184
|
+
conditions.push("co.is_owned_entity = ?");
|
|
1185
|
+
params.push(is_owned_entity ? 1 : 0);
|
|
1186
|
+
}
|
|
1097
1187
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1098
1188
|
const validOrderBy = ["name", "created_at", "updated_at"].includes(order_by) ? order_by : "name";
|
|
1099
1189
|
const validOrderDir = order_dir === "desc" ? "DESC" : "ASC";
|
|
@@ -1149,6 +1239,14 @@ function updateCompany(id, input, db) {
|
|
|
1149
1239
|
setClauses.push("project_id = ?");
|
|
1150
1240
|
params.push(input.project_id);
|
|
1151
1241
|
}
|
|
1242
|
+
if (input.is_owned_entity !== undefined) {
|
|
1243
|
+
setClauses.push("is_owned_entity = ?");
|
|
1244
|
+
params.push(input.is_owned_entity ? 1 : 0);
|
|
1245
|
+
}
|
|
1246
|
+
if ("entity_type" in input && input.entity_type !== undefined) {
|
|
1247
|
+
setClauses.push("entity_type = ?");
|
|
1248
|
+
params.push(input.entity_type);
|
|
1249
|
+
}
|
|
1152
1250
|
params.push(id);
|
|
1153
1251
|
d.run(`UPDATE companies SET ${setClauses.join(", ")} WHERE id = ?`, params);
|
|
1154
1252
|
logActivity(d, { company_id: id, action: "company.updated", details: `Updated company: ${existing.name}` });
|
|
@@ -1236,7 +1334,11 @@ function deleteRelationship(id, db) {
|
|
|
1236
1334
|
function rowToCompanyRelationship(row) {
|
|
1237
1335
|
return {
|
|
1238
1336
|
...row,
|
|
1239
|
-
relationship_type: row.relationship_type
|
|
1337
|
+
relationship_type: row.relationship_type,
|
|
1338
|
+
start_date: row.start_date ?? null,
|
|
1339
|
+
end_date: row.end_date ?? null,
|
|
1340
|
+
is_primary: !!row.is_primary,
|
|
1341
|
+
status: row.status ?? "active"
|
|
1240
1342
|
};
|
|
1241
1343
|
}
|
|
1242
1344
|
function createCompanyRelationship(input, db) {
|
|
@@ -1248,7 +1350,17 @@ function createCompanyRelationship(input, db) {
|
|
|
1248
1350
|
if (!company)
|
|
1249
1351
|
throw new Error(`Company ${input.company_id} not found`);
|
|
1250
1352
|
const id = uuid();
|
|
1251
|
-
d.run(`INSERT INTO company_relationships (id, contact_id, company_id, relationship_type, notes) VALUES (?, ?, ?, ?, ?)`, [
|
|
1353
|
+
d.run(`INSERT INTO company_relationships (id, contact_id, company_id, relationship_type, notes, start_date, end_date, is_primary, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1354
|
+
id,
|
|
1355
|
+
input.contact_id,
|
|
1356
|
+
input.company_id,
|
|
1357
|
+
input.relationship_type,
|
|
1358
|
+
input.notes ?? null,
|
|
1359
|
+
input.start_date ?? null,
|
|
1360
|
+
input.end_date ?? null,
|
|
1361
|
+
input.is_primary ? 1 : 0,
|
|
1362
|
+
input.status ?? "active"
|
|
1363
|
+
]);
|
|
1252
1364
|
return rowToCompanyRelationship(d.query(`SELECT * FROM company_relationships WHERE id = ?`).get(id));
|
|
1253
1365
|
}
|
|
1254
1366
|
function listCompanyRelationships(opts = {}, db) {
|
|
@@ -1277,19 +1389,23 @@ function deleteCompanyRelationship(id, db) {
|
|
|
1277
1389
|
}
|
|
1278
1390
|
|
|
1279
1391
|
// src/db/notes.ts
|
|
1280
|
-
function addNote(contactId, body, createdBy, db) {
|
|
1392
|
+
function addNote(contactId, body, createdBy, db, companyId) {
|
|
1281
1393
|
const d = db || getDatabase();
|
|
1282
1394
|
const contact = d.query(`SELECT id FROM contacts WHERE id = ?`).get(contactId);
|
|
1283
1395
|
if (!contact)
|
|
1284
1396
|
throw new ContactNotFoundError(contactId);
|
|
1285
1397
|
const id = uuid();
|
|
1286
|
-
d.run(`INSERT INTO contact_notes (id, contact_id, body, created_by) VALUES (?, ?, ?, ?)`, [id, contactId, body, createdBy ?? null]);
|
|
1398
|
+
d.run(`INSERT INTO contact_notes (id, contact_id, body, created_by, company_id) VALUES (?, ?, ?, ?, ?)`, [id, contactId, body, createdBy ?? null, companyId ?? null]);
|
|
1287
1399
|
return d.query(`SELECT * FROM contact_notes WHERE id = ?`).get(id);
|
|
1288
1400
|
}
|
|
1289
1401
|
function listNotes(contactId, db) {
|
|
1290
1402
|
const d = db || getDatabase();
|
|
1291
1403
|
return d.query(`SELECT * FROM contact_notes WHERE contact_id = ? ORDER BY created_at ASC`).all(contactId);
|
|
1292
1404
|
}
|
|
1405
|
+
function listNotesForContactAtCompany(contactId, companyId, db) {
|
|
1406
|
+
const d = db || getDatabase();
|
|
1407
|
+
return d.query(`SELECT * FROM contact_notes WHERE contact_id = ? AND company_id = ? ORDER BY created_at ASC`).all(contactId, companyId);
|
|
1408
|
+
}
|
|
1293
1409
|
function deleteNote(noteId, db) {
|
|
1294
1410
|
const d = db || getDatabase();
|
|
1295
1411
|
d.run(`DELETE FROM contact_notes WHERE id = ?`, [noteId]);
|
|
@@ -2124,6 +2240,454 @@ async function pullGoogleContactsAsInputs(opts = {}) {
|
|
|
2124
2240
|
return people.filter((p) => p.emailAddresses?.some((e) => e.value)).map(googlePersonToContactInput);
|
|
2125
2241
|
}
|
|
2126
2242
|
|
|
2243
|
+
// src/db/org-members.ts
|
|
2244
|
+
function rowToOrgMember(row) {
|
|
2245
|
+
return {
|
|
2246
|
+
id: row.id,
|
|
2247
|
+
company_id: row.company_id,
|
|
2248
|
+
contact_id: row.contact_id,
|
|
2249
|
+
title: row.title,
|
|
2250
|
+
specialization: row.specialization,
|
|
2251
|
+
office_phone: row.office_phone,
|
|
2252
|
+
response_sla_hours: row.response_sla_hours,
|
|
2253
|
+
notes: row.notes,
|
|
2254
|
+
created_at: row.created_at,
|
|
2255
|
+
updated_at: row.updated_at
|
|
2256
|
+
};
|
|
2257
|
+
}
|
|
2258
|
+
function addOrgMember(input, db) {
|
|
2259
|
+
const d = db || getDatabase();
|
|
2260
|
+
const id = uuid();
|
|
2261
|
+
const timestamp = now();
|
|
2262
|
+
d.run(`INSERT INTO org_members (id, company_id, contact_id, title, specialization, office_phone, response_sla_hours, notes, created_at, updated_at)
|
|
2263
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
2264
|
+
id,
|
|
2265
|
+
input.company_id,
|
|
2266
|
+
input.contact_id,
|
|
2267
|
+
input.title ?? null,
|
|
2268
|
+
input.specialization ?? null,
|
|
2269
|
+
input.office_phone ?? null,
|
|
2270
|
+
input.response_sla_hours ?? null,
|
|
2271
|
+
input.notes ?? null,
|
|
2272
|
+
timestamp,
|
|
2273
|
+
timestamp
|
|
2274
|
+
]);
|
|
2275
|
+
return rowToOrgMember(d.query(`SELECT * FROM org_members WHERE id = ?`).get(id));
|
|
2276
|
+
}
|
|
2277
|
+
function listOrgMembers(companyId, db) {
|
|
2278
|
+
const d = db || getDatabase();
|
|
2279
|
+
const rows = d.query(`SELECT * FROM org_members WHERE company_id = ? ORDER BY created_at ASC`).all(companyId);
|
|
2280
|
+
return rows.map(rowToOrgMember);
|
|
2281
|
+
}
|
|
2282
|
+
function updateOrgMember(id, input, db) {
|
|
2283
|
+
const d = db || getDatabase();
|
|
2284
|
+
const setClauses = ["updated_at = ?"];
|
|
2285
|
+
const params = [now()];
|
|
2286
|
+
if ("title" in input) {
|
|
2287
|
+
setClauses.push("title = ?");
|
|
2288
|
+
params.push(input.title ?? null);
|
|
2289
|
+
}
|
|
2290
|
+
if ("specialization" in input) {
|
|
2291
|
+
setClauses.push("specialization = ?");
|
|
2292
|
+
params.push(input.specialization ?? null);
|
|
2293
|
+
}
|
|
2294
|
+
if ("office_phone" in input) {
|
|
2295
|
+
setClauses.push("office_phone = ?");
|
|
2296
|
+
params.push(input.office_phone ?? null);
|
|
2297
|
+
}
|
|
2298
|
+
if ("response_sla_hours" in input) {
|
|
2299
|
+
setClauses.push("response_sla_hours = ?");
|
|
2300
|
+
params.push(input.response_sla_hours ?? null);
|
|
2301
|
+
}
|
|
2302
|
+
if ("notes" in input) {
|
|
2303
|
+
setClauses.push("notes = ?");
|
|
2304
|
+
params.push(input.notes ?? null);
|
|
2305
|
+
}
|
|
2306
|
+
params.push(id);
|
|
2307
|
+
d.run(`UPDATE org_members SET ${setClauses.join(", ")} WHERE id = ?`, params);
|
|
2308
|
+
return rowToOrgMember(d.query(`SELECT * FROM org_members WHERE id = ?`).get(id));
|
|
2309
|
+
}
|
|
2310
|
+
function removeOrgMember(id, db) {
|
|
2311
|
+
const d = db || getDatabase();
|
|
2312
|
+
d.run(`DELETE FROM org_members WHERE id = ?`, [id]);
|
|
2313
|
+
}
|
|
2314
|
+
function listOrgMembersForContact(contactId, db) {
|
|
2315
|
+
const d = db || getDatabase();
|
|
2316
|
+
const rows = d.query(`SELECT * FROM org_members WHERE contact_id = ? ORDER BY created_at ASC`).all(contactId);
|
|
2317
|
+
return rows.map(rowToOrgMember);
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
// src/db/vendor-comms.ts
|
|
2321
|
+
function rowToVendorComm(row) {
|
|
2322
|
+
return {
|
|
2323
|
+
id: row.id,
|
|
2324
|
+
company_id: row.company_id,
|
|
2325
|
+
contact_id: row.contact_id,
|
|
2326
|
+
comm_date: row.comm_date,
|
|
2327
|
+
type: row.type,
|
|
2328
|
+
direction: row.direction,
|
|
2329
|
+
subject: row.subject,
|
|
2330
|
+
body: row.body,
|
|
2331
|
+
status: row.status,
|
|
2332
|
+
invoice_amount: row.invoice_amount,
|
|
2333
|
+
invoice_currency: row.invoice_currency,
|
|
2334
|
+
invoice_ref: row.invoice_ref,
|
|
2335
|
+
follow_up_date: row.follow_up_date,
|
|
2336
|
+
follow_up_done: !!row.follow_up_done,
|
|
2337
|
+
created_at: row.created_at
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
function logVendorCommunication(input, db) {
|
|
2341
|
+
const d = db || getDatabase();
|
|
2342
|
+
const id = uuid();
|
|
2343
|
+
d.run(`INSERT INTO vendor_communications
|
|
2344
|
+
(id, company_id, contact_id, comm_date, type, direction, subject, body, status,
|
|
2345
|
+
invoice_amount, invoice_currency, invoice_ref, follow_up_date, follow_up_done)
|
|
2346
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
2347
|
+
id,
|
|
2348
|
+
input.company_id,
|
|
2349
|
+
input.contact_id ?? null,
|
|
2350
|
+
input.comm_date,
|
|
2351
|
+
input.type ?? "email",
|
|
2352
|
+
input.direction ?? "outbound",
|
|
2353
|
+
input.subject ?? null,
|
|
2354
|
+
input.body ?? null,
|
|
2355
|
+
input.status ?? "sent",
|
|
2356
|
+
input.invoice_amount ?? null,
|
|
2357
|
+
input.invoice_currency ?? null,
|
|
2358
|
+
input.invoice_ref ?? null,
|
|
2359
|
+
input.follow_up_date ?? null,
|
|
2360
|
+
input.follow_up_done ? 1 : 0
|
|
2361
|
+
]);
|
|
2362
|
+
return rowToVendorComm(d.query(`SELECT * FROM vendor_communications WHERE id = ?`).get(id));
|
|
2363
|
+
}
|
|
2364
|
+
function listVendorCommunications(companyId, opts = {}, db) {
|
|
2365
|
+
const d = db || getDatabase();
|
|
2366
|
+
const conditions = ["company_id = ?"];
|
|
2367
|
+
const params = [companyId];
|
|
2368
|
+
if (opts.type) {
|
|
2369
|
+
conditions.push("type = ?");
|
|
2370
|
+
params.push(opts.type);
|
|
2371
|
+
}
|
|
2372
|
+
if (opts.status) {
|
|
2373
|
+
conditions.push("status = ?");
|
|
2374
|
+
params.push(opts.status);
|
|
2375
|
+
}
|
|
2376
|
+
if (opts.direction) {
|
|
2377
|
+
conditions.push("direction = ?");
|
|
2378
|
+
params.push(opts.direction);
|
|
2379
|
+
}
|
|
2380
|
+
const where = conditions.join(" AND ");
|
|
2381
|
+
const rows = d.query(`SELECT * FROM vendor_communications WHERE ${where} ORDER BY comm_date DESC`).all(...params);
|
|
2382
|
+
return rows.map(rowToVendorComm);
|
|
2383
|
+
}
|
|
2384
|
+
function listPendingFollowUps(db) {
|
|
2385
|
+
const d = db || getDatabase();
|
|
2386
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
2387
|
+
const rows = d.query(`SELECT * FROM vendor_communications
|
|
2388
|
+
WHERE follow_up_date <= ? AND follow_up_done = 0
|
|
2389
|
+
ORDER BY follow_up_date ASC`).all(today);
|
|
2390
|
+
return rows.map(rowToVendorComm);
|
|
2391
|
+
}
|
|
2392
|
+
function listMissingInvoices(db) {
|
|
2393
|
+
const d = db || getDatabase();
|
|
2394
|
+
const rows = d.query(`SELECT * FROM vendor_communications
|
|
2395
|
+
WHERE type = 'invoice_request' AND status IN ('awaiting_response','no_response')
|
|
2396
|
+
ORDER BY comm_date ASC`).all();
|
|
2397
|
+
return rows.map(rowToVendorComm);
|
|
2398
|
+
}
|
|
2399
|
+
function markFollowUpDone(id, db) {
|
|
2400
|
+
const d = db || getDatabase();
|
|
2401
|
+
d.run(`UPDATE vendor_communications SET follow_up_done = 1 WHERE id = ?`, [id]);
|
|
2402
|
+
return rowToVendorComm(d.query(`SELECT * FROM vendor_communications WHERE id = ?`).get(id));
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
// src/db/contact-tasks.ts
|
|
2406
|
+
function rowToContactTask(row) {
|
|
2407
|
+
return {
|
|
2408
|
+
id: row.id,
|
|
2409
|
+
title: row.title,
|
|
2410
|
+
description: row.description,
|
|
2411
|
+
contact_id: row.contact_id,
|
|
2412
|
+
assigned_by: row.assigned_by,
|
|
2413
|
+
deadline: row.deadline,
|
|
2414
|
+
status: row.status,
|
|
2415
|
+
priority: row.priority,
|
|
2416
|
+
entity_id: row.entity_id,
|
|
2417
|
+
linked_todos_task_id: row.linked_todos_task_id,
|
|
2418
|
+
escalation_rules: JSON.parse(row.escalation_rules || "[]"),
|
|
2419
|
+
created_at: row.created_at,
|
|
2420
|
+
updated_at: row.updated_at
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
function createContactTask(input, db) {
|
|
2424
|
+
const d = db || getDatabase();
|
|
2425
|
+
const id = uuid();
|
|
2426
|
+
const timestamp = now();
|
|
2427
|
+
d.run(`INSERT INTO contact_tasks
|
|
2428
|
+
(id, title, description, contact_id, assigned_by, deadline, status, priority,
|
|
2429
|
+
entity_id, linked_todos_task_id, escalation_rules, created_at, updated_at)
|
|
2430
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
2431
|
+
id,
|
|
2432
|
+
input.title,
|
|
2433
|
+
input.description ?? null,
|
|
2434
|
+
input.contact_id,
|
|
2435
|
+
input.assigned_by ?? null,
|
|
2436
|
+
input.deadline ?? null,
|
|
2437
|
+
input.status ?? "pending",
|
|
2438
|
+
input.priority ?? "medium",
|
|
2439
|
+
input.entity_id ?? null,
|
|
2440
|
+
input.linked_todos_task_id ?? null,
|
|
2441
|
+
JSON.stringify(input.escalation_rules ?? []),
|
|
2442
|
+
timestamp,
|
|
2443
|
+
timestamp
|
|
2444
|
+
]);
|
|
2445
|
+
return rowToContactTask(d.query(`SELECT * FROM contact_tasks WHERE id = ?`).get(id));
|
|
2446
|
+
}
|
|
2447
|
+
function listContactTasks(opts = {}, db) {
|
|
2448
|
+
const d = db || getDatabase();
|
|
2449
|
+
const conditions = [];
|
|
2450
|
+
const params = [];
|
|
2451
|
+
if (opts.contact_id) {
|
|
2452
|
+
conditions.push("contact_id = ?");
|
|
2453
|
+
params.push(opts.contact_id);
|
|
2454
|
+
}
|
|
2455
|
+
if (opts.entity_id) {
|
|
2456
|
+
conditions.push("entity_id = ?");
|
|
2457
|
+
params.push(opts.entity_id);
|
|
2458
|
+
}
|
|
2459
|
+
if (opts.status) {
|
|
2460
|
+
conditions.push("status = ?");
|
|
2461
|
+
params.push(opts.status);
|
|
2462
|
+
}
|
|
2463
|
+
if (opts.priority) {
|
|
2464
|
+
conditions.push("priority = ?");
|
|
2465
|
+
params.push(opts.priority);
|
|
2466
|
+
}
|
|
2467
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2468
|
+
const rows = d.query(`SELECT * FROM contact_tasks ${where} ORDER BY deadline ASC, priority DESC, created_at ASC`).all(...params);
|
|
2469
|
+
return rows.map(rowToContactTask);
|
|
2470
|
+
}
|
|
2471
|
+
function updateContactTask(id, input, db) {
|
|
2472
|
+
const d = db || getDatabase();
|
|
2473
|
+
const setClauses = ["updated_at = ?"];
|
|
2474
|
+
const params = [now()];
|
|
2475
|
+
if (input.title !== undefined) {
|
|
2476
|
+
setClauses.push("title = ?");
|
|
2477
|
+
params.push(input.title);
|
|
2478
|
+
}
|
|
2479
|
+
if ("description" in input) {
|
|
2480
|
+
setClauses.push("description = ?");
|
|
2481
|
+
params.push(input.description ?? null);
|
|
2482
|
+
}
|
|
2483
|
+
if ("assigned_by" in input) {
|
|
2484
|
+
setClauses.push("assigned_by = ?");
|
|
2485
|
+
params.push(input.assigned_by ?? null);
|
|
2486
|
+
}
|
|
2487
|
+
if ("deadline" in input) {
|
|
2488
|
+
setClauses.push("deadline = ?");
|
|
2489
|
+
params.push(input.deadline ?? null);
|
|
2490
|
+
}
|
|
2491
|
+
if (input.status !== undefined) {
|
|
2492
|
+
setClauses.push("status = ?");
|
|
2493
|
+
params.push(input.status);
|
|
2494
|
+
}
|
|
2495
|
+
if (input.priority !== undefined) {
|
|
2496
|
+
setClauses.push("priority = ?");
|
|
2497
|
+
params.push(input.priority);
|
|
2498
|
+
}
|
|
2499
|
+
if ("entity_id" in input) {
|
|
2500
|
+
setClauses.push("entity_id = ?");
|
|
2501
|
+
params.push(input.entity_id ?? null);
|
|
2502
|
+
}
|
|
2503
|
+
if ("linked_todos_task_id" in input) {
|
|
2504
|
+
setClauses.push("linked_todos_task_id = ?");
|
|
2505
|
+
params.push(input.linked_todos_task_id ?? null);
|
|
2506
|
+
}
|
|
2507
|
+
if (input.escalation_rules !== undefined) {
|
|
2508
|
+
setClauses.push("escalation_rules = ?");
|
|
2509
|
+
params.push(JSON.stringify(input.escalation_rules));
|
|
2510
|
+
}
|
|
2511
|
+
params.push(id);
|
|
2512
|
+
d.run(`UPDATE contact_tasks SET ${setClauses.join(", ")} WHERE id = ?`, params);
|
|
2513
|
+
return rowToContactTask(d.query(`SELECT * FROM contact_tasks WHERE id = ?`).get(id));
|
|
2514
|
+
}
|
|
2515
|
+
function deleteContactTask(id, db) {
|
|
2516
|
+
const d = db || getDatabase();
|
|
2517
|
+
d.run(`DELETE FROM contact_tasks WHERE id = ?`, [id]);
|
|
2518
|
+
}
|
|
2519
|
+
function listOverdueTasks(db) {
|
|
2520
|
+
const d = db || getDatabase();
|
|
2521
|
+
const now_iso = new Date().toISOString();
|
|
2522
|
+
const rows = d.query(`SELECT * FROM contact_tasks
|
|
2523
|
+
WHERE deadline < ? AND status NOT IN ('completed','cancelled')
|
|
2524
|
+
ORDER BY deadline ASC`).all(now_iso);
|
|
2525
|
+
return rows.map(rowToContactTask);
|
|
2526
|
+
}
|
|
2527
|
+
function checkEscalations(db) {
|
|
2528
|
+
const overdue = listOverdueTasks(db);
|
|
2529
|
+
const now_ms = Date.now();
|
|
2530
|
+
const results = [];
|
|
2531
|
+
for (const task of overdue) {
|
|
2532
|
+
if (!task.deadline || task.escalation_rules.length === 0)
|
|
2533
|
+
continue;
|
|
2534
|
+
const deadlineMs = new Date(task.deadline).getTime();
|
|
2535
|
+
const daysPastDeadline = (now_ms - deadlineMs) / (1000 * 60 * 60 * 24);
|
|
2536
|
+
const triggered = task.escalation_rules.filter((rule) => daysPastDeadline >= rule.after_days);
|
|
2537
|
+
if (triggered.length > 0) {
|
|
2538
|
+
results.push({ task, rules_triggered: triggered });
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
return results;
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
// src/db/applications.ts
|
|
2545
|
+
function rowToApplication(row) {
|
|
2546
|
+
return {
|
|
2547
|
+
id: row.id,
|
|
2548
|
+
program_name: row.program_name,
|
|
2549
|
+
provider_company_id: row.provider_company_id,
|
|
2550
|
+
type: row.type,
|
|
2551
|
+
value_usd: row.value_usd,
|
|
2552
|
+
applicant_contact_id: row.applicant_contact_id,
|
|
2553
|
+
primary_contact_id: row.primary_contact_id,
|
|
2554
|
+
status: row.status,
|
|
2555
|
+
submitted_date: row.submitted_date,
|
|
2556
|
+
decision_date: row.decision_date,
|
|
2557
|
+
follow_up_date: row.follow_up_date,
|
|
2558
|
+
notes: row.notes,
|
|
2559
|
+
method: row.method ?? null,
|
|
2560
|
+
form_url: row.form_url,
|
|
2561
|
+
metadata: JSON.parse(row.metadata || "{}"),
|
|
2562
|
+
created_at: row.created_at,
|
|
2563
|
+
updated_at: row.updated_at
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
function createApplication(input, db) {
|
|
2567
|
+
const d = db || getDatabase();
|
|
2568
|
+
const id = uuid();
|
|
2569
|
+
const timestamp = now();
|
|
2570
|
+
d.run(`INSERT INTO applications
|
|
2571
|
+
(id, program_name, provider_company_id, type, value_usd, applicant_contact_id, primary_contact_id,
|
|
2572
|
+
status, submitted_date, decision_date, follow_up_date, notes, method, form_url, metadata, created_at, updated_at)
|
|
2573
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
2574
|
+
id,
|
|
2575
|
+
input.program_name,
|
|
2576
|
+
input.provider_company_id ?? null,
|
|
2577
|
+
input.type ?? "other",
|
|
2578
|
+
input.value_usd ?? null,
|
|
2579
|
+
input.applicant_contact_id ?? null,
|
|
2580
|
+
input.primary_contact_id ?? null,
|
|
2581
|
+
input.status ?? "draft",
|
|
2582
|
+
input.submitted_date ?? null,
|
|
2583
|
+
input.decision_date ?? null,
|
|
2584
|
+
input.follow_up_date ?? null,
|
|
2585
|
+
input.notes ?? null,
|
|
2586
|
+
input.method ?? null,
|
|
2587
|
+
input.form_url ?? null,
|
|
2588
|
+
JSON.stringify(input.metadata ?? {}),
|
|
2589
|
+
timestamp,
|
|
2590
|
+
timestamp
|
|
2591
|
+
]);
|
|
2592
|
+
return rowToApplication(d.query(`SELECT * FROM applications WHERE id = ?`).get(id));
|
|
2593
|
+
}
|
|
2594
|
+
function listApplications(opts = {}, db) {
|
|
2595
|
+
const d = db || getDatabase();
|
|
2596
|
+
const conditions = [];
|
|
2597
|
+
const params = [];
|
|
2598
|
+
if (opts.type) {
|
|
2599
|
+
conditions.push("type = ?");
|
|
2600
|
+
params.push(opts.type);
|
|
2601
|
+
}
|
|
2602
|
+
if (opts.status) {
|
|
2603
|
+
conditions.push("status = ?");
|
|
2604
|
+
params.push(opts.status);
|
|
2605
|
+
}
|
|
2606
|
+
if (opts.provider_company_id) {
|
|
2607
|
+
conditions.push("provider_company_id = ?");
|
|
2608
|
+
params.push(opts.provider_company_id);
|
|
2609
|
+
}
|
|
2610
|
+
if (opts.applicant_contact_id) {
|
|
2611
|
+
conditions.push("applicant_contact_id = ?");
|
|
2612
|
+
params.push(opts.applicant_contact_id);
|
|
2613
|
+
}
|
|
2614
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2615
|
+
const rows = d.query(`SELECT * FROM applications ${where} ORDER BY created_at DESC`).all(...params);
|
|
2616
|
+
return rows.map(rowToApplication);
|
|
2617
|
+
}
|
|
2618
|
+
function updateApplication(id, input, db) {
|
|
2619
|
+
const d = db || getDatabase();
|
|
2620
|
+
const setClauses = ["updated_at = ?"];
|
|
2621
|
+
const params = [now()];
|
|
2622
|
+
if (input.program_name !== undefined) {
|
|
2623
|
+
setClauses.push("program_name = ?");
|
|
2624
|
+
params.push(input.program_name);
|
|
2625
|
+
}
|
|
2626
|
+
if ("provider_company_id" in input) {
|
|
2627
|
+
setClauses.push("provider_company_id = ?");
|
|
2628
|
+
params.push(input.provider_company_id ?? null);
|
|
2629
|
+
}
|
|
2630
|
+
if (input.type !== undefined) {
|
|
2631
|
+
setClauses.push("type = ?");
|
|
2632
|
+
params.push(input.type);
|
|
2633
|
+
}
|
|
2634
|
+
if ("value_usd" in input) {
|
|
2635
|
+
setClauses.push("value_usd = ?");
|
|
2636
|
+
params.push(input.value_usd ?? null);
|
|
2637
|
+
}
|
|
2638
|
+
if ("applicant_contact_id" in input) {
|
|
2639
|
+
setClauses.push("applicant_contact_id = ?");
|
|
2640
|
+
params.push(input.applicant_contact_id ?? null);
|
|
2641
|
+
}
|
|
2642
|
+
if ("primary_contact_id" in input) {
|
|
2643
|
+
setClauses.push("primary_contact_id = ?");
|
|
2644
|
+
params.push(input.primary_contact_id ?? null);
|
|
2645
|
+
}
|
|
2646
|
+
if (input.status !== undefined) {
|
|
2647
|
+
setClauses.push("status = ?");
|
|
2648
|
+
params.push(input.status);
|
|
2649
|
+
}
|
|
2650
|
+
if ("submitted_date" in input) {
|
|
2651
|
+
setClauses.push("submitted_date = ?");
|
|
2652
|
+
params.push(input.submitted_date ?? null);
|
|
2653
|
+
}
|
|
2654
|
+
if ("decision_date" in input) {
|
|
2655
|
+
setClauses.push("decision_date = ?");
|
|
2656
|
+
params.push(input.decision_date ?? null);
|
|
2657
|
+
}
|
|
2658
|
+
if ("follow_up_date" in input) {
|
|
2659
|
+
setClauses.push("follow_up_date = ?");
|
|
2660
|
+
params.push(input.follow_up_date ?? null);
|
|
2661
|
+
}
|
|
2662
|
+
if ("notes" in input) {
|
|
2663
|
+
setClauses.push("notes = ?");
|
|
2664
|
+
params.push(input.notes ?? null);
|
|
2665
|
+
}
|
|
2666
|
+
if ("method" in input) {
|
|
2667
|
+
setClauses.push("method = ?");
|
|
2668
|
+
params.push(input.method ?? null);
|
|
2669
|
+
}
|
|
2670
|
+
if ("form_url" in input) {
|
|
2671
|
+
setClauses.push("form_url = ?");
|
|
2672
|
+
params.push(input.form_url ?? null);
|
|
2673
|
+
}
|
|
2674
|
+
if (input.metadata !== undefined) {
|
|
2675
|
+
setClauses.push("metadata = ?");
|
|
2676
|
+
params.push(JSON.stringify(input.metadata));
|
|
2677
|
+
}
|
|
2678
|
+
params.push(id);
|
|
2679
|
+
d.run(`UPDATE applications SET ${setClauses.join(", ")} WHERE id = ?`, params);
|
|
2680
|
+
return rowToApplication(d.query(`SELECT * FROM applications WHERE id = ?`).get(id));
|
|
2681
|
+
}
|
|
2682
|
+
function listFollowUpDue(db) {
|
|
2683
|
+
const d = db || getDatabase();
|
|
2684
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
2685
|
+
const rows = d.query(`SELECT * FROM applications
|
|
2686
|
+
WHERE follow_up_date <= ? AND status = 'follow_up_needed'
|
|
2687
|
+
ORDER BY follow_up_date ASC`).all(today);
|
|
2688
|
+
return rows.map(rowToApplication);
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2127
2691
|
// src/mcp/index.ts
|
|
2128
2692
|
var server = new Server({ name: "contacts", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
2129
2693
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
@@ -2560,23 +3124,27 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
2560
3124
|
},
|
|
2561
3125
|
{
|
|
2562
3126
|
name: "add_note",
|
|
2563
|
-
description: "Add a structured timestamped note to a contact. Returns the note object with id, body, created_at.",
|
|
3127
|
+
description: "Add a structured timestamped note to a contact. Returns the note object with id, body, created_at. Optionally scope the note to a specific company context.",
|
|
2564
3128
|
inputSchema: {
|
|
2565
3129
|
type: "object",
|
|
2566
3130
|
properties: {
|
|
2567
3131
|
contact_id: { type: "string" },
|
|
2568
3132
|
note: { type: "string", description: "Note text/body" },
|
|
2569
|
-
created_by: { type: "string", description: "Agent or user who created the note (optional)" }
|
|
3133
|
+
created_by: { type: "string", description: "Agent or user who created the note (optional)" },
|
|
3134
|
+
company_id: { type: "string", description: "Optional company context \u2014 scope this note to a specific contact-company pair" }
|
|
2570
3135
|
},
|
|
2571
3136
|
required: ["contact_id", "note"]
|
|
2572
3137
|
}
|
|
2573
3138
|
},
|
|
2574
3139
|
{
|
|
2575
3140
|
name: "list_notes",
|
|
2576
|
-
description: "List all structured notes for a contact, ordered by date ascending.",
|
|
3141
|
+
description: "List all structured notes for a contact, ordered by date ascending. Optionally filter by company_id to show only notes scoped to that contact-company pair.",
|
|
2577
3142
|
inputSchema: {
|
|
2578
3143
|
type: "object",
|
|
2579
|
-
properties: {
|
|
3144
|
+
properties: {
|
|
3145
|
+
contact_id: { type: "string" },
|
|
3146
|
+
company_id: { type: "string", description: "Optional company ID \u2014 if provided, only returns notes scoped to this contact-company pair" }
|
|
3147
|
+
},
|
|
2580
3148
|
required: ["contact_id"]
|
|
2581
3149
|
}
|
|
2582
3150
|
},
|
|
@@ -2890,14 +3458,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
2890
3458
|
},
|
|
2891
3459
|
{
|
|
2892
3460
|
name: "add_company_relationship",
|
|
2893
|
-
description: "Declare a typed relationship between a contact and a company
|
|
3461
|
+
description: "Declare a typed relationship between a contact and a company. relationship_type: client|vendor|partner|employee|contractor|investor|advisor|tax_preparer|bank_manager|attorney|registered_agent|accountant|payroll_specialist|insurance_broker|other. Supports optional start_date, end_date, is_primary, and status fields.",
|
|
2894
3462
|
inputSchema: {
|
|
2895
3463
|
type: "object",
|
|
2896
3464
|
properties: {
|
|
2897
3465
|
contact_id: { type: "string" },
|
|
2898
3466
|
company_id: { type: "string" },
|
|
2899
|
-
relationship_type: { type: "string", enum: ["client", "vendor", "partner", "employee", "contractor", "investor", "advisor", "other"] },
|
|
2900
|
-
notes: { type: "string" }
|
|
3467
|
+
relationship_type: { type: "string", enum: ["client", "vendor", "partner", "employee", "contractor", "investor", "advisor", "tax_preparer", "bank_manager", "attorney", "registered_agent", "accountant", "payroll_specialist", "insurance_broker", "other"] },
|
|
3468
|
+
notes: { type: "string" },
|
|
3469
|
+
start_date: { type: "string", description: "ISO date when the relationship began (YYYY-MM-DD)" },
|
|
3470
|
+
end_date: { type: "string", description: "ISO date when the relationship ended (YYYY-MM-DD)" },
|
|
3471
|
+
is_primary: { type: "boolean", description: "Whether this is the primary contact for this relationship type at this company" },
|
|
3472
|
+
status: { type: "string", enum: ["active", "inactive", "ended"], description: "Relationship status (default: active)" }
|
|
2901
3473
|
},
|
|
2902
3474
|
required: ["contact_id", "company_id", "relationship_type"]
|
|
2903
3475
|
}
|
|
@@ -2978,6 +3550,293 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
2978
3550
|
},
|
|
2979
3551
|
required: ["query"]
|
|
2980
3552
|
}
|
|
3553
|
+
},
|
|
3554
|
+
{
|
|
3555
|
+
name: "get_contact_workload",
|
|
3556
|
+
description: "Get a comprehensive workload summary for a contact: entities/companies they manage, active tasks assigned to them, overdue tasks, pending applications they're linked to, days since last contact, org memberships with specializations. Essential for checking if a key contact is overloaded or responsive.",
|
|
3557
|
+
inputSchema: {
|
|
3558
|
+
type: "object",
|
|
3559
|
+
properties: {
|
|
3560
|
+
contact_id: { type: "string" }
|
|
3561
|
+
},
|
|
3562
|
+
required: ["contact_id"]
|
|
3563
|
+
}
|
|
3564
|
+
},
|
|
3565
|
+
{
|
|
3566
|
+
name: "list_overdue_contact_tasks",
|
|
3567
|
+
description: "List all contact tasks that are past their deadline and not yet completed/cancelled. Includes escalation rules so agents know who to escalate to.",
|
|
3568
|
+
inputSchema: {
|
|
3569
|
+
type: "object",
|
|
3570
|
+
properties: {}
|
|
3571
|
+
}
|
|
3572
|
+
},
|
|
3573
|
+
{
|
|
3574
|
+
name: "check_escalations",
|
|
3575
|
+
description: "Check all contact tasks with escalation rules to see which ones should be escalated now based on days overdue vs escalation thresholds. Returns tasks with the contact to escalate to.",
|
|
3576
|
+
inputSchema: {
|
|
3577
|
+
type: "object",
|
|
3578
|
+
properties: {}
|
|
3579
|
+
}
|
|
3580
|
+
},
|
|
3581
|
+
{
|
|
3582
|
+
name: "add_org_member",
|
|
3583
|
+
description: "Add a contact as an org member of a company (employee, team member) with optional title, specialization, office phone, SLA hours, and notes.",
|
|
3584
|
+
inputSchema: {
|
|
3585
|
+
type: "object",
|
|
3586
|
+
properties: {
|
|
3587
|
+
company_id: { type: "string" },
|
|
3588
|
+
contact_id: { type: "string" },
|
|
3589
|
+
title: { type: "string" },
|
|
3590
|
+
specialization: { type: "string" },
|
|
3591
|
+
office_phone: { type: "string" },
|
|
3592
|
+
response_sla_hours: { type: "number" },
|
|
3593
|
+
notes: { type: "string" }
|
|
3594
|
+
},
|
|
3595
|
+
required: ["company_id", "contact_id"]
|
|
3596
|
+
}
|
|
3597
|
+
},
|
|
3598
|
+
{
|
|
3599
|
+
name: "list_org_members",
|
|
3600
|
+
description: "List all org members (contacts) of a company.",
|
|
3601
|
+
inputSchema: {
|
|
3602
|
+
type: "object",
|
|
3603
|
+
properties: {
|
|
3604
|
+
company_id: { type: "string" }
|
|
3605
|
+
},
|
|
3606
|
+
required: ["company_id"]
|
|
3607
|
+
}
|
|
3608
|
+
},
|
|
3609
|
+
{
|
|
3610
|
+
name: "update_org_member",
|
|
3611
|
+
description: "Update an org member's title, specialization, office phone, SLA hours, or notes.",
|
|
3612
|
+
inputSchema: {
|
|
3613
|
+
type: "object",
|
|
3614
|
+
properties: {
|
|
3615
|
+
id: { type: "string" },
|
|
3616
|
+
title: { type: "string" },
|
|
3617
|
+
specialization: { type: "string" },
|
|
3618
|
+
office_phone: { type: "string" },
|
|
3619
|
+
response_sla_hours: { type: "number" },
|
|
3620
|
+
notes: { type: "string" }
|
|
3621
|
+
},
|
|
3622
|
+
required: ["id"]
|
|
3623
|
+
}
|
|
3624
|
+
},
|
|
3625
|
+
{
|
|
3626
|
+
name: "remove_org_member",
|
|
3627
|
+
description: "Remove a contact from a company's org members by org member record ID.",
|
|
3628
|
+
inputSchema: {
|
|
3629
|
+
type: "object",
|
|
3630
|
+
properties: {
|
|
3631
|
+
id: { type: "string" }
|
|
3632
|
+
},
|
|
3633
|
+
required: ["id"]
|
|
3634
|
+
}
|
|
3635
|
+
},
|
|
3636
|
+
{
|
|
3637
|
+
name: "log_vendor_communication",
|
|
3638
|
+
description: "Log a vendor communication (email, invoice request, call, payment, etc.) for a company. Track invoice amounts, references, follow-up dates, and status.",
|
|
3639
|
+
inputSchema: {
|
|
3640
|
+
type: "object",
|
|
3641
|
+
properties: {
|
|
3642
|
+
company_id: { type: "string" },
|
|
3643
|
+
contact_id: { type: "string" },
|
|
3644
|
+
comm_date: { type: "string", description: "ISO date (defaults to today)" },
|
|
3645
|
+
type: { type: "string", enum: ["email", "call", "meeting", "invoice_request", "invoice_received", "payment", "dispute", "other"] },
|
|
3646
|
+
direction: { type: "string", enum: ["inbound", "outbound"] },
|
|
3647
|
+
subject: { type: "string" },
|
|
3648
|
+
body: { type: "string" },
|
|
3649
|
+
status: { type: "string", enum: ["sent", "awaiting_response", "responded", "no_response", "resolved"] },
|
|
3650
|
+
invoice_amount: { type: "number" },
|
|
3651
|
+
invoice_currency: { type: "string" },
|
|
3652
|
+
invoice_ref: { type: "string" },
|
|
3653
|
+
follow_up_date: { type: "string", description: "ISO date for follow-up (YYYY-MM-DD)" }
|
|
3654
|
+
},
|
|
3655
|
+
required: ["company_id", "type"]
|
|
3656
|
+
}
|
|
3657
|
+
},
|
|
3658
|
+
{
|
|
3659
|
+
name: "list_vendor_communications",
|
|
3660
|
+
description: "List vendor communications for a company, optionally filtered by type or status.",
|
|
3661
|
+
inputSchema: {
|
|
3662
|
+
type: "object",
|
|
3663
|
+
properties: {
|
|
3664
|
+
company_id: { type: "string" },
|
|
3665
|
+
type: { type: "string", enum: ["email", "call", "meeting", "invoice_request", "invoice_received", "payment", "dispute", "other"] },
|
|
3666
|
+
status: { type: "string", enum: ["sent", "awaiting_response", "responded", "no_response", "resolved"] }
|
|
3667
|
+
},
|
|
3668
|
+
required: ["company_id"]
|
|
3669
|
+
}
|
|
3670
|
+
},
|
|
3671
|
+
{
|
|
3672
|
+
name: "list_missing_invoices",
|
|
3673
|
+
description: "List all invoice_request communications with no_response or awaiting_response status \u2014 vendors who haven't sent invoices yet.",
|
|
3674
|
+
inputSchema: {
|
|
3675
|
+
type: "object",
|
|
3676
|
+
properties: {}
|
|
3677
|
+
}
|
|
3678
|
+
},
|
|
3679
|
+
{
|
|
3680
|
+
name: "list_pending_followups",
|
|
3681
|
+
description: "List all vendor communications with follow_up_date on or before today that haven't been marked done.",
|
|
3682
|
+
inputSchema: {
|
|
3683
|
+
type: "object",
|
|
3684
|
+
properties: {}
|
|
3685
|
+
}
|
|
3686
|
+
},
|
|
3687
|
+
{
|
|
3688
|
+
name: "mark_followup_done",
|
|
3689
|
+
description: "Mark a vendor communication follow-up as done.",
|
|
3690
|
+
inputSchema: {
|
|
3691
|
+
type: "object",
|
|
3692
|
+
properties: {
|
|
3693
|
+
id: { type: "string" }
|
|
3694
|
+
},
|
|
3695
|
+
required: ["id"]
|
|
3696
|
+
}
|
|
3697
|
+
},
|
|
3698
|
+
{
|
|
3699
|
+
name: "create_contact_task",
|
|
3700
|
+
description: "Create a task assigned to a contact, with optional deadline, priority, entity link, escalation rules, and todos task link.",
|
|
3701
|
+
inputSchema: {
|
|
3702
|
+
type: "object",
|
|
3703
|
+
properties: {
|
|
3704
|
+
title: { type: "string" },
|
|
3705
|
+
contact_id: { type: "string" },
|
|
3706
|
+
description: { type: "string" },
|
|
3707
|
+
assigned_by: { type: "string" },
|
|
3708
|
+
deadline: { type: "string", description: "ISO date (YYYY-MM-DD)" },
|
|
3709
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
3710
|
+
entity_id: { type: "string", description: "Company/entity ID this task is for" },
|
|
3711
|
+
escalation_rules: { type: "array", items: { type: "object", properties: { after_days: { type: "number" }, escalate_to_contact_id: { type: "string" }, method: { type: "string", enum: ["email", "note", "both"] } }, required: ["after_days", "escalate_to_contact_id", "method"] } },
|
|
3712
|
+
linked_todos_task_id: { type: "string" }
|
|
3713
|
+
},
|
|
3714
|
+
required: ["title", "contact_id"]
|
|
3715
|
+
}
|
|
3716
|
+
},
|
|
3717
|
+
{
|
|
3718
|
+
name: "list_contact_tasks",
|
|
3719
|
+
description: "List contact tasks, optionally filtered by contact, entity, status, or priority.",
|
|
3720
|
+
inputSchema: {
|
|
3721
|
+
type: "object",
|
|
3722
|
+
properties: {
|
|
3723
|
+
contact_id: { type: "string" },
|
|
3724
|
+
entity_id: { type: "string" },
|
|
3725
|
+
status: { type: "string", enum: ["pending", "awaiting_response", "in_progress", "completed", "cancelled", "escalated"] },
|
|
3726
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] }
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
},
|
|
3730
|
+
{
|
|
3731
|
+
name: "update_contact_task",
|
|
3732
|
+
description: "Update a contact task's title, status, deadline, priority, description, or escalation rules.",
|
|
3733
|
+
inputSchema: {
|
|
3734
|
+
type: "object",
|
|
3735
|
+
properties: {
|
|
3736
|
+
id: { type: "string" },
|
|
3737
|
+
title: { type: "string" },
|
|
3738
|
+
status: { type: "string", enum: ["pending", "awaiting_response", "in_progress", "completed", "cancelled", "escalated"] },
|
|
3739
|
+
deadline: { type: "string" },
|
|
3740
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
3741
|
+
description: { type: "string" },
|
|
3742
|
+
escalation_rules: { type: "array", items: { type: "object" } }
|
|
3743
|
+
},
|
|
3744
|
+
required: ["id"]
|
|
3745
|
+
}
|
|
3746
|
+
},
|
|
3747
|
+
{
|
|
3748
|
+
name: "delete_contact_task",
|
|
3749
|
+
description: "Delete a contact task by ID.",
|
|
3750
|
+
inputSchema: {
|
|
3751
|
+
type: "object",
|
|
3752
|
+
properties: {
|
|
3753
|
+
id: { type: "string" }
|
|
3754
|
+
},
|
|
3755
|
+
required: ["id"]
|
|
3756
|
+
}
|
|
3757
|
+
},
|
|
3758
|
+
{
|
|
3759
|
+
name: "create_application",
|
|
3760
|
+
description: "Create an application record (grant, AI credits, startup program, visa, trademark, tax filing, etc.).",
|
|
3761
|
+
inputSchema: {
|
|
3762
|
+
type: "object",
|
|
3763
|
+
properties: {
|
|
3764
|
+
program_name: { type: "string" },
|
|
3765
|
+
type: { type: "string", enum: ["ai_credits", "grant", "startup_program", "visa", "trademark", "tax_filing", "loan", "other"] },
|
|
3766
|
+
value_usd: { type: "number" },
|
|
3767
|
+
provider_company_id: { type: "string" },
|
|
3768
|
+
primary_contact_id: { type: "string" },
|
|
3769
|
+
status: { type: "string", enum: ["draft", "submitted", "pending", "approved", "rejected", "follow_up_needed", "expired", "cancelled"] },
|
|
3770
|
+
submitted_date: { type: "string" },
|
|
3771
|
+
follow_up_date: { type: "string" },
|
|
3772
|
+
notes: { type: "string" },
|
|
3773
|
+
method: { type: "string", enum: ["email", "form", "typeform", "hubspot", "manual", "browser", "feathery", "other"] },
|
|
3774
|
+
form_url: { type: "string" }
|
|
3775
|
+
},
|
|
3776
|
+
required: ["program_name"]
|
|
3777
|
+
}
|
|
3778
|
+
},
|
|
3779
|
+
{
|
|
3780
|
+
name: "list_applications",
|
|
3781
|
+
description: "List applications, optionally filtered by type, status, or provider company.",
|
|
3782
|
+
inputSchema: {
|
|
3783
|
+
type: "object",
|
|
3784
|
+
properties: {
|
|
3785
|
+
type: { type: "string", enum: ["ai_credits", "grant", "startup_program", "visa", "trademark", "tax_filing", "loan", "other"] },
|
|
3786
|
+
status: { type: "string", enum: ["draft", "submitted", "pending", "approved", "rejected", "follow_up_needed", "expired", "cancelled"] },
|
|
3787
|
+
provider_company_id: { type: "string" }
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
},
|
|
3791
|
+
{
|
|
3792
|
+
name: "update_application",
|
|
3793
|
+
description: "Update an application's program name, status, dates, value, notes, or other fields.",
|
|
3794
|
+
inputSchema: {
|
|
3795
|
+
type: "object",
|
|
3796
|
+
properties: {
|
|
3797
|
+
id: { type: "string" },
|
|
3798
|
+
program_name: { type: "string" },
|
|
3799
|
+
type: { type: "string", enum: ["ai_credits", "grant", "startup_program", "visa", "trademark", "tax_filing", "loan", "other"] },
|
|
3800
|
+
value_usd: { type: "number" },
|
|
3801
|
+
provider_company_id: { type: "string" },
|
|
3802
|
+
primary_contact_id: { type: "string" },
|
|
3803
|
+
status: { type: "string", enum: ["draft", "submitted", "pending", "approved", "rejected", "follow_up_needed", "expired", "cancelled"] },
|
|
3804
|
+
submitted_date: { type: "string" },
|
|
3805
|
+
decision_date: { type: "string" },
|
|
3806
|
+
follow_up_date: { type: "string" },
|
|
3807
|
+
notes: { type: "string" },
|
|
3808
|
+
method: { type: "string" },
|
|
3809
|
+
form_url: { type: "string" }
|
|
3810
|
+
},
|
|
3811
|
+
required: ["id"]
|
|
3812
|
+
}
|
|
3813
|
+
},
|
|
3814
|
+
{
|
|
3815
|
+
name: "get_followup_due_applications",
|
|
3816
|
+
description: "List applications with follow_up_date on or before today that haven't been completed or cancelled.",
|
|
3817
|
+
inputSchema: {
|
|
3818
|
+
type: "object",
|
|
3819
|
+
properties: {}
|
|
3820
|
+
}
|
|
3821
|
+
},
|
|
3822
|
+
{
|
|
3823
|
+
name: "list_owned_entities",
|
|
3824
|
+
description: "List all companies marked as owned entities (is_owned_entity=true) \u2014 your legal entities, subsidiaries, and operating companies.",
|
|
3825
|
+
inputSchema: {
|
|
3826
|
+
type: "object",
|
|
3827
|
+
properties: {}
|
|
3828
|
+
}
|
|
3829
|
+
},
|
|
3830
|
+
{
|
|
3831
|
+
name: "get_entity_team",
|
|
3832
|
+
description: "Get the full team for an owned entity \u2014 all contacts linked via company_relationships, with their roles and primary status.",
|
|
3833
|
+
inputSchema: {
|
|
3834
|
+
type: "object",
|
|
3835
|
+
properties: {
|
|
3836
|
+
company_id: { type: "string" }
|
|
3837
|
+
},
|
|
3838
|
+
required: ["company_id"]
|
|
3839
|
+
}
|
|
2981
3840
|
}
|
|
2982
3841
|
]
|
|
2983
3842
|
}));
|
|
@@ -3337,11 +4196,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3337
4196
|
return { content: [{ type: "text", text: JSON.stringify({ contact: created, action: "created" }, null, 2) }] };
|
|
3338
4197
|
}
|
|
3339
4198
|
case "add_note": {
|
|
3340
|
-
const noteObj = addNote(a.contact_id, a.note, a.created_by);
|
|
4199
|
+
const noteObj = addNote(a.contact_id, a.note, a.created_by, undefined, a.company_id);
|
|
3341
4200
|
return { content: [{ type: "text", text: JSON.stringify(noteObj, null, 2) }] };
|
|
3342
4201
|
}
|
|
3343
4202
|
case "list_notes": {
|
|
3344
|
-
const
|
|
4203
|
+
const db = getDatabase();
|
|
4204
|
+
const companyId = a.company_id;
|
|
4205
|
+
const notes = companyId ? listNotesForContactAtCompany(a.contact_id, companyId, db) : listNotes(a.contact_id);
|
|
3345
4206
|
return { content: [{ type: "text", text: JSON.stringify(notes, null, 2) }] };
|
|
3346
4207
|
}
|
|
3347
4208
|
case "delete_note": {
|
|
@@ -3544,7 +4405,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3544
4405
|
contact_id: a.contact_id,
|
|
3545
4406
|
company_id: a.company_id,
|
|
3546
4407
|
relationship_type: a.relationship_type,
|
|
3547
|
-
notes: a.notes
|
|
4408
|
+
notes: a.notes,
|
|
4409
|
+
start_date: a.start_date,
|
|
4410
|
+
end_date: a.end_date,
|
|
4411
|
+
is_primary: a.is_primary,
|
|
4412
|
+
status: a.status
|
|
3548
4413
|
});
|
|
3549
4414
|
return { content: [{ type: "text", text: JSON.stringify(rel, null, 2) }] };
|
|
3550
4415
|
}
|
|
@@ -3675,6 +4540,227 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3675
4540
|
}));
|
|
3676
4541
|
return { content: [{ type: "text", text: JSON.stringify(mapped, null, 2) }] };
|
|
3677
4542
|
}
|
|
4543
|
+
case "get_contact_workload": {
|
|
4544
|
+
const db = getDatabase();
|
|
4545
|
+
const { contact_id } = a;
|
|
4546
|
+
const contact = getContact(contact_id);
|
|
4547
|
+
const companyRels = listCompanyRelationships({ contact_id }, db);
|
|
4548
|
+
const activeTasks = listContactTasks({ contact_id, status: "pending" }, db);
|
|
4549
|
+
const overdueTasks = listOverdueTasks(db).filter((t) => t.contact_id === contact_id);
|
|
4550
|
+
const orgMemberships = listOrgMembersForContact(contact_id, db);
|
|
4551
|
+
const daysSince = contact.last_contacted_at ? Math.floor((Date.now() - new Date(contact.last_contacted_at).getTime()) / 86400000) : null;
|
|
4552
|
+
return {
|
|
4553
|
+
content: [{
|
|
4554
|
+
type: "text",
|
|
4555
|
+
text: JSON.stringify({
|
|
4556
|
+
contact,
|
|
4557
|
+
company_relationships: companyRels,
|
|
4558
|
+
active_tasks: activeTasks,
|
|
4559
|
+
overdue_tasks: overdueTasks,
|
|
4560
|
+
org_memberships: orgMemberships,
|
|
4561
|
+
days_since_last_contact: daysSince,
|
|
4562
|
+
total_entities_managed: companyRels.length
|
|
4563
|
+
}, null, 2)
|
|
4564
|
+
}]
|
|
4565
|
+
};
|
|
4566
|
+
}
|
|
4567
|
+
case "list_overdue_contact_tasks": {
|
|
4568
|
+
const db = getDatabase();
|
|
4569
|
+
const overdue = listOverdueTasks(db);
|
|
4570
|
+
return { content: [{ type: "text", text: JSON.stringify(overdue, null, 2) }] };
|
|
4571
|
+
}
|
|
4572
|
+
case "check_escalations": {
|
|
4573
|
+
const db = getDatabase();
|
|
4574
|
+
const escalations = checkEscalations(db);
|
|
4575
|
+
return { content: [{ type: "text", text: JSON.stringify(escalations, null, 2) }] };
|
|
4576
|
+
}
|
|
4577
|
+
case "add_org_member": {
|
|
4578
|
+
const db = getDatabase();
|
|
4579
|
+
const input = {
|
|
4580
|
+
company_id: a.company_id,
|
|
4581
|
+
contact_id: a.contact_id,
|
|
4582
|
+
title: a.title,
|
|
4583
|
+
specialization: a.specialization,
|
|
4584
|
+
office_phone: a.office_phone,
|
|
4585
|
+
response_sla_hours: a.response_sla_hours,
|
|
4586
|
+
notes: a.notes
|
|
4587
|
+
};
|
|
4588
|
+
const member = addOrgMember(input, db);
|
|
4589
|
+
return { content: [{ type: "text", text: JSON.stringify(member, null, 2) }] };
|
|
4590
|
+
}
|
|
4591
|
+
case "list_org_members": {
|
|
4592
|
+
const db = getDatabase();
|
|
4593
|
+
const members = listOrgMembers(a.company_id, db);
|
|
4594
|
+
return { content: [{ type: "text", text: JSON.stringify(members, null, 2) }] };
|
|
4595
|
+
}
|
|
4596
|
+
case "update_org_member": {
|
|
4597
|
+
const db = getDatabase();
|
|
4598
|
+
const { id: memberId, ...memberRest } = a;
|
|
4599
|
+
const input = {
|
|
4600
|
+
title: memberRest.title,
|
|
4601
|
+
specialization: memberRest.specialization,
|
|
4602
|
+
office_phone: memberRest.office_phone,
|
|
4603
|
+
response_sla_hours: memberRest.response_sla_hours,
|
|
4604
|
+
notes: memberRest.notes
|
|
4605
|
+
};
|
|
4606
|
+
const updated = updateOrgMember(memberId, input, db);
|
|
4607
|
+
return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
|
|
4608
|
+
}
|
|
4609
|
+
case "remove_org_member": {
|
|
4610
|
+
const db = getDatabase();
|
|
4611
|
+
removeOrgMember(a.id, db);
|
|
4612
|
+
return { content: [{ type: "text", text: JSON.stringify({ deleted: true }) }] };
|
|
4613
|
+
}
|
|
4614
|
+
case "log_vendor_communication": {
|
|
4615
|
+
const db = getDatabase();
|
|
4616
|
+
const input = {
|
|
4617
|
+
company_id: a.company_id,
|
|
4618
|
+
contact_id: a.contact_id,
|
|
4619
|
+
comm_date: a.comm_date ?? new Date().toISOString().slice(0, 10),
|
|
4620
|
+
type: a.type,
|
|
4621
|
+
direction: a.direction,
|
|
4622
|
+
subject: a.subject,
|
|
4623
|
+
body: a.body,
|
|
4624
|
+
status: a.status,
|
|
4625
|
+
invoice_amount: a.invoice_amount,
|
|
4626
|
+
invoice_currency: a.invoice_currency,
|
|
4627
|
+
invoice_ref: a.invoice_ref,
|
|
4628
|
+
follow_up_date: a.follow_up_date
|
|
4629
|
+
};
|
|
4630
|
+
const comm = logVendorCommunication(input, db);
|
|
4631
|
+
return { content: [{ type: "text", text: JSON.stringify(comm, null, 2) }] };
|
|
4632
|
+
}
|
|
4633
|
+
case "list_vendor_communications": {
|
|
4634
|
+
const db = getDatabase();
|
|
4635
|
+
const comms = listVendorCommunications(a.company_id, {
|
|
4636
|
+
type: a.type,
|
|
4637
|
+
status: a.status
|
|
4638
|
+
}, db);
|
|
4639
|
+
return { content: [{ type: "text", text: JSON.stringify(comms, null, 2) }] };
|
|
4640
|
+
}
|
|
4641
|
+
case "list_missing_invoices": {
|
|
4642
|
+
const db = getDatabase();
|
|
4643
|
+
const missing = listMissingInvoices(db);
|
|
4644
|
+
return { content: [{ type: "text", text: JSON.stringify(missing, null, 2) }] };
|
|
4645
|
+
}
|
|
4646
|
+
case "list_pending_followups": {
|
|
4647
|
+
const db = getDatabase();
|
|
4648
|
+
const pending = listPendingFollowUps(db);
|
|
4649
|
+
return { content: [{ type: "text", text: JSON.stringify(pending, null, 2) }] };
|
|
4650
|
+
}
|
|
4651
|
+
case "mark_followup_done": {
|
|
4652
|
+
const db = getDatabase();
|
|
4653
|
+
const updated = markFollowUpDone(a.id, db);
|
|
4654
|
+
return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
|
|
4655
|
+
}
|
|
4656
|
+
case "create_contact_task": {
|
|
4657
|
+
const db = getDatabase();
|
|
4658
|
+
const input = {
|
|
4659
|
+
title: a.title,
|
|
4660
|
+
contact_id: a.contact_id,
|
|
4661
|
+
description: a.description,
|
|
4662
|
+
assigned_by: a.assigned_by,
|
|
4663
|
+
deadline: a.deadline,
|
|
4664
|
+
priority: a.priority,
|
|
4665
|
+
entity_id: a.entity_id,
|
|
4666
|
+
escalation_rules: a.escalation_rules,
|
|
4667
|
+
linked_todos_task_id: a.linked_todos_task_id
|
|
4668
|
+
};
|
|
4669
|
+
const task = createContactTask(input, db);
|
|
4670
|
+
return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }] };
|
|
4671
|
+
}
|
|
4672
|
+
case "list_contact_tasks": {
|
|
4673
|
+
const db = getDatabase();
|
|
4674
|
+
const tasks = listContactTasks({
|
|
4675
|
+
contact_id: a.contact_id,
|
|
4676
|
+
entity_id: a.entity_id,
|
|
4677
|
+
status: a.status,
|
|
4678
|
+
priority: a.priority
|
|
4679
|
+
}, db);
|
|
4680
|
+
return { content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }] };
|
|
4681
|
+
}
|
|
4682
|
+
case "update_contact_task": {
|
|
4683
|
+
const db = getDatabase();
|
|
4684
|
+
const { id: taskId, ...taskRest } = a;
|
|
4685
|
+
const input = {
|
|
4686
|
+
title: taskRest.title,
|
|
4687
|
+
status: taskRest.status,
|
|
4688
|
+
deadline: taskRest.deadline,
|
|
4689
|
+
priority: taskRest.priority,
|
|
4690
|
+
description: taskRest.description,
|
|
4691
|
+
escalation_rules: taskRest.escalation_rules
|
|
4692
|
+
};
|
|
4693
|
+
const updated = updateContactTask(taskId, input, db);
|
|
4694
|
+
return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
|
|
4695
|
+
}
|
|
4696
|
+
case "delete_contact_task": {
|
|
4697
|
+
const db = getDatabase();
|
|
4698
|
+
deleteContactTask(a.id, db);
|
|
4699
|
+
return { content: [{ type: "text", text: JSON.stringify({ deleted: true }) }] };
|
|
4700
|
+
}
|
|
4701
|
+
case "create_application": {
|
|
4702
|
+
const db = getDatabase();
|
|
4703
|
+
const input = {
|
|
4704
|
+
program_name: a.program_name,
|
|
4705
|
+
type: a.type,
|
|
4706
|
+
value_usd: a.value_usd,
|
|
4707
|
+
provider_company_id: a.provider_company_id,
|
|
4708
|
+
primary_contact_id: a.primary_contact_id,
|
|
4709
|
+
status: a.status,
|
|
4710
|
+
submitted_date: a.submitted_date,
|
|
4711
|
+
follow_up_date: a.follow_up_date,
|
|
4712
|
+
notes: a.notes,
|
|
4713
|
+
method: a.method,
|
|
4714
|
+
form_url: a.form_url
|
|
4715
|
+
};
|
|
4716
|
+
const app = createApplication(input, db);
|
|
4717
|
+
return { content: [{ type: "text", text: JSON.stringify(app, null, 2) }] };
|
|
4718
|
+
}
|
|
4719
|
+
case "list_applications": {
|
|
4720
|
+
const db = getDatabase();
|
|
4721
|
+
const apps = listApplications({
|
|
4722
|
+
type: a.type,
|
|
4723
|
+
status: a.status,
|
|
4724
|
+
provider_company_id: a.provider_company_id
|
|
4725
|
+
}, db);
|
|
4726
|
+
return { content: [{ type: "text", text: JSON.stringify(apps, null, 2) }] };
|
|
4727
|
+
}
|
|
4728
|
+
case "update_application": {
|
|
4729
|
+
const db = getDatabase();
|
|
4730
|
+
const { id: appId, ...appRest } = a;
|
|
4731
|
+
const input = {
|
|
4732
|
+
program_name: appRest.program_name,
|
|
4733
|
+
type: appRest.type,
|
|
4734
|
+
value_usd: appRest.value_usd,
|
|
4735
|
+
provider_company_id: appRest.provider_company_id,
|
|
4736
|
+
primary_contact_id: appRest.primary_contact_id,
|
|
4737
|
+
status: appRest.status,
|
|
4738
|
+
submitted_date: appRest.submitted_date,
|
|
4739
|
+
decision_date: appRest.decision_date,
|
|
4740
|
+
follow_up_date: appRest.follow_up_date,
|
|
4741
|
+
notes: appRest.notes,
|
|
4742
|
+
method: appRest.method,
|
|
4743
|
+
form_url: appRest.form_url
|
|
4744
|
+
};
|
|
4745
|
+
const updated = updateApplication(appId, input, db);
|
|
4746
|
+
return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
|
|
4747
|
+
}
|
|
4748
|
+
case "get_followup_due_applications": {
|
|
4749
|
+
const db = getDatabase();
|
|
4750
|
+
const apps = listFollowUpDue(db);
|
|
4751
|
+
return { content: [{ type: "text", text: JSON.stringify(apps, null, 2) }] };
|
|
4752
|
+
}
|
|
4753
|
+
case "list_owned_entities": {
|
|
4754
|
+
const result = listCompanies({ limit: 200 });
|
|
4755
|
+
const owned = result.companies.filter((c) => c.is_owned_entity);
|
|
4756
|
+
return { content: [{ type: "text", text: JSON.stringify(owned, null, 2) }] };
|
|
4757
|
+
}
|
|
4758
|
+
case "get_entity_team": {
|
|
4759
|
+
const db = getDatabase();
|
|
4760
|
+
const company = getCompany(a.company_id);
|
|
4761
|
+
const team = listCompanyRelationships({ company_id: a.company_id }, db);
|
|
4762
|
+
return { content: [{ type: "text", text: JSON.stringify({ company, team }, null, 2) }] };
|
|
4763
|
+
}
|
|
3678
4764
|
default:
|
|
3679
4765
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
3680
4766
|
}
|