@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 +8 -0
- package/.github/workflows/docker.yml +51 -0
- package/.github/workflows/npm.yml +53 -0
- package/AGENTS.md +111 -0
- package/Dockerfile +22 -0
- package/LICENSE +21 -0
- package/README.md +292 -0
- package/dist/index.js +26252 -0
- package/examples/README.md +34 -0
- package/examples/config.json +10 -0
- package/package.json +30 -0
- package/src/config.ts +63 -0
- package/src/connections.ts +116 -0
- package/src/docker.ts +64 -0
- package/src/gateway.ts +114 -0
- package/src/handlers.ts +113 -0
- package/src/index.ts +9 -0
- package/src/jobs.ts +74 -0
- package/src/search.ts +94 -0
- package/src/types.ts +51 -0
- package/templates/AGENTS.md +187 -0
- package/tsconfig.json +29 -0
package/src/search.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import MiniSearch from "minisearch";
|
|
2
|
+
import type { ToolCatalogEntry, SearchFilters, SearchResult } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export class SearchEngine {
|
|
5
|
+
private miniSearch: MiniSearch<ToolCatalogEntry> | null = null;
|
|
6
|
+
private catalog: Map<string, ToolCatalogEntry> = new Map();
|
|
7
|
+
private indexDirty = true;
|
|
8
|
+
|
|
9
|
+
constructor() {}
|
|
10
|
+
|
|
11
|
+
updateCatalog(tools: ToolCatalogEntry[]) {
|
|
12
|
+
this.catalog.clear();
|
|
13
|
+
for (const tool of tools) {
|
|
14
|
+
this.catalog.set(tool.id, tool);
|
|
15
|
+
}
|
|
16
|
+
this.indexDirty = true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
addTool(tool: ToolCatalogEntry) {
|
|
20
|
+
this.catalog.set(tool.id, tool);
|
|
21
|
+
this.indexDirty = true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
removeTool(id: string) {
|
|
25
|
+
this.catalog.delete(id);
|
|
26
|
+
this.indexDirty = true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getTools(): ToolCatalogEntry[] {
|
|
30
|
+
return Array.from(this.catalog.values());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getTool(id: string): ToolCatalogEntry | undefined {
|
|
34
|
+
return this.catalog.get(id);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private ensureIndex() {
|
|
38
|
+
if (!this.indexDirty && this.miniSearch) return;
|
|
39
|
+
|
|
40
|
+
const tools = Array.from(this.catalog.values());
|
|
41
|
+
|
|
42
|
+
if (tools.length === 0) {
|
|
43
|
+
this.miniSearch = null;
|
|
44
|
+
this.indexDirty = false;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.miniSearch = new MiniSearch<ToolCatalogEntry>({
|
|
49
|
+
fields: ["name", "title", "description", "server"],
|
|
50
|
+
storeFields: ["id", "server", "name", "title", "description", "inputSchema", "outputSchema"],
|
|
51
|
+
searchOptions: {
|
|
52
|
+
boost: { name: 3, title: 2 },
|
|
53
|
+
fuzzy: 0.2,
|
|
54
|
+
prefix: true,
|
|
55
|
+
combineWith: "OR",
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
this.miniSearch.addAll(tools);
|
|
60
|
+
this.indexDirty = false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
search(query: string, filters: SearchFilters = {}, limit = 50): SearchResult[] {
|
|
64
|
+
this.ensureIndex();
|
|
65
|
+
|
|
66
|
+
if (!this.miniSearch || !query.trim()) {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const maxLimit = Math.min(limit, 50);
|
|
71
|
+
const results = this.miniSearch.search(query.toLowerCase()).slice(0, 100);
|
|
72
|
+
|
|
73
|
+
const filtered = results
|
|
74
|
+
.filter((result) => {
|
|
75
|
+
if (filters.server && result.server !== filters.server) return false;
|
|
76
|
+
return true;
|
|
77
|
+
})
|
|
78
|
+
.map((result) => ({
|
|
79
|
+
id: result.id,
|
|
80
|
+
server: result.server,
|
|
81
|
+
name: result.name,
|
|
82
|
+
description: result.description,
|
|
83
|
+
score: result.score || 0,
|
|
84
|
+
}))
|
|
85
|
+
.sort((a, b) => b.score - a.score)
|
|
86
|
+
.slice(0, maxLimit);
|
|
87
|
+
|
|
88
|
+
return filtered;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
warmup() {
|
|
92
|
+
this.ensureIndex();
|
|
93
|
+
}
|
|
94
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface UpstreamConfig {
|
|
2
|
+
type: "local" | "remote";
|
|
3
|
+
command?: string[];
|
|
4
|
+
url?: string;
|
|
5
|
+
transport?: "streamable_http" | "websocket";
|
|
6
|
+
endpoint?: string;
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
lazy?: boolean; // if true, only connect on first request
|
|
9
|
+
idleTimeout?: number; // milliseconds before sleeping (default: 2hrs)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface GatewayConfig {
|
|
13
|
+
[serverKey: string]: UpstreamConfig;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ToolCatalogEntry {
|
|
17
|
+
id: string;
|
|
18
|
+
server: string;
|
|
19
|
+
name: string;
|
|
20
|
+
title?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
inputSchema?: any;
|
|
23
|
+
outputSchema?: any;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SearchFilters {
|
|
27
|
+
server?: string;
|
|
28
|
+
tags?: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface JobRecord {
|
|
32
|
+
id: string;
|
|
33
|
+
status: "queued" | "running" | "completed" | "failed";
|
|
34
|
+
toolId: string;
|
|
35
|
+
args: any;
|
|
36
|
+
priority?: number;
|
|
37
|
+
createdAt: number;
|
|
38
|
+
startedAt?: number;
|
|
39
|
+
finishedAt?: number;
|
|
40
|
+
result?: any;
|
|
41
|
+
error?: string;
|
|
42
|
+
logs: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface SearchResult {
|
|
46
|
+
id: string;
|
|
47
|
+
server: string;
|
|
48
|
+
name: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
score: number;
|
|
51
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# MCP Gateway System Guide
|
|
2
|
+
|
|
3
|
+
You are connected to an MCP Gateway that provides unified access to multiple tool servers. Follow these patterns to efficiently discover and use tools without hitting context limits.
|
|
4
|
+
|
|
5
|
+
## Core Principle
|
|
6
|
+
|
|
7
|
+
**NEVER list all available tools.** The gateway exposes tools from multiple servers (50+ total). Instead, use search-describe-invoke workflow.
|
|
8
|
+
|
|
9
|
+
## Available Gateway Tools
|
|
10
|
+
|
|
11
|
+
The gateway provides these tools for tool discovery and execution:
|
|
12
|
+
|
|
13
|
+
| Tool | Purpose |
|
|
14
|
+
|------|---------|
|
|
15
|
+
| `gateway.search` | Find relevant tools with BM25 scoring and fuzzy matching |
|
|
16
|
+
| `gateway.describe` | Get full schema for a specific tool |
|
|
17
|
+
| `gateway.invoke` | Execute a tool synchronously |
|
|
18
|
+
| `gateway.invoke_async` | Execute with job queue for long-running operations |
|
|
19
|
+
| `gateway.invoke_status` | Check async job status |
|
|
20
|
+
|
|
21
|
+
## Three-Step Workflow
|
|
22
|
+
|
|
23
|
+
### 1. Search for Tools
|
|
24
|
+
|
|
25
|
+
Use `gateway.search` to find what you need:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"query": "kubernetes pods list",
|
|
30
|
+
"limit": 5,
|
|
31
|
+
"filters": { "server": "kubernetes" }
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Search Features:**
|
|
36
|
+
- **BM25 scoring**: Exact matches get highest scores
|
|
37
|
+
- **Fuzzy matching**: Handles typos ("kubenetes" → finds kubernetes)
|
|
38
|
+
- **Prefix search**: "pod" matches "pods_list"
|
|
39
|
+
- **Field boosting**: Name matches (3x), title matches (2x)
|
|
40
|
+
|
|
41
|
+
**Search Examples:**
|
|
42
|
+
- Kubernetes resources? → `"kubernetes pods list"`
|
|
43
|
+
- GitHub issues? → `"github issue create"`
|
|
44
|
+
- Browser automation? → `"browser navigate url"`
|
|
45
|
+
- Documentation lookup? → `"context7 react hooks"`
|
|
46
|
+
|
|
47
|
+
### 2. Describe Tool Schema
|
|
48
|
+
|
|
49
|
+
Use `gateway.describe` with the tool ID from search results:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"id": "kubernetes::pods_list"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This returns the complete schema including all parameters, types, and descriptions.
|
|
58
|
+
|
|
59
|
+
### 3. Invoke the Tool
|
|
60
|
+
|
|
61
|
+
Use `gateway.invoke` or `gateway.invoke_async`:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"id": "kubernetes::pods_list",
|
|
66
|
+
"args": { "namespace": "default" },
|
|
67
|
+
"timeoutMs": 30000
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
For long-running operations:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"id": "some-server::long-running-task",
|
|
76
|
+
"args": { "param": "value" },
|
|
77
|
+
"priority": 10,
|
|
78
|
+
"timeoutMs": 60000
|
|
79
|
+
}
|
|
80
|
+
// Returns: { "jobId": "job_123456789_abc123" }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Check status with `gateway.invoke_status`:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"jobId": "job_123456789_abc123"
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Tool ID Format
|
|
92
|
+
|
|
93
|
+
All tools use `serverKey::toolName` format:
|
|
94
|
+
- `kubernetes::pods_list`
|
|
95
|
+
- `playwright::browser_navigate`
|
|
96
|
+
- `github::search_code`
|
|
97
|
+
- `server-name::tool_name`
|
|
98
|
+
|
|
99
|
+
The `serverKey` is defined in the gateway configuration, not the original server name. Available servers depend on the gateway configuration.
|
|
100
|
+
|
|
101
|
+
## Common Patterns
|
|
102
|
+
|
|
103
|
+
### Pattern: Quick Single Tool Use
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
1. Search: "slack send message" (limit: 3)
|
|
107
|
+
2. Describe: "slack::post_message"
|
|
108
|
+
3. Invoke: {"channel": "#general", "text": "Hello"}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Pattern: Exploring Related Tools
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
1. Search: "kubernetes pods" (limit: 10)
|
|
115
|
+
2. Review results, identify the one you need
|
|
116
|
+
3. Describe: "kubernetes::pods_list"
|
|
117
|
+
4. Invoke: with required args
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Pattern: Multi-Step Workflow
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
1. Search: "github repo list" (limit: 5)
|
|
124
|
+
2. Invoke: list_repos, note repo name
|
|
125
|
+
3. Search: "github issue create" (limit: 5)
|
|
126
|
+
4. Invoke: create_issue using repo from step 2
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Pattern: Long-Running Operation
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
1. Invoke async: {"priority": 10}
|
|
133
|
+
2. Response: {"jobId": "job_123456789_abc123"}
|
|
134
|
+
3. Poll: gateway.invoke_status until completed
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Best Practices
|
|
138
|
+
|
|
139
|
+
**DO:**
|
|
140
|
+
- Start every task by searching for relevant tools
|
|
141
|
+
- Use `limit` (typically 3-10) to control results
|
|
142
|
+
- Describe tools before invoking to verify parameters
|
|
143
|
+
- Use specific, action-oriented search terms
|
|
144
|
+
- Filter by `server` when you know the source
|
|
145
|
+
|
|
146
|
+
**DON'T:**
|
|
147
|
+
- Never search for generic terms - be specific ("kubernetes pods" not "kubernetes")
|
|
148
|
+
- Don't guess tool parameters - always describe first
|
|
149
|
+
- Don't invoke without confirming the schema
|
|
150
|
+
- There's no `list_tools` tool - use search instead
|
|
151
|
+
|
|
152
|
+
## Search Tips
|
|
153
|
+
|
|
154
|
+
| Task | Bad Search | Good Search |
|
|
155
|
+
|------|-----------|-------------|
|
|
156
|
+
| List pods | "kubernetes" | "kubernetes pods list" |
|
|
157
|
+
| Create issue | "github" | "github issue create" |
|
|
158
|
+
| Navigate URL | "browser" | "browser navigate url" |
|
|
159
|
+
| Get component | "button" | "component button" |
|
|
160
|
+
|
|
161
|
+
## Timeout Guidelines
|
|
162
|
+
|
|
163
|
+
- Default: 30000ms (30 seconds)
|
|
164
|
+
- Quick ops (list, get): 10000ms
|
|
165
|
+
- Long ops (deploy, build): 60000ms+
|
|
166
|
+
- Async jobs: no timeout, poll with `invoke_status`
|
|
167
|
+
|
|
168
|
+
## Error Recovery
|
|
169
|
+
|
|
170
|
+
If a tool invocation fails:
|
|
171
|
+
1. Describe the tool again to verify parameters
|
|
172
|
+
2. Check error message for missing/invalid arguments
|
|
173
|
+
3. Search for alternative tools if needed
|
|
174
|
+
4. Adjust timeout if operation timed out
|
|
175
|
+
5. For async jobs, check status for progress
|
|
176
|
+
|
|
177
|
+
## Remember
|
|
178
|
+
|
|
179
|
+
- You only need 1-3 tools for most tasks
|
|
180
|
+
- Search results are ranked by relevance (BM25)
|
|
181
|
+
- Tool IDs use `serverKey::toolName` format
|
|
182
|
+
- Always describe before invoking
|
|
183
|
+
- Use reasonable limits (3-10 results)
|
|
184
|
+
- The gateway handles authentication and routing to upstream servers
|
|
185
|
+
- Fuzzy matching handles typos automatically
|
|
186
|
+
|
|
187
|
+
This approach keeps your context focused on the specific tools needed for the current task.
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedIndexedAccess": true,
|
|
22
|
+
"noImplicitOverride": true,
|
|
23
|
+
|
|
24
|
+
// Some stricter flags (disabled by default)
|
|
25
|
+
"noUnusedLocals": false,
|
|
26
|
+
"noUnusedParameters": false,
|
|
27
|
+
"noPropertyAccessFromIndexSignature": false
|
|
28
|
+
}
|
|
29
|
+
}
|