@adrata/adrata-mcp 1.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.
Files changed (41) hide show
  1. package/README.md +548 -0
  2. package/access/auth.js +289 -0
  3. package/access/oauth.js +1059 -0
  4. package/access/resource-metadata.js +167 -0
  5. package/access/tiers.js +422 -0
  6. package/analytics.js +634 -0
  7. package/api-bridge.js +499 -0
  8. package/governance/money.js +141 -0
  9. package/output-formatter.js +589 -0
  10. package/package.json +68 -0
  11. package/resources.js +246 -0
  12. package/security.js +690 -0
  13. package/server.js +2139 -0
  14. package/server.json +55 -0
  15. package/skills/backlog-triage/SKILL.md +115 -0
  16. package/skills/board-review/SKILL.md +96 -0
  17. package/skills/incident-to-card/SKILL.md +126 -0
  18. package/skills/log-outreach.md +62 -0
  19. package/skills/ship-the-card/SKILL.md +155 -0
  20. package/tool-annotations.js +269 -0
  21. package/tools/billing.js +149 -0
  22. package/tools/email-tools.js +652 -0
  23. package/tools/enterprise-tools.js +651 -0
  24. package/tools/free-search.js +160 -0
  25. package/tools/memory.js +440 -0
  26. package/tools/morning-brief.js +551 -0
  27. package/tools/paper-tools.js +563 -0
  28. package/tools/scheduling.js +322 -0
  29. package/tools/work-board-tools.js +758 -0
  30. package/toolsets/communications.js +276 -0
  31. package/toolsets/crm.js +495 -0
  32. package/toolsets/extensibility.js +1131 -0
  33. package/toolsets/infrastructure.js +757 -0
  34. package/toolsets/intelligence.js +232 -0
  35. package/toolsets/knowledge.js +154 -0
  36. package/toolsets/matrix.js +217 -0
  37. package/toolsets/outreach.js +432 -0
  38. package/toolsets/prospecting.js +314 -0
  39. package/toolsets/revenue/always-loaded.js +341 -0
  40. package/toolsets/revenue/sloan-tools.js +81 -0
  41. package/transport-http.js +505 -0
