@khanglvm/tool-hub-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 (4) hide show
  1. package/README.md +87 -0
  2. package/cli.js +130 -0
  3. package/package.json +45 -0
  4. package/postinstall.js +197 -0
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # @khanglvm/tool-hub-mcp
2
+
3
+ **Serverless MCP Aggregator** - Reduce AI context token consumption by 60-97%
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Zero-install usage (recommended)
9
+ npx @khanglvm/tool-hub-mcp setup
10
+ npx @khanglvm/tool-hub-mcp serve
11
+ npx @khanglvm/tool-hub-mcp benchmark
12
+ ```
13
+
14
+ Also works with other JS package runners:
15
+ ```bash
16
+ bunx @khanglvm/tool-hub-mcp setup
17
+ pnpm dlx @khanglvm/tool-hub-mcp setup
18
+ yarn dlx @khanglvm/tool-hub-mcp setup
19
+ ```
20
+
21
+ ## What It Does
22
+
23
+ When using multiple MCP servers with AI clients (Claude Code, OpenCode, etc.), each server exposes all its tools to the AI. With 5+ servers, you can easily consume **25,000+ tokens** just for tool definitions.
24
+
25
+ `tool-hub-mcp` acts as a single MCP endpoint that exposes only **5 meta-tools**:
26
+
27
+ | Tool | Description |
28
+ |------|-------------|
29
+ | `hub_list` | List all registered MCP servers |
30
+ | `hub_discover` | Get tools from a specific server |
31
+ | `hub_search` | Search for tools across servers |
32
+ | `hub_execute` | Execute a tool from a server |
33
+ | `hub_help` | Get detailed help for a tool |
34
+
35
+ **Result:** ~461 tokens instead of 1,200-25,000+ = **61-97% reduction**
36
+
37
+ ## Commands
38
+
39
+ ```bash
40
+ # Import configs from AI CLI tools (Claude Code, OpenCode, etc.)
41
+ npx @khanglvm/tool-hub-mcp setup
42
+
43
+ # Run as MCP server (for AI client integration)
44
+ npx @khanglvm/tool-hub-mcp serve
45
+
46
+ # Add MCP servers manually
47
+ npx @khanglvm/tool-hub-mcp add jira --command npx --arg -y --arg @lvmk/jira-mcp
48
+
49
+ # Run token efficiency benchmark
50
+ npx @khanglvm/tool-hub-mcp benchmark
51
+
52
+ # Run speed/latency benchmark
53
+ npx @khanglvm/tool-hub-mcp benchmark speed
54
+ ```
55
+
56
+ ## MCP Client Configuration
57
+
58
+ Add to your AI client config:
59
+
60
+ **Claude Code:**
61
+ ```bash
62
+ claude mcp add tool-hub -- npx @khanglvm/tool-hub-mcp serve
63
+ ```
64
+
65
+ **Manual JSON config:**
66
+ ```json
67
+ {
68
+ "mcpServers": {
69
+ "tool-hub": {
70
+ "command": "npx",
71
+ "args": ["@khanglvm/tool-hub-mcp", "serve"]
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ ## Platforms
78
+
79
+ This package automatically installs the correct binary for your platform:
80
+
81
+ - macOS (Apple Silicon & Intel)
82
+ - Linux (x64 & ARM64)
83
+ - Windows (x64)
84
+
85
+ ## License
86
+
87
+ MIT © khanglvm
package/cli.js ADDED
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI wrapper for tool-hub-mcp Go binary.
5
+ * Locates the platform-specific binary from optionalDependencies and spawns it.
6
+ *
7
+ * Architecture: Thin wrapper that passes through all args and stdio to the Go binary.
8
+ */
9
+
10
+ const { spawn } = require('child_process');
11
+ const path = require('path');
12
+ const fs = require('fs');
13
+ const os = require('os');
14
+
15
+ /**
16
+ * Get the platform-specific package name based on current OS and architecture.
17
+ * @returns {string} Package name like '@khanglvm/tool-hub-mcp-darwin-arm64'
18
+ */
19
+ function getPlatformPackageName() {
20
+ const platform = os.platform();
21
+ const arch = os.arch();
22
+
23
+ // Map Node.js platform/arch to our package naming convention
24
+ const platformMap = {
25
+ 'darwin': 'darwin',
26
+ 'linux': 'linux',
27
+ 'win32': 'win32'
28
+ };
29
+
30
+ const archMap = {
31
+ 'arm64': 'arm64',
32
+ 'x64': 'x64',
33
+ 'x86_64': 'x64' // Fallback
34
+ };
35
+
36
+ const mappedPlatform = platformMap[platform];
37
+ const mappedArch = archMap[arch];
38
+
39
+ if (!mappedPlatform || !mappedArch) {
40
+ throw new Error(`Unsupported platform: ${platform}-${arch}`);
41
+ }
42
+
43
+ return `@khanglvm/tool-hub-mcp-${mappedPlatform}-${mappedArch}`;
44
+ }
45
+
46
+ /**
47
+ * Find the binary path from the installed platform package.
48
+ * Searches in node_modules relative to this script.
49
+ * @returns {string|null} Path to binary or null if not found
50
+ */
51
+ function findBinaryFromPackage() {
52
+ const packageName = getPlatformPackageName();
53
+ const binaryName = os.platform() === 'win32' ? 'tool-hub-mcp.exe' : 'tool-hub-mcp';
54
+
55
+ // Get platform directory name (e.g., 'darwin-arm64')
56
+ const platformDir = packageName.replace('@khanglvm/tool-hub-mcp-', '');
57
+
58
+ // Search locations (support various node_modules layouts + local dev)
59
+ const searchPaths = [
60
+ // Local development (platforms subdirectory)
61
+ path.join(__dirname, 'platforms', platformDir, 'bin', binaryName),
62
+ // Standard node_modules
63
+ path.join(__dirname, 'node_modules', packageName, 'bin', binaryName),
64
+ // Hoisted (pnpm, yarn workspaces)
65
+ path.join(__dirname, '..', packageName, 'bin', binaryName),
66
+ // Global install
67
+ path.join(__dirname, '..', '..', packageName, 'bin', binaryName),
68
+ ];
69
+
70
+ for (const searchPath of searchPaths) {
71
+ if (fs.existsSync(searchPath)) {
72
+ return searchPath;
73
+ }
74
+ }
75
+
76
+ return null;
77
+ }
78
+
79
+ /**
80
+ * Find binary downloaded by postinstall fallback.
81
+ * @returns {string|null} Path to binary or null if not found
82
+ */
83
+ function findDownloadedBinary() {
84
+ const binaryName = os.platform() === 'win32' ? 'tool-hub-mcp.exe' : 'tool-hub-mcp';
85
+ const downloadPath = path.join(__dirname, 'bin', binaryName);
86
+
87
+ if (fs.existsSync(downloadPath)) {
88
+ return downloadPath;
89
+ }
90
+
91
+ return null;
92
+ }
93
+
94
+ /**
95
+ * Main entry point - find binary and spawn with all arguments.
96
+ */
97
+ function main() {
98
+ // Try platform package first, then fallback to downloaded binary
99
+ let binaryPath = findBinaryFromPackage() || findDownloadedBinary();
100
+
101
+ if (!binaryPath) {
102
+ console.error('Error: tool-hub-mcp binary not found.');
103
+ console.error('This may happen if:');
104
+ console.error(' 1. optionalDependencies were disabled during install');
105
+ console.error(' 2. postinstall script failed to download the binary');
106
+ console.error('');
107
+ console.error('Try reinstalling: npm install @khanglvm/tool-hub-mcp');
108
+ console.error('Or download manually from: https://github.com/khanglvm/tool-hub-mcp/releases');
109
+ process.exit(1);
110
+ }
111
+
112
+ // Spawn the binary with all arguments passed through
113
+ const child = spawn(binaryPath, process.argv.slice(2), {
114
+ stdio: 'inherit', // Pass through stdin/stdout/stderr
115
+ shell: false
116
+ });
117
+
118
+ // Forward exit code
119
+ child.on('close', (code) => {
120
+ process.exit(code ?? 0);
121
+ });
122
+
123
+ // Handle spawn errors
124
+ child.on('error', (err) => {
125
+ console.error(`Failed to start tool-hub-mcp: ${err.message}`);
126
+ process.exit(1);
127
+ });
128
+ }
129
+
130
+ main();
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@khanglvm/tool-hub-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Serverless MCP Aggregator - Reduce AI context token consumption by 60-97%",
5
+ "bin": {
6
+ "tool-hub-mcp": "./cli.js"
7
+ },
8
+ "main": "./cli.js",
9
+ "files": [
10
+ "cli.js",
11
+ "postinstall.js"
12
+ ],
13
+ "scripts": {
14
+ "postinstall": "node postinstall.js"
15
+ },
16
+ "optionalDependencies": {
17
+ "@khanglvm/tool-hub-mcp-darwin-arm64": "1.0.0",
18
+ "@khanglvm/tool-hub-mcp-darwin-x64": "1.0.0",
19
+ "@khanglvm/tool-hub-mcp-linux-x64": "1.0.0",
20
+ "@khanglvm/tool-hub-mcp-linux-arm64": "1.0.0",
21
+ "@khanglvm/tool-hub-mcp-win32-x64": "1.0.0"
22
+ },
23
+ "keywords": [
24
+ "mcp",
25
+ "model-context-protocol",
26
+ "ai",
27
+ "claude",
28
+ "opencode",
29
+ "aggregator",
30
+ "serverless"
31
+ ],
32
+ "author": "khanglvm",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/khanglvm/tool-hub-mcp.git"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/khanglvm/tool-hub-mcp/issues"
40
+ },
41
+ "homepage": "https://github.com/khanglvm/tool-hub-mcp#readme",
42
+ "engines": {
43
+ "node": ">=18"
44
+ }
45
+ }
package/postinstall.js ADDED
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Postinstall fallback for tool-hub-mcp.
5
+ * Downloads the Go binary from GitHub Releases if optionalDependencies were not installed.
6
+ *
7
+ * This handles environments where:
8
+ * - optionalDependencies are disabled (some CI environments)
9
+ * - Package manager fails to resolve platform-specific packages
10
+ */
11
+
12
+ const https = require('https');
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const os = require('os');
16
+ const { execSync } = require('child_process');
17
+
18
+ // Configuration
19
+ const GITHUB_REPO = 'khanglvm/tool-hub-mcp';
20
+ const BINARY_NAME = 'tool-hub-mcp';
21
+
22
+ /**
23
+ * Get platform suffix for GitHub release assets.
24
+ * @returns {string} Suffix like 'Darwin-arm64' or 'Linux-x86_64'
25
+ */
26
+ function getPlatformSuffix() {
27
+ const platform = os.platform();
28
+ const arch = os.arch();
29
+
30
+ const platformMap = {
31
+ 'darwin': 'Darwin',
32
+ 'linux': 'Linux',
33
+ 'win32': 'Windows'
34
+ };
35
+
36
+ const archMap = {
37
+ 'arm64': 'arm64',
38
+ 'x64': 'x86_64'
39
+ };
40
+
41
+ const mappedPlatform = platformMap[platform];
42
+ const mappedArch = archMap[arch];
43
+
44
+ if (!mappedPlatform || !mappedArch) {
45
+ throw new Error(`Unsupported platform: ${platform}-${arch}`);
46
+ }
47
+
48
+ const suffix = `${mappedPlatform}-${mappedArch}`;
49
+ return platform === 'win32' ? `${suffix}.exe` : suffix;
50
+ }
51
+
52
+ /**
53
+ * Check if platform package binary already exists.
54
+ * @returns {boolean} True if binary found in optionalDependencies
55
+ */
56
+ function hasPlatformBinary() {
57
+ const platform = os.platform();
58
+ const arch = os.arch();
59
+
60
+ const archMap = { 'arm64': 'arm64', 'x64': 'x64' };
61
+ const platformMap = { 'darwin': 'darwin', 'linux': 'linux', 'win32': 'win32' };
62
+
63
+ const packageName = `@khanglvm/tool-hub-mcp-${platformMap[platform]}-${archMap[arch]}`;
64
+ const binaryName = platform === 'win32' ? `${BINARY_NAME}.exe` : BINARY_NAME;
65
+
66
+ const searchPaths = [
67
+ path.join(__dirname, 'node_modules', packageName, 'bin', binaryName),
68
+ path.join(__dirname, '..', packageName, 'bin', binaryName),
69
+ path.join(__dirname, '..', '..', packageName, 'bin', binaryName),
70
+ ];
71
+
72
+ return searchPaths.some(p => fs.existsSync(p));
73
+ }
74
+
75
+ /**
76
+ * Follow redirects and download file.
77
+ * @param {string} url - URL to download
78
+ * @param {string} dest - Destination file path
79
+ * @returns {Promise<void>}
80
+ */
81
+ function downloadFile(url, dest) {
82
+ return new Promise((resolve, reject) => {
83
+ const file = fs.createWriteStream(dest);
84
+
85
+ const request = (url) => {
86
+ https.get(url, (response) => {
87
+ // Handle redirects (GitHub releases use 302)
88
+ if (response.statusCode === 301 || response.statusCode === 302) {
89
+ file.close();
90
+ fs.unlinkSync(dest);
91
+ request(response.headers.location);
92
+ return;
93
+ }
94
+
95
+ if (response.statusCode !== 200) {
96
+ file.close();
97
+ fs.unlinkSync(dest);
98
+ reject(new Error(`HTTP ${response.statusCode}: Failed to download ${url}`));
99
+ return;
100
+ }
101
+
102
+ response.pipe(file);
103
+ file.on('finish', () => {
104
+ file.close();
105
+ resolve();
106
+ });
107
+ }).on('error', (err) => {
108
+ file.close();
109
+ fs.unlinkSync(dest);
110
+ reject(err);
111
+ });
112
+ };
113
+
114
+ request(url);
115
+ });
116
+ }
117
+
118
+ /**
119
+ * Get the latest release version from GitHub API.
120
+ * @returns {Promise<string>} Version tag like 'v1.0.0'
121
+ */
122
+ function getLatestVersion() {
123
+ return new Promise((resolve, reject) => {
124
+ const options = {
125
+ hostname: 'api.github.com',
126
+ path: `/repos/${GITHUB_REPO}/releases/latest`,
127
+ headers: { 'User-Agent': 'tool-hub-mcp-installer' }
128
+ };
129
+
130
+ https.get(options, (response) => {
131
+ let data = '';
132
+ response.on('data', chunk => data += chunk);
133
+ response.on('end', () => {
134
+ try {
135
+ const release = JSON.parse(data);
136
+ if (release.tag_name) {
137
+ resolve(release.tag_name);
138
+ } else {
139
+ reject(new Error('No releases found'));
140
+ }
141
+ } catch (e) {
142
+ reject(e);
143
+ }
144
+ });
145
+ }).on('error', reject);
146
+ });
147
+ }
148
+
149
+ /**
150
+ * Main postinstall logic.
151
+ */
152
+ async function main() {
153
+ // Skip if platform binary already installed via optionalDependencies
154
+ if (hasPlatformBinary()) {
155
+ console.log('tool-hub-mcp: Binary found from optionalDependencies ✓');
156
+ return;
157
+ }
158
+
159
+ console.log('tool-hub-mcp: optionalDependencies not available, downloading from GitHub...');
160
+
161
+ try {
162
+ // Get latest version
163
+ const version = await getLatestVersion();
164
+ console.log(`tool-hub-mcp: Downloading version ${version}...`);
165
+
166
+ // Prepare download
167
+ const suffix = getPlatformSuffix();
168
+ const binaryName = os.platform() === 'win32' ? `${BINARY_NAME}.exe` : BINARY_NAME;
169
+ const downloadUrl = `https://github.com/${GITHUB_REPO}/releases/download/${version}/${BINARY_NAME}-${suffix}`;
170
+
171
+ // Create bin directory
172
+ const binDir = path.join(__dirname, 'bin');
173
+ if (!fs.existsSync(binDir)) {
174
+ fs.mkdirSync(binDir, { recursive: true });
175
+ }
176
+
177
+ const destPath = path.join(binDir, binaryName);
178
+
179
+ // Download binary
180
+ await downloadFile(downloadUrl, destPath);
181
+
182
+ // Make executable (Unix only)
183
+ if (os.platform() !== 'win32') {
184
+ fs.chmodSync(destPath, 0o755);
185
+ }
186
+
187
+ console.log('tool-hub-mcp: Binary downloaded and installed ✓');
188
+
189
+ } catch (error) {
190
+ console.error(`tool-hub-mcp: Failed to download binary: ${error.message}`);
191
+ console.error('You can manually download from: https://github.com/khanglvm/tool-hub-mcp/releases');
192
+ // Don't fail the install - user can still manually download
193
+ process.exit(0);
194
+ }
195
+ }
196
+
197
+ main();