@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.
- package/README.md +548 -0
- package/access/auth.js +289 -0
- package/access/oauth.js +1059 -0
- package/access/resource-metadata.js +167 -0
- package/access/tiers.js +422 -0
- package/analytics.js +634 -0
- package/api-bridge.js +499 -0
- package/governance/money.js +141 -0
- package/output-formatter.js +589 -0
- package/package.json +68 -0
- package/resources.js +246 -0
- package/security.js +690 -0
- package/server.js +2139 -0
- package/server.json +55 -0
- package/skills/backlog-triage/SKILL.md +115 -0
- package/skills/board-review/SKILL.md +96 -0
- package/skills/incident-to-card/SKILL.md +126 -0
- package/skills/log-outreach.md +62 -0
- package/skills/ship-the-card/SKILL.md +155 -0
- package/tool-annotations.js +269 -0
- package/tools/billing.js +149 -0
- package/tools/email-tools.js +652 -0
- package/tools/enterprise-tools.js +651 -0
- package/tools/free-search.js +160 -0
- package/tools/memory.js +440 -0
- package/tools/morning-brief.js +551 -0
- package/tools/paper-tools.js +563 -0
- package/tools/scheduling.js +322 -0
- package/tools/work-board-tools.js +758 -0
- package/toolsets/communications.js +276 -0
- package/toolsets/crm.js +495 -0
- package/toolsets/extensibility.js +1131 -0
- package/toolsets/infrastructure.js +757 -0
- package/toolsets/intelligence.js +232 -0
- package/toolsets/knowledge.js +154 -0
- package/toolsets/matrix.js +217 -0
- package/toolsets/outreach.js +432 -0
- package/toolsets/prospecting.js +314 -0
- package/toolsets/revenue/always-loaded.js +341 -0
- package/toolsets/revenue/sloan-tools.js +81 -0
- 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
|
+
}
|