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 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.
@@ -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
+ }