@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
package/access/auth.js ADDED
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Authentication and tier detection for Adrata MCP Server.
3
+ *
4
+ * Five auth modes (checked in priority order):
5
+ * 1. ADRATA_OAUTH_TOKEN env var -> enterprise tier
6
+ * 2. Shared agent session (~/.config/adrata/agent.json,
7
+ * written by `adrata login` / `@adrata login`) -> enterprise tier
8
+ * 3. Stored OAuth tokens (~/.adrata/tokens.json) -> enterprise tier
9
+ * 4. ADRATA_API_KEY env var / legacy cli.json -> pro tier
10
+ * 5. No credentials -> free tier
11
+ *
12
+ * The agent store is the primary on-disk session: the Adrata CLI, the @adrata
13
+ * terminal agent, and this MCP server all read the same file, so one device-flow
14
+ * sign-in serves all three surfaces. The MCP's own tokens.json (written by
15
+ * connect_workspace) and the legacy cli.json keep working unchanged.
16
+ */
17
+
18
+ import { TIERS, getToolTier, tierSatisfies } from './tiers.js';
19
+ import { loadTokens, getValidToken, OAUTH_SCOPE, describeOAuthScopeCapabilities } from './oauth.js';
20
+ import { existsSync, readFileSync } from 'node:fs';
21
+ import { homedir } from 'node:os';
22
+ import { isAbsolute, resolve, join } from 'node:path';
23
+
24
+ const DEFAULT_CLI_CONFIG_PATH = join(homedir(), '.config', 'adrata', 'cli.json');
25
+ const DEFAULT_AGENT_CONFIG_PATH = join(homedir(), '.config', 'adrata', 'agent.json');
26
+
27
+ /**
28
+ * A relative ADRATA_CONFIG_FILE resolves against $HOME, never the process cwd:
29
+ * the MCP server's cwd is whatever directory the MCP client launched it from,
30
+ * so cwd-relative resolution made the same value name a different file than
31
+ * the CLI saw. (packages/adrata-cli/src/auth.js implements the identical rule.)
32
+ */
33
+ export function getCliConfigPath() {
34
+ const override = process.env.ADRATA_CONFIG_FILE?.trim();
35
+ if (!override) return DEFAULT_CLI_CONFIG_PATH;
36
+ return isAbsolute(override) ? override : resolve(homedir(), override);
37
+ }
38
+
39
+ /** Path to the shared agent session store ($ADRATA_AGENT_CONFIG overrides, as in @adrata/agent). */
40
+ export function getAgentConfigPath() {
41
+ return process.env.ADRATA_AGENT_CONFIG?.trim() || DEFAULT_AGENT_CONFIG_PATH;
42
+ }
43
+
44
+ export function loadCliConfig() {
45
+ try {
46
+ const configPath = getCliConfigPath();
47
+ if (!existsSync(configPath)) return {};
48
+ return JSON.parse(readFileSync(configPath, 'utf8'));
49
+ } catch {
50
+ return {};
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Read the shared agent session (device-flow OAuth, written by `adrata login`).
56
+ * Returns { accessToken, apiBase, workspaceId } or null when absent/unusable.
57
+ */
58
+ export function loadAgentSession() {
59
+ try {
60
+ const path = getAgentConfigPath();
61
+ if (!existsSync(path)) return null;
62
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
63
+ if (!parsed || typeof parsed !== 'object') return null;
64
+ const str = (key) => (typeof parsed[key] === 'string' && parsed[key] ? parsed[key] : null);
65
+ const accessToken = str('accessToken');
66
+ if (!accessToken) return null;
67
+ return {
68
+ accessToken,
69
+ apiBase: str('apiBase')?.replace(/\/+$/, '') ?? null,
70
+ workspaceId: str('workspaceId'),
71
+ };
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Determine the user's tier from environment / stored tokens / API key.
79
+ *
80
+ * Checks (in priority order):
81
+ * 1. ADRATA_OAUTH_TOKEN env var (enterprise)
82
+ * 2. Shared agent session — agent.json from `adrata login` (enterprise)
83
+ * 3. Stored OAuth tokens from connect_workspace flow (enterprise)
84
+ * 4. ADRATA_API_KEY env var / legacy cli.json token (pro)
85
+ * 5. No credentials (free)
86
+ */
87
+ export function authenticate() {
88
+ const oauthToken = process.env.ADRATA_OAUTH_TOKEN || '';
89
+ const cliConfig = loadCliConfig();
90
+ const apiKey = process.env.ADRATA_API_KEY || process.env.ADRATA_API_TOKEN || cliConfig.token || '';
91
+ const apiUrl = process.env.ADRATA_API_URL || cliConfig.apiUrl || null;
92
+ const workspaceId = process.env.ADRATA_WORKSPACE_ID || cliConfig.workspaceId || null;
93
+
94
+ if (oauthToken) {
95
+ return {
96
+ tier: TIERS.ENTERPRISE,
97
+ token: oauthToken,
98
+ apiKey,
99
+ apiUrl,
100
+ workspaceId,
101
+ authenticated: true,
102
+ source: 'env',
103
+ };
104
+ }
105
+
106
+ // Shared agent session (device-flow OAuth) — the primary on-disk store,
107
+ // shared with the Adrata CLI and the @adrata terminal agent.
108
+ const agentSession = loadAgentSession();
109
+ if (agentSession) {
110
+ return {
111
+ tier: TIERS.ENTERPRISE,
112
+ token: agentSession.accessToken,
113
+ apiKey,
114
+ // The agent session is bound to the API that issued it, so its apiBase
115
+ // outranks a legacy cli.json apiUrl. An explicit ADRATA_API_URL still
116
+ // wins so the issuer guard can fail closed on a mismatch.
117
+ apiUrl: process.env.ADRATA_API_URL || agentSession.apiBase || apiUrl || null,
118
+ workspaceId: agentSession.workspaceId || workspaceId,
119
+ authenticated: true,
120
+ source: 'agent_config',
121
+ issuerApiBase: agentSession.apiBase || null,
122
+ };
123
+ }
124
+
125
+ // Check for stored OAuth tokens from connect_workspace flow
126
+ const storedTokens = loadTokens();
127
+ if (storedTokens && storedTokens.accessToken) {
128
+ return {
129
+ tier: TIERS.ENTERPRISE,
130
+ token: storedTokens.accessToken,
131
+ apiKey,
132
+ // A stored session remains bound to the API that issued it. An explicit
133
+ // configured URL still wins so the existing issuer guard can fail closed
134
+ // on a mismatch; otherwise persist the connected environment across MCP
135
+ // process restarts.
136
+ apiUrl: apiUrl || storedTokens.apiBase || null,
137
+ workspaceId: storedTokens.workspaceId || workspaceId,
138
+ authenticated: true,
139
+ source: 'stored',
140
+ issuerApiBase: storedTokens.apiBase || null,
141
+ _storedTokens: storedTokens,
142
+ };
143
+ }
144
+
145
+ if (apiKey) {
146
+ return {
147
+ tier: TIERS.PRO,
148
+ token: null,
149
+ apiKey,
150
+ apiUrl,
151
+ workspaceId,
152
+ authenticated: true,
153
+ source: process.env.ADRATA_API_KEY || process.env.ADRATA_API_TOKEN ? 'api_key' : 'cli_config',
154
+ };
155
+ }
156
+
157
+ return {
158
+ tier: TIERS.FREE,
159
+ token: null,
160
+ apiKey: null,
161
+ apiUrl,
162
+ workspaceId,
163
+ authenticated: false,
164
+ source: 'none',
165
+ };
166
+ }
167
+
168
+ /**
169
+ * Re-authenticate after a connect/disconnect event.
170
+ * Returns a fresh auth context.
171
+ */
172
+ export function reauthenticate() {
173
+ return authenticate();
174
+ }
175
+
176
+ export function assertOAuthIssuerMatchesTarget(authContext, apiBase) {
177
+ const issuerBound = authContext.source === 'stored' || authContext.source === 'agent_config';
178
+ if (!issuerBound || !authContext.issuerApiBase) return;
179
+ const issuer = new URL(authContext.issuerApiBase).origin;
180
+ const target = new URL(apiBase).origin;
181
+ if (issuer !== target) {
182
+ throw new Error(
183
+ `OAuth session/API mismatch: this session was issued by ${issuer}, but MCP is targeting ${target}. Reconnect to the target workspace or correct ADRATA_API_URL.`,
184
+ );
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Check whether the current auth context can call a given tool.
190
+ * Returns { allowed: true } or { allowed: false, response: <MCP response> }.
191
+ */
192
+ export function checkToolAccess(toolName, authContext) {
193
+ const requiredTier = getToolTier(toolName);
194
+
195
+ if (tierSatisfies(authContext.tier, requiredTier)) {
196
+ return { allowed: true };
197
+ }
198
+
199
+ // Build a gated response depending on what tier is required
200
+ if (requiredTier === TIERS.PRO) {
201
+ return {
202
+ allowed: false,
203
+ response: proTeaserResponse(toolName),
204
+ };
205
+ }
206
+
207
+ if (requiredTier === TIERS.ENTERPRISE) {
208
+ if (authContext.tier === TIERS.PRO) {
209
+ return {
210
+ allowed: false,
211
+ response: enterprisePromptResponse(toolName),
212
+ };
213
+ }
214
+ // Free tier trying enterprise tool
215
+ return {
216
+ allowed: false,
217
+ response: enterprisePromptResponse(toolName),
218
+ };
219
+ }
220
+
221
+ // Fallback
222
+ return {
223
+ allowed: false,
224
+ response: proTeaserResponse(toolName),
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Generate a teaser + upgrade CTA for pro-locked tools called from free tier.
230
+ */
231
+ function proTeaserResponse(toolName) {
232
+ return {
233
+ content: [{
234
+ type: 'text',
235
+ text: JSON.stringify({
236
+ tool: toolName,
237
+ tier_required: 'pro',
238
+ teaser: `The "${toolName}" tool provides deep intelligence and analytics powered by Adrata's AI engine. This is a preview of what's available with Adrata Pro.`,
239
+ sample_capabilities: [
240
+ 'Full company and contact intelligence',
241
+ 'Buying intent signals and deal authority mapping',
242
+ 'Pipeline analytics and revenue forecasting',
243
+ 'Competitive intelligence briefs',
244
+ 'Priority Pursuits ranked daily action lists',
245
+ 'Meeting summaries and action items',
246
+ ],
247
+ upgrade: {
248
+ message: 'Upgrade to Adrata Pro to unlock all intelligence tools.',
249
+ url: 'https://adrata.com/pricing',
250
+ action: 'Set the ADRATA_API_KEY environment variable to activate Pro features.',
251
+ },
252
+ }, null, 2),
253
+ }],
254
+ };
255
+ }
256
+
257
+ /**
258
+ * Generate a workspace connection prompt for enterprise-locked tools.
259
+ *
260
+ * `connect_workspace` requests OAUTH_SCOPE, which is deliberately read-only
261
+ * (no write or admin scopes) — a machine/OAuth principal cannot complete a
262
+ * governed write, so the API's scope_guard middleware 403s any write from
263
+ * this token regardless of the MCP tool tier. This prompt must not promise
264
+ * "full CRM read/write access" or "administration": that contradicts the
265
+ * scope connect_workspace is about to request, in the same message that
266
+ * tells the caller to go run it.
267
+ */
268
+ function enterprisePromptResponse(toolName) {
269
+ const grantedAccess = describeOAuthScopeCapabilities(OAUTH_SCOPE);
270
+ return {
271
+ content: [{
272
+ type: 'text',
273
+ text: JSON.stringify({
274
+ tool: toolName,
275
+ tier_required: 'enterprise',
276
+ message: `The "${toolName}" tool requires a connected Adrata workspace with OAuth authentication.`,
277
+ grantedByConnecting: grantedAccess.summary,
278
+ note: grantedAccess.canWrite
279
+ ? undefined
280
+ : `Connecting with the default (read-only) grant unlocks this tool's read paths. If "${toolName}" performs a write, the Adrata API rejects it with 403 insufficient_scope until the connection also holds the matching write:* scope. Run connect_workspace with writeAccess:true to request the CRM write scopes; workspace administration still requires a signed-in human session.`,
281
+ connect: {
282
+ message: 'Connect your Adrata workspace to unlock enterprise-tier read access.',
283
+ action: 'Run the connect_workspace tool to authenticate via OAuth, or set the ADRATA_OAUTH_TOKEN environment variable manually.',
284
+ forWrites: 'Run connect_workspace with writeAccess:true to additionally request CRM write scopes (companies, people, buyer groups, opportunities, actions, tasks).',
285
+ },
286
+ }, null, 2),
287
+ }],
288
+ };
289
+ }