@winstonsayno/mcp-gateway 2.3.0 → 2.5.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/CHANGELOG.md CHANGED
@@ -9,6 +9,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [2.5.0] - 2026-10-07
13
+
14
+ Usage quotas and metering export.
15
+
16
+ ### Added
17
+ - **`quotas.rules`**: tool-call limits per `hour` / `day` / `month` (UTC calendar periods), per client (default, with
18
+ `clients` globs) or per tenant (`per: tenant`, shared pool), optionally for some `servers` / `tools`. Over quota →
19
+ `-32007`; REST `429` + `Retry-After` + quota details. Checked after the policy, before cache and upstream.
20
+ - **Metering**: every tool call is counted in hourly buckets per client, tenant, server and tool (calls, errors,
21
+ duration). `GET /api/v1/usage` exports JSON or CSV (`format=csv`, formula-injection safe) grouped by `client`,
22
+ `tenant`, `server`, `tool`, `hour`, `day`, with `since` / `until` / `client` / `tenant` / `server` filters;
23
+ `quotas.meteringRetentionDays` (35).
24
+ - `GET /api/v1/quotas` — live counters with `resetsAt`. Tenant admins / owners can read their own tenant's usage.
25
+ - Exports: `UsageMeter`, `usageCsv`, `periodBounds`, `ERR_QUOTA_EXCEEDED`.
26
+
27
+ ## [2.4.0] - 2026-10-07
28
+
29
+ Upstream catalog / registry with one-click add.
30
+
31
+ ### Added
32
+ - **`catalog:`** — built-in entries for the reference MCP servers (filesystem, memory, everything,
33
+ sequential-thinking, fetch, git, time, GitHub remote) plus `sources` (JSON files or URLs) merged by id.
34
+ - **One-click install** (`catalog.install: true`, off by default): `POST /api/v1/catalog/:id/install` with `serverId`,
35
+ `env` (process env for stdio, `${VAR}` substitution for remote `url` / `headers`) and `args`; validation of required
36
+ inputs; `409` on id clashes. `DELETE /api/v1/catalog/servers/:id` removes it. Operator keys only.
37
+ - `catalog.serversFile` persists installed servers (atomic write, mode 600) and loads them on start; they survive hot
38
+ reloads; config-file ids win on clashes.
39
+ - **Dashboard**: *Add a server* card on the Servers tab (prompts for id, env and args; shows where an entry is
40
+ installed), also in the demo (read-only).
41
+ - Exports: `Catalog`, `InstalledServers`, `BUILTIN_CATALOG`, `buildServerConfig`, `loadCatalogSource`.
42
+
12
43
  ## [2.3.0] - 2026-10-07
13
44
 
14
45
  Tenants / workspaces with role-based access control.
package/README.md CHANGED
@@ -579,6 +579,24 @@ await gateway.start();
579
579
  process.on('SIGTERM', () => gateway.stop());
