@mentoringo/vantage-ops-mcp 1.3.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 ADDED
@@ -0,0 +1,268 @@
1
+ # @mentoringo/vantage-ops-mcp
2
+
3
+ MCP server that makes Vantage CRM operable from Claude Code. Read-only DynamoDB queries plus authenticated write tools that flow through the Vantage Lambda API.
4
+
5
+ Install this in your local environment so Claude Code can search, mutate, and orchestrate Vantage records on your behalf — creating companies, launching campaigns, tracking outreach, advancing pipeline stages, and more.
6
+
7
+ **Location:** This package lives at `tools/vantage-ops-mcp/` inside the `mentoringo/vantage` monorepo. It is published to npm as `@mentoringo/vantage-ops-mcp` so end users do not need to clone the repo.
8
+
9
+ ## Architecture
10
+
11
+ ```mermaid
12
+ flowchart LR
13
+ subgraph local["Your machine"]
14
+ CC[Claude Code]
15
+ MCP[vantage-ops MCP<br/>this package]
16
+ ENV[(env<br/>VANTAGE_MCP_KEY)]
17
+ end
18
+ subgraph aws["AWS (us-east-1)"]
19
+ APIGW[API Gateway<br/>/mcp/proxy+]
20
+ AUTH[api_key_authorizer]
21
+ LAM[Unified Lambda<br/>scope-gated handlers]
22
+ DDB[(DynamoDB<br/>vantage-*)]
23
+ end
24
+
25
+ CC -- stdio --> MCP
26
+ ENV -. resolve on startup .-> MCP
27
+ MCP -- HTTPS + Bearer key --> APIGW
28
+ APIGW --> AUTH
29
+ AUTH -- allow + scopes --> LAM
30
+ LAM -- @require_scope<br/>checks scope --> DDB
31
+
32
+ style MCP fill:#e0f2fe
33
+ style AUTH fill:#fef3c7
34
+ style LAM fill:#fef3c7
35
+ ```
36
+
37
+ **One path.** Reads and writes both flow through `/mcp/{proxy+}` with a scope-gated bearer token. Reads hit a `/v2/mcp-reads` dispatcher (scope `mcp:read`) that executes allowlisted DynamoDB queries with the Lambda's IAM role; writes hit resource endpoints that run the same handlers the dashboard uses, preserving slug generation, stage history, audit trails, and access control. **No AWS credentials are required on the operator machine.**
38
+
39
+ **Full tool reference:** [docs/TOOLS.md](docs/TOOLS.md)
40
+ **Scope / key model:** [docs/SCOPES.md](docs/SCOPES.md)
41
+ **Common workflows:** [docs/RECIPES.md](docs/RECIPES.md)
42
+
43
+ ---
44
+
45
+ ## Who this is for
46
+
47
+ Team operators with a provisioned `VANTAGE_MCP_KEY`. If you don't have a key yet, ask the platform admin — your key's scopes determine which tools you can use.
48
+
49
+ Typical rollouts:
50
+
51
+ | Role | Scopes on key | Tools available |
52
+ |---|---|---|
53
+ | Full operator (Justin, biz partner) | `*` or all `:*` | Every read + write tool |
54
+ | BD researcher | `companies:*`, `contacts:*`, `activities:write` | Read everything + create contacts + log activities |
55
+ | SDR / caller | `contacts:write`, `activities:write`, `outreach:write` | Log touches, update contacts, record outreach attempts |
56
+ | Read-only analyst | `*:read` | All reads, no mutations |
57
+
58
+ ---
59
+
60
+ ## Prerequisites
61
+
62
+ - **Node 20+**
63
+ - **`VANTAGE_MCP_KEY`** — issued once by the platform admin; covers both reads and writes. Your key's scopes (`mcp:read`, `contacts:write`, etc.) determine which tools you can call.
64
+
65
+ ---
66
+
67
+ ## Install
68
+
69
+ Add to `~/.claude.json` under `mcpServers`:
70
+
71
+ ```json
72
+ {
73
+ "mcpServers": {
74
+ "vantage-ops": {
75
+ "command": "npx",
76
+ "args": ["-y", "@mentoringo/vantage-ops-mcp@latest"],
77
+ "env": {
78
+ "VANTAGE_MCP_KEY": "vk_live_xxxxxxxxxxxxxxxx"
79
+ }
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ Restart Claude Code. The first invocation pre-warms the npx cache (~3 seconds); subsequent starts are instant.
86
+
87
+ To pin a specific version instead of tracking `@latest`, swap the tag: `@mentoringo/vantage-ops-mcp@1.1.0`.
88
+
89
+ ### Bundled skills
90
+
91
+ Slash-command workflows (`/vet-leads`, `/lead-enrich`, `/contact-enrich`, `/campaign-launch`) ship in `.claude/skills/` and need to be linked into `~/.claude/skills/` so Claude Code discovers them. Until a published `install-skills` workflow lands (tracked in vantage roadmap), copy them by cloning vantage once:
92
+
93
+ ```bash
94
+ git clone git@github.com:mentoringo/vantage.git ~/repo/vantage
95
+ ln -s ~/repo/vantage/tools/vantage-ops-mcp/.claude/skills/* ~/.claude/skills/
96
+ ```
97
+
98
+ The MCP tools themselves do not require the clone — npx handles those.
99
+
100
+ ### From-source install (contributors)
101
+
102
+ ```bash
103
+ git clone git@github.com:mentoringo/vantage.git ~/repo/vantage
104
+ cd ~/repo/vantage/tools/vantage-ops-mcp
105
+ npm install
106
+ npm run build
107
+ npm run install-claude
108
+ ```
109
+
110
+ `npm run install-claude` does two things idempotently:
111
+
112
+ 1. **Registers the MCP server** in `~/.claude.json` under `mcpServers.vantage-ops` pointing at the local `dist/`.
113
+ 2. **Symlinks the bundled skills** under `.claude/skills/*` into `~/.claude/skills/`.
114
+
115
+ To uninstall: `npm run install-claude -- --uninstall`. To preview without changes: `npm run install-claude -- --dry-run`.
116
+
117
+ ## Configure
118
+
119
+ The `env` block in `~/.claude.json` (above) is the canonical place for `VANTAGE_MCP_KEY`. Legacy local-clone installs may still read `~/repo/.env` — both work, but new installs should use the env block.
120
+
121
+ The key was handed to you once at provisioning — if you lost it, it cannot be recovered. Ask for a replacement.
122
+
123
+ Verify after restart:
124
+
125
+ ```
126
+ vantage_mcp_health
127
+ ```
128
+
129
+ Expected:
130
+
131
+ ```json
132
+ {
133
+ "api_url": "https://mbmhmzrur4.execute-api.us-east-1.amazonaws.com/prod",
134
+ "key_configured": true
135
+ }
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Tool inventory at a glance
141
+
142
+ **24 read tools + 23 write tools + 2 health = 49 total.**
143
+
144
+ ### Read tools (need `mcp:read` scope on key)
145
+
146
+ | Domain | Tools |
147
+ |---|---|
148
+ | Companies | `companies_search`, `company_get`, `companies_stats` |
149
+ | Contacts | `contacts_search`, `contact_get`, `contacts_by_company` |
150
+ | Opportunities / Pipeline | `pipeline_list`, `pipeline_get`, `pipeline_summary` |
151
+ | Campaigns | `campaigns_list`, `campaign_get`, `campaigns_stats` |
152
+ | Clients / Users | `clients_list`, `client_get`, `users_list` |
153
+ | Ops | `health`, `mcp_health`, `table_counts` |
154
+
155
+ ### Write tools (need a scoped `VANTAGE_MCP_KEY`)
156
+
157
+ | Domain | Tools | Scope |
158
+ |---|---|---|
159
+ | Companies | `company_create`, `company_update`, `company_delete` | `companies:write` |
160
+ | Contacts | `contact_create`, `contact_update`, `contact_delete` | `contacts:write` |
161
+ | Opportunities | `opportunity_create`, `opportunity_update`, `opportunity_update_stage`, `opportunity_delete` | `opportunities:write` |
162
+ | Campaigns | `campaign_create`, `campaign_update`, `campaign_delete`, `campaign_start`, `campaign_pause`, `campaign_add_targets`, `campaign_add_recipients`, `campaign_recipient_update`, `campaign_source_contacts` | `campaigns:write` |
163
+ | Apollo | `apollo_search_contacts`, `apollo_search_companies`, `apollo_unlock_email`, `apollo_import_contact` | `apollo:write` |
164
+ | Templates | `template_create`, `template_update`, `template_delete` | `templates:write` |
165
+ | Outreach | `outreach_record`, `outreach_update` | `outreach:write` |
166
+ | Activities | `activity_log` | `activities:write` |
167
+
168
+ All tool names are prefixed with `vantage_` when called (e.g. `vantage_company_create`). Full schemas in [docs/TOOLS.md](docs/TOOLS.md).
169
+
170
+ ---
171
+
172
+ ## Bundled skills
173
+
174
+ The MCP server is the tool surface; the bundled skills are slash-command workflows that orchestrate those tools end-to-end. After linking the skills directory (see Install), all four are usable from any Claude Code session as `/<skill-name>`.
175
+
176
+ | Skill | Stage in lifecycle | What it does |
177
+ |---|---|---|
178
+ | `/vet-leads` | Pre-import | Deterministic file QA on a raw lead list (xlsx/csv) — flags role mailboxes, name-vs-email typos, missing fields, reseller-vs-end-user mismatches, dedup hits. Annotates each row with a `disposition` column. Free; no Apollo. |
179
+ | `/lead-enrich` | Pre-import | Enriches a lead list with domains (for auto-logo), current company names, industry, dedup. Respects `disposition` from `/vet-leads` if present. |
180
+ | `/contact-enrich` | Post-import | Fills `linkedin_url` + `title` on already-imported Vantage contacts. Apollo `unlock_email` (paid, optional) + WebSearch fallback. |
181
+ | `/campaign-launch` | End-to-end | Builds a Vantage campaign from a lead file or ICP frame — companies + contacts + templates + targets + recipients staged in `draft` for human sign-off. |
182
+
183
+ Designed lifecycle:
184
+
185
+ ```
186
+ /vet-leads <file> → vetted.csv (operator reviews)
187
+ /lead-enrich <vetted.csv> → enriched-import.csv
188
+ import via Vantage UI / API
189
+ /contact-enrich --campaign → fills LinkedIn + title gaps
190
+ /campaign-launch <id> → templates, sequence, ready to launch
191
+ ```
192
+
193
+ Each skill is also registered as an MCP `prompt` for forward-compat with the Claude Agent SDK and any future Claude Code support for prompt-as-slash invocation. Filesystem skill files are the canonical source — the prompts read from the same `.claude/skills/<name>/SKILL.md` at server startup.
194
+
195
+ ---
196
+
197
+ ## Quick-check after install
198
+
199
+ ```
200
+ vantage_health
201
+ vantage_mcp_health
202
+ vantage_companies_search status=prospect limit=3
203
+ ```
204
+
205
+ If all three respond, you're good.
206
+
207
+ ---
208
+
209
+ ## Updating
210
+
211
+ With the `@latest` tag in `~/.claude.json`, npx fetches the newest published version on each Claude Code restart. To force a refresh without restarting Claude Code, clear the npm cache for this package: `npm cache clean --force` (or `rm -rf ~/.npm/_npx/<hash>`).
212
+
213
+ For pinned installs (`@1.1.0` rather than `@latest`), edit `~/.claude.json` to bump the version tag and restart.
214
+
215
+ For from-source installs, `git pull && npm install && npm run build` then restart Claude Code.
216
+
217
+ ---
218
+
219
+ ## Troubleshooting
220
+
221
+ | Symptom | Fix |
222
+ |---|---|
223
+ | **Tool missing in Claude session** | Restart Claude Code. If pinned to a specific version, check the version's CHANGELOG. If on `@latest`, clear the npx cache to force a re-fetch. |
224
+ | **Vantage UI shows recipient as "Sent" after they replied** | The campaign's embedded `recipients[contact].engagement_status` is a separate field from `outreach.status` and does not auto-sync. Use `vantage_outreach_update` AND `vantage_campaign_recipient_update` together when capturing an inbound reply. |
225
+ | `VANTAGE_MCP_KEY not set` on first write | Confirm the `env` block in `~/.claude.json` has the key and Claude Code was restarted. |
226
+ | `403 insufficient scope. Required: X` | Your key doesn't cover this tool — ask admin for an expanded key (see `docs/SCOPES.md`) |
227
+ | Read tools fail with `403 Insufficient scope. Required: mcp:read` | Your key lacks `mcp:read` — ask admin to re-mint with reads enabled |
228
+ | `401 Unauthorized` on every write | Key revoked, expired, or mistyped — verify with admin |
229
+ | `404 Not found` on a specific opp/contact | Record doesn't exist in V2 table — check `vantage_pipeline_get` / `vantage_contact_get` first |
230
+
231
+ ---
232
+
233
+ ## Testing
234
+
235
+ Three layers:
236
+
237
+ ```bash
238
+ npm test # 72 tests — schema contracts + mocked HTTP (runs offline in <200ms)
239
+ npm run smoke # live end-to-end against prod API — needs a valid VANTAGE_MCP_KEY
240
+ npm run build # TypeScript type check
241
+ ```
242
+
243
+ `npm test` is safe in any environment — sentinel values are inlined in the test script, `global.fetch` is stubbed, the network is never touched.
244
+
245
+ `npm run smoke` creates a test contact + opportunity in prod against the sharptrain client (`34ec0933-…`), exercises every write tool, then cleans up. Each step reports pass/fail; exit code is 0 on full pass. Your key needs: `contacts:write`, `opportunities:write`, `activities:write`. Provide your key via shell env or a local `.env.test` (gitignored).
246
+
247
+ See `tests/client.test.ts` for HTTP shape coverage, `tests/tools.test.ts` for schema contracts, and `scripts/smoke.ts` for the live happy path.
248
+
249
+ ---
250
+
251
+ ## Key lifecycle
252
+
253
+ - **Default TTL:** 90 days
254
+ - **Recovery:** plaintext is not stored, only a SHA256 hash. Lost keys must be revoked + re-issued
255
+ - **Rotation:** request a new key before expiry; run in parallel briefly, then revoke the old one
256
+ - **Leak response:** admin runs `vantage_keys.py revoke --id <uuid>` immediately
257
+
258
+ ---
259
+
260
+ ## Next steps
261
+
262
+ - Read [docs/TOOLS.md](docs/TOOLS.md) for the full tool schema reference
263
+ - Read [docs/SCOPES.md](docs/SCOPES.md) to understand which scopes unlock which tools
264
+ - Read [docs/RECIPES.md](docs/RECIPES.md) for common workflows (launching a campaign, tracking an outreach touch, etc.)
265
+
266
+ For development conventions and contribution guide see [CLAUDE.md](CLAUDE.md).
267
+
268
+ Questions? Ping the platform admin.
@@ -0,0 +1,70 @@
1
+ export declare const tables: {
2
+ readonly companies: "companies";
3
+ readonly contacts: "contacts";
4
+ readonly campaigns: "campaigns";
5
+ readonly campaignTargets: "campaign-targets";
6
+ readonly opportunities: "opportunities";
7
+ readonly clients: "clients";
8
+ readonly users: "users";
9
+ readonly outreach: "outreach";
10
+ readonly activities: "activities";
11
+ };
12
+ export declare class VantageReadError extends Error {
13
+ status: number;
14
+ body: unknown;
15
+ constructor(status: number, body: unknown, message: string);
16
+ }
17
+ export declare function getItem(table: string, key: Record<string, any>): Promise<Record<string, any> | null>;
18
+ export interface QueryExtras {
19
+ limit?: number;
20
+ scanForward?: boolean;
21
+ expressionNames?: Record<string, string>;
22
+ exclusiveStartKey?: Record<string, any>;
23
+ }
24
+ export declare function queryByIndex(table: string, indexName: string, keyCondition: string, expressionValues: Record<string, any>, o?: QueryExtras): Promise<{
25
+ items: Record<string, any>[];
26
+ count: number;
27
+ lastEvaluatedKey?: Record<string, any>;
28
+ }>;
29
+ export declare function queryTable(table: string, keyCondition: string, expressionValues: Record<string, any>, o?: {
30
+ limit?: number;
31
+ scanForward?: boolean;
32
+ exclusiveStartKey?: Record<string, any>;
33
+ }): Promise<{
34
+ items: Record<string, any>[];
35
+ count: number;
36
+ lastEvaluatedKey?: Record<string, any>;
37
+ }>;
38
+ export interface ScanOptions {
39
+ limit?: number;
40
+ filterExpression?: string;
41
+ expressionValues?: Record<string, any>;
42
+ expressionNames?: Record<string, string>;
43
+ projectionExpression?: string;
44
+ exclusiveStartKey?: Record<string, any>;
45
+ }
46
+ export declare function scanTable(table: string, o?: ScanOptions): Promise<{
47
+ items: Record<string, any>[];
48
+ count: number;
49
+ scannedCount: number;
50
+ lastEvaluatedKey?: Record<string, any>;
51
+ }>;
52
+ export declare function scanAll(table: string, o?: ScanOptions, maxItems?: number): Promise<{
53
+ items: Record<string, any>[];
54
+ scannedCount: number;
55
+ truncated: boolean;
56
+ }>;
57
+ export declare function scanCount(table: string, o?: {
58
+ filterExpression?: string;
59
+ expressionValues?: Record<string, any>;
60
+ expressionNames?: Record<string, string>;
61
+ }): Promise<number>;
62
+ export declare function describeTable(table: string): Promise<{
63
+ itemCount: number;
64
+ tableSizeBytes: number;
65
+ }>;
66
+ export declare function apiHealth(baseUrl?: string): Promise<{
67
+ healthy: boolean;
68
+ message: string;
69
+ }>;
70
+ export declare function readsConfigured(): boolean;
package/dist/dynamo.js ADDED
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Non-AWS read client — talks to Vantage's /v2/mcp-reads dispatcher
3
+ * over HTTPS using the same VANTAGE_MCP_KEY used for writes.
4
+ *
5
+ * This module keeps the function names (getItem, queryByIndex, scanTable,
6
+ * scanAll, scanCount) the read tools have always imported so swapping
7
+ * AWS CLI for HTTP is invisible at the call sites. Each function shapes
8
+ * its request for the dispatcher (op + table alias + DDB expression
9
+ * arguments) and returns the same object shape the previous unmarshalling
10
+ * layer produced — plain JS objects, not DynamoDB JSON.
11
+ *
12
+ * See: vantage/lambdas/api/mcp_reads/handler.py
13
+ */
14
+ import { readFileSync, existsSync } from "node:fs";
15
+ import { homedir } from "node:os";
16
+ import { join } from "node:path";
17
+ const DEFAULT_API_URL = "https://mbmhmzrur4.execute-api.us-east-1.amazonaws.com/prod";
18
+ // Logical aliases the backend allowlist understands. The full DDB table
19
+ // name is resolved server-side so staging/prod reuse the same client.
20
+ export const tables = {
21
+ companies: "companies",
22
+ contacts: "contacts",
23
+ campaigns: "campaigns",
24
+ // Junction: (campaign_id, company_id) — exposes attached targets without
25
+ // requiring callers to round-trip through the REST handler.
26
+ campaignTargets: "campaign-targets",
27
+ opportunities: "opportunities",
28
+ clients: "clients",
29
+ users: "users",
30
+ outreach: "outreach",
31
+ activities: "activities",
32
+ };
33
+ // ── env/config resolution ──────────────────────────────────────────────
34
+ // Mirrors mcp-client.ts so the read path and write path resolve the same
35
+ // key+URL from the same sources (env block, then ~/repo/.env).
36
+ function parseDotenv(path) {
37
+ const out = {};
38
+ if (!existsSync(path))
39
+ return out;
40
+ try {
41
+ const text = readFileSync(path, "utf8");
42
+ for (const raw of text.split(/\r?\n/)) {
43
+ const line = raw.trim();
44
+ if (!line || line.startsWith("#"))
45
+ continue;
46
+ const m = line.match(/^(?:export\s+)?([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/i);
47
+ if (!m)
48
+ continue;
49
+ let [, key, value] = m;
50
+ value = value.trim();
51
+ if ((value.startsWith('"') && value.endsWith('"')) ||
52
+ (value.startsWith("'") && value.endsWith("'"))) {
53
+ value = value.slice(1, -1);
54
+ }
55
+ out[key] = value;
56
+ }
57
+ }
58
+ catch {
59
+ // Unreadable — caller falls back to process.env.
60
+ }
61
+ return out;
62
+ }
63
+ function resolveConfig() {
64
+ let apiKey = process.env.VANTAGE_MCP_KEY || null;
65
+ let apiUrl = process.env.VANTAGE_API_URL || DEFAULT_API_URL;
66
+ if (!apiKey) {
67
+ const envFile = parseDotenv(join(homedir(), "repo", ".env"));
68
+ apiKey = envFile.VANTAGE_MCP_KEY || null;
69
+ apiUrl = apiUrl || envFile.VANTAGE_API_URL || DEFAULT_API_URL;
70
+ }
71
+ return { apiUrl, apiKey };
72
+ }
73
+ const { apiUrl, apiKey } = resolveConfig();
74
+ export class VantageReadError extends Error {
75
+ status;
76
+ body;
77
+ constructor(status, body, message) {
78
+ super(message);
79
+ this.status = status;
80
+ this.body = body;
81
+ this.name = "VantageReadError";
82
+ }
83
+ }
84
+ async function dispatch(body) {
85
+ if (!apiKey) {
86
+ throw new Error("VANTAGE_MCP_KEY not set. Add it to ~/repo/.env or the mcp.json env block " +
87
+ "for the vantage-ops server. Read tools now use the same key as writes.");
88
+ }
89
+ const url = `${apiUrl}/mcp/v2/mcp-reads`;
90
+ const res = await fetch(url, {
91
+ method: "POST",
92
+ headers: {
93
+ Authorization: `Bearer ${apiKey}`,
94
+ "Content-Type": "application/json",
95
+ },
96
+ body: JSON.stringify(body),
97
+ });
98
+ const text = await res.text();
99
+ let parsed = text;
100
+ if (text) {
101
+ try {
102
+ parsed = JSON.parse(text);
103
+ }
104
+ catch {
105
+ // Non-JSON body — surfacing the raw text is more useful than hiding it.
106
+ }
107
+ }
108
+ if (!res.ok) {
109
+ const message = typeof parsed === "object" && parsed && "message" in parsed
110
+ ? String(parsed.message)
111
+ : `Vantage reads ${body.op} returned ${res.status}`;
112
+ throw new VantageReadError(res.status, parsed, message);
113
+ }
114
+ return parsed;
115
+ }
116
+ // ── Public API (same shape as the prior AWS-CLI-backed module) ─────────
117
+ export async function getItem(table, key) {
118
+ const out = await dispatch({
119
+ op: "get_item",
120
+ table,
121
+ key,
122
+ });
123
+ return out.item ?? null;
124
+ }
125
+ export async function queryByIndex(table, indexName, keyCondition, expressionValues, o = {}) {
126
+ const out = await dispatch({
127
+ op: "query",
128
+ table,
129
+ index: indexName,
130
+ key_condition: keyCondition,
131
+ expression_values: expressionValues,
132
+ expression_names: o.expressionNames,
133
+ limit: o.limit,
134
+ scan_forward: o.scanForward,
135
+ exclusive_start_key: o.exclusiveStartKey,
136
+ });
137
+ return {
138
+ items: out.items || [],
139
+ count: out.count || 0,
140
+ lastEvaluatedKey: out.last_evaluated_key,
141
+ };
142
+ }
143
+ export async function queryTable(table, keyCondition, expressionValues, o = {}) {
144
+ const out = await dispatch({
145
+ op: "query",
146
+ table,
147
+ key_condition: keyCondition,
148
+ expression_values: expressionValues,
149
+ limit: o.limit,
150
+ scan_forward: o.scanForward,
151
+ exclusive_start_key: o.exclusiveStartKey,
152
+ });
153
+ return {
154
+ items: out.items || [],
155
+ count: out.count || 0,
156
+ lastEvaluatedKey: out.last_evaluated_key,
157
+ };
158
+ }
159
+ export async function scanTable(table, o = {}) {
160
+ const out = await dispatch({
161
+ op: "scan",
162
+ table,
163
+ filter_expression: o.filterExpression,
164
+ expression_values: o.expressionValues,
165
+ expression_names: o.expressionNames,
166
+ projection_expression: o.projectionExpression,
167
+ limit: o.limit,
168
+ exclusive_start_key: o.exclusiveStartKey,
169
+ });
170
+ return {
171
+ items: out.items || [],
172
+ count: out.count || 0,
173
+ scannedCount: out.scanned_count || 0,
174
+ lastEvaluatedKey: out.last_evaluated_key,
175
+ };
176
+ }
177
+ // Paginates through an entire table (or stops after maxItems).
178
+ // Use for aggregate stats or filter-only searches where the single-page
179
+ // scan misses matches past the first 1 MB page.
180
+ export async function scanAll(table, o = {}, maxItems = 10000) {
181
+ const items = [];
182
+ let scannedCount = 0;
183
+ let exclusiveStartKey = o.exclusiveStartKey;
184
+ let truncated = false;
185
+ while (true) {
186
+ const page = await scanTable(table, { ...o, exclusiveStartKey });
187
+ items.push(...page.items);
188
+ scannedCount += page.scannedCount;
189
+ if (items.length >= maxItems) {
190
+ truncated = !!page.lastEvaluatedKey;
191
+ items.length = Math.min(items.length, maxItems);
192
+ break;
193
+ }
194
+ if (!page.lastEvaluatedKey)
195
+ break;
196
+ exclusiveStartKey = page.lastEvaluatedKey;
197
+ }
198
+ return { items, scannedCount, truncated };
199
+ }
200
+ export async function scanCount(table, o = {}) {
201
+ const out = await dispatch({
202
+ op: "scan",
203
+ table,
204
+ filter_expression: o.filterExpression,
205
+ expression_values: o.expressionValues,
206
+ expression_names: o.expressionNames,
207
+ select: "COUNT",
208
+ });
209
+ return out.count || 0;
210
+ }
211
+ export async function describeTable(table) {
212
+ const out = await dispatch({
213
+ op: "describe_table",
214
+ table,
215
+ });
216
+ return {
217
+ itemCount: out.item_count || 0,
218
+ tableSizeBytes: out.table_size_bytes || 0,
219
+ };
220
+ }
221
+ // ── Health check via HTTP (unchanged) ──────────────────────────────────
222
+ export async function apiHealth(baseUrl = process.env.VANTAGE_PUBLIC_API_URL || "https://vantage.mentoringo.com") {
223
+ try {
224
+ const res = await fetch(`${baseUrl}/api/health`, { method: "GET" });
225
+ return {
226
+ healthy: res.ok,
227
+ message: `HTTP ${res.status}`,
228
+ };
229
+ }
230
+ catch (e) {
231
+ return { healthy: false, message: e.message || String(e) };
232
+ }
233
+ }
234
+ // Exposed for diagnostics (vantage_mcp_health tool).
235
+ export function readsConfigured() {
236
+ return !!apiKey;
237
+ }
238
+ //# sourceMappingURL=dynamo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamo.js","sourceRoot":"","sources":["../src/dynamo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,eAAe,GACnB,6DAA6D,CAAC;AAEhE,wEAAwE;AACxE,sEAAsE;AACtE,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,SAAS,EAAE,WAAW;IACtB,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,yEAAyE;IACzE,4DAA4D;IAC5D,eAAe,EAAE,kBAAkB;IACnC,aAAa,EAAE,eAAe;IAC9B,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,OAAO;IACd,QAAQ,EAAE,UAAU;IACpB,UAAU,EAAE,YAAY;CAChB,CAAC;AAEX,0EAA0E;AAC1E,yEAAyE;AACzE,+DAA+D;AAE/D,SAAS,WAAW,CAAC,IAAY;IAC/B,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACxC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;YACxB,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAS;YAC5C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;YACvE,IAAI,CAAC,CAAC;gBAAE,SAAS;YACjB,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;YACvB,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;YACrB,IACE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;gBAC9C,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAC9C,CAAC;gBACD,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAC7B,CAAC;YACD,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACnB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,iDAAiD;IACnD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,aAAa;IACpB,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC;IACjD,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,eAAe,CAAC;IAC5D,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAC7D,MAAM,GAAG,OAAO,CAAC,eAAe,IAAI,IAAI,CAAC;QACzC,MAAM,GAAG,MAAM,IAAI,OAAO,CAAC,eAAe,IAAI,eAAe,CAAC;IAChE,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,CAAC;AAE3C,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACtB;IAAuB;IAA1C,YAAmB,MAAc,EAAS,IAAa,EAAE,OAAe;QACtE,KAAK,CAAC,OAAO,CAAC,CAAC;QADE,WAAM,GAAN,MAAM,CAAQ;QAAS,SAAI,GAAJ,IAAI,CAAS;QAErD,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAsBD,KAAK,UAAU,QAAQ,CAAU,IAAkB;IACjD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,2EAA2E;YACzE,wEAAwE,CAC3E,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,GAAG,MAAM,mBAAmB,CAAC;IACzC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC3B,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,MAAM,EAAE;YACjC,cAAc,EAAE,kBAAkB;SACnC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;KAC3B,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,MAAM,GAAY,IAAI,CAAC;IAC3B,IAAI,IAAI,EAAE,CAAC;QACT,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,wEAAwE;QAC1E,CAAC;IACH,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,OAAO,GACX,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,IAAI,SAAS,IAAI,MAAM;YACzD,CAAC,CAAC,MAAM,CAAE,MAA+B,CAAC,OAAO,CAAC;YAClD,CAAC,CAAC,iBAAiB,IAAI,CAAC,EAAE,aAAa,GAAG,CAAC,MAAM,EAAE,CAAC;QACxD,MAAM,IAAI,gBAAgB,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,MAAW,CAAC;AACrB,CAAC;AAED,0EAA0E;AAE1E,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,KAAa,EACb,GAAwB;IAExB,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAuC;QAC/D,EAAE,EAAE,UAAU;QACd,KAAK;QACL,GAAG;KACJ,CAAC,CAAC;IACH,OAAO,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC;AAC1B,CAAC;AASD,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAAa,EACb,SAAiB,EACjB,YAAoB,EACpB,gBAAqC,EACrC,IAAiB,EAAE;IAEnB,MAAM,GAAG,GAAG,MAAM,QAAQ,CAA4E;QACpG,EAAE,EAAE,OAAO;QACX,KAAK;QACL,KAAK,EAAE,SAAS;QAChB,aAAa,EAAE,YAAY;QAC3B,iBAAiB,EAAE,gBAAgB;QACnC,gBAAgB,EAAE,CAAC,CAAC,eAAe;QACnC,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,YAAY,EAAE,CAAC,CAAC,WAAW;QAC3B,mBAAmB,EAAE,CAAC,CAAC,iBAAiB;KACzC,CAAC,CAAC;IACH,OAAO;QACL,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE;QACtB,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC;QACrB,gBAAgB,EAAE,GAAG,CAAC,kBAAkB;KACzC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,KAAa,EACb,YAAoB,EACpB,gBAAqC,EACrC,IAAwF,EAAE;IAE1F,MAAM,GAAG,GAAG,MAAM,QAAQ,CAA4E;QACpG,EAAE,EAAE,OAAO;QACX,KAAK;QACL,aAAa,EAAE,YAAY;QAC3B,iBAAiB,EAAE,gBAAgB;QACnC,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,YAAY,EAAE,CAAC,CAAC,WAAW;QAC3B,mBAAmB,EAAE,CAAC,CAAC,iBAAiB;KACzC,CAAC,CAAC;IACH,OAAO;QACL,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE;QACtB,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC;QACrB,gBAAgB,EAAE,GAAG,CAAC,kBAAkB;KACzC,CAAC;AACJ,CAAC;AAWD,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,KAAa,EACb,IAAiB,EAAE;IAOnB,MAAM,GAAG,GAAG,MAAM,QAAQ,CAKvB;QACD,EAAE,EAAE,MAAM;QACV,KAAK;QACL,iBAAiB,EAAE,CAAC,CAAC,gBAAgB;QACrC,iBAAiB,EAAE,CAAC,CAAC,gBAAgB;QACrC,gBAAgB,EAAE,CAAC,CAAC,eAAe;QACnC,qBAAqB,EAAE,CAAC,CAAC,oBAAoB;QAC7C,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,mBAAmB,EAAE,CAAC,CAAC,iBAAiB;KACzC,CAAC,CAAC;IACH,OAAO;QACL,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE;QACtB,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC;QACrB,YAAY,EAAE,GAAG,CAAC,aAAa,IAAI,CAAC;QACpC,gBAAgB,EAAE,GAAG,CAAC,kBAAkB;KACzC,CAAC;AACJ,CAAC;AAED,+DAA+D;AAC/D,wEAAwE;AACxE,gDAAgD;AAChD,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,KAAa,EACb,IAAiB,EAAE,EACnB,QAAQ,GAAG,KAAK;IAEhB,MAAM,KAAK,GAA0B,EAAE,CAAC;IACxC,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,iBAAiB,GAAG,CAAC,CAAC,iBAAiB,CAAC;IAC5C,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,EAAE,GAAG,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC;QACjE,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,YAAY,IAAI,IAAI,CAAC,YAAY,CAAC;QAElC,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;YAC7B,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC;YACpC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;YAChD,MAAM;QACR,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,gBAAgB;YAAE,MAAM;QAClC,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,CAAC;IAC5C,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;AAC5C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,KAAa,EACb,IAII,EAAE;IAEN,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAoB;QAC5C,EAAE,EAAE,MAAM;QACV,KAAK;QACL,iBAAiB,EAAE,CAAC,CAAC,gBAAgB;QACrC,iBAAiB,EAAE,CAAC,CAAC,gBAAgB;QACrC,gBAAgB,EAAE,CAAC,CAAC,eAAe;QACnC,MAAM,EAAE,OAAO;KAChB,CAAC,CAAC;IACH,OAAO,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAAa;IAEb,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAmD;QAC3E,EAAE,EAAE,gBAAgB;QACpB,KAAK;KACN,CAAC,CAAC;IACH,OAAO;QACL,SAAS,EAAE,GAAG,CAAC,UAAU,IAAI,CAAC;QAC9B,cAAc,EAAE,GAAG,CAAC,gBAAgB,IAAI,CAAC;KAC1C,CAAC;AACJ,CAAC;AAED,0EAA0E;AAE1E,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,UAAkB,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,gCAAgC;IAExF,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,aAAa,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QACpE,OAAO;YACL,OAAO,EAAE,GAAG,CAAC,EAAE;YACf,OAAO,EAAE,QAAQ,GAAG,CAAC,MAAM,EAAE;SAC9B,CAAC;IACJ,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7D,CAAC;AACH,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,CAAC,MAAM,CAAC;AAClB,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,51 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
5
+ import { registerCompanyTools } from "./tools/company-tools.js";
6
+ import { registerContactTools } from "./tools/contact-tools.js";
7
+ import { registerCampaignTools } from "./tools/campaign-tools.js";
8
+ import { registerPipelineTools } from "./tools/pipeline-tools.js";
9
+ import { registerAdminTools } from "./tools/admin-tools.js";
10
+ import { registerMcpWriteTools } from "./tools/mcp-write-tools.js";
11
+ import { registerBundledSkillPrompts } from "./prompts.js";
12
+ const server = new McpServer({
13
+ name: "vantage-ops",
14
+ version: "1.0.0",
15
+ });
16
+ registerCompanyTools(server);
17
+ registerContactTools(server);
18
+ registerCampaignTools(server);
19
+ registerPipelineTools(server);
20
+ registerAdminTools(server);
21
+ registerMcpWriteTools(server);
22
+ registerBundledSkillPrompts(server);
23
+ const mode = process.env.MCP_TRANSPORT ?? "stdio";
24
+ if (mode === "http") {
25
+ const port = parseInt(process.env.PORT ?? "9402");
26
+ const apiKey = process.env.MCP_API_KEY;
27
+ const app = createMcpExpressApp({ host: "0.0.0.0" });
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ app.post("/mcp", async (req, res) => {
30
+ if (apiKey) {
31
+ const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
32
+ const keyFromHeader = req.headers["x-api-key"];
33
+ const keyFromQuery = url.searchParams.get("key");
34
+ if (keyFromHeader !== apiKey && keyFromQuery !== apiKey) {
35
+ res.writeHead(401, { "Content-Type": "application/json" });
36
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32001, message: "Unauthorized" }, id: null }));
37
+ return;
38
+ }
39
+ }
40
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
41
+ await server.connect(transport);
42
+ await transport.handleRequest(req, res, req.body);
43
+ res.on("close", () => { transport.close(); });
44
+ });
45
+ app.listen(port, () => console.log(`vantage-ops HTTP on :${port}${apiKey ? " (auth enabled)" : " (no auth)"}`));
46
+ }
47
+ else {
48
+ const transport = new StdioServerTransport();
49
+ await server.connect(transport);
50
+ }
51
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}