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.
- package/README.md +105 -0
- package/index.js +169 -0
- 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
|
+
}
|