aimcpgate 0.2.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 +19 -0
- package/bin/mcp-gate.js +37 -0
- package/install.js +172 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# aimcpgate (npm wrapper)
|
|
2
|
+
|
|
3
|
+
npm wrapper for [aiMCPGate](https://github.com/akomyagin/aiMCPGate) — an MCP
|
|
4
|
+
gateway/proxy written in Go: one MCP endpoint that multiplexes calls across
|
|
5
|
+
several upstream MCP servers, aggregates their tool catalogs, and logs every
|
|
6
|
+
call.
|
|
7
|
+
|
|
8
|
+
This package contains no code of its own: on install it downloads the
|
|
9
|
+
prebuilt `mcp-gate` binary for your platform from the project's GitHub
|
|
10
|
+
Releases and verifies its SHA256 checksum.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx aimcpgate serve -c ./config.yaml
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Full documentation, configuration reference, and sources:
|
|
17
|
+
<https://github.com/akomyagin/aiMCPGate>.
|
|
18
|
+
|
|
19
|
+
License: MIT.
|
package/bin/mcp-gate.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Thin shim around the real mcp-gate Go binary: makes sure it is present
|
|
3
|
+
// (postinstall normally fetched it; installs done with --ignore-scripts get a
|
|
4
|
+
// lazy download on first run) and then execs it with stdio inherited — the
|
|
5
|
+
// gateway talks MCP over stdin/stdout, so the pipes must pass through
|
|
6
|
+
// untouched.
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { spawn } = require('child_process');
|
|
10
|
+
const { ensureBinary, binaryPath, isValidBinary } = require('../install.js');
|
|
11
|
+
|
|
12
|
+
async function main() {
|
|
13
|
+
let bin = binaryPath();
|
|
14
|
+
if (!isValidBinary(bin)) {
|
|
15
|
+
// Missing OR truncated to zero bytes (interrupted install) — (re)download.
|
|
16
|
+
bin = await ensureBinary();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const child = spawn(bin, process.argv.slice(2), { stdio: 'inherit' });
|
|
20
|
+
child.on('error', (err) => {
|
|
21
|
+
console.error(`mcp-gate: failed to start ${bin}: ${err.message}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
});
|
|
24
|
+
child.on('exit', (code, signal) => {
|
|
25
|
+
if (signal) {
|
|
26
|
+
// Re-raise the signal so callers observe the same termination cause.
|
|
27
|
+
process.kill(process.pid, signal);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
process.exit(code === null ? 1 : code);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
main().catch((err) => {
|
|
35
|
+
console.error(`mcp-gate: ${String(err.message || err)}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
});
|
package/install.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// aimcpgate npm installer: downloads the prebuilt `mcp-gate` binary for this
|
|
2
|
+
// platform from GitHub Releases and verifies its SHA256 checksum against the
|
|
3
|
+
// release's SHA256SUMS before extracting it into npm/bin/.
|
|
4
|
+
//
|
|
5
|
+
// Runs as the package's postinstall script; the bin/mcp-gate.js shim also
|
|
6
|
+
// requires ensureBinary() from here, so an install done with --ignore-scripts
|
|
7
|
+
// still self-heals lazily on first run.
|
|
8
|
+
//
|
|
9
|
+
// Zero external dependencies by design: only Node's standard library.
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const crypto = require('crypto');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const https = require('https');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const { execFileSync } = require('child_process');
|
|
18
|
+
|
|
19
|
+
const REPO = 'akomyagin/aiMCPGate';
|
|
20
|
+
|
|
21
|
+
// VERSION_PLACEHOLDER is what package.json carries in the repo checkout; the
|
|
22
|
+
// release workflow stamps the real tag version before `npm publish`. Seeing it
|
|
23
|
+
// at install time means a dev checkout, where there is no release to download.
|
|
24
|
+
const VERSION_PLACEHOLDER = '0.0.0-dev';
|
|
25
|
+
|
|
26
|
+
// binaryPath returns where the downloaded binary lives (or will live).
|
|
27
|
+
function binaryPath() {
|
|
28
|
+
const exe = process.platform === 'win32' ? 'mcp-gate.exe' : 'mcp-gate';
|
|
29
|
+
return path.join(__dirname, 'bin', exe);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// isValidBinary reports whether the binary at p looks installed: it must exist
|
|
33
|
+
// and be non-empty. An interrupted install (killed process, full disk) can
|
|
34
|
+
// leave a truncated/zero-size file behind — treating it as "not installed"
|
|
35
|
+
// lets ensureBinary re-download it, keeping the lazy self-heal promise. No
|
|
36
|
+
// full checksum here by design: that would cost a network round-trip on every
|
|
37
|
+
// CLI invocation; this only guards against an obviously broken empty file.
|
|
38
|
+
function isValidBinary(p) {
|
|
39
|
+
try {
|
|
40
|
+
return fs.statSync(p).size > 0;
|
|
41
|
+
} catch {
|
|
42
|
+
return false; // missing (or unreadable) — not installed
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// assetName maps the Node platform/arch to the goreleaser archive name
|
|
47
|
+
// (name_template in .goreleaser.yaml: mcp-gate_<version>_<os>_<arch>, tar.gz
|
|
48
|
+
// everywhere except a zip override for windows).
|
|
49
|
+
function assetName(version) {
|
|
50
|
+
const goos = { linux: 'linux', darwin: 'darwin', win32: 'windows' }[process.platform];
|
|
51
|
+
const goarch = { x64: 'amd64', arm64: 'arm64' }[process.arch];
|
|
52
|
+
if (!goos || !goarch) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`aimcpgate: no prebuilt mcp-gate binary for ${process.platform}/${process.arch}; ` +
|
|
55
|
+
`build from source: https://github.com/${REPO}`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
const ext = goos === 'windows' ? 'zip' : 'tar.gz';
|
|
59
|
+
return `mcp-gate_${version}_${goos}_${goarch}.${ext}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// download GETs a URL (following redirects — GitHub release assets redirect to
|
|
63
|
+
// a CDN) and resolves with the response body as a Buffer.
|
|
64
|
+
function download(url, redirectsLeft = 5) {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
const req = https.get(url, { headers: { 'User-Agent': 'aimcpgate-npm-installer' } }, (res) => {
|
|
67
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
68
|
+
res.resume();
|
|
69
|
+
if (redirectsLeft <= 0) return reject(new Error(`too many redirects fetching ${url}`));
|
|
70
|
+
return resolve(download(res.headers.location, redirectsLeft - 1));
|
|
71
|
+
}
|
|
72
|
+
if (res.statusCode !== 200) {
|
|
73
|
+
res.resume();
|
|
74
|
+
return reject(new Error(`GET ${url}: HTTP ${res.statusCode}`));
|
|
75
|
+
}
|
|
76
|
+
const chunks = [];
|
|
77
|
+
res.on('data', (c) => chunks.push(c));
|
|
78
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
79
|
+
res.on('error', reject);
|
|
80
|
+
});
|
|
81
|
+
req.on('error', reject);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// verifyChecksum checks the archive against its SHA256SUMS entry
|
|
86
|
+
// ("<hex> <asset>" per line) and throws on any mismatch or missing entry.
|
|
87
|
+
function verifyChecksum(archive, asset, sumsText) {
|
|
88
|
+
let expected = null;
|
|
89
|
+
for (const line of sumsText.split('\n')) {
|
|
90
|
+
const fields = line.trim().split(/\s+/);
|
|
91
|
+
if (fields.length === 2 && fields[1] === asset) {
|
|
92
|
+
expected = fields[0].toLowerCase();
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!expected) {
|
|
97
|
+
throw new Error(`SHA256SUMS has no entry for ${asset}`);
|
|
98
|
+
}
|
|
99
|
+
const actual = crypto.createHash('sha256').update(archive).digest('hex');
|
|
100
|
+
if (actual !== expected) {
|
|
101
|
+
throw new Error(`checksum mismatch for ${asset}: expected ${expected}, got ${actual}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// extract unpacks the verified archive into npm/bin/. It shells out to the
|
|
106
|
+
// system `tar` rather than hand-rolling a tar/zip parser: with zero allowed
|
|
107
|
+
// npm dependencies a correct parser is the riskier path, while `tar` ships on
|
|
108
|
+
// every platform this package supports — Linux, macOS, and Windows 10+ (whose
|
|
109
|
+
// bundled bsdtar extracts .zip archives too, covering the windows asset).
|
|
110
|
+
function extract(archive, asset) {
|
|
111
|
+
const binDir = path.join(__dirname, 'bin');
|
|
112
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
113
|
+
const tmp = path.join(os.tmpdir(), `aimcpgate-${process.pid}-${asset}`);
|
|
114
|
+
fs.writeFileSync(tmp, archive);
|
|
115
|
+
try {
|
|
116
|
+
execFileSync('tar', ['-xf', tmp, '-C', binDir], { stdio: 'inherit' });
|
|
117
|
+
} finally {
|
|
118
|
+
fs.rmSync(tmp, { force: true });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ensureBinary downloads, verifies, and extracts the binary if it is not
|
|
123
|
+
// already present. Returns the binary path. Exported for the bin shim's lazy
|
|
124
|
+
// path (installs done with --ignore-scripts).
|
|
125
|
+
async function ensureBinary() {
|
|
126
|
+
const bin = binaryPath();
|
|
127
|
+
if (isValidBinary(bin)) return bin;
|
|
128
|
+
|
|
129
|
+
const version = require('./package.json').version;
|
|
130
|
+
if (version === VERSION_PLACEHOLDER) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
'aimcpgate: package.json still carries the dev placeholder version — ' +
|
|
133
|
+
'this is a repo checkout, not a published package; build mcp-gate from source instead'
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const asset = assetName(version);
|
|
138
|
+
const base = `https://github.com/${REPO}/releases/download/v${version}`;
|
|
139
|
+
console.log(`aimcpgate: downloading ${asset} ...`);
|
|
140
|
+
const [archive, sums] = await Promise.all([
|
|
141
|
+
download(`${base}/${asset}`),
|
|
142
|
+
download(`${base}/SHA256SUMS`),
|
|
143
|
+
]);
|
|
144
|
+
verifyChecksum(archive, asset, sums.toString('utf8'));
|
|
145
|
+
extract(archive, asset);
|
|
146
|
+
|
|
147
|
+
if (!fs.existsSync(bin)) {
|
|
148
|
+
throw new Error(`archive ${asset} did not contain ${path.basename(bin)}`);
|
|
149
|
+
}
|
|
150
|
+
if (process.platform !== 'win32') {
|
|
151
|
+
fs.chmodSync(bin, 0o755);
|
|
152
|
+
}
|
|
153
|
+
console.log(`aimcpgate: installed ${bin}`);
|
|
154
|
+
return bin;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = { ensureBinary, binaryPath, isValidBinary };
|
|
158
|
+
|
|
159
|
+
if (require.main === module) {
|
|
160
|
+
// postinstall entry point. In a dev checkout (placeholder version) there is
|
|
161
|
+
// nothing to download — skip quietly instead of failing `npm install` on the
|
|
162
|
+
// repo itself; the real version is stamped in CI before publishing.
|
|
163
|
+
const version = require('./package.json').version;
|
|
164
|
+
if (version === VERSION_PLACEHOLDER) {
|
|
165
|
+
console.log('aimcpgate: dev checkout (placeholder version), skipping binary download');
|
|
166
|
+
process.exit(0);
|
|
167
|
+
}
|
|
168
|
+
ensureBinary().catch((err) => {
|
|
169
|
+
console.error(String(err.message || err));
|
|
170
|
+
process.exit(1);
|
|
171
|
+
});
|
|
172
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aimcpgate",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MCP gateway/proxy: one MCP endpoint multiplexing several upstream MCP servers, with an aggregated tool catalog and a call log. npm wrapper that downloads the prebuilt mcp-gate Go binary from GitHub Releases.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"mcp-gate": "bin/mcp-gate.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"postinstall": "node install.js"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/akomyagin/aiMCPGate.git"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/akomyagin/aiMCPGate",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/akomyagin/aiMCPGate/issues"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"model-context-protocol",
|
|
22
|
+
"gateway",
|
|
23
|
+
"proxy",
|
|
24
|
+
"multiplexer"
|
|
25
|
+
],
|
|
26
|
+
"author": "Alexander Komyagin",
|
|
27
|
+
"license": "MIT"
|
|
28
|
+
}
|