@vruum/mcp 0.2.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vruum (vruum-gtm)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # Vruum MCP (`@vruum/mcp`)
2
+
3
+ Official MCP access to [Vruum](https://vruum.ai) — the AI revenue platform. Operate outbound, deals, pipeline, and CRM automation from your agent: people, deals, outreach, engagement, and research tools over one MCP.
4
+
5
+ This package is the **stdio bridge** to the hosted Vruum MCP server. It serves the advertised tool surface locally (no credentials needed to introspect) and proxies tool execution to `https://api.vruum.ai/mcp` under your Vruum token. Every call is authorized server-side; the bridge grants no authority beyond what your Vruum account can already do.
6
+
7
+ > **Connecting a modern client? Prefer the remote server directly.**
8
+ > Clients that support remote MCP (Claude Code, Claude Desktop, Cursor, Codex, …) should connect straight to `https://api.vruum.ai/mcp` (OAuth 2.1, or `Authorization: Bearer vk_live_…`). This bridge exists for stdio-only clients and for tool-surface introspection. See [vruum.ai/docs/mcp](https://vruum.ai/docs/mcp).
9
+
10
+ ## Install & connect
11
+
12
+ ```json
13
+ {
14
+ "mcpServers": {
15
+ "vruum": {
16
+ "command": "npx",
17
+ "args": ["-y", "@vruum/mcp"],
18
+ "env": { "VRUUM_MCP_TOKEN": "vk_live_…" }
19
+ }
20
+ }
21
+ }
22
+ ```
23
+
24
+ Get a token in the Vruum web app under **Settings → API tokens**. A Vruum account is required to execute tools; listing tools works without one.
25
+
26
+ Already using the CLI? `npx @vruum/cli` → `vruum login --token vk_live_…` stores credentials in `~/.vruum/credentials`, and the bridge picks them up automatically.
27
+
28
+ ### Configuration
29
+
30
+ | Env var | Meaning | Default |
31
+ | --- | --- | --- |
32
+ | `VRUUM_MCP_TOKEN` | Vruum personal access token | — (falls back to `VRUUM_TOKEN`, then `~/.vruum/credentials`) |
33
+ | `VRUUM_MCP_URL` | Hosted MCP endpoint | `https://api.vruum.ai/mcp` |
34
+ | `VRUUM_MCP_TIMEOUT_MS` | Per-call timeout (research/import tools run long) | `300000` |
35
+ | `VRUUM_CONFIG_DIR` | Credentials directory | `~/.vruum` |
36
+
37
+ ## What's inside
38
+
39
+ - **[`tools.json`](tools.json)** — the client-advertised tool surface: **29 curated compound tools** (`search`, `fetch`, `research`, `skill`, and the `manage_*` / `get_*` families covering people, deals, outreach, engagement, content, campaigns, and revenue), each carrying MCP safety annotations. This is exactly what a real Vruum session is advertised — one tool per decision rather than a sprawl of granular endpoints, so an agent never has to disambiguate between overlapping legacy verbs. Generated from the live server definition and served verbatim for `tools/list`.
40
+ - **[`src/index.ts`](src/index.ts)** — the bridge: static listings, proxied calls, and structured errors
41
+ (a missing token is an explanation, not a crash). **A failed call is never automatically
42
+ retried** — many of these tools send outreach or spend money, and an ambiguous failure may
43
+ mean the server already executed, so a silent replay could double-send. The dead connection
44
+ is discarded, the error says so, and your agent decides whether the tool is safe to re-run.
45
+
46
+ The distinctive design position of the Vruum MCP: **your AI harness authors all sales and marketing prose.** The server schedules, gates, persists, and sends — it has no server-side message generation. Outreach drafts surface to your agent as work items rather than being written for you.
47
+
48
+ ## Development
49
+
50
+ This repository is a **build artifact of the Vruum monorepo**, resynced automatically on release — `tools.json` is regenerated from the live server definition, so it never drifts from what the hosted server actually exposes.
51
+
52
+ Issues and PRs are welcome here: maintainers upstream accepted changes into the monorepo, and they flow back on the next sync.
53
+
54
+ ```sh
55
+ npm install
56
+ npm test # builds, then black-box JSON-RPC tests against the built binary
57
+ ```
58
+
59
+ ## Links
60
+
61
+ - [Vruum](https://vruum.ai) · [MCP docs](https://vruum.ai/docs/mcp) · [Getting started](https://vruum.ai/docs/getting-started)
62
+ - Official registry entry: `ai.vruum/mcp` · Claude/Codex plugin: [`vruum-gtm/skills`](https://github.com/vruum-gtm/skills)
63
+
64
+ ## License
65
+
66
+ [MIT](LICENSE)
package/dist/index.js ADDED
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @vruum/mcp — stdio bridge for the hosted Vruum MCP.
4
+ *
5
+ * Two jobs, deliberately small:
6
+ *
7
+ * 1. `tools/list` is served STATICALLY from the bundled `tools.json` (the
8
+ * member-visible surface of https://api.vruum.ai/mcp, generated in the
9
+ * Vruum monorepo by `backend/scripts/dump_member_tools.py`). This is what
10
+ * lets registry crawlers and stdio-only clients introspect the tool
11
+ * surface without credentials.
12
+ *
13
+ * 2. `tools/call` proxies to the hosted server over Streamable HTTP with the
14
+ * caller's Vruum token. Without a token, calls return a structured error
15
+ * explaining how to get one — never a hang or a crash.
16
+ *
17
+ * Token resolution (mirrors @vruum/cli's `lib/config.ts` precedence):
18
+ * VRUUM_MCP_TOKEN env > VRUUM_TOKEN env > ~/.vruum/credentials (written by
19
+ * `vruum login`; directory overridable via VRUUM_CONFIG_DIR).
20
+ *
21
+ * Endpoint resolution:
22
+ * VRUUM_MCP_URL env > credentials apiUrl + "/mcp" > https://api.vruum.ai/mcp
23
+ *
24
+ * Note: `tools.json` intentionally carries no outputSchema — declaring one
25
+ * would make this bridge's local error responses (which have no
26
+ * structuredContent) protocol violations under SDK output validation.
27
+ */
28
+ import { readFileSync } from "node:fs";
29
+ import { homedir } from "node:os";
30
+ import { dirname, join } from "node:path";
31
+ import { fileURLToPath } from "node:url";
32
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
33
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
34
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
35
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
36
+ import { CallToolRequestSchema, CallToolResultSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
37
+ const DEFAULT_MCP_URL = "https://api.vruum.ai/mcp";
38
+ // The SDK's per-request default is 60s, which real Vruum tools (research,
39
+ // imports) legitimately exceed. Overridable via VRUUM_MCP_TIMEOUT_MS.
40
+ const DEFAULT_CALL_TIMEOUT_MS = 300_000;
41
+ function resolveCallTimeoutMs() {
42
+ const raw = Number(process.env.VRUUM_MCP_TIMEOUT_MS);
43
+ return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_CALL_TIMEOUT_MS;
44
+ }
45
+ const HERE = dirname(fileURLToPath(import.meta.url));
46
+ // dist/index.js → package root sibling tools.json (also works from src/ via tsx).
47
+ const TOOLS_PATH = join(HERE, "..", "tools.json");
48
+ const PKG_PATH = join(HERE, "..", "package.json");
49
+ function readJson(path) {
50
+ return JSON.parse(readFileSync(path, "utf8"));
51
+ }
52
+ function readCredentials() {
53
+ try {
54
+ const dir = process.env.VRUUM_CONFIG_DIR || join(homedir(), ".vruum");
55
+ const parsed = readJson(join(dir, "credentials"));
56
+ if (!parsed || typeof parsed.token !== "string" || !parsed.token)
57
+ return null;
58
+ return parsed;
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ function resolveToken(creds) {
65
+ return (process.env.VRUUM_MCP_TOKEN || process.env.VRUUM_TOKEN || creds?.token || null);
66
+ }
67
+ function resolveMcpUrl(creds) {
68
+ if (process.env.VRUUM_MCP_URL)
69
+ return process.env.VRUUM_MCP_URL;
70
+ if (creds?.apiUrl)
71
+ return creds.apiUrl.replace(/\/+$/, "") + "/mcp";
72
+ return DEFAULT_MCP_URL;
73
+ }
74
+ const NO_TOKEN_HELP = [
75
+ "No Vruum token configured, so this call was not sent.",
76
+ "",
77
+ "The bridge needs a Vruum personal access token to execute tools:",
78
+ " 1. Sign in at https://vruum.ai and create a token under Settings → API tokens (vk_live_…),",
79
+ " 2. then either `export VRUUM_MCP_TOKEN=<token>` for this client,",
80
+ " or run `npx @vruum/cli` → `vruum login --token <token>` once.",
81
+ "",
82
+ "If your MCP client supports remote servers, prefer connecting it directly to",
83
+ `${DEFAULT_MCP_URL} (OAuth) instead of this stdio bridge.`,
84
+ ].join("\n");
85
+ function errorResult(text) {
86
+ return { content: [{ type: "text", text }], isError: true };
87
+ }
88
+ /**
89
+ * Lazily-connected client to the hosted server.
90
+ *
91
+ * Failure semantics, deliberately conservative: a failed `tools/call` is
92
+ * NEVER automatically retried. Many Vruum tools mutate state, send outreach
93
+ * externally, or spend money, and an ambiguous failure (timeout, dropped
94
+ * response) may mean the server already executed — a client-side replay could
95
+ * double-send. The failed connection is discarded so the NEXT call gets a
96
+ * fresh session, the error is surfaced structurally, and the calling harness
97
+ * decides whether the tool is safe to retry. (This mirrors Vruum's own
98
+ * server-side durable-send-intent discipline: ambiguous outcomes park, they
99
+ * are not replayed.)
100
+ */
101
+ class RemoteProxy {
102
+ url;
103
+ token;
104
+ version;
105
+ client = null;
106
+ /** Serializes connection creation so concurrent first calls share one
107
+ * client instead of racing to overwrite `this.client` (and later closing
108
+ * each other's healthy connections). */
109
+ connecting = null;
110
+ constructor(url, token, version) {
111
+ this.url = url;
112
+ this.token = token;
113
+ this.version = version;
114
+ }
115
+ getClient() {
116
+ if (this.client)
117
+ return Promise.resolve(this.client);
118
+ if (!this.connecting) {
119
+ this.connecting = (async () => {
120
+ const transport = new StreamableHTTPClientTransport(new URL(this.url), {
121
+ requestInit: {
122
+ headers: { Authorization: `Bearer ${this.token}` },
123
+ },
124
+ });
125
+ const client = new Client({ name: "vruum-mcp-bridge", version: this.version }, { capabilities: {} });
126
+ await client.connect(transport);
127
+ this.client = client;
128
+ return client;
129
+ })().finally(() => {
130
+ this.connecting = null;
131
+ });
132
+ }
133
+ return this.connecting;
134
+ }
135
+ /** Discard `failed` only if it is still the current client — a concurrent
136
+ * call may already have replaced it with a healthy connection. */
137
+ discard(failed) {
138
+ if (this.client === failed)
139
+ this.client = null;
140
+ void failed.close().catch(() => {
141
+ /* already dead */
142
+ });
143
+ }
144
+ /**
145
+ * Raw tools/call — deliberately NOT `client.callTool()`: the helper
146
+ * validates results against outputSchemas cached by `listTools()`, and the
147
+ * remote advertises outputSchemas this bridge intentionally does not. The
148
+ * raw request form has no cache and no validation, so passthrough stays
149
+ * passthrough. Timeout is raised above the SDK's 60s default because real
150
+ * Vruum tools (research, imports) legitimately run longer.
151
+ */
152
+ rawCall(client, name, args) {
153
+ return client.request({ method: "tools/call", params: { name, arguments: args } }, CallToolResultSchema, { timeout: resolveCallTimeoutMs(), resetTimeoutOnProgress: true });
154
+ }
155
+ async call(name, args) {
156
+ let client;
157
+ try {
158
+ client = await this.getClient();
159
+ }
160
+ catch (connectErr) {
161
+ // First-connect failures (DNS, invalid token, endpoint down) get the
162
+ // same structured error path as call failures — never a raw handler
163
+ // throw that surfaces as an opaque JSON-RPC internal error.
164
+ const msg = connectErr instanceof Error ? connectErr.message : String(connectErr);
165
+ return errorResult(`Could not connect to the Vruum MCP at ${this.url}: ${msg}\n` +
166
+ "Check that your token is valid (Settings → API tokens) and the " +
167
+ "endpoint is reachable.");
168
+ }
169
+ try {
170
+ return await this.rawCall(client, name, args);
171
+ }
172
+ catch (callErr) {
173
+ this.discard(client);
174
+ const msg = callErr instanceof Error ? callErr.message : String(callErr);
175
+ return errorResult(`Vruum MCP call '${name}' failed against ${this.url}: ${msg}\n` +
176
+ "NOT automatically retried — the server may or may not have executed " +
177
+ "the action, and replaying a send/spend tool could duplicate it. " +
178
+ "Verify state with a read tool (e.g. fetch/search) before retrying; " +
179
+ "the next call will use a fresh connection.");
180
+ }
181
+ }
182
+ }
183
+ async function main() {
184
+ const pkg = readJson(PKG_PATH);
185
+ const toolsDoc = readJson(TOOLS_PATH);
186
+ const creds = readCredentials();
187
+ const token = resolveToken(creds);
188
+ const mcpUrl = resolveMcpUrl(creds);
189
+ const proxy = token ? new RemoteProxy(mcpUrl, token, pkg.version) : null;
190
+ const server = new Server({ name: "vruum-mcp", version: pkg.version }, {
191
+ // `listChanged: false` is the honest declaration: the listing is a
192
+ // static snapshot bundled at release time, so this server never emits
193
+ // notifications/tools/list_changed.
194
+ capabilities: { tools: { listChanged: false } },
195
+ instructions: "Bridge to the hosted Vruum revenue-platform MCP. Tool listings are " +
196
+ "served locally; tool calls execute against " +
197
+ `${mcpUrl} under your Vruum token and are authorized server-side.`,
198
+ });
199
+ server.setRequestHandler(ListToolsRequestSchema, async (request) => {
200
+ // The full list is returned in one page, so `nextCursor` is never set and
201
+ // a client can never legitimately hold a cursor for this server. Spec:
202
+ // "Invalid cursors SHOULD result in an error with code -32602."
203
+ const cursor = request.params?.cursor;
204
+ if (cursor !== undefined) {
205
+ throw new McpError(ErrorCode.InvalidParams, "Invalid cursor: this server returns all tools in a single page and " +
206
+ "never issues a nextCursor.");
207
+ }
208
+ return { tools: toolsDoc.tools };
209
+ });
210
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
211
+ const { name, arguments: args } = request.params;
212
+ // Spec (server/tools#error-handling): errors in *finding* the tool are
213
+ // PROTOCOL errors, not `isError` results — only errors originating from a
214
+ // tool's execution use `isError: true` (so the model can self-correct).
215
+ if (!toolsDoc.tools.some((t) => t.name === name)) {
216
+ throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${name}`);
217
+ }
218
+ // A missing token IS an execution error — the tool exists and the model
219
+ // should see why it could not run.
220
+ if (!proxy)
221
+ return errorResult(NO_TOKEN_HELP);
222
+ return proxy.call(name, (args ?? {}));
223
+ });
224
+ await server.connect(new StdioServerTransport());
225
+ }
226
+ main().catch((err) => {
227
+ console.error(`vruum-mcp fatal: ${err instanceof Error ? err.message : err}`);
228
+ process.exit(1);
229
+ });
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@vruum/mcp",
3
+ "version": "0.2.2",
4
+ "mcpName": "ai.vruum/mcp",
5
+ "description": "Stdio MCP bridge for the hosted Vruum revenue-platform MCP (https://api.vruum.ai/mcp): serves the advertised tool surface for credential-free introspection and proxies tool calls with your Vruum token.",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "bin": {
9
+ "vruum-mcp": "dist/index.js"
10
+ },
11
+ "main": "dist/index.js",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/vruum-gtm/mcp.git"
15
+ },
16
+ "homepage": "https://vruum.ai/docs/mcp",
17
+ "bugs": {
18
+ "url": "https://github.com/vruum-gtm/mcp/issues"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "tools.json",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc -p tsconfig.json",
31
+ "postbuild": "chmod +x dist/index.js",
32
+ "typecheck": "tsc -p tsconfig.json --noEmit",
33
+ "test": "npm run build && node --test test/*.test.mjs",
34
+ "prepublishOnly": "npm run typecheck && npm run test"
35
+ },
36
+ "dependencies": {
37
+ "@modelcontextprotocol/sdk": "^1.30.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^24.0.0",
41
+ "typescript": "^5.7.0"
42
+ },
43
+ "keywords": [
44
+ "vruum",
45
+ "mcp",
46
+ "mcp-server",
47
+ "model-context-protocol",
48
+ "sales",
49
+ "crm",
50
+ "revenue",
51
+ "outreach",
52
+ "gtm"
53
+ ]
54
+ }