@adrata/adrata-mcp 1.0.43 → 1.0.44
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/package.json +2 -2
- package/server.js +21 -8
- package/server.json +2 -2
- package/tools/free-search.js +131 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adrata/adrata-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.44",
|
|
4
4
|
"description": "Adrata MCP Server \u2014 connect Claude Code, Codex, Gemini, Cursor, and other AI tools to your CRM. About 275 tools registered at startup for companies, people, deals, actions, buyer groups, warm intros, webhooks and intelligence, plus 65 more behind eight named toolsets you load with enable_toolset.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node server.js",
|
|
12
|
-
"test": "node --test analytics.test.js server.test.js api-bridge.test.js http/edge-block.test.js http/rate-limit.test.js audit-flush.test.js buyer-group-writes.test.js note-writes.test.js mcp-spec.test.js packaging.test.js product-profile.test.js security.test.js security.cap-contract.test.js tool-annotations.test.js toolsets.test.js toolsets/communications.test.js toolsets/prospecting.test.js access/auth.test.js access/oauth-callback.test.js access/oauth-session.test.js access/oauth-capabilities.test.js access/oauth-scope-grantability.test.js scripts/local-dev-server.test.js tools/competitive-coverage.test.js tools/describe-count.test.js tools/email-tools.test.js tools/scheduling.test.js tools/work-board-tools.test.js tools/launches/work-launch-tools.test.js tools/work-hub/audit.test.js tools/work-hub/field-changes.test.js tools/work-hub/criteria-quality.test.js tools/roadmap-tools.test.js tools/provisioning/onboarding-tools.test.js tools/source-control/connection-tools.test.js governance/money.test.js http/rate-limit-policy.test.js"
|
|
12
|
+
"test": "node --test analytics.test.js server.test.js api-bridge.test.js http/edge-block.test.js http/rate-limit.test.js audit-flush.test.js buyer-group-writes.test.js note-writes.test.js mcp-spec.test.js packaging.test.js product-profile.test.js security.test.js security.cap-contract.test.js tool-annotations.test.js toolsets.test.js toolsets/communications.test.js toolsets/prospecting.test.js access/auth.test.js access/oauth-callback.test.js access/oauth-session.test.js access/oauth-capabilities.test.js access/oauth-scope-grantability.test.js scripts/local-dev-server.test.js tools/competitive-coverage.test.js tools/describe-count.test.js tools/email-tools.test.js tools/scheduling.test.js tools/work-board-tools.test.js tools/launches/work-launch-tools.test.js tools/work-hub/audit.test.js tools/work-hub/field-changes.test.js tools/work-hub/criteria-quality.test.js tools/roadmap-tools.test.js tools/provisioning/onboarding-tools.test.js tools/source-control/connection-tools.test.js governance/money.test.js http/rate-limit-policy.test.js tools/free-search.test.js"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
15
15
|
"mcp",
|
package/server.js
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
getValidAgentToken,
|
|
41
41
|
} from './access/auth.js';
|
|
42
42
|
import { TIERS } from './access/tiers.js';
|
|
43
|
-
import { findCompany, findPerson } from './tools/free-search.js';
|
|
43
|
+
import { findCompany, findPerson, findCompanyInWorkspace, findPersonInWorkspace } from './tools/free-search.js';
|
|
44
44
|
import { applySecurityLayer } from './security.js';
|
|
45
45
|
import { describeEdgeBlock } from './http/edge-block.js';
|
|
46
46
|
import { createRateLimitRegistry } from './http/rate-limit.js';
|
|
@@ -1123,17 +1123,30 @@ server.tool('move_pipeline_card',
|
|
|
1123
1123
|
}
|
|
1124
1124
|
});
|
|
1125
1125
|
|
|
1126
|
-
// =====
|
|
1126
|
+
// ===== find_company / find_person =====
|
|
1127
|
+
//
|
|
1128
|
+
// A seat with a credential gets its WORKSPACE RECORD (found / not found / could
|
|
1129
|
+
// not check). Only a server with no credential at all falls back to the
|
|
1130
|
+
// training-data profile prompt, which carries no upsell. See tools/free-search.js.
|
|
1131
|
+
|
|
1132
|
+
function hasWorkspaceCredential() {
|
|
1133
|
+
const auth = currentAuth();
|
|
1134
|
+
return Boolean(auth?.token || auth?.apiKey);
|
|
1135
|
+
}
|
|
1127
1136
|
|
|
1128
1137
|
server.tool('find_company',
|
|
1129
|
-
'
|
|
1130
|
-
{ name: z.string().describe('Company name
|
|
1131
|
-
async (a) =>
|
|
1138
|
+
'Find a company. Connected to a workspace, returns that workspace\'s company record (or says plainly that none matches, or that the workspace could not be checked). With no workspace connected, returns an unverified profile from Claude\'s training data.',
|
|
1139
|
+
{ name: z.string().describe('Company name or domain (e.g. "Stripe", "stripe.com")') },
|
|
1140
|
+
async (a) => (hasWorkspaceCredential()
|
|
1141
|
+
? findCompanyInWorkspace(a.name, (query) => api('GET', '/api/v1/companies', { params: { search: query, limit: 25, page: 1 } }))
|
|
1142
|
+
: findCompany(a.name)));
|
|
1132
1143
|
|
|
1133
1144
|
server.tool('find_person',
|
|
1134
|
-
'
|
|
1135
|
-
{ name: z.string().describe('Full name of the person
|
|
1136
|
-
async (a) =>
|
|
1145
|
+
'Find a person. Connected to a workspace, returns that workspace\'s person record (or says plainly that none matches, or that the workspace could not be checked). With no workspace connected, returns an unverified profile from Claude\'s training data.',
|
|
1146
|
+
{ name: z.string().describe('Full name or email of the person (e.g. "Patrick Collison")') },
|
|
1147
|
+
async (a) => (hasWorkspaceCredential()
|
|
1148
|
+
? findPersonInWorkspace(a.name, (query) => api('GET', '/api/v1/people', { params: { search: query, limit: 25, page: 1 } }))
|
|
1149
|
+
: findPerson(a.name)));
|
|
1137
1150
|
|
|
1138
1151
|
// ===== GOVERNED WRITES (shared) =====
|
|
1139
1152
|
//
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "com.adrata/adrata-mcp",
|
|
4
4
|
"description": "Adrata revenue-intelligence MCP server: companies, people, opportunities, actions, buyer groups, enrichment, email, and workspace operations for AI agents.",
|
|
5
5
|
"status": "active",
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.44",
|
|
7
7
|
"websiteUrl": "https://adrata.com/developers",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/adrata/adrata",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"registryType": "npm",
|
|
16
16
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
17
17
|
"identifier": "@adrata/adrata-mcp",
|
|
18
|
-
"version": "1.0.
|
|
18
|
+
"version": "1.0.44",
|
|
19
19
|
"transport": {
|
|
20
20
|
"type": "stdio"
|
|
21
21
|
},
|
package/tools/free-search.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* find_company / find_person
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Connected to a workspace, these return the WORKSPACE RECORD — the thing a
|
|
5
|
+
* seller asking "find Acme" means. Measured on Colin's day-one audit
|
|
6
|
+
* (2026-09-12): a connected seat got back a raw model prompt ending "Upgrade to
|
|
7
|
+
* Adrata Pro for enriched intelligence" instead of the record sitting in its own
|
|
8
|
+
* CRM. Owner ruling 2026-09-11: no seat sees credits, prices, quotas or upsells.
|
|
6
9
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
10
|
+
* Three states, never two:
|
|
11
|
+
* - found: the workspace record(s), labelled as such;
|
|
12
|
+
* - not found: said plainly — the workspace was checked and has no match;
|
|
13
|
+
* - could not look: said plainly — nothing was ruled out.
|
|
14
|
+
*
|
|
15
|
+
* With no credential at all there is no workspace to check, so the tool falls
|
|
16
|
+
* back to a structured training-data profile prompt, labelled unverified, with
|
|
17
|
+
* no upsell in it.
|
|
9
18
|
*/
|
|
10
19
|
|
|
11
20
|
// ---------------------------------------------------------------------------
|
|
@@ -56,8 +65,7 @@ IMPORTANT: Respond with ONLY a valid JSON object matching this exact schema. No
|
|
|
56
65
|
"competitors": ["Competitor 1", "Competitor 2", "Competitor 3"],
|
|
57
66
|
"recent_highlights": "Notable recent developments, funding, acquisitions, or strategic moves from training data",
|
|
58
67
|
"ideal_sales_angles": "2-3 sentence suggestion for how a seller might approach this company",
|
|
59
|
-
"data_freshness": "Based on training data through early 2025. Some details may have changed."
|
|
60
|
-
"upgrade_prompt": "Want verified contacts, real-time headcount, funding rounds, hiring signals, and intent data? Upgrade to Adrata Pro for enriched intelligence."
|
|
68
|
+
"data_freshness": "Based on training data through early 2025. Some details may have changed."
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
Rules:
|
|
@@ -84,8 +92,7 @@ IMPORTANT: Respond with ONLY a valid JSON object matching this exact schema. No
|
|
|
84
92
|
"notable_achievements": "Key accomplishments, board seats, speaking engagements, publications",
|
|
85
93
|
"linkedin_likely": "Likely LinkedIn URL pattern (e.g. linkedin.com/in/firstname-lastname) — note: unverified",
|
|
86
94
|
"sales_context": "2-3 sentence suggestion for how a seller might approach this person — what they likely care about",
|
|
87
|
-
"data_freshness": "Based on training data through early 2025. Current role may have changed."
|
|
88
|
-
"upgrade_prompt": "Want verified email, phone, social profiles, recent activity, and buyer signals? Upgrade to Adrata Pro for enriched contact intelligence."
|
|
95
|
+
"data_freshness": "Based on training data through early 2025. Current role may have changed."
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
Rules:
|
|
@@ -158,3 +165,118 @@ export function findPerson(name) {
|
|
|
158
165
|
cacheSet(cacheKey, result);
|
|
159
166
|
return result;
|
|
160
167
|
}
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// Workspace-record lookup (connected seats)
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
const WORKSPACE_MATCH_LIMIT = 5;
|
|
174
|
+
|
|
175
|
+
/** Pull the record array out of the API's list envelope, whatever its shape. */
|
|
176
|
+
export function recordsFromListResponse(response, collection) {
|
|
177
|
+
const candidates = [
|
|
178
|
+
response,
|
|
179
|
+
response?.data,
|
|
180
|
+
response?.data?.[collection],
|
|
181
|
+
response?.data?.items,
|
|
182
|
+
response?.data?.data,
|
|
183
|
+
response?.[collection],
|
|
184
|
+
response?.items,
|
|
185
|
+
];
|
|
186
|
+
for (const candidate of candidates) {
|
|
187
|
+
if (Array.isArray(candidate)) return candidate;
|
|
188
|
+
}
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function normalize(value) {
|
|
193
|
+
return typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function recordName(record, kind) {
|
|
197
|
+
if (kind === 'person') {
|
|
198
|
+
return (
|
|
199
|
+
record?.fullName ||
|
|
200
|
+
record?.name ||
|
|
201
|
+
[record?.firstName, record?.lastName].filter(Boolean).join(' ')
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
return record?.name;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function exactMatches(records, query, kind) {
|
|
208
|
+
const wanted = normalize(query);
|
|
209
|
+
return records.filter((record) => {
|
|
210
|
+
if (normalize(recordName(record, kind)) === wanted) return true;
|
|
211
|
+
if (kind === 'company') {
|
|
212
|
+
return [record?.domain, record?.website].some((d) => normalize(d).replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, '') === wanted);
|
|
213
|
+
}
|
|
214
|
+
return normalize(record?.email) === wanted;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function textResult(payload, isError = false) {
|
|
219
|
+
return {
|
|
220
|
+
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
|
|
221
|
+
...(isError ? { isError: true } : {}),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Look the name up in the connected workspace and return what is there.
|
|
227
|
+
*
|
|
228
|
+
* `lookup(name)` performs the authenticated list read and resolves to the raw
|
|
229
|
+
* API response; it is injected so this stays testable without a server.
|
|
230
|
+
*/
|
|
231
|
+
async function findInWorkspace(kind, name, lookup) {
|
|
232
|
+
const label = kind === 'company' ? 'company' : 'person';
|
|
233
|
+
const query = String(name ?? '').trim();
|
|
234
|
+
let response;
|
|
235
|
+
try {
|
|
236
|
+
response = await lookup(query);
|
|
237
|
+
} catch {
|
|
238
|
+
// The honest third state. Never an empty result, never "not found".
|
|
239
|
+
return textResult(
|
|
240
|
+
{
|
|
241
|
+
source: 'workspace',
|
|
242
|
+
status: 'could_not_check',
|
|
243
|
+
query,
|
|
244
|
+
message: `Couldn't check the workspace for a ${label} named "${query}" right now, so nothing was ruled out. Try again in a moment.`,
|
|
245
|
+
},
|
|
246
|
+
true,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const records = recordsFromListResponse(response, kind === 'company' ? 'companies' : 'people');
|
|
251
|
+
if (records.length === 0) {
|
|
252
|
+
return textResult({
|
|
253
|
+
source: 'workspace',
|
|
254
|
+
status: 'not_found',
|
|
255
|
+
query,
|
|
256
|
+
message: `No ${label} matching "${query}" is in this workspace.`,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const exact = exactMatches(records, query, kind);
|
|
261
|
+
const matches = (exact.length > 0 ? exact : records).slice(0, WORKSPACE_MATCH_LIMIT);
|
|
262
|
+
return textResult({
|
|
263
|
+
source: 'workspace',
|
|
264
|
+
status: exact.length > 0 ? 'found' : 'similar_found',
|
|
265
|
+
query,
|
|
266
|
+
message:
|
|
267
|
+
exact.length > 0
|
|
268
|
+
? `Found ${exact.length === 1 ? 'the' : exact.length} workspace ${label} record${exact.length === 1 ? '' : 's'} for "${query}".`
|
|
269
|
+
: `No exact match for "${query}"; these workspace ${label} records are the closest.`,
|
|
270
|
+
[kind === 'company' ? 'companies' : 'people']: matches,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** find_company for a seat with a credential: the workspace record, three-state. */
|
|
275
|
+
export function findCompanyInWorkspace(name, lookup) {
|
|
276
|
+
return findInWorkspace('company', name, lookup);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** find_person for a seat with a credential: the workspace record, three-state. */
|
|
280
|
+
export function findPersonInWorkspace(name, lookup) {
|
|
281
|
+
return findInWorkspace('person', name, lookup);
|
|
282
|
+
}
|