@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,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intelligence Toolset (5 tools, Pro tier)
|
|
3
|
+
*
|
|
4
|
+
* get_competitive_intel, get_meeting_brief, get_deal_coaching,
|
|
5
|
+
* get_signals_dashboard, get_forecast
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import {
|
|
10
|
+
md, mdError,
|
|
11
|
+
formatCompetitiveIntel, formatMeetingBrief, formatDealCoaching,
|
|
12
|
+
formatSignalsDashboard, formatForecast,
|
|
13
|
+
} from '../output-formatter.js';
|
|
14
|
+
|
|
15
|
+
export function register(server, api, AUTH) {
|
|
16
|
+
|
|
17
|
+
// -----------------------------------------------------------------------
|
|
18
|
+
// get_competitive_intel
|
|
19
|
+
// -----------------------------------------------------------------------
|
|
20
|
+
server.tool(
|
|
21
|
+
'get_competitive_intel',
|
|
22
|
+
'Get competitive intelligence for a company: battlecard positioning, competitor strengths/weaknesses, recent competitive moves, and displacement strategies.',
|
|
23
|
+
{
|
|
24
|
+
companyId: z.string().optional().describe('Company ID'),
|
|
25
|
+
name: z.string().optional().describe('Company name to search'),
|
|
26
|
+
},
|
|
27
|
+
async (args) => {
|
|
28
|
+
try {
|
|
29
|
+
let companyId = args.companyId;
|
|
30
|
+
let company;
|
|
31
|
+
|
|
32
|
+
if (!companyId && args.name) {
|
|
33
|
+
const search = await api('GET', '/api/v1/companies', { params: { search: args.name, limit: 3, page: 1 } });
|
|
34
|
+
const match = (search?.data || [])[0];
|
|
35
|
+
if (!match) return mdError(`Company "${args.name}" not found`);
|
|
36
|
+
companyId = match.id;
|
|
37
|
+
company = match;
|
|
38
|
+
} else if (companyId) {
|
|
39
|
+
company = await api('GET', `/api/v1/companies/${companyId}`);
|
|
40
|
+
company = company?.data || company;
|
|
41
|
+
} else {
|
|
42
|
+
return mdError('Provide companyId or name');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const competitors = await api('GET', '/api/v1/competitors', { params: { companyId } }).catch(() => ({ data: [] }));
|
|
46
|
+
return md(formatCompetitiveIntel(company, competitors));
|
|
47
|
+
} catch (err) {
|
|
48
|
+
return mdError('Competitive intel failed', err.message);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// -----------------------------------------------------------------------
|
|
54
|
+
// get_meeting_brief
|
|
55
|
+
// -----------------------------------------------------------------------
|
|
56
|
+
server.tool(
|
|
57
|
+
'get_meeting_brief',
|
|
58
|
+
'Generate a pre-meeting intelligence brief: attendee profiles, company context, recent signals, talking points, and suggested agenda. Walk into every meeting prepared.',
|
|
59
|
+
{
|
|
60
|
+
meetingId: z.string().optional().describe('Meeting ID for a specific meeting'),
|
|
61
|
+
companyId: z.string().optional().describe('Company ID for ad-hoc brief'),
|
|
62
|
+
personIds: z.array(z.string()).optional().describe('Person IDs of attendees'),
|
|
63
|
+
},
|
|
64
|
+
async (args) => {
|
|
65
|
+
try {
|
|
66
|
+
let meeting = {};
|
|
67
|
+
let attendees = [];
|
|
68
|
+
let companyIntel = null;
|
|
69
|
+
|
|
70
|
+
if (args.meetingId) {
|
|
71
|
+
// Base record is the calendar event (real attendees + timing); the
|
|
72
|
+
// copilot summary/action-items only exist for recorded meetings, so
|
|
73
|
+
// they stay best-effort and degrade to empty for plain calendar events.
|
|
74
|
+
meeting = await api('GET', `/api/v1/events/${args.meetingId}`).catch(() => ({}));
|
|
75
|
+
meeting = meeting?.data || meeting;
|
|
76
|
+
|
|
77
|
+
// Get action items and summary
|
|
78
|
+
const [summary, actionItems] = await Promise.all([
|
|
79
|
+
api('GET', `/api/v1/meetings/${args.meetingId}/summary`).catch(() => ({})),
|
|
80
|
+
api('GET', `/api/v1/meetings/${args.meetingId}/action-items`).catch(() => ({ data: [] })),
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
if (summary?.data) meeting.summary = summary.data;
|
|
84
|
+
if (actionItems?.data) meeting.actionItems = actionItems.data;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Resolve attendees
|
|
88
|
+
const personIds = args.personIds || meeting.attendeeIds || [];
|
|
89
|
+
if (personIds.length > 0) {
|
|
90
|
+
attendees = await Promise.all(
|
|
91
|
+
personIds.slice(0, 10).map(id =>
|
|
92
|
+
api('GET', `/api/v1/people/${id}`).then(r => r?.data || r).catch(() => ({ id }))
|
|
93
|
+
)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Company intel
|
|
98
|
+
const companyId = args.companyId || meeting.companyId || attendees[0]?.companyId;
|
|
99
|
+
if (companyId) {
|
|
100
|
+
const [company, signals] = await Promise.all([
|
|
101
|
+
api('GET', `/api/v1/companies/${companyId}`).then(r => r?.data || r).catch(() => null),
|
|
102
|
+
api('GET', '/api/v1/intent-signals', { params: { companyId } }).catch(() => ({ data: [] })),
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
if (company) {
|
|
106
|
+
companyIntel = {
|
|
107
|
+
...company,
|
|
108
|
+
recentSignals: signals?.data || [],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return md(formatMeetingBrief(meeting, attendees, companyIntel));
|
|
114
|
+
} catch (err) {
|
|
115
|
+
return mdError('Meeting brief failed', err.message);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// -----------------------------------------------------------------------
|
|
121
|
+
// get_deal_coaching
|
|
122
|
+
// -----------------------------------------------------------------------
|
|
123
|
+
server.tool(
|
|
124
|
+
'get_deal_coaching',
|
|
125
|
+
'MEDDPIC analysis for a deal with risk assessment, forcing questions, and recommended next move. Orchestrates opportunity, stakeholder, and activity data into coaching insights.',
|
|
126
|
+
{
|
|
127
|
+
opportunityId: z.string().describe('Opportunity/deal ID'),
|
|
128
|
+
},
|
|
129
|
+
async (args) => {
|
|
130
|
+
try {
|
|
131
|
+
// Parallel: opportunity, authority, actions
|
|
132
|
+
const [opportunity, authority, actions] = await Promise.all([
|
|
133
|
+
api('GET', `/api/v1/opportunities/${args.opportunityId}`).then(r => r?.data || r),
|
|
134
|
+
api('GET', '/api/v1/deal-authority/buyer-intelligence', { params: { opportunityId: args.opportunityId } }).catch(() => ({})),
|
|
135
|
+
api('GET', '/api/v1/actions', { params: { companyId: args.opportunityId, limit: 20, page: 1 } }).catch(() => ({ data: [] })),
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
const authData = authority?.data || authority || {};
|
|
139
|
+
const actList = actions?.data || [];
|
|
140
|
+
|
|
141
|
+
// Build MEDDPIC analysis from available data
|
|
142
|
+
const analysis = {
|
|
143
|
+
meddpic: {
|
|
144
|
+
metrics: { score: opportunity.amount ? 7 : 3, detail: opportunity.amount ? `$${opportunity.amount.toLocaleString()} identified` : 'No metrics defined' },
|
|
145
|
+
economicBuyer: { score: authData.decisionMaker ? 8 : 3, detail: authData.decisionMaker || 'Not identified' },
|
|
146
|
+
decisionCriteria: { score: opportunity.stage === 'Qualification' || opportunity.stage === 'Proposal' ? 6 : 3, detail: opportunity.stage || 'Early stage' },
|
|
147
|
+
decisionProcess: { score: opportunity.probability ? Math.min(10, Math.round(opportunity.probability / 10)) : 3, detail: `${opportunity.probability || 0}% probability` },
|
|
148
|
+
identifyPain: { score: actList.length > 5 ? 7 : actList.length > 0 ? 5 : 2, detail: `${actList.length} interactions logged` },
|
|
149
|
+
champion: { score: authData.champion ? 9 : 2, detail: authData.champion || 'No champion identified' },
|
|
150
|
+
},
|
|
151
|
+
risks: [],
|
|
152
|
+
nextAction: '',
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Identify risks
|
|
156
|
+
if (!authData.champion) analysis.risks.push('No champion identified \u2014 deals without champions close at 10% rate');
|
|
157
|
+
if (!authData.decisionMaker) analysis.risks.push('Economic buyer unknown \u2014 you may be single-threaded');
|
|
158
|
+
if (opportunity.probability && opportunity.probability < 30) analysis.risks.push('Low probability \u2014 validate that the pain is real and urgent');
|
|
159
|
+
if (actList.length < 3) analysis.risks.push('Low activity \u2014 engagement may be stalling');
|
|
160
|
+
|
|
161
|
+
// Recommend next move
|
|
162
|
+
if (!authData.champion) {
|
|
163
|
+
analysis.nextAction = 'Find your champion. Ask: "Who else on your team would benefit from this?" Map the buying committee before advancing.';
|
|
164
|
+
} else if (!authData.decisionMaker) {
|
|
165
|
+
analysis.nextAction = 'Get to the economic buyer. Ask your champion: "Walk me through how budget decisions get made for initiatives like this."';
|
|
166
|
+
} else {
|
|
167
|
+
analysis.nextAction = 'Advance to next stage by confirming decision criteria and timeline with the economic buyer.';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return md(formatDealCoaching(opportunity, analysis));
|
|
171
|
+
} catch (err) {
|
|
172
|
+
return mdError('Deal coaching failed', err.message);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// -----------------------------------------------------------------------
|
|
178
|
+
// get_signals_dashboard
|
|
179
|
+
// -----------------------------------------------------------------------
|
|
180
|
+
server.tool(
|
|
181
|
+
'get_signals_dashboard',
|
|
182
|
+
'Intent signals across all accounts, grouped by urgency. Shows buying signals, leadership changes, hiring patterns, funding events, and competitive moves.',
|
|
183
|
+
{
|
|
184
|
+
days: z.number().optional().describe('Lookback period in days (default 30)'),
|
|
185
|
+
companyId: z.string().optional().describe('Filter to a specific company'),
|
|
186
|
+
},
|
|
187
|
+
async (args) => {
|
|
188
|
+
try {
|
|
189
|
+
const params = { limit: 50, page: 1 };
|
|
190
|
+
if (args.companyId) params.companyId = args.companyId;
|
|
191
|
+
if (args.days) params.days = args.days;
|
|
192
|
+
|
|
193
|
+
const signals = await api('GET', '/api/v1/intent-signals', { params });
|
|
194
|
+
return md(formatSignalsDashboard(signals));
|
|
195
|
+
} catch (err) {
|
|
196
|
+
return mdError('Signals dashboard failed', err.message);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// -----------------------------------------------------------------------
|
|
202
|
+
// get_forecast
|
|
203
|
+
// -----------------------------------------------------------------------
|
|
204
|
+
server.tool(
|
|
205
|
+
'get_forecast',
|
|
206
|
+
'Pipeline forecast with confidence intervals, stage breakdown, at-risk deals, and win rate trends. The executive-level view of your pipeline health.',
|
|
207
|
+
{
|
|
208
|
+
period: z.string().optional().describe('Forecast period: 7d, 30d, 90d, qtd, ytd (default: qtd)'),
|
|
209
|
+
},
|
|
210
|
+
async (args) => {
|
|
211
|
+
try {
|
|
212
|
+
const period = args.period || 'qtd';
|
|
213
|
+
|
|
214
|
+
// Parallel: forecast + pipeline metrics
|
|
215
|
+
const [forecast, pipeline] = await Promise.all([
|
|
216
|
+
api('GET', '/api/v1/forecast', { params: { period } }).catch(() => ({})),
|
|
217
|
+
api('GET', '/api/v1/analytics/pipeline', { params: { period } }).catch(() => ({})),
|
|
218
|
+
]);
|
|
219
|
+
|
|
220
|
+
// Merge data
|
|
221
|
+
const merged = {
|
|
222
|
+
...(forecast?.data || forecast || {}),
|
|
223
|
+
...(pipeline?.data || pipeline || {}),
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return md(formatForecast({ data: merged }));
|
|
227
|
+
} catch (err) {
|
|
228
|
+
return mdError('Forecast failed', err.message);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Hub Toolset (4 tools, Pro tier)
|
|
3
|
+
*
|
|
4
|
+
* search_knowledge, get_account_wiki, create_knowledge_file, link_file_to_entity
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { md, mdError, table } from '../output-formatter.js';
|
|
9
|
+
|
|
10
|
+
export function register(server, api, AUTH) {
|
|
11
|
+
|
|
12
|
+
// -----------------------------------------------------------------------
|
|
13
|
+
// search_knowledge
|
|
14
|
+
// -----------------------------------------------------------------------
|
|
15
|
+
server.tool(
|
|
16
|
+
'search_knowledge',
|
|
17
|
+
'Search wiki pages and knowledge files (battle cards, playbooks, competitive intel). Returns matching content ranked by relevance.',
|
|
18
|
+
{
|
|
19
|
+
query: z.string().describe('Search query'),
|
|
20
|
+
pageType: z.string().optional().describe('Filter by page type (e.g. battlecard, playbook, overview)'),
|
|
21
|
+
limit: z.number().optional().describe('Max results (default 25)'),
|
|
22
|
+
},
|
|
23
|
+
async (args) => {
|
|
24
|
+
try {
|
|
25
|
+
const params = { q: args.query };
|
|
26
|
+
if (args.pageType) params.pageType = args.pageType;
|
|
27
|
+
if (args.limit) params.limit = args.limit;
|
|
28
|
+
|
|
29
|
+
const result = await api('GET', '/api/v1/knowledge/wiki/search', { params });
|
|
30
|
+
const items = result?.data || [];
|
|
31
|
+
|
|
32
|
+
if (items.length === 0) {
|
|
33
|
+
return md('## Knowledge Search\n\nNo results found.\n');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let text = `## Knowledge Search: "${args.query}"\n\n`;
|
|
37
|
+
const rows = items.map(item => [
|
|
38
|
+
item.title || '\u2014',
|
|
39
|
+
item.pageType || item.fileType || '\u2014',
|
|
40
|
+
item.category || '\u2014',
|
|
41
|
+
item.updatedAt ? new Date(item.updatedAt).toLocaleDateString() : '\u2014',
|
|
42
|
+
]);
|
|
43
|
+
text += table(['Title', 'Type', 'Category', 'Updated'], rows);
|
|
44
|
+
return md(text);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
return mdError('Knowledge search failed', err.message);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// -----------------------------------------------------------------------
|
|
52
|
+
// get_account_wiki
|
|
53
|
+
// -----------------------------------------------------------------------
|
|
54
|
+
server.tool(
|
|
55
|
+
'get_account_wiki',
|
|
56
|
+
'Get the compiled wiki for a company/account. Returns all wiki pages with intelligence, battle cards, and playbook content for that account.',
|
|
57
|
+
{
|
|
58
|
+
accountId: z.string().describe('Company/account ID'),
|
|
59
|
+
},
|
|
60
|
+
async (args) => {
|
|
61
|
+
try {
|
|
62
|
+
const result = await api('GET', `/api/v1/knowledge/wiki/${args.accountId}`);
|
|
63
|
+
const pages = result?.data || result?.pages || [];
|
|
64
|
+
|
|
65
|
+
if (!pages || pages.length === 0) {
|
|
66
|
+
return md(`## Account Wiki\n\nNo wiki pages found for account ${args.accountId}.\n`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let text = `## Account Wiki (${pages.length} pages)\n\n`;
|
|
70
|
+
for (const page of pages) {
|
|
71
|
+
text += `### ${page.title || page.pageType || 'Untitled'}\n\n`;
|
|
72
|
+
if (page.content) {
|
|
73
|
+
// Truncate long content for readability
|
|
74
|
+
const content = page.content.length > 500
|
|
75
|
+
? page.content.slice(0, 500) + '...'
|
|
76
|
+
: page.content;
|
|
77
|
+
text += content + '\n\n';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return md(text);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
return mdError('Account wiki retrieval failed', err.message);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// -----------------------------------------------------------------------
|
|
88
|
+
// create_knowledge_file
|
|
89
|
+
// -----------------------------------------------------------------------
|
|
90
|
+
server.tool(
|
|
91
|
+
'create_knowledge_file',
|
|
92
|
+
'Create a knowledge file (battle card, playbook, competitive brief, objection handler, etc.). Saves to the knowledge hub for team-wide access.',
|
|
93
|
+
{
|
|
94
|
+
title: z.string().describe('File title'),
|
|
95
|
+
fileType: z.string().describe('Type: battlecard, playbook, competitive_brief, objection_handler, process_doc, template'),
|
|
96
|
+
content: z.string().describe('File content (markdown supported)'),
|
|
97
|
+
category: z.string().optional().describe('Category for organization'),
|
|
98
|
+
},
|
|
99
|
+
async (args) => {
|
|
100
|
+
try {
|
|
101
|
+
const body = {
|
|
102
|
+
title: args.title,
|
|
103
|
+
fileType: args.fileType,
|
|
104
|
+
content: args.content,
|
|
105
|
+
};
|
|
106
|
+
if (args.category) body.category = args.category;
|
|
107
|
+
|
|
108
|
+
const result = await api('POST', '/api/v1/knowledge/files', { body });
|
|
109
|
+
const file = result?.data || result;
|
|
110
|
+
|
|
111
|
+
return md(
|
|
112
|
+
`## Knowledge File Created\n\n` +
|
|
113
|
+
`- **Title:** ${file.title || args.title}\n` +
|
|
114
|
+
`- **ID:** ${file.id || '\u2014'}\n` +
|
|
115
|
+
`- **Type:** ${file.fileType || args.fileType}\n` +
|
|
116
|
+
`- **Category:** ${file.category || args.category || '\u2014'}\n`
|
|
117
|
+
);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
return mdError('Knowledge file creation failed', err.message);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// -----------------------------------------------------------------------
|
|
125
|
+
// link_file_to_entity
|
|
126
|
+
// -----------------------------------------------------------------------
|
|
127
|
+
server.tool(
|
|
128
|
+
'link_file_to_entity',
|
|
129
|
+
'Connect a knowledge file to a CRM record (company, person, opportunity). Links battle cards to accounts, playbooks to deals, etc.',
|
|
130
|
+
{
|
|
131
|
+
fileId: z.string().describe('Knowledge file ID'),
|
|
132
|
+
entityType: z.string().describe('Entity type: company, person, opportunity'),
|
|
133
|
+
entityId: z.string().describe('Entity ID to link to'),
|
|
134
|
+
},
|
|
135
|
+
async (args) => {
|
|
136
|
+
try {
|
|
137
|
+
const body = {
|
|
138
|
+
entityType: args.entityType,
|
|
139
|
+
entityId: args.entityId,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
await api('POST', `/api/v1/knowledge/files/${args.fileId}/entities`, { body });
|
|
143
|
+
|
|
144
|
+
return md(
|
|
145
|
+
`## File Linked\n\n` +
|
|
146
|
+
`- **File:** ${args.fileId}\n` +
|
|
147
|
+
`- **Linked to:** ${args.entityType} ${args.entityId}\n`
|
|
148
|
+
);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
return mdError('File linking failed', err.message);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Matrix Toolset (10 tools, Pro tier)
|
|
3
|
+
*
|
|
4
|
+
* Read-only wrappers around the URL-only Matrix analytics surface.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { md, mdError } from '../output-formatter.js';
|
|
9
|
+
|
|
10
|
+
function renderJson(title, data) {
|
|
11
|
+
return md(`## ${title}\n\n\`\`\`json\n${JSON.stringify(data, null, 2)}\n\`\`\`\n`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function register(server, api, AUTH) {
|
|
15
|
+
registerGetTool(server, api, {
|
|
16
|
+
names: ['analytics.matrix.pipeline_snapshot_diff', 'matrix_pipeline_snapshot_diff'],
|
|
17
|
+
description: 'Read Matrix pipeline snapshot movement: open pipeline, created amount, slipped amount, and delta.',
|
|
18
|
+
path: '/api/v1/analytics/matrix/pipeline/snapshot-diff',
|
|
19
|
+
title: 'Matrix Pipeline Snapshot Diff',
|
|
20
|
+
schema: {
|
|
21
|
+
sinceDays: z.number().int().min(1).max(90).optional().describe('Baseline window in days. Default 1.'),
|
|
22
|
+
},
|
|
23
|
+
params: (args) => ({ sinceDays: args.sinceDays }),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
registerGetTool(server, api, {
|
|
27
|
+
names: ['analytics.matrix.pipeline_deal_health_queue', 'matrix_pipeline_deal_health_queue'],
|
|
28
|
+
description: 'Read Matrix deal-health queue ranked by dollars at risk and evidence recency.',
|
|
29
|
+
path: '/api/v1/analytics/matrix/pipeline/deal-health-queue',
|
|
30
|
+
title: 'Matrix Deal Health Queue',
|
|
31
|
+
schema: {
|
|
32
|
+
limit: z.number().int().min(1).max(50).optional().describe('Max deals to return. Default 20.'),
|
|
33
|
+
},
|
|
34
|
+
params: (args) => ({ limit: args.limit }),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
registerGetTool(server, api, {
|
|
38
|
+
names: ['analytics.matrix.market_competitor_pulse', 'matrix_market_competitor_pulse'],
|
|
39
|
+
description: 'Read Matrix competitor pulse rows with win-rate and event context.',
|
|
40
|
+
path: '/api/v1/analytics/matrix/market/competitor-pulse',
|
|
41
|
+
title: 'Matrix Competitor Pulse',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
registerGetTool(server, api, {
|
|
45
|
+
names: ['analytics.matrix.market_competitor_deal_coaching', 'matrix_market_competitor_deal_coaching'],
|
|
46
|
+
description: 'Read Matrix competitor-aware deal coaching recommendations for in-flight opportunities.',
|
|
47
|
+
path: '/api/v1/analytics/matrix/market/competitor-deal-coaching',
|
|
48
|
+
title: 'Matrix Competitor Deal Coaching',
|
|
49
|
+
schema: {
|
|
50
|
+
limit: z.number().int().min(1).max(50).optional().describe('Max coaching rows to return. Default 10.'),
|
|
51
|
+
},
|
|
52
|
+
params: (args) => ({ limit: args.limit }),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
registerGetTool(server, api, {
|
|
56
|
+
names: ['analytics.matrix.people_causal_lift', 'matrix_people_causal_lift'],
|
|
57
|
+
description: 'Read Matrix people causal-lift estimates with confidence intervals and assumptions.',
|
|
58
|
+
path: '/api/v1/analytics/matrix/people/causal-lift',
|
|
59
|
+
title: 'Matrix People Causal Lift',
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
registerGetTool(server, api, {
|
|
63
|
+
names: ['analytics.matrix.people_single_thread_risk', 'matrix_people_single_thread_risk'],
|
|
64
|
+
description: 'Read Matrix single-thread and champion-risk opportunities by rep.',
|
|
65
|
+
path: '/api/v1/analytics/matrix/people/single-thread-risk',
|
|
66
|
+
title: 'Matrix People Single-Thread Risk',
|
|
67
|
+
schema: {
|
|
68
|
+
limit: z.number().int().min(1).max(50).optional().describe('Max opportunities to return. Default 20.'),
|
|
69
|
+
},
|
|
70
|
+
params: (args) => ({ limit: args.limit }),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
registerGetTool(server, api, {
|
|
74
|
+
names: ['analytics.matrix.forecast_commit_best_worst', 'matrix_forecast_commit_best_worst'],
|
|
75
|
+
description: 'Read Matrix forecast commit, best-case, worst-case, and confidence bands.',
|
|
76
|
+
path: '/api/v1/analytics/matrix/forecast/commit-best-worst',
|
|
77
|
+
title: 'Matrix Forecast Commit Best Worst',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
registerGetTool(server, api, {
|
|
81
|
+
names: ['analytics.matrix.forecast_multi_model_consensus', 'matrix_forecast_multi_model_consensus'],
|
|
82
|
+
description: 'Read Matrix DHC/survival/conformal disagreement rows for forecast inspection.',
|
|
83
|
+
path: '/api/v1/analytics/matrix/forecast/multi-model-consensus',
|
|
84
|
+
title: 'Matrix Forecast Multi-Model Consensus',
|
|
85
|
+
schema: {
|
|
86
|
+
limit: z.number().int().min(1).max(50).optional().describe('Max rows to return. Default 12.'),
|
|
87
|
+
},
|
|
88
|
+
params: (args) => ({ limit: args.limit }),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
registerGetTool(server, api, {
|
|
92
|
+
names: ['analytics.matrix.anomalies_feed', 'matrix_anomalies_feed'],
|
|
93
|
+
description: 'Read active Matrix anomalies ranked by dollars at risk with evidence IDs and suggested workflow handoffs.',
|
|
94
|
+
path: '/api/v1/analytics/matrix/anomalies/feed',
|
|
95
|
+
title: 'Matrix Anomalies Feed',
|
|
96
|
+
schema: {
|
|
97
|
+
limit: z.number().int().min(1).max(25).optional().describe('Max anomalies to return. Default 10.'),
|
|
98
|
+
},
|
|
99
|
+
params: (args) => ({ limit: args.limit }),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
registerPostTool(server, api, {
|
|
103
|
+
names: ['analytics.matrix.nl_ask', 'matrix_nl_ask'],
|
|
104
|
+
description: 'Ask Matrix a verified analytics question. Best for pipeline health, deal movement, forecast commit, competitor pulse, and seller risk. Returns cited rows when available.',
|
|
105
|
+
path: '/api/v1/analytics/matrix/nl/ask',
|
|
106
|
+
title: 'Matrix Answer',
|
|
107
|
+
schema: {
|
|
108
|
+
question: z.string().describe('Question to ask Matrix. Example: Which deals slipped this week?'),
|
|
109
|
+
},
|
|
110
|
+
body: (args) => ({ question: args.question }),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Backwards-compatible aliases from the first Matrix MCP batch.
|
|
114
|
+
server.tool(
|
|
115
|
+
'ask_matrix',
|
|
116
|
+
'Alias for matrix_nl_ask.',
|
|
117
|
+
{ question: z.string().describe('Question to ask Matrix.') },
|
|
118
|
+
async (args) => callPost(api, '/api/v1/analytics/matrix/nl/ask', { question: args.question }, 'Matrix Answer')
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
server.tool(
|
|
122
|
+
'get_matrix_anomalies',
|
|
123
|
+
'Alias for matrix_anomalies_feed.',
|
|
124
|
+
{
|
|
125
|
+
limit: z.number().optional().describe('Max anomalies to return. Default 10, max 25.'),
|
|
126
|
+
},
|
|
127
|
+
async (args) => {
|
|
128
|
+
try {
|
|
129
|
+
const result = await api('GET', '/api/v1/analytics/matrix/anomalies/feed', { params: { limit: args.limit } });
|
|
130
|
+
return renderJson('Matrix Anomalies Feed', result?.data || result);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
return mdError('Matrix anomalies failed', err.message);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
server.tool(
|
|
138
|
+
'get_matrix_deal_probability',
|
|
139
|
+
'Get a Matrix posterior win probability and evidence chain for one opportunity.',
|
|
140
|
+
{
|
|
141
|
+
opportunityId: z.string().describe('Opportunity ID'),
|
|
142
|
+
},
|
|
143
|
+
async (args) => {
|
|
144
|
+
try {
|
|
145
|
+
const result = await api('GET', `/api/v1/analytics/matrix/forecast/deal-probability/${args.opportunityId}`);
|
|
146
|
+
return renderJson('Matrix Deal Probability', result?.data || result);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
return mdError('Matrix deal probability failed', err.message);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
server.tool(
|
|
154
|
+
'get_matrix_recommended_actions',
|
|
155
|
+
'Get EV-ranked Matrix actions for an opportunity, including workflow template keys.',
|
|
156
|
+
{
|
|
157
|
+
opportunityId: z.string().describe('Opportunity ID'),
|
|
158
|
+
},
|
|
159
|
+
async (args) => {
|
|
160
|
+
try {
|
|
161
|
+
const result = await api('GET', `/api/v1/analytics/matrix/forecast/recommended-actions/${args.opportunityId}`);
|
|
162
|
+
return renderJson('Matrix Recommended Actions', result?.data || result);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
return mdError('Matrix recommended actions failed', err.message);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
server.tool(
|
|
170
|
+
'get_matrix_digest_preview',
|
|
171
|
+
'Preview the Matrix morning digest: top anomalies, slipping deals, competitor event, and deep links.',
|
|
172
|
+
{},
|
|
173
|
+
async () => {
|
|
174
|
+
try {
|
|
175
|
+
const result = await api('GET', '/api/v1/analytics/matrix/digest/preview');
|
|
176
|
+
return renderJson('Matrix Digest Preview', result?.data || result);
|
|
177
|
+
} catch (err) {
|
|
178
|
+
return mdError('Matrix digest preview failed', err.message);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function registerGetTool(server, api, { name, names, description, path, title, schema = {}, params = () => ({}) }) {
|
|
185
|
+
for (const toolName of names || [name]) {
|
|
186
|
+
server.tool(toolName, description, schema, async (args) => {
|
|
187
|
+
try {
|
|
188
|
+
const result = await api('GET', path, { params: params(args) });
|
|
189
|
+
return renderJson(title, result?.data || result);
|
|
190
|
+
} catch (err) {
|
|
191
|
+
return mdError(`${title} failed`, err.message);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function registerPostTool(server, api, { name, names, description, path, title, schema = {}, body = () => ({}) }) {
|
|
198
|
+
for (const toolName of names || [name]) {
|
|
199
|
+
server.tool(toolName, description, schema, async (args) => {
|
|
200
|
+
try {
|
|
201
|
+
const result = await api('POST', path, { body: body(args) });
|
|
202
|
+
return renderJson(title, result?.data || result);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
return mdError(`${title} failed`, err.message);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function callPost(api, path, body, title) {
|
|
211
|
+
try {
|
|
212
|
+
const result = await api('POST', path, { body });
|
|
213
|
+
return renderJson(title, result?.data || result);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
return mdError(`${title} failed`, err.message);
|
|
216
|
+
}
|
|
217
|
+
}
|