voidgate-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 (3) hide show
  1. package/README.md +105 -0
  2. package/index.js +169 -0
  3. package/package.json +27 -0
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # voidgate-mcp
2
+
3
+ Stdio MCP bridge for [VoidGate](https://github.com/your-org/voidgate).
4
+ Wraps VoidGate's Streamable HTTP endpoint into a standard MCP stdio server so any AI client (Claude Desktop, Cursor, Windsurf, …) can use it without extra configuration.
5
+
6
+ ## How it works
7
+
8
+ ```
9
+ AI client (Claude Desktop / Cursor / …)
10
+ ↕ MCP stdio
11
+ npx voidgate-mcp [app-id]
12
+ ↕ Streamable HTTP (JSON-RPC 2.0)
13
+ VoidGate /server/{connectionId}?key=…
14
+ ↕ HTTP
15
+ Your business API
16
+ ```
17
+
18
+ ## Requirements
19
+
20
+ - Node.js ≥ 18
21
+ - A running VoidGate instance
22
+ - A valid `x-access-token` (user JWT from your auth service)
23
+
24
+ ## Usage
25
+
26
+ ### Mode A — direct URL
27
+
28
+ If you already have a VoidGate Streamable HTTP URL:
29
+
30
+ ```bash
31
+ VOIDGATE_URL="http://localhost:8081/server/c_xxx?key=k_yyy" npx -y voidgate-mcp
32
+ ```
33
+
34
+ ### Mode B — auto-connect (recommended)
35
+
36
+ The bridge calls `POST /v1/mcp/connections` to get or reuse a connection URL automatically.
37
+
38
+ ```bash
39
+ VOIDGATE_BASE_URL="http://localhost:8081" \
40
+ VOIDGATE_TOKEN="eyJhbGci..." \
41
+ VOIDGATE_APP_MCP_ID="1" \
42
+ npx -y voidgate-mcp
43
+ ```
44
+
45
+ You can also pass the app ID as a positional argument (useful for Claude Desktop config):
46
+
47
+ ```bash
48
+ VOIDGATE_BASE_URL="http://localhost:8081" \
49
+ VOIDGATE_TOKEN="eyJhbGci..." \
50
+ npx -y voidgate-mcp 1
51
+ ```
52
+
53
+ ## Claude Desktop setup
54
+
55
+ Edit `~/.claude/claude_desktop_config.json` (Mac/Linux) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
56
+
57
+ ```json
58
+ {
59
+ "mcpServers": {
60
+ "invoice-service": {
61
+ "command": "npx",
62
+ "args": ["-y", "voidgate-mcp", "1"],
63
+ "env": {
64
+ "VOIDGATE_BASE_URL": "https://your-voidgate.com",
65
+ "VOIDGATE_TOKEN": "eyJhbGci..."
66
+ }
67
+ },
68
+ "recognition-service": {
69
+ "command": "npx",
70
+ "args": ["-y", "voidgate-mcp", "2"],
71
+ "env": {
72
+ "VOIDGATE_BASE_URL": "https://your-voidgate.com",
73
+ "VOIDGATE_TOKEN": "eyJhbGci..."
74
+ }
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ Restart Claude Desktop. The tools from each VoidGate app will appear automatically.
81
+
82
+ ## Cursor / other clients
83
+
84
+ Add to your MCP client config with the same `command` / `args` / `env` structure above.
85
+ Most clients that support MCP stdio follow the same schema.
86
+
87
+ ## Environment variables
88
+
89
+ | Variable | Required | Description |
90
+ |---|---|---|
91
+ | `VOIDGATE_URL` | A or B | Full Streamable HTTP URL (skip auto-connect) |
92
+ | `VOIDGATE_BASE_URL` | B | Base URL of your VoidGate instance |
93
+ | `VOIDGATE_TOKEN` | B | User access token (`x-access-token`) |
94
+ | `VOIDGATE_APP_MCP_ID` | B* | App MCP ID (can also be the first CLI arg) |
95
+
96
+ \* Required in Mode B unless passed as positional argument.
97
+
98
+ ## Logs
99
+
100
+ All bridge logs go to **stderr** so they never interfere with the MCP stdio stream.
101
+ Redirect stderr to a file for debugging:
102
+
103
+ ```bash
104
+ VOIDGATE_URL="..." npx voidgate-mcp 2>bridge.log
105
+ ```
package/index.js ADDED
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * voidgate-mcp — stdio MCP bridge for VoidGate
4
+ *
5
+ * Usage:
6
+ * # Mode 1: direct Streamable HTTP URL
7
+ * VOIDGATE_URL="http://host/server/c_xxx?key=k_yyy" npx voidgate-mcp
8
+ *
9
+ * # Mode 2: auto-connect (VoidGate creates the connection)
10
+ * VOIDGATE_BASE_URL="http://host" \
11
+ * VOIDGATE_TOKEN="eyJ..." \
12
+ * VOIDGATE_APP_MCP_ID="1" \
13
+ * npx voidgate-mcp
14
+ *
15
+ * # Mode 2b: app ID as positional arg (overrides env var)
16
+ * VOIDGATE_BASE_URL="http://host" VOIDGATE_TOKEN="eyJ..." npx voidgate-mcp 1
17
+ *
18
+ * Claude Desktop config example (~/.claude/claude_desktop_config.json):
19
+ * {
20
+ * "mcpServers": {
21
+ * "invoice-service": {
22
+ * "command": "npx",
23
+ * "args": ["-y", "voidgate-mcp", "1"],
24
+ * "env": {
25
+ * "VOIDGATE_BASE_URL": "https://your-voidgate.com",
26
+ * "VOIDGATE_TOKEN": "eyJ..."
27
+ * }
28
+ * }
29
+ * }
30
+ * }
31
+ */
32
+
33
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
34
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
35
+ import {
36
+ CallToolRequestSchema,
37
+ ListToolsRequestSchema,
38
+ } from "@modelcontextprotocol/sdk/types.js";
39
+
40
+ // ─── config resolution ────────────────────────────────────────────────────────
41
+
42
+ function env(key) {
43
+ return (process.env[key] ?? "").trim();
44
+ }
45
+
46
+ async function resolveURL() {
47
+ // Mode 1: direct URL already known
48
+ const direct = env("VOIDGATE_URL");
49
+ if (direct) return direct;
50
+
51
+ // Mode 2: auto-connect via VoidGate management API
52
+ const baseUrl = env("VOIDGATE_BASE_URL").replace(/\/$/, "");
53
+ const token = env("VOIDGATE_TOKEN");
54
+
55
+ // App MCP ID: positional CLI arg takes precedence over env var
56
+ const rawId = process.argv[2]?.trim() || env("VOIDGATE_APP_MCP_ID");
57
+
58
+ if (!baseUrl || !token || !rawId) {
59
+ throw new Error(
60
+ "Configuration missing.\n" +
61
+ " Option A — set VOIDGATE_URL\n" +
62
+ " Option B — set VOIDGATE_BASE_URL + VOIDGATE_TOKEN + VOIDGATE_APP_MCP_ID (or pass ID as first arg)"
63
+ );
64
+ }
65
+
66
+ const appMcpId = parseInt(rawId, 10);
67
+ if (!Number.isFinite(appMcpId) || appMcpId <= 0) {
68
+ throw new Error(`Invalid app MCP ID: "${rawId}" — must be a positive integer`);
69
+ }
70
+
71
+ log(`Connecting to VoidGate app #${appMcpId} at ${baseUrl} …`);
72
+
73
+ const res = await fetch(`${baseUrl}/v1/mcp/connections`, {
74
+ method: "POST",
75
+ headers: {
76
+ "Content-Type": "application/json",
77
+ "x-access-token": token,
78
+ },
79
+ body: JSON.stringify({ appMcpId }),
80
+ });
81
+
82
+ if (!res.ok) {
83
+ const body = await res.text().catch(() => "");
84
+ throw new Error(`VoidGate connection failed (HTTP ${res.status}): ${body}`);
85
+ }
86
+
87
+ const data = await res.json();
88
+ if (!data.url) throw new Error("VoidGate returned no MCP URL");
89
+
90
+ log(data.reused ? "Reusing existing connection." : "New connection created.");
91
+ return data.url;
92
+ }
93
+
94
+ // ─── VoidGate RPC helper ──────────────────────────────────────────────────────
95
+
96
+ async function rpc(mcpUrl, method, params) {
97
+ const res = await fetch(mcpUrl, {
98
+ method: "POST",
99
+ headers: {
100
+ "Content-Type": "application/json",
101
+ Accept: "application/json, text/event-stream",
102
+ },
103
+ body: JSON.stringify({
104
+ jsonrpc: "2.0",
105
+ id: Date.now(),
106
+ method,
107
+ params: params ?? {},
108
+ }),
109
+ });
110
+
111
+ if (!res.ok) {
112
+ throw new Error(`VoidGate RPC error: HTTP ${res.status} on ${method}`);
113
+ }
114
+
115
+ let payload;
116
+ const ct = res.headers.get("content-type") ?? "";
117
+ if (ct.includes("text/event-stream")) {
118
+ // Stateless Streamable HTTP may return SSE; grab first data line
119
+ const text = await res.text();
120
+ const line = text.split("\n").find((l) => l.startsWith("data:"));
121
+ if (!line) throw new Error(`No data in SSE response for ${method}`);
122
+ payload = JSON.parse(line.slice(5).trim());
123
+ } else {
124
+ payload = await res.json();
125
+ }
126
+
127
+ if (payload.error) {
128
+ const msg = payload.error.message ?? JSON.stringify(payload.error);
129
+ throw new Error(`VoidGate error on ${method}: ${msg}`);
130
+ }
131
+ return payload.result;
132
+ }
133
+
134
+ // ─── logging (stderr only — stdout is reserved for MCP stdio) ────────────────
135
+
136
+ function log(msg) {
137
+ process.stderr.write(`[voidgate-mcp] ${msg}\n`);
138
+ }
139
+
140
+ // ─── main ─────────────────────────────────────────────────────────────────────
141
+
142
+ async function main() {
143
+ const mcpUrl = await resolveURL();
144
+ log(`MCP URL: ${mcpUrl}`);
145
+
146
+ const server = new Server(
147
+ { name: "voidgate", version: "1.0.0" },
148
+ { capabilities: { tools: {} } }
149
+ );
150
+
151
+ // tools/list — forward to VoidGate, return upstream result unchanged
152
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
153
+ return await rpc(mcpUrl, "tools/list", {});
154
+ });
155
+
156
+ // tools/call — forward to VoidGate, return upstream result unchanged
157
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
158
+ return await rpc(mcpUrl, "tools/call", req.params);
159
+ });
160
+
161
+ const transport = new StdioServerTransport();
162
+ await server.connect(transport);
163
+ log("Bridge ready — listening on stdio.");
164
+ }
165
+
166
+ main().catch((err) => {
167
+ process.stderr.write(`[voidgate-mcp] fatal: ${err.message}\n`);
168
+ process.exit(1);
169
+ });
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "voidgate-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Stdio MCP bridge for VoidGate — connect any MCP app to Claude Desktop, Cursor, and other AI clients",
5
+ "type": "module",
6
+ "bin": {
7
+ "voidgate-mcp": "index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "README.md"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "keywords": [
17
+ "mcp",
18
+ "model-context-protocol",
19
+ "voidgate",
20
+ "claude",
21
+ "ai"
22
+ ],
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "@modelcontextprotocol/sdk": "^1.0.0"
26
+ }
27
+ }