@@ -0,0 +1,589 @@
1
+ /**
2
+ * Output Formatter for Composite Tools
3
+ *
4
+ * Formats all composite tool output as sales-flavored markdown.
5
+ * Tables, scores, progress bars, coaching narratives — NOT raw JSON.
6
+ * Inspired by Notion's custom markdown approach for token efficiency.
7
+ */
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Helpers
11
+ // ---------------------------------------------------------------------------
12
+
13
+ export function progressBar(pct, width = 20) {
14
+ const filled = Math.round((pct / 100) * width);
15
+ return '\u2501'.repeat(filled) + '\u2500'.repeat(width - filled) + ` ${pct}%`;
16
+ }
17
+
18
+ export function scoreEmoji(score, max = 10) {
19
+ const pct = (score / max) * 100;
20
+ if (pct >= 80) return '\uD83D\uDD25';
21
+ if (pct >= 60) return '\u2728';
22
+ if (pct >= 40) return '\u26A0\uFE0F';
23
+ return '\u274C';
24
+ }
25
+
26
+ export function replyTypeEmoji(type) {
27
+ const map = {
28
+ positive: '\u2705 Positive',
29
+ meeting_request: '\uD83D\uDD25 Meeting request',
30
+ ooo: '\u23F8\uFE0F OOO',
31
+ not_interested: '\u274C Not interested',
32
+ question: '\u2753 Question',
33
+ forward: '\u27A1\uFE0F Forward',
34
+ bounce: '\u26D4 Bounce',
35
+ auto_reply: '\uD83E\uDD16 Auto-reply',
36
+ };
37
+ return map[type] || type;
38
+ }
39
+
40
+ export function signalEmoji(type) {
41
+ const map = {
42
+ intent: '\uD83D\uDD25',
43
+ hiring: '\uD83D\uDCC8',
44
+ leadership_change: '\uD83D\uDC64',
45
+ funding: '\uD83D\uDCB0',
46
+ expansion: '\uD83C\uDF1F',
47
+ tech_stack: '\uD83D\uDD27',
48
+ news: '\uD83D\uDCF0',
49
+ competitor: '\u2694\uFE0F',
50
+ };
51
+ return map[type] || '\uD83D\uDCA1';
52
+ }
53
+
54
+ export function daysAgoLabel(dateStr) {
55
+ if (!dateStr) return '\u2014';
56
+ const days = Math.floor((Date.now() - new Date(dateStr).getTime()) / 86400000);
57
+ if (days === 0) return 'Today';
58
+ if (days === 1) return '1d ago';
59
+ return `${days}d ago`;
60
+ }
61
+
62
+ export function formatDate(dateStr) {
63
+ if (!dateStr) return '\u2014';
64
+ return new Date(dateStr).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
65
+ }
66
+
67
+ export function daysSince(dateStr) {
68
+ if (!dateStr) return null;
69
+ return Math.floor((Date.now() - new Date(dateStr).getTime()) / 86400000);
70
+ }
71
+
72
+ export function table(headers, rows) {
73
+ const hdr = `| ${headers.join(' | ')} |`;
74
+ const sep = `|${headers.map(() => '------').join('|')}|`;
75
+ const body = rows.map(r => `| ${r.join(' | ')} |`).join('\n');
76
+ return `${hdr}\n${sep}\n${body}`;
77
+ }
78
+
79
+ export function section(title, content) {
80
+ return `### ${title}\n${content}`;
81
+ }
82
+
83
+ export function formatFirmographicValue(metric, options = {}) {
84
+ if (metric === null || metric === undefined || metric === '') return '\u2014';
85
+ if (typeof metric !== 'object') {
86
+ return options.currency && typeof metric === 'number'
87
+ ? formatCurrency(metric, options.currency)
88
+ : String(metric);
89
+ }
90
+ const value = metric?.value ?? null;
91
+ const range = metric?.range ?? null;
92
+ const displayValue = value !== null && value !== undefined
93
+ ? options.currency ? formatCurrency(value, options.currency) : String(value)
94
+ : range || '\u2014';
95
+ const status = metric?.status || 'unknown';
96
+ const confidence = typeof metric?.confidence === 'number'
97
+ ? `${Math.round(metric.confidence * 100)}%`
98
+ : '\u2014';
99
+ const source = formatFirmographicSource(metric?.source);
100
+ return `${displayValue} (${status}, confidence ${confidence}, source ${source})`;
101
+ }
102
+
103
+ export function formatCompanyFirmographics(snapshot) {
104
+ const data = snapshot?.data || snapshot || {};
105
+ let md = `## ${data.name || 'Company'} \u2014 Firmographics\n\n`;
106
+ md += section('Employee Count', '');
107
+ md += `- **Value:** ${formatFirmographicValue(data.employeeCount)}\n`;
108
+ md += firmographicMetadataLines(data.employeeCount);
109
+ md += '\n';
110
+ md += section('Revenue', '');
111
+ md += `- **Value:** ${formatFirmographicValue(data.revenue, { currency: data.revenue?.currency || 'USD' })}\n`;
112
+ if (data.revenue?.metric) md += `- **Metric:** ${data.revenue.metric}\n`;
113
+ md += firmographicMetadataLines(data.revenue);
114
+ if (Array.isArray(data.revenue?.assumptions) && data.revenue.assumptions.length) {
115
+ md += `- **Assumptions:** ${data.revenue.assumptions.join('; ')}\n`;
116
+ }
117
+ if (Array.isArray(data.missing) && data.missing.length) {
118
+ md += `\n${section('Missing', data.missing.map(item => `- ${item}`).join('\n') + '\n')}`;
119
+ }
120
+ return md;
121
+ }
122
+
123
+ function firmographicMetadataLines(metric) {
124
+ let md = '';
125
+ if (metric?.refreshedAt || metric?.observedAt) {
126
+ md += `- **Freshness:** ${metric.refreshedAt || metric.observedAt}\n`;
127
+ }
128
+ if (Array.isArray(metric?.evidenceUrls) && metric.evidenceUrls.length) {
129
+ md += `- **Evidence:** ${metric.evidenceUrls.slice(0, 3).join(', ')}\n`;
130
+ }
131
+ return md;
132
+ }
133
+
134
+ function formatFirmographicSource(source) {
135
+ if (!source) return '\u2014';
136
+ if (typeof source === 'string') return source;
137
+ return source.provider || source.name || source.type || 'source metadata';
138
+ }
139
+
140
+ function formatCurrency(value, currency) {
141
+ if (typeof value !== 'number' || !Number.isFinite(value)) return String(value);
142
+ return new Intl.NumberFormat('en-US', {
143
+ style: 'currency',
144
+ currency,
145
+ maximumFractionDigits: 0,
146
+ }).format(value);
147
+ }
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // Composite formatters
151
+ // ---------------------------------------------------------------------------
152
+
153
+ export function formatQualification(company, icpScore, signals, authority) {
154
+ const score = icpScore?.score ?? icpScore?.totalScore ?? 0;
155
+ const signalsList = (signals?.data || signals || []).slice(0, 8);
156
+ const authorityData = authority?.data || authority || {};
157
+
158
+ let md = `## ${company.name || 'Company'} \u2014 ICP Score: ${score}/100\n`;
159
+ md += progressBar(score) + '\n\n';
160
+
161
+ // Signals table
162
+ const signalRows = signalsList.map(s => [
163
+ signalEmoji(s.type) + ' ' + (s.type || 'Signal'),
164
+ String(s.score ?? s.strength ?? '\u2014'),
165
+ s.detail || s.description || s.summary || '\u2014',
166
+ ]);
167
+
168
+ if (signalRows.length > 0) {
169
+ md += table(['Signal', 'Score', 'Detail'], signalRows) + '\n\n';
170
+ }
171
+
172
+ // Authority
173
+ if (authorityData.champion || authorityData.stakeholders) {
174
+ md += section('Deal Authority', '');
175
+ if (authorityData.champion) md += `- **Champion:** ${authorityData.champion}\n`;
176
+ if (authorityData.decisionMaker) md += `- **Decision Maker:** ${authorityData.decisionMaker}\n`;
177
+ if (authorityData.stakeholders?.length) {
178
+ md += `- **Stakeholders:** ${authorityData.stakeholders.length} identified\n`;
179
+ }
180
+ md += '\n';
181
+ }
182
+
183
+ // Coaching narrative
184
+ md += section('Your Move', '');
185
+ if (signalsList.length > 0) {
186
+ const topSignal = signalsList[0];
187
+ md += `${topSignal.detail || topSignal.description || 'Recent activity detected'}. `;
188
+ }
189
+ md += 'Focus on the strongest signal and open with a question that shows you\'ve done your homework.\n';
190
+
191
+ return md;
192
+ }
193
+
194
+ export function formatCompanyResearch(company, enrichment, signals, competitors) {
195
+ const c = company || {};
196
+ let md = `## ${c.name || 'Company'} \u2014 Research Report\n\n`;
197
+
198
+ // Overview
199
+ md += section('Overview', '');
200
+ md += `- **Industry:** ${c.industry || '\u2014'}\n`;
201
+ md += `- **Size:** ${formatFirmographicValue(c.firmographics?.employeeCount || c.employeeCount)} employees\n`;
202
+ md += `- **Revenue:** ${formatFirmographicValue(c.firmographics?.revenue || c.annualRevenue || c.revenue, { currency: c.currency || 'USD' })}\n`;
203
+ md += `- **Website:** ${c.website || c.domain || '\u2014'}\n`;
204
+ md += `- **Status:** ${c.status || '\u2014'}\n\n`;
205
+
206
+ // Enrichment data
207
+ const enr = enrichment?.data || enrichment || {};
208
+ if (enr.techStack || enr.technologies) {
209
+ md += section('Tech Stack', (enr.techStack || enr.technologies || []).join(', ') + '\n\n');
210
+ }
211
+ if (enr.funding || enr.totalFunding) {
212
+ md += section('Funding', `${enr.funding || enr.totalFunding}\n\n`);
213
+ }
214
+ if (enr.news?.length) {
215
+ md += section('Recent News', enr.news.slice(0, 5).map(n => `- ${n.title || n}`).join('\n') + '\n\n');
216
+ }
217
+
218
+ // Competitors
219
+ const comps = (competitors?.data || competitors || []).slice(0, 5);
220
+ if (comps.length > 0) {
221
+ md += section('Competitors', comps.map(c => `- ${c.name || c}`).join('\n') + '\n\n');
222
+ }
223
+
224
+ // Signals
225
+ const sigs = (signals?.data || signals || []).slice(0, 5);
226
+ if (sigs.length > 0) {
227
+ md += section('Intent Signals', sigs.map(s =>
228
+ `- ${signalEmoji(s.type)} ${s.detail || s.description || s.type} (${formatDate(s.date || s.createdAt)})`
229
+ ).join('\n') + '\n\n');
230
+ }
231
+
232
+ return md;
233
+ }
234
+
235
+ export function formatPersonResearch(person, enrichment, actions) {
236
+ const p = person || {};
237
+ let md = `## ${p.name || p.firstName + ' ' + p.lastName || 'Person'}\n\n`;
238
+
239
+ md += section('Profile', '');
240
+ md += `- **Title:** ${p.title || p.currentRole || '\u2014'}\n`;
241
+ md += `- **Company:** ${p.companyName || '\u2014'}\n`;
242
+ md += `- **Email:** ${p.email || '\u2014'}\n`;
243
+ md += `- **Phone:** ${p.phone || '\u2014'}\n`;
244
+ md += `- **LinkedIn:** ${p.linkedinUrl || '\u2014'}\n`;
245
+ md += `- **Department:** ${p.department || '\u2014'}\n`;
246
+ md += `- **Seniority:** ${p.seniority || '\u2014'}\n\n`;
247
+
248
+ // Enrichment
249
+ const enr = enrichment?.data || enrichment || {};
250
+ if (enr.background || enr.bio) {
251
+ md += section('Background', (enr.background || enr.bio) + '\n\n');
252
+ }
253
+ if (enr.expertise?.length) {
254
+ md += section('Expertise', enr.expertise.join(', ') + '\n\n');
255
+ }
256
+
257
+ // Recent actions
258
+ const acts = (actions?.data || actions || []).slice(0, 5);
259
+ if (acts.length > 0) {
260
+ md += section('Recent Activity', '');
261
+ md += acts.map(a => `- ${formatDate(a.createdAt)} \u2014 ${a.type}: ${a.title} ${a.outcome ? '(' + a.outcome + ')' : ''}`).join('\n');
262
+ md += '\n\n';
263
+ }
264
+
265
+ return md;
266
+ }
267
+
268
+ export function formatSpeedrunList(items) {
269
+ const list = (items?.data || items || []).slice(0, 25);
270
+ if (list.length === 0) return '## Priority Pursuits\n\nNo priority pursuits are ready today. Check back tomorrow or add target accounts.\n';
271
+
272
+ let md = `## Today's Priority Pursuits \u2014 ${list.length} Priorities\n\n`;
273
+ const rows = list.map((item, i) => [
274
+ String(i + 1),
275
+ item.personName || item.name || '\u2014',
276
+ item.companyName || '\u2014',
277
+ String(item.score || item.rank || '\u2014'),
278
+ item.reason || item.signal || '\u2014',
279
+ item.nextAction || '\u2014',
280
+ ]);
281
+ md += table(['#', 'Person', 'Company', 'Score', 'Why Now', 'Next Move'], rows);
282
+ return md;
283
+ }
284
+
285
+ export function formatNextContacts(contacts, overdueCount, promisesCount) {
286
+ const list = (contacts?.data || contacts || []).slice(0, 10);
287
+ if (list.length === 0) return '## Next Contacts\n\nNo contacts queued. Use discover_prospects to find new leads.\n';
288
+
289
+ let md = `## Your Next ${list.length} Contacts\n\n`;
290
+ const rows = list.map((c, i) => [
291
+ String(i + 1),
292
+ c.name || c.personName || '\u2014',
293
+ c.companyName || '\u2014',
294
+ c.signal ? signalEmoji(c.signalType) + ' ' + c.signal : '\u2014',
295
+ c.lastAction ? `${c.lastActionType || 'Touch'} ${formatDate(c.lastActionDate)}` : '\u2014',
296
+ c.daysSinceTouch != null ? `${c.daysSinceTouch}d` : '\u2014',
297
+ c.nextAction || '\u2014',
298
+ ]);
299
+ md += table(['#', 'Person', 'Company', 'Signal', 'Last Touch', 'Days', 'Next Move'], rows);
300
+
301
+ const notes = [];
302
+ if (overdueCount) notes.push(`${overdueCount} overdue`);
303
+ if (promisesCount) notes.push(`${promisesCount} promises unfulfilled`);
304
+ if (notes.length > 0) md += `\n\n${notes.join('. ')}.`;
305
+
306
+ return md;
307
+ }
308
+
309
+ export function formatInbox(replies) {
310
+ const list = (replies?.data || replies || []).slice(0, 15);
311
+ if (list.length === 0) return '## Inbox\n\nNo new replies. Your sequences are running \u2014 check back later.\n';
312
+
313
+ let md = `## ${list.length} New Replies\n\n`;
314
+ const rows = list.map(r => [
315
+ r.from || r.sender || '\u2014',
316
+ r.company || r.companyName || '\u2014',
317
+ r.subject || '\u2014',
318
+ replyTypeEmoji(r.replyType || r.type || 'unknown'),
319
+ r.suggestedAction || r.action || '\u2014',
320
+ ]);
321
+ md += table(['From', 'Company', 'Subject', 'Type', 'Action'], rows);
322
+
323
+ // Highlight actionable items
324
+ const actionable = list.filter(r => ['positive', 'meeting_request'].includes(r.replyType || r.type));
325
+ if (actionable.length > 0) {
326
+ md += '\n\n';
327
+ actionable.forEach(r => {
328
+ md += `**${r.from || r.sender}** \u2014 ${r.suggestedAction || 'Follow up'}.\n`;
329
+ });
330
+ }
331
+
332
+ return md;
333
+ }
334
+
335
+ export function formatCompetitiveIntel(company, competitors) {
336
+ const c = company || {};
337
+ const comps = (competitors?.data || competitors || []).slice(0, 5);
338
+ let md = `## Competitive Intel \u2014 ${c.name || 'Target'}\n\n`;
339
+
340
+ if (comps.length === 0) {
341
+ md += 'No competitive data available yet. Run enrichment to gather intel.\n';
342
+ return md;
343
+ }
344
+
345
+ comps.forEach(comp => {
346
+ md += `### vs. ${comp.name || comp.competitor || 'Competitor'}\n`;
347
+ if (comp.strengths) md += `- **Their strengths:** ${comp.strengths}\n`;
348
+ if (comp.weaknesses) md += `- **Their weaknesses:** ${comp.weaknesses}\n`;
349
+ if (comp.positioning) md += `- **Our positioning:** ${comp.positioning}\n`;
350
+ if (comp.recentMoves) md += `- **Recent moves:** ${comp.recentMoves}\n`;
351
+ md += '\n';
352
+ });
353
+
354
+ return md;
355
+ }
356
+
357
+ export function formatMeetingBrief(meeting, attendees, companyIntel) {
358
+ const m = meeting || {};
359
+ let md = `## Meeting Brief \u2014 ${m.title || m.subject || 'Upcoming Meeting'}\n`;
360
+ md += `**When:** ${formatDate(m.startTime || m.date)} | **Duration:** ${m.duration || '\u2014'}\n\n`;
361
+
362
+ // Attendees
363
+ const atts = (attendees || []).slice(0, 10);
364
+ if (atts.length > 0) {
365
+ md += section('Attendees', '');
366
+ atts.forEach(a => {
367
+ md += `- **${a.name || a.email}** \u2014 ${a.title || '\u2014'} at ${a.companyName || '\u2014'}`;
368
+ if (a.engagementScore) md += ` (engagement: ${a.engagementScore})`;
369
+ md += '\n';
370
+ });
371
+ md += '\n';
372
+ }
373
+
374
+ // Company intel
375
+ if (companyIntel) {
376
+ md += section('Company Context', '');
377
+ md += `- **Industry:** ${companyIntel.industry || '\u2014'}\n`;
378
+ md += `- **Size:** ${companyIntel.employeeCount || '\u2014'}\n`;
379
+ if (companyIntel.recentSignals?.length) {
380
+ md += '- **Recent signals:** ' + companyIntel.recentSignals.slice(0, 3).map(s => s.summary || s.detail || s.type).join('; ') + '\n';
381
+ }
382
+ md += '\n';
383
+ }
384
+
385
+ md += section('Talking Points', '');
386
+ md += '1. Open with a question about their current priorities\n';
387
+ md += '2. Reference any recent signals or news\n';
388
+ md += '3. Align your solution to their stated pain points\n';
389
+
390
+ return md;
391
+ }
392
+
393
+ export function formatDealCoaching(opportunity, analysis) {
394
+ const o = opportunity || {};
395
+ const a = analysis || {};
396
+ let md = `## Deal Coaching \u2014 ${o.name || 'Deal'}\n`;
397
+ md += `**Stage:** ${o.stage || '\u2014'} | **Amount:** $${(o.amount || 0).toLocaleString()} | **Close:** ${formatDate(o.closeDate || o.expectedCloseDate)}\n\n`;
398
+
399
+ // MEDDPIC
400
+ const meddpic = a.meddpic || {};
401
+ const dimensions = [
402
+ ['Metrics', meddpic.metrics],
403
+ ['Economic Buyer', meddpic.economicBuyer],
404
+ ['Decision Criteria', meddpic.decisionCriteria],
405
+ ['Decision Process', meddpic.decisionProcess],
406
+ ['Identify Pain', meddpic.identifyPain],
407
+ ['Champion', meddpic.champion],
408
+ ];
409
+
410
+ if (Object.keys(meddpic).length > 0) {
411
+ md += section('MEDDPIC Analysis', '');
412
+ const rows = dimensions.map(([dim, val]) => {
413
+ const score = val?.score ?? '\u2014';
414
+ const detail = val?.detail || val?.notes || val || '\u2014';
415
+ return [dim, String(score), String(detail)];
416
+ });
417
+ md += table(['Dimension', 'Score', 'Status'], rows) + '\n\n';
418
+ }
419
+
420
+ // Risks
421
+ if (a.risks?.length) {
422
+ md += section('Risks', a.risks.map(r => `- \u26A0\uFE0F ${r}`).join('\n') + '\n\n');
423
+ }
424
+
425
+ // Next move
426
+ md += section('Recommended Next Move', a.nextAction || a.recommendation || 'Advance to next stage by confirming decision criteria with the economic buyer.\n');
427
+
428
+ return md;
429
+ }
430
+
431
+ export function formatForecast(data) {
432
+ const f = data?.data || data || {};
433
+ let md = '## Pipeline Forecast\n\n';
434
+
435
+ if (f.totalPipeline != null) md += `- **Total Pipeline:** $${(f.totalPipeline || 0).toLocaleString()}\n`;
436
+ if (f.weightedPipeline != null) md += `- **Weighted:** $${(f.weightedPipeline || 0).toLocaleString()}\n`;
437
+ if (f.commit != null) md += `- **Commit:** $${(f.commit || 0).toLocaleString()}\n`;
438
+ if (f.bestCase != null) md += `- **Best Case:** $${(f.bestCase || 0).toLocaleString()}\n`;
439
+ if (f.closedWon != null) md += `- **Closed Won:** $${(f.closedWon || 0).toLocaleString()}\n`;
440
+ md += '\n';
441
+
442
+ // Stage breakdown
443
+ const stages = f.stageBreakdown || f.stages || [];
444
+ if (stages.length > 0) {
445
+ const rows = stages.map(s => [
446
+ s.stage || s.name,
447
+ String(s.count || 0),
448
+ `$${(s.amount || 0).toLocaleString()}`,
449
+ s.winRate != null ? `${s.winRate}%` : '\u2014',
450
+ ]);
451
+ md += table(['Stage', 'Deals', 'Amount', 'Win Rate'], rows) + '\n\n';
452
+ }
453
+
454
+ // At-risk deals
455
+ if (f.atRisk?.length) {
456
+ md += section('At-Risk Deals', '');
457
+ f.atRisk.forEach(d => {
458
+ md += `- \u26A0\uFE0F **${d.name}** \u2014 $${(d.amount || 0).toLocaleString()} \u2014 ${d.reason || 'Stalled'}\n`;
459
+ });
460
+ md += '\n';
461
+ }
462
+
463
+ return md;
464
+ }
465
+
466
+ export function formatSignalsDashboard(signals) {
467
+ const list = (signals?.data || signals || []).slice(0, 20);
468
+ if (list.length === 0) return '## Signals Dashboard\n\nNo active signals. Enable enrichment to start tracking intent.\n';
469
+
470
+ let md = `## Signals Dashboard \u2014 ${list.length} Active Signals\n\n`;
471
+
472
+ // Group by urgency/type
473
+ const grouped = {};
474
+ list.forEach(s => {
475
+ const key = s.urgency || s.type || 'other';
476
+ if (!grouped[key]) grouped[key] = [];
477
+ grouped[key].push(s);
478
+ });
479
+
480
+ Object.entries(grouped).forEach(([key, sigs]) => {
481
+ md += `### ${key.charAt(0).toUpperCase() + key.slice(1)} (${sigs.length})\n`;
482
+ sigs.forEach(s => {
483
+ md += `- ${signalEmoji(s.type)} **${s.companyName || '\u2014'}** \u2014 ${s.detail || s.description || s.type} (${formatDate(s.date || s.createdAt)})\n`;
484
+ });
485
+ md += '\n';
486
+ });
487
+
488
+ return md;
489
+ }
490
+
491
+ export function formatSequence(sequence) {
492
+ const s = sequence?.data || sequence || {};
493
+ let md = `## Sequence Created: "${s.name || 'New Sequence'}"\n\n`;
494
+
495
+ const steps = s.steps || [];
496
+ if (steps.length > 0) {
497
+ const rows = steps.map((step, i) => [
498
+ String(i + 1),
499
+ step.type || 'Email',
500
+ step.delay ? `${step.delay} days` : '\u2014',
501
+ step.subject || step.description || '\u2014',
502
+ ]);
503
+ md += table(['Step', 'Type', 'Delay', 'Subject'], rows) + '\n\n';
504
+ }
505
+
506
+ if (s.optimization) md += `Bandit optimization: ${s.optimization}\n`;
507
+ if (s.businessHours) md += `Business hours: ${s.businessHours}\n`;
508
+ md += 'Ready to enroll contacts.\n';
509
+
510
+ return md;
511
+ }
512
+
513
+ export function formatOutreachAnalytics(data) {
514
+ const d = data?.data || data || {};
515
+ let md = '## Outreach Analytics\n\n';
516
+
517
+ if (d.totalSent != null) md += `- **Sent:** ${d.totalSent}\n`;
518
+ if (d.openRate != null) md += `- **Open Rate:** ${d.openRate}%\n`;
519
+ if (d.replyRate != null) md += `- **Reply Rate:** ${d.replyRate}%\n`;
520
+ if (d.bounceRate != null) md += `- **Bounce Rate:** ${d.bounceRate}%\n`;
521
+ md += '\n';
522
+
523
+ // Per-sequence breakdown
524
+ const seqs = d.sequences || d.breakdown || [];
525
+ if (seqs.length > 0) {
526
+ const rows = seqs.map(s => [
527
+ s.name || '\u2014',
528
+ String(s.sent || 0),
529
+ `${s.openRate || 0}%`,
530
+ `${s.replyRate || 0}%`,
531
+ `${s.bounceRate || 0}%`,
532
+ ]);
533
+ md += table(['Sequence', 'Sent', 'Opens', 'Replies', 'Bounces'], rows) + '\n';
534
+ }
535
+
536
+ return md;
537
+ }
538
+
539
+ export function formatEntityTimeline(entity, actions) {
540
+ const e = entity || {};
541
+ const acts = (actions?.data || actions || []).slice(0, 25);
542
+ let md = `## Timeline \u2014 ${e.name || 'Entity'}\n\n`;
543
+
544
+ if (acts.length === 0) {
545
+ md += 'No activity recorded yet.\n';
546
+ return md;
547
+ }
548
+
549
+ acts.forEach(a => {
550
+ const icon = a.type === 'email' ? '\u2709\uFE0F' : a.type === 'call' ? '\uD83D\uDCDE' : a.type === 'meeting' ? '\uD83D\uDCC5' : '\uD83D\uDCDD';
551
+ md += `${icon} **${formatDate(a.createdAt)}** \u2014 ${a.title || a.type}`;
552
+ if (a.outcome) md += ` (${a.outcome})`;
553
+ md += '\n';
554
+ });
555
+
556
+ return md;
557
+ }
558
+
559
+ export function formatDeliverability(data) {
560
+ const d = data?.data || data || {};
561
+ let md = '## Deliverability Report\n\n';
562
+
563
+ if (d.domainHealth != null) {
564
+ md += `**Domain Health:** ${d.domainHealth}/100\n`;
565
+ md += progressBar(d.domainHealth) + '\n\n';
566
+ }
567
+
568
+ if (d.spf) md += `- **SPF:** ${d.spf}\n`;
569
+ if (d.dkim) md += `- **DKIM:** ${d.dkim}\n`;
570
+ if (d.dmarc) md += `- **DMARC:** ${d.dmarc}\n`;
571
+ if (d.blacklisted != null) md += `- **Blacklisted:** ${d.blacklisted ? 'YES \u26A0\uFE0F' : 'No \u2705'}\n`;
572
+ if (d.inboxPlacement != null) md += `- **Inbox Placement:** ${d.inboxPlacement}%\n`;
573
+
574
+ return md;
575
+ }
576
+
577
+ // ---------------------------------------------------------------------------
578
+ // Generic wrapper: any composite tool can use this to return markdown
579
+ // ---------------------------------------------------------------------------
580
+
581
+ export function md(text) {
582
+ return { content: [{ type: 'text', text }] };
583
+ }
584
+
585
+ export function mdError(message, hint) {
586
+ let text = `## Error\n\n${message}`;
587
+ if (hint) text += `\n\n**Hint:** ${hint}`;
588
+ return { content: [{ type: 'text', text }] };
589
+ }
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@adrata/adrata-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Adrata MCP Server — connect Claude Code, Codex, Gemini, Cursor, and other AI tools to your CRM. 80+ tools for companies, people, deals, actions, buyer groups, warm intros, webhooks, intelligence, and more.",
5
+ "type": "module",
6
+ "main": "server.js",
7
+ "bin": {
8
+ "adrata-mcp": "./server.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node server.js",
12
+ "test": "node --test server.test.js api-bridge.test.js buyer-group-writes.test.js note-writes.test.js mcp-spec.test.js packaging.test.js security.test.js toolsets.test.js access/auth.test.js access/oauth-callback.test.js access/oauth-session.test.js access/oauth-capabilities.test.js tools/email-tools.test.js tools/scheduling.test.js tools/work-board-tools.test.js governance/money.test.js"
13
+ },
14
+ "keywords": [
15
+ "mcp",
16
+ "adrata",
17
+ "crm",
18
+ "claude",
19
+ "ai",
20
+ "sales",
21
+ "model-context-protocol",
22
+ "claude-code",
23
+ "codex",
24
+ "gemini",
25
+ "cursor",
26
+ "windsurf"
27
+ ],
28
+ "license": "UNLICENSED",
29
+ "private": false,
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://gitlab.com/adratagit/speedrun"
36
+ },
37
+ "homepage": "https://adrata.com",
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ },
41
+ "files": [
42
+ "server.js",
43
+ "api-bridge.js",
44
+ "analytics.js",
45
+ "security.js",
46
+ "transport-http.js",
47
+ "resources.js",
48
+ "tool-annotations.js",
49
+ "output-formatter.js",
50
+ "access/",
51
+ "tools/",
52
+ "governance/",
53
+ "toolsets/",
54
+ "skills/",
55
+ "server.json",
56
+ "README.md",
57
+ "!**/*.test.js"
58
+ ],
59
+ "dependencies": {
60
+ "@modelcontextprotocol/sdk": "^1.12.1",
61
+ "zod": "^4.4.3"
62
+ },
63
+ "overrides": {
64
+ "@hono/node-server": "2.0.12",
65
+ "fast-uri": "3.1.4",
66
+ "hono": "4.12.27"
67
+ }
68
+ }