580
580
  ```
581
581
 
582
+ ## What's New in v2.5
583
+
584
+ | Feature | Description |
585
+ |---------|-------------|
586
+ | **Usage quotas** | per key or per tenant, hour / day / month, scoped by server / tool — `429` + `Retry-After` |
587
+ | **Metering export** | `GET /api/v1/usage` as JSON or CSV, grouped by key / tenant / server / tool / hour / day |
588
+
589
+ Details: [CHANGELOG](CHANGELOG.md).
590
+
591
+ ## What's New in v2.4
592
+
593
+ | Feature | Description |
594
+ |---------|-------------|
595
+ | **Upstream catalog** | built-in reference servers + your own JSON catalogs (`catalog.sources`) |
596
+ | **One-click add** | dashboard *Add a server* / `POST /api/v1/catalog/:id/install`, optional persistence (`serversFile`) |
597
+
598
+ Details: [CHANGELOG](CHANGELOG.md).
599
+
582
600
  ## What's New in v2.3
583
601
 
584
602
  | Feature | Description |
@@ -460,6 +460,10 @@ tbody tr:hover { background: var(--surface-2); }
460
460
  <div class="page-head"><div><h1 data-i18n="srvTitle">Servers & tools</h1><p data-i18n="srvSub">Upstream MCP servers behind this gateway</p></div>
461
461
  <button class="btn sm" id="srvRefresh" type="button"><svg class="spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3-6.7"/></svg><span data-i18n="refresh">Refresh</span></button></div>
462
462
  <div class="grid servers stagger" id="servers"></div>
463
+ <div class="card" id="catalogCard" hidden style="margin-top:14px">
464
+ <h2><span data-i18n="catalog">Add a server</span><span class="right muted" id="catalogNote"></span></h2>
465
+ <ul class="stream" id="catalog"></ul>
466
+ </div>
463
467
  </section>
464
468
 
465
469
  <!-- Playground -->
@@ -573,7 +577,7 @@ const I18N = {
573
577
  remember: 'Remember on this device', saveConnect: 'Save & connect', clear: 'Clear', close: 'Close',
574
578
  tabOverview: 'Overview', tabServers: 'Servers', tabPlayground: 'Playground', tabHistory: 'History', tabConnect: 'Connect',
575
579
  ovTitle: 'Overview', ovSub: 'Live traffic through the gateway', window: 'Time window',
576
- reqRate: 'Request rate', requests: 'Requests', errors: 'Errors', latency: 'Latency', topTools: 'Top tools', perKey: 'Usage per key', perServer: 'Calls per server', approvals: 'Pending approvals', tenants: 'Workspaces', role: 'Role', members: 'members', roleSaved: 'Role updated', approve: 'Approve', deny: 'Deny', approved: 'Approved', denied: 'Denied', expires: 'expires',
580
+ reqRate: 'Request rate', requests: 'Requests', errors: 'Errors', latency: 'Latency', topTools: 'Top tools', perKey: 'Usage per key', perServer: 'Calls per server', approvals: 'Pending approvals', tenants: 'Workspaces', catalog: 'Add a server', add: 'Add', added: 'Server added', installOff: 'read-only (catalog.install is off)', installedAs: 'installed as', role: 'Role', members: 'members', roleSaved: 'Role updated', approve: 'Approve', deny: 'Deny', approved: 'Approved', denied: 'Denied', expires: 'expires',
577
581
  errRate: 'Error rate', liveStream: 'Live requests', pause: 'Pause', health: 'Server health',
578
582
  kRpm: 'Requests / min', kP50: 'p50 latency', kP95: 'p95 latency', kErr: 'Error rate', kSrv: 'Servers online',
579
583
  kTotal: '{n} in window', kP99: 'p99 {v}', kErrN: '{n} failed', kTools: '{n} tools',
@@ -618,7 +622,7 @@ const I18N = {
618
622
  remember: '在此设备上记住', saveConnect: '保存并连接', clear: '清除', close: '关闭',
619
623
  tabOverview: '总览', tabServers: '服务器', tabPlayground: '调试台', tabHistory: '历史', tabConnect: '接入',
620
624
  ovTitle: '总览', ovSub: '网关实时流量', window: '时间范围',
621
- reqRate: '请求速率', requests: '请求', errors: '错误', latency: '延迟', topTools: '热门工具', perKey: '各密钥用量', perServer: '各服务器调用', approvals: '待审批调用', tenants: '工作区', role: '角色', members: '成员', roleSaved: '角色已更新', approve: '批准', deny: '拒绝', approved: '已批准', denied: '已拒绝', expires: '到期',
625
+ reqRate: '请求速率', requests: '请求', errors: '错误', latency: '延迟', topTools: '热门工具', perKey: '各密钥用量', perServer: '各服务器调用', approvals: '待审批调用', tenants: '工作区', catalog: '添加服务器', add: '添加', added: '已添加服务器', installOff: '只读(未开启 catalog.install)', installedAs: '已安装为', role: '角色', members: '成员', roleSaved: '角色已更新', approve: '批准', deny: '拒绝', approved: '已批准', denied: '已拒绝', expires: '到期',
622
626
  errRate: '错误率', liveStream: '实时请求', pause: '暂停', health: '服务器健康',
623
627
  kRpm: '每分钟请求', kP50: 'p50 延迟', kP95: 'p95 延迟', kErr: '错误率', kSrv: '在线服务器',
624
628
  kTotal: '窗口内 {n} 次', kP99: 'p99 {v}', kErrN: '{n} 次失败', kTools: '{n} 个工具',
@@ -1000,6 +1004,41 @@ document.addEventListener('change', async (e) => {
1000
1004
  loadTenants();
1001
1005
  });
1002
1006
 
1007
+ // Upstream catalog: one-click add of well-known MCP servers (operators; needs catalog.install: true).
1008
+ async function loadCatalog() {
1009
+ const card = $('#catalogCard');
1010
+ let r;
1011
+ try { r = await api('/catalog'); } catch { card.hidden = true; return; }
1012
+ const list = r.entries || [];
1013
+ card.hidden = list.length === 0;
1014
+ state.catalog = r;
1015
+ $('#catalogNote').textContent = r.install ? '' : t('installOff');
1016
+ $('#catalog').innerHTML = list.map((e) => `<li><div style="flex:1;min-width:0"><b>${esc(e.name)}</b> <span class="badge">${esc(e.template.transport)}</span>
1017
+ ${(e.installed || []).length ? `<span class="badge ok">${esc(t('installedAs'))} ${esc(e.installed.join(', '))}</span>` : ''}
1018
+ <div class="muted" style="font-size:12.5px">${esc(e.description || '')}${e.homepage ? ` · <a href="${esc(e.homepage)}" target="_blank" rel="noopener noreferrer">docs</a>` : ''}</div></div>
1019
+ ${r.install ? `<button class="btn sm primary" type="button" data-install="${esc(e.id)}">${esc(t('add'))}</button>` : ''}</li>`).join('');
1020
+ }
1021
+ document.addEventListener('click', async (e) => {
1022
+ const b = e.target.closest && e.target.closest('[data-install]');
1023
+ if (!b) return;
1024
+ const entry = (state.catalog?.entries || []).find((x) => x.id === b.dataset.install);
1025
+ if (!entry) return;
1026
+ const serverId = prompt('Server id', entry.installed && entry.installed.length ? `${entry.id}-2` : entry.id);
1027
+ if (!serverId) return;
1028
+ const env = {};
1029
+ for (const v of entry.env || []) { const val = prompt(`${v.name}${v.description ? ` — ${v.description}` : ''}`, ''); if (val === null) return; if (val) env[v.name] = val; }
1030
+ const args = [];
1031
+ for (const a of entry.args || []) { const val = prompt(`${a.name}${a.description ? ` — ${a.description}` : ''}`, a.default || ''); if (val === null) return; args.push(val); }
1032
+ b.classList.add('loading');
1033
+ try {
1034
+ await api(`/catalog/${encodeURIComponent(entry.id)}/install`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ serverId, env, args }) });
1035
+ toast(t('added'));
1036
+ loadServers();
1037
+ } catch (err) { toast(err.message || String(err)); }
1038
+ b.classList.remove('loading');
1039
+ loadCatalog();
1040
+ });
1041
+
1003
1042
  async function loadServers() {
1004
1043
  const btn = $('#srvRefresh'); btn.classList.add('loading');
1005
1044
  try {
@@ -1678,6 +1717,7 @@ reloadAll().then(() => {
1678
1717
  // Refresh the slower-moving pieces in the background.
1679
1718
  setInterval(() => { if (!document.hidden) loadApprovals(); }, 3000);
1680
1719
  loadTenants();
1720
+ loadCatalog();
1681
1721
  setInterval(() => { if (!document.hidden) loadTenants(); }, 30000);
1682
1722
  setInterval(() => { if (!document.hidden) { loadServers(); if (state.live === 'live') loadStats().catch(() => {}); } }, 10000);
1683
1723
  document.addEventListener('visibilitychange', () => { if (!document.hidden && state.live !== 'live') startLive(); });
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Upstream catalog / registry: templates for well-known MCP servers that an
3
+ * operator can add with one click (dashboard) or one request
4
+ * (`POST /api/v1/catalog/:id/install`).
5
+ *
6
+ * The built-in entries cover the reference servers; `catalog.sources` adds
7
+ * entries from JSON files or URLs (`{ "entries": [...] }` or an array).
8
+ * Installing is off unless `catalog.install: true` — it spawns processes.
9
+ * Installed servers are kept in `catalog.serversFile` (JSON) when set, and
10
+ * loaded again on start.
11
+ *
12
+ * @module catalog
13
+ */
14
+ import type { CatalogConfig, McpServerConfig } from '../utils/types.js';
15
+ export interface CatalogEnvVar {
16
+ name: string;
17
+ description?: string;
18
+ required?: boolean;
19
+ secret?: boolean;
20
+ }
21
+ export interface CatalogEntry {
22
+ id: string;
23
+ name: string;
24
+ description: string;
25
+ homepage?: string;
26
+ tags?: string[];
27
+ /** Server config template (`id` / `name` come from the install request). */
28
+ template: Omit<McpServerConfig, 'id' | 'name'>;
29
+ /** Environment variables (stdio) or `${VAR}` placeholders the template needs. */
30
+ env?: CatalogEnvVar[];
31
+ /** Extra positional args the user must supply (e.g. allowed directories). */
32
+ args?: Array<{
33
+ name: string;
34
+ description?: string;
35
+ required?: boolean;
36
+ default?: string;
37
+ }>;
38
+ source?: string;
39
+ }
40
+ /** Built-in catalog (reference MCP servers). */
41
+ export declare const BUILTIN_CATALOG: CatalogEntry[];
42
+ /** Load entries from one source (file path relative to `baseDir`, or http(s) URL). */
43
+ export declare function loadCatalogSource(source: string, baseDir?: string, fetchImpl?: typeof fetch): Promise<CatalogEntry[]>;
44
+ export declare class Catalog {
45
+ private config;
46
+ private readonly baseDir;
47
+ private entries;
48
+ constructor(config: () => CatalogConfig | undefined, baseDir?: () => string | undefined);
49
+ /** (Re)load `catalog.sources`; failures are logged and skipped. */
50
+ refresh(fetchImpl?: typeof fetch): Promise<void>;
51
+ list(): readonly CatalogEntry[];
52
+ get(id: string): CatalogEntry | undefined;
53
+ installEnabled(): boolean;
54
+ }
55
+ export interface InstallRequest {
56
+ /** Server id (default: the entry id). */
57
+ serverId?: string;
58
+ name?: string;
59
+ /** Values for `entry.env` (stdio: process env; remote: `${VAR}` substitution). */
60
+ env?: Record<string, string>;
61
+ /** Values for `entry.args`, in order. */
62
+ args?: string[];
63
+ tags?: string[];
64
+ }
65
+ /** Build a server config from a catalog entry. Throws with a user-facing message on missing input. */
66
+ export declare function buildServerConfig(entry: CatalogEntry, req: InstallRequest): McpServerConfig;
67
+ /** Servers installed from the catalog, persisted in `catalog.serversFile`. */
68
+ export declare class InstalledServers {
69
+ private readonly file;
70
+ private servers;
71
+ constructor(file: () => string | undefined);
72
+ load(): McpServerConfig[];
73
+ list(): readonly McpServerConfig[];
74
+ add(s: McpServerConfig): Promise<void>;
75
+ remove(id: string): Promise<boolean>;
76
+ private save;
77
+ }
78
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/catalog/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGxE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,4EAA4E;IAC5E,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC;IAC/C,iFAAiF;IACjF,GAAG,CAAC,EAAE,aAAa,EAAE,CAAC;IACtB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3F,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAQD,gDAAgD;AAChD,eAAO,MAAM,eAAe,EAAE,YAAY,EAoEzC,CAAC;AAQF,sFAAsF;AACtF,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,SAAgB,EAAE,SAAS,GAAE,OAAO,KAAa,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAczI;AAED,qBAAa,OAAO;IAGN,OAAO,CAAC,MAAM;IAAmC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFrF,OAAO,CAAC,OAAO,CAAwC;gBAEnC,MAAM,EAAE,MAAM,aAAa,GAAG,SAAS,EAAmB,OAAO,GAAE,MAAM,MAAM,GAAG,SAA2B;IAEjI,mEAAmE;IAC7D,OAAO,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBtD,IAAI,IAAI,SAAS,YAAY,EAAE;IAI/B,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIzC,cAAc,IAAI,OAAO;CAG1B;AAED,MAAM,WAAW,cAAc;IAC7B,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,sGAAsG;AACtG,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,cAAc,GAAG,eAAe,CAgC3F;AAED,8EAA8E;AAC9E,qBAAa,gBAAgB;IAGf,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,OAAO,CAAyB;gBAEX,IAAI,EAAE,MAAM,MAAM,GAAG,SAAS;IAE3D,IAAI,IAAI,eAAe,EAAE;IAazB,IAAI,IAAI,SAAS,eAAe,EAAE;IAI5B,GAAG,CAAC,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;YAQ5B,IAAI;CAOnB"}
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Upstream catalog / registry: templates for well-known MCP servers that an
3
+ * operator can add with one click (dashboard) or one request
4
+ * (`POST /api/v1/catalog/:id/install`).
5
+ *
6
+ * The built-in entries cover the reference servers; `catalog.sources` adds
7
+ * entries from JSON files or URLs (`{ "entries": [...] }` or an array).
8
+ * Installing is off unless `catalog.install: true` — it spawns processes.
9
+ * Installed servers are kept in `catalog.serversFile` (JSON) when set, and
10
+ * loaded again on start.
11
+ *
12
+ * @module catalog
13
+ */
14
+ import { readFile, writeFile, rename } from 'fs/promises';
15
+ import { existsSync, readFileSync } from 'fs';
16
+ import { isAbsolute, resolve } from 'path';
17
+ import { logger } from '../utils/logger.js';
18
+ const npx = (pkg, ...extra) => ({
19
+ transport: 'stdio',
20
+ command: 'npx',
21
+ args: ['-y', pkg, ...extra],
22
+ });
23
+ /** Built-in catalog (reference MCP servers). */
24
+ export const BUILTIN_CATALOG = [
25
+ {
26
+ id: 'filesystem',
27
+ name: 'Filesystem',
28
+ description: 'Read, write and search files in allowed directories',
29
+ homepage: 'https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem',
30
+ tags: ['files'],
31
+ template: npx('@modelcontextprotocol/server-filesystem'),
32
+ args: [{ name: 'directory', description: 'Allowed directory', required: true, default: '/data' }],
33
+ },
34
+ {
35
+ id: 'memory',
36
+ name: 'Memory',
37
+ description: 'Knowledge-graph based persistent memory',
38
+ homepage: 'https://github.com/modelcontextprotocol/servers/tree/main/src/memory',
39
+ tags: ['memory'],
40
+ template: npx('@modelcontextprotocol/server-memory'),
41
+ },
42
+ {
43
+ id: 'everything',
44
+ name: 'Everything (test server)',
45
+ description: 'Reference server exercising every MCP feature',
46
+ homepage: 'https://github.com/modelcontextprotocol/servers/tree/main/src/everything',
47
+ tags: ['test'],
48
+ template: npx('@modelcontextprotocol/server-everything'),
49
+ },
50
+ {
51
+ id: 'sequential-thinking',
52
+ name: 'Sequential Thinking',
53
+ description: 'Structured step-by-step problem solving',
54
+ homepage: 'https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking',
55
+ tags: ['reasoning'],
56
+ template: npx('@modelcontextprotocol/server-sequential-thinking'),
57
+ },
58
+ {
59
+ id: 'fetch',
60
+ name: 'Fetch',
61
+ description: 'Fetch web pages and convert them to markdown',
62
+ homepage: 'https://github.com/modelcontextprotocol/servers/tree/main/src/fetch',
63
+ tags: ['web'],
64
+ template: { transport: 'stdio', command: 'uvx', args: ['mcp-server-fetch'] },
65
+ },
66
+ {
67
+ id: 'git',
68
+ name: 'Git',
69
+ description: 'Read, search and manipulate a Git repository',
70
+ homepage: 'https://github.com/modelcontextprotocol/servers/tree/main/src/git',
71
+ tags: ['code'],
72
+ template: { transport: 'stdio', command: 'uvx', args: ['mcp-server-git', '--repository'] },
73
+ args: [{ name: 'repository', description: 'Path of the repository', required: true }],
74
+ },
75
+ {
76
+ id: 'time',
77
+ name: 'Time',
78
+ description: 'Current time and time-zone conversion',
79
+ homepage: 'https://github.com/modelcontextprotocol/servers/tree/main/src/time',
80
+ tags: ['utility'],
81
+ template: { transport: 'stdio', command: 'uvx', args: ['mcp-server-time'] },
82
+ },
83
+ {
84
+ id: 'github',
85
+ name: 'GitHub',
86
+ description: 'GitHub repositories, issues and pull requests (remote server)',
87
+ homepage: 'https://github.com/github/github-mcp-server',
88
+ tags: ['code', 'remote'],
89
+ template: { transport: 'streamable-http', url: 'https://api.githubcopilot.com/mcp/', headers: { Authorization: 'Bearer ${GITHUB_TOKEN}' } },
90
+ env: [{ name: 'GITHUB_TOKEN', description: 'GitHub personal access token', required: true, secret: true }],
91
+ },
92
+ ];
93
+ function validEntry(e) {
94
+ if (!e || typeof e !== 'object')
95
+ return false;
96
+ const x = e;
97
+ return typeof x.id === 'string' && /^[A-Za-z0-9._-]+$/.test(x.id) && typeof x.name === 'string' && !!x.template && typeof x.template === 'object' && typeof x.template.transport === 'string';
98
+ }
99
+ /** Load entries from one source (file path relative to `baseDir`, or http(s) URL). */
100
+ export async function loadCatalogSource(source, baseDir = process.cwd(), fetchImpl = fetch) {
101
+ let raw;
102
+ if (/^https?:\/\//.test(source)) {
103
+ const r = await fetchImpl(source, { headers: { accept: 'application/json' }, signal: AbortSignal.timeout(10_000) });
104
+ if (!r.ok)
105
+ throw new Error(`HTTP ${r.status}`);
106
+ raw = await r.json();
107
+ }
108
+ else {
109
+ raw = JSON.parse(await readFile(isAbsolute(source) ? source : resolve(baseDir, source), 'utf8'));
110
+ }
111
+ const list = Array.isArray(raw) ? raw : raw?.entries;
112
+ if (!Array.isArray(list))
113
+ throw new Error('expected an array or { "entries": [...] }');
114
+ const ok = list.filter(validEntry).map((e) => ({ ...e, description: e.description ?? '', source }));
115
+ if (ok.length !== list.length)
116
+ logger.warn(`Catalog ${source}: ${list.length - ok.length} invalid entries skipped`);
117
+ return ok;
118
+ }
119
+ export class Catalog {
120
+ config;
121
+ baseDir;
122
+ entries = [...BUILTIN_CATALOG];
123
+ constructor(config, baseDir = () => undefined) {
124
+ this.config = config;
125
+ this.baseDir = baseDir;
126
+ }
127
+ /** (Re)load `catalog.sources`; failures are logged and skipped. */
128
+ async refresh(fetchImpl) {
129
+ const cfg = this.config();
130
+ const out = cfg?.builtins === false ? [] : [...BUILTIN_CATALOG];
131
+ for (const src of cfg?.sources ?? []) {
132
+ try {
133
+ for (const e of await loadCatalogSource(src, this.baseDir(), fetchImpl)) {
134
+ const i = out.findIndex((x) => x.id === e.id);
135
+ if (i >= 0)
136
+ out[i] = e;
137
+ else
138
+ out.push(e);
139
+ }
140
+ }
141
+ catch (err) {
142
+ logger.warn(`Catalog source ${src} failed: ${err instanceof Error ? err.message : String(err)}`);
143
+ }
144
+ }
145
+ this.entries = out;
146
+ }
147
+ list() {
148
+ return this.entries;
149
+ }
150
+ get(id) {
151
+ return this.entries.find((e) => e.id === id);
152
+ }
153
+ installEnabled() {
154
+ return this.config()?.install === true;
155
+ }
156
+ }
157
+ /** Build a server config from a catalog entry. Throws with a user-facing message on missing input. */
158
+ export function buildServerConfig(entry, req) {
159
+ const id = req.serverId ?? entry.id;
160
+ if (!/^[A-Za-z0-9._-]+$/.test(id))
161
+ throw new Error('serverId: letters, digits, ".", "_" and "-" only');
162
+ const env = req.env ?? {};
163
+ for (const v of entry.env ?? []) {
164
+ if (v.required && !env[v.name])
165
+ throw new Error(`env.${v.name} is required (${v.description ?? 'see the catalog entry'})`);
166
+ }
167
+ const extra = [];
168
+ (entry.args ?? []).forEach((a, i) => {
169
+ const value = req.args?.[i] ?? a.default;
170
+ if (value === undefined || value === '') {
171
+ if (a.required)
172
+ throw new Error(`args[${i}] (${a.name}) is required`);
173
+ return;
174
+ }
175
+ extra.push(value);
176
+ });
177
+ const subst = (s) => s.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (m, k) => env[k] ?? m);
178
+ const t = entry.template;
179
+ const cfg = {
180
+ timeout: 30_000,
181
+ maxConcurrency: 10,
182
+ enabled: true,
183
+ ...t,
184
+ id,
185
+ name: req.name ?? entry.name,
186
+ tags: [...new Set([...(t.tags ?? []), ...(entry.tags ?? []), ...(req.tags ?? []), 'catalog'])],
187
+ };
188
+ if (extra.length)
189
+ cfg.args = [...(t.args ?? []), ...extra];
190
+ if (t.headers)
191
+ cfg.headers = Object.fromEntries(Object.entries(t.headers).map(([k, v]) => [k, subst(v)]));
192
+ if (t.url)
193
+ cfg.url = subst(t.url);
194
+ if (t.transport === 'stdio' && Object.keys(env).length)
195
+ cfg.env = { ...(t.env ?? {}), ...env };
196
+ return cfg;
197
+ }
198
+ /** Servers installed from the catalog, persisted in `catalog.serversFile`. */
199
+ export class InstalledServers {
200
+ file;
201
+ servers = [];
202
+ constructor(file) {
203
+ this.file = file;
204
+ }
205
+ load() {
206
+ const f = this.file();
207
+ if (!f || !existsSync(f))
208
+ return (this.servers = []);
209
+ try {
210
+ const raw = JSON.parse(readFileSync(f, 'utf8'));
211
+ this.servers = Array.isArray(raw.servers) ? raw.servers : [];
212
+ }
213
+ catch (err) {
214
+ logger.error(`Cannot read catalog.serversFile ${f}: ${err instanceof Error ? err.message : String(err)}`);
215
+ this.servers = [];
216
+ }
217
+ return this.servers;
218
+ }
219
+ list() {
220
+ return this.servers;
221
+ }
222
+ async add(s) {
223
+ this.servers = [...this.servers.filter((x) => x.id !== s.id), s];
224
+ await this.save();
225
+ }
226
+ async remove(id) {
227
+ const before = this.servers.length;
228
+ this.servers = this.servers.filter((x) => x.id !== id);
229
+ if (this.servers.length === before)
230
+ return false;
231
+ await this.save();
232
+ return true;
233
+ }
234
+ async save() {
235
+ const f = this.file();
236
+ if (!f)
237
+ return;
238
+ const tmp = `${f}.tmp`;
239
+ await writeFile(tmp, JSON.stringify({ servers: this.servers }, null, 2) + '\n', { mode: 0o600 });
240
+ await rename(tmp, f);
241
+ }
242
+ }
243
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/catalog/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AA+fvD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMzE;AAED,wBAAsB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAwB5E;AAyCD,wBAAgB,qBAAqB,IAAI,MAAM,CAiH9C"}
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AA8hBvD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMzE;AAED,wBAAsB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAwB5E;AAyCD,wBAAgB,qBAAqB,IAAI,MAAM,CAiH9C"}
@@ -399,6 +399,35 @@ const GatewayConfigSchema = z.object({
399
399
  ctx.addIssue({ code: z.ZodIssueCode.custom, message: bad });
400
400
  })
401
401
  .optional(),
402
+ quotas: z
403
+ .object({
404
+ rules: z
405
+ .array(z
406
+ .object({
407
+ name: z.string().min(1).optional(),
408
+ limit: z.number().int().min(0),
409
+ period: z.enum(['hour', 'day', 'month']),
410
+ per: z.enum(['client', 'tenant']).optional(),
411
+ clients: z.array(z.string().min(1)).optional(),
412
+ tenants: z.array(z.string().min(1)).optional(),
413
+ servers: z.array(z.string().min(1)).optional(),
414
+ tools: z.array(z.string().min(1)).optional(),
415
+ })
416
+ .strict())
417
+ .optional(),
418
+ meteringRetentionDays: z.number().int().positive().optional(),
419
+ })
420
+ .strict()
421
+ .optional(),
422
+ catalog: z
423
+ .object({
424
+ builtins: z.boolean().optional(),
425
+ sources: z.array(z.string().min(1)).optional(),
426
+ install: z.boolean().optional(),
427
+ serversFile: z.string().min(1).optional(),
428
+ })
429
+ .strict()
430
+ .optional(),
402
431
  tenants: z
403
432
  .array(z
404
433
  .object({