@eznix/mcp-gateway 1.3.3

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/.dockerignore ADDED
@@ -0,0 +1,8 @@
1
+ node_modules
2
+ .bun
3
+ dist
4
+ *.log
5
+ .git
6
+ .gitignore
7
+ .env
8
+ *.local
@@ -0,0 +1,51 @@
1
+ name: Docker Build & Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: read
10
+ packages: write
11
+
12
+ jobs:
13
+ docker:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Set up QEMU
20
+ uses: docker/setup-qemu-action@v3
21
+
22
+ - name: Set up Docker Buildx
23
+ uses: docker/setup-buildx-action@v3
24
+
25
+ - name: Login to GHCR
26
+ uses: docker/login-action@v3
27
+ with:
28
+ registry: ghcr.io
29
+ username: ${{ github.actor }}
30
+ password: ${{ secrets.GITHUB_TOKEN }}
31
+
32
+ - name: Extract metadata for Docker
33
+ id: meta
34
+ uses: docker/metadata-action@v5
35
+ with:
36
+ images: ghcr.io/${{ github.repository_owner }}/mcp-gateway
37
+ tags: |
38
+ type=semver,pattern={{version}}
39
+ type=semver,pattern={{major}}
40
+ type=semver,pattern={{major}}.{{minor}}
41
+
42
+ - name: Build and push Docker image
43
+ uses: docker/build-push-action@v5
44
+ with:
45
+ context: .
46
+ platforms: linux/amd64,linux/arm64
47
+ push: true
48
+ tags: ${{ steps.meta.outputs.tags }}
49
+ labels: ${{ steps.meta.outputs.labels }}
50
+ cache-from: type=gha
51
+ cache-to: type=gha,mode=max
@@ -0,0 +1,53 @@
1
+ name: Publish to npm (Bun)
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ id-token: write
13
+ contents: write
14
+ steps:
15
+ - name: Checkout repository
16
+ uses: actions/checkout@v6
17
+
18
+ - name: Setup Bun
19
+ uses: oven-sh/setup-bun@v2
20
+ with:
21
+ bun-version: latest
22
+
23
+ - name: Cache dependencies
24
+ uses: actions/cache@v5
25
+ with:
26
+ path: |
27
+ ~/.bun/install/cache
28
+ key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
29
+
30
+ - name: Install dependencies
31
+ run: bun install --frozen-lockfile
32
+
33
+ - name: Build package
34
+ run: bun run build
35
+
36
+ - name: Publish to npm
37
+ if: startsWith(github.ref, 'refs/tags/v')
38
+ env:
39
+ NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
40
+ run: bun publish --verbose -p --access public
41
+
42
+ - name: Update CHANGELOG
43
+ id: changelog
44
+ uses: requarks/changelog-action@v1
45
+ with:
46
+ token: ${{ github.token }}
47
+ tag: ${{ github.ref_name }}
48
+ useGitmojis: false
49
+
50
+ - name: Create Release
51
+ uses: softprops/action-gh-release@v2
52
+ with:
53
+ body: ${{ steps.changelog.outputs.changes }}
package/AGENTS.md ADDED
@@ -0,0 +1,111 @@
1
+ ---
2
+ description: Use Bun instead of Node.js, npm, pnpm, or vite.
3
+ globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json"
4
+ alwaysApply: false
5
+ ---
6
+
7
+ Default to using Bun instead of Node.js.
8
+
9
+ - Use `bun <file>` instead of `node <file>` or `ts-node <file>`
10
+ - Use `bun test` instead of `jest` or `vitest`
11
+ - Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
12
+ - Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
13
+ - Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
14
+ - Use `bunx <package> <command>` instead of `npx <package> <command>`
15
+ - Bun automatically loads .env, so don't use dotenv.
16
+
17
+ ## APIs
18
+
19
+ - `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
20
+ - `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
21
+ - `Bun.redis` for Redis. Don't use `ioredis`.
22
+ - `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
23
+ - `WebSocket` is built-in. Don't use `ws`.
24
+ - Prefer `Bun.file` over `node:fs`'s readFile/writeFile
25
+ - Bun.$`ls` instead of execa.
26
+
27
+ ## Testing
28
+
29
+ Use `bun test` to run tests.
30
+
31
+ ```ts#index.test.ts
32
+ import { test, expect } from "bun:test";
33
+
34
+ test("hello world", () => {
35
+ expect(1).toBe(1);
36
+ });
37
+ ```
38
+
39
+ ## Frontend
40
+
41
+ Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
42
+
43
+ Server:
44
+
45
+ ```ts#index.ts
46
+ import index from "./index.html"
47
+
48
+ Bun.serve({
49
+ routes: {
50
+ "/": index,
51
+ "/api/users/:id": {
52
+ GET: (req) => {
53
+ return new Response(JSON.stringify({ id: req.params.id }));
54
+ },
55
+ },
56
+ },
57
+ // optional websocket support
58
+ websocket: {
59
+ open: (ws) => {
60
+ ws.send("Hello, world!");
61
+ },
62
+ message: (ws, message) => {
63
+ ws.send(message);
64
+ },
65
+ close: (ws) => {
66
+ // handle close
67
+ }
68
+ },
69
+ development: {
70
+ hmr: true,
71
+ console: true,
72
+ }
73
+ })
74
+ ```
75
+
76
+ HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
77
+
78
+ ```html#index.html
79
+ <html>
80
+ <body>
81
+ <h1>Hello, world!</h1>
82
+ <script type="module" src="./frontend.tsx"></script>
83
+ </body>
84
+ </html>
85
+ ```
86
+
87
+ With the following `frontend.tsx`:
88
+
89
+ ```tsx#frontend.tsx
90
+ import React from "react";
91
+ import { createRoot } from "react-dom/client";
92
+
93
+ // import .css files directly and it works
94
+ import './index.css';
95
+
96
+ const root = createRoot(document.body);
97
+
98
+ export default function Frontend() {
99
+ return <h1>Hello, world!</h1>;
100
+ }
101
+
102
+ root.render(<Frontend />);
103
+ ```
104
+
105
+ Then, run index.ts
106
+
107
+ ```sh
108
+ bun --hot ./index.ts
109
+ ```
110
+
111
+ For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`.
package/Dockerfile ADDED
@@ -0,0 +1,22 @@
1
+ FROM oven/bun:1-alpine
2
+
3
+ RUN adduser -D -s /bin/sh gateway
4
+
5
+ WORKDIR /app
6
+
7
+ COPY package.json bun.lock ./
8
+ RUN bun install
9
+
10
+ COPY . .
11
+
12
+ RUN bun build src/docker.ts --target=bun --outfile=/app/gateway
13
+
14
+ # Create config directory for non-root user
15
+ RUN mkdir -p /home/gateway/.config/mcp-gateway && chown -R gateway:gateway /home/gateway
16
+
17
+ # Use non-root user
18
+ USER gateway
19
+
20
+ EXPOSE 3000
21
+
22
+ CMD ["/app/gateway"]
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Bruno Bernard
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,292 @@
1
+ # MCP Gateway
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@eznix/mcp-gateway)](https://www.npmjs.com/package/@eznix/mcp-gateway)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@eznix/mcp-gateway)](https://www.npmjs.com/package/@eznix/mcp-gateway)
5
+ [![License](https://img.shields.io/npm/l/@eznix/mcp-gateway)](LICENSE)
6
+ [![GitHub stars](https://img.shields.io/github/stars/eznix86/mcp-gateway)](https://github.com/eznix86/mcp-gateway/stargazers)
7
+
8
+ MCP Gateway is a server aggregation tool that connects multiple Model Context Protocol (MCP) servers into a single gateway, exposing all tools from connected servers through unified search, describe, and invoke interfaces and it exposes only 5 tools.
9
+
10
+ ## The Context Limit Problem
11
+
12
+ When connecting an client (Claude Code, Opencode, etc.) to multiple MCP servers, each server lists all its tools. With 10+ MCPs each exposing 10-50 tools, you can easily exceed 500+ tool descriptions in the system prompt:
13
+
14
+ ```
15
+ 10 servers × 20 tools each = 200+ tool descriptions
16
+ Each tool: 200-500 chars → 40KB-100KB of description just for tool schemas!
17
+ ```
18
+
19
+ This creates two problems:
20
+ 1. **Context overflow**: Many LLMs hit their context limit before any conversation happens
21
+ 2. **Cognitive overload**: LLMs struggle to choose the right tool from hundreds of options
22
+
23
+ ## The Gateway Solution
24
+
25
+ MCP Gateway solves this by providing **tool search** instead of dumping all tool schemas:
26
+
27
+ ```
28
+ ┌─────────────┐ gateway.search ┌─────────────────┐ kubernetes::pods_list ┌──────────────────┐
29
+ │ AI Client │ ───────────────────► │ MCP Gateway │ ─────────────────────────► │ Kubernetes MCP │
30
+ │ │ │ │ │ │
31
+ │ │ ◄────────────────────│ │ ◄───────────────────────── │ │
32
+ └─────────────┘ pods_list schema └─────────────────┘ pods output └──────────────────┘
33
+ ```
34
+
35
+ ## How It Works
36
+
37
+ MCP Gateway operates as both an MCP client (connecting to upstream servers) and an MCP server (exposing tools to downstream clients):
38
+
39
+ ```
40
+ ┌──────────────┐ MCP ┌─────────────────┐ MCP ┌──────────────────┐
41
+ │ AI Client │ ◄──────────── │ MCP Gateway │ ◄──────────── │ Upstream Server │
42
+ │ (Claude, etc)│ │ (this gateway) │ │ (playwright, │
43
+ └──────────────┘ └─────────────────┘ │ kubernetes...) │
44
+ └──────────────────┘
45
+ ```
46
+
47
+ 1. Gateway starts and reads configuration
48
+ 2. For each configured upstream server, Gateway connects via stdio (local) or HTTP/WebSocket (remote)
49
+ 3. Gateway fetches the tool catalog from each server
50
+ 4. All tools are indexed in a unified catalog with search capabilities
51
+ 5. AI clients connect to Gateway and use `gateway.search` to find relevant tools
52
+ 6. Only the tools the client actually needs are invoked
53
+
54
+ You will notice around ~40% reduction of initial token used.
55
+
56
+ ## Installation
57
+
58
+ ### Claude Code
59
+
60
+ Add to your Claude MCP configuration:
61
+
62
+ ```json
63
+ {
64
+ "mcpServers": {
65
+ "gateway": {
66
+ "command": "bunx",
67
+ "args": ["@eznix/mcp-gateway@latest"]
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ ### OpenCode
74
+
75
+ Add to your OpenCode MCP configuration:
76
+
77
+ ```json
78
+ {
79
+ "$schema": "https://opencode.ai/config.json",
80
+ "mcp": {
81
+ "mcp-gateway": {
82
+ "type": "local",
83
+ "command": ["bunx", "@eznix/mcp-gateway@latest"]
84
+ },
85
+ }
86
+ }
87
+ ```
88
+
89
+ You may append your global AGENTS.md (`~/.config/opencode/AGENTS.md`) with this [template](./templates/AGENTS.md).
90
+
91
+ You may now just simple enumerate the MCP available.
92
+
93
+ ## Configuration
94
+
95
+ MCP Gateway reads configuration from a JSON file. By default, it looks for:
96
+
97
+ 1. Path provided as first command-line argument
98
+ 2. `MCP_GATEWAY_CONFIG` environment variable
99
+ 3. `~/.config/mcp-gateway/config.json`
100
+
101
+ ### Configuration Format
102
+
103
+ ```json
104
+ {
105
+ "local-server": {
106
+ "type": "local",
107
+ "command": ["bun", "run", "/path/to/server.ts"],
108
+ },
109
+ "remote-server": {
110
+ "type": "remote",
111
+ "url": "https://mcp.example.com",
112
+ "enabled": true
113
+ },
114
+ "websocket-server": {
115
+ "type": "remote",
116
+ "url": "wss://mcp.example.com/ws",
117
+ "enabled": true
118
+ }
119
+ }
120
+ ```
121
+
122
+ Each entry specifies:
123
+ - `type`: `"local"` or `"remote"`
124
+ - `command` (local only): Array with command and arguments to spawn the upstream server
125
+ - `url` (remote only): Full URL of the remote MCP server
126
+ - `transport` (optional, remote only): Override transport detection (`"streamable_http"` or `"websocket"`). Usually auto-detected from URL protocol.
127
+ - `enabled`: Set to false to skip connecting to this server
128
+
129
+ ### Docker
130
+
131
+ Run MCP Gateway in Docker with HTTP transport:
132
+
133
+ ```bash
134
+ # Build the image
135
+ docker build -t mcp-gateway .
136
+
137
+ # Run with config mounted
138
+ docker run -p 3000:3000 \
139
+ -v ./examples/config.json:/home/gateway/.config/mcp-gateway/config.json:ro \
140
+ mcp-gateway
141
+ ```
142
+
143
+ **Endpoints:**
144
+
145
+ | Endpoint | Description |
146
+ |----------|-------------|
147
+ | `GET /` | Gateway info and endpoints |
148
+ | `GET /health` | Health check |
149
+ | `/mcp` | MCP protocol endpoint |
150
+
151
+ **Example:**
152
+
153
+ ```bash
154
+ curl http://localhost:3000/
155
+ # {"name":"MCP Gateway",...,"endpoints":{"mcp":"/mcp","health":"/health"}}
156
+
157
+ curl http://localhost:3000/health
158
+ # {"status":"ok"}
159
+ ```
160
+
161
+ ### Remote Server Configuration
162
+
163
+ Remote servers are auto-detected based on the URL protocol:
164
+ - `http://` or `https://` → Streamable HTTP (recommended)
165
+ - `ws://` or `wss://` → WebSocket
166
+
167
+ ```json
168
+ {
169
+ "gh-grep": {
170
+ "type": "remote",
171
+ "url": "https://mcp.grep.app"
172
+ },
173
+ "custom-websocket": {
174
+ "type": "remote",
175
+ "url": "wss://my-server.com/mcp"
176
+ }
177
+ }
178
+ ```
179
+
180
+ ## Available Tools
181
+
182
+ ### `gateway.search`
183
+
184
+ Search for tools across all connected servers.
185
+
186
+ ```typescript
187
+ {
188
+ query: "kubernetes pods",
189
+ limit: 10, // optional, max 50
190
+ filters: {
191
+ server: "kubernetes", // optional, filter by server name
192
+ }
193
+ }
194
+ ```
195
+
196
+ Returns matching tools with relevance scores. Tools matching in name are boosted.
197
+
198
+ ### `gateway.describe`
199
+
200
+ Get detailed information about a specific tool.
201
+
202
+ ```typescript
203
+ {
204
+ id: "kubernetes::pods_list" // format: serverKey::toolName
205
+ }
206
+ ```
207
+
208
+ Returns the full tool schema including inputSchema.
209
+
210
+ ### `gateway.invoke`
211
+
212
+ Execute a tool synchronously and get immediate results.
213
+
214
+ ```typescript
215
+ {
216
+ id: "kubernetes::pods_list",
217
+ args: { namespace: "default" },
218
+ timeoutMs: 30000 // optional, default 30 seconds
219
+ }
220
+ ```
221
+
222
+ ### `gateway.invoke_async`
223
+
224
+ Start an asynchronous tool execution. Returns a job ID for polling.
225
+
226
+ ```typescript
227
+ {
228
+ id: "some-server::long-running-tool",
229
+ args: { ... },
230
+ priority: 10, // optional, higher values run first
231
+ timeoutMs: 60000
232
+ }
233
+ ```
234
+
235
+ ### `gateway.invoke_status`
236
+
237
+ Check the status of an async job.
238
+
239
+ ```typescript
240
+ {
241
+ jobId: "job_123456789_abc123"
242
+ }
243
+ ```
244
+
245
+ ## Tool ID Format
246
+
247
+ All gateway tools use the format `serverKey::toolName` to identify tools:
248
+
249
+ ```
250
+ kubernetes::pods_list
251
+ playwright::browser_navigate
252
+ github::create_issue
253
+ ```
254
+
255
+ The `serverKey` is the key name in your configuration file.
256
+
257
+ ## Architecture
258
+
259
+ ### Components
260
+
261
+ - **MCPGateway class**: Main orchestrator
262
+ - **Upstream connection manager**: Manages connections to MCP servers (stdio for local, HTTP/WebSocket for remote)
263
+ - **Tool catalog**: In-memory index of all available tools with metadata
264
+ - **Job queue**: Handles async tool invocations with priority ordering and concurrency limits (max 3 concurrent by default)
265
+ - **Search engine**: MiniSearch with BM25 scoring and fuzzy matching
266
+
267
+ ### Search Algorithm
268
+
269
+ The search uses [MiniSearch](https://github.com/lucaong/minisearch) with BM25 ranking:
270
+
271
+ - **BM25 scoring**: Relevance algorithm
272
+ - **Field boosting**: Name matches (3x), title matches (2x), description/server matches (1x)
273
+ - **Fuzzy matching**: Handles typos with 0.2 threshold (e.g., "kubenetes" → finds "kubernetes")
274
+ - **Prefix search**: Partial word matching (e.g., "pod" matches "pods_list")
275
+
276
+ ### Contributing
277
+
278
+ ```bash
279
+ git clone https://github.com/eznix86/mcp-gateway.git
280
+ cd mcp-gateway
281
+ bun install
282
+
283
+ # Run locally (stdio transport)
284
+ bun run index.ts
285
+
286
+ # Run with Docker (HTTP transport on port 3000)
287
+ bun run docker:build && bun run docker:run
288
+ ```
289
+
290
+ ## License
291
+
292
+ MIT License. See the [LICENSE](LICENSE).