@winstonsayno/mcp-gateway 2.2.0 → 2.4.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 +32 -0
- package/README.md +18 -0
- package/dashboard/index.html +77 -2
- package/dist/auth/scopes.d.ts +9 -0
- package/dist/auth/scopes.d.ts.map +1 -1
- package/dist/auth/scopes.js +4 -2
- package/dist/auth/scopes.js.map +1 -1
- package/dist/auth/tenants.d.ts +41 -0
- package/dist/auth/tenants.d.ts.map +1 -0
- package/dist/auth/tenants.js +79 -0
- package/dist/auth/tenants.js.map +1 -0
- package/dist/catalog/index.d.ts +78 -0
- package/dist/catalog/index.d.ts.map +1 -0
- package/dist/catalog/index.js +243 -0
- package/dist/catalog/index.js.map +1 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +25 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/gateway/api.d.ts +16 -0
- package/dist/gateway/api.d.ts.map +1 -1
- package/dist/gateway/api.js +154 -8
- package/dist/gateway/api.js.map +1 -1
- package/dist/gateway/index.d.ts +12 -0
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js +78 -3
- package/dist/gateway/index.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/endpoint.d.ts.map +1 -1
- package/dist/mcp/endpoint.js +4 -0
- package/dist/mcp/endpoint.js.map +1 -1
- package/dist/utils/types.d.ts +27 -0
- package/dist/utils/types.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [2.4.0] - 2026-10-07
|
|
13
|
+
|
|
14
|
+
Upstream catalog / registry with one-click add.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **`catalog:`** — built-in entries for the reference MCP servers (filesystem, memory, everything,
|
|
18
|
+
sequential-thinking, fetch, git, time, GitHub remote) plus `sources` (JSON files or URLs) merged by id.
|
|
19
|
+
- **One-click install** (`catalog.install: true`, off by default): `POST /api/v1/catalog/:id/install` with `serverId`,
|
|
20
|
+
`env` (process env for stdio, `${VAR}` substitution for remote `url` / `headers`) and `args`; validation of required
|
|
21
|
+
inputs; `409` on id clashes. `DELETE /api/v1/catalog/servers/:id` removes it. Operator keys only.
|
|
22
|
+
- `catalog.serversFile` persists installed servers (atomic write, mode 600) and loads them on start; they survive hot
|
|
23
|
+
reloads; config-file ids win on clashes.
|
|
24
|
+
- **Dashboard**: *Add a server* card on the Servers tab (prompts for id, env and args; shows where an entry is
|
|
25
|
+
installed), also in the demo (read-only).
|
|
26
|
+
- Exports: `Catalog`, `InstalledServers`, `BUILTIN_CATALOG`, `buildServerConfig`, `loadCatalogSource`.
|
|
27
|
+
|
|
28
|
+
## [2.3.0] - 2026-10-07
|
|
29
|
+
|
|
30
|
+
Tenants / workspaces with role-based access control.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- **`tenants:`** — workspaces that own servers (`servers` globs) and members (client id globs) with roles
|
|
34
|
+
**owner / admin / viewer**. Members are confined to their tenants' servers on top of their key / token scope; tool
|
|
35
|
+
calls (REST and `/mcp`) need `admin` or `owner` on the server's tenant (viewers are read-only: `403` / `-32003`).
|
|
36
|
+
Clients outside every tenant keep operator access.
|
|
37
|
+
- Tenant-aware approvals: admins / owners list and decide held calls for their tenants' servers only.
|
|
38
|
+
- **API**: `GET /api/v1/tenants[/:id]`, `PUT /api/v1/tenants/:id/members`, `DELETE /api/v1/tenants/:id/members/:client`
|
|
39
|
+
(owners / operators; last owner protected; runtime changes are not persisted). MCP sessions pick up membership
|
|
40
|
+
changes.
|
|
41
|
+
- **Dashboard**: *Workspaces* card (role badges, member role selectors for owners), also in the demo.
|
|
42
|
+
- Hot reloadable; exports `withTenantScope`, `membershipsOf`, `roleIn`, `canCall`, `ROLE_RANK`.
|
|
43
|
+
|
|
12
44
|
## [2.2.0] - 2026-10-07
|
|
13
45
|
|
|
14
46
|
Tool result caching and in-flight de-duplication.
|
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.4
|
|
583
|
+
|
|
584
|
+
| Feature | Description |
|
|
585
|
+
|---------|-------------|
|
|
586
|
+
| **Upstream catalog** | built-in reference servers + your own JSON catalogs (`catalog.sources`) |
|
|
587
|
+
| **One-click add** | dashboard *Add a server* / `POST /api/v1/catalog/:id/install`, optional persistence (`serversFile`) |
|
|
588
|
+
|
|
589
|
+
Details: [CHANGELOG](CHANGELOG.md).
|
|
590
|
+
|
|
591
|
+
## What's New in v2.3
|
|
592
|
+
|
|
593
|
+
| Feature | Description |
|
|
594
|
+
|---------|-------------|
|
|
595
|
+
| **Tenants** | `tenants:` workspaces owning servers, with members matched by client id |
|
|
596
|
+
| **RBAC** | owner / admin / viewer roles enforced on REST, `/mcp` and approvals; dashboard *Workspaces* card |
|
|
597
|
+
|
|
598
|
+
Details: [CHANGELOG](CHANGELOG.md).
|
|
599
|
+
|
|
582
600
|
## What's New in v2.2
|
|
583
601
|
|
|
584
602
|
| Feature | Description |
|
package/dashboard/index.html
CHANGED
|
@@ -423,6 +423,10 @@ tbody tr:hover { background: var(--surface-2); }
|
|
|
423
423
|
<h2><span data-i18n="approvals">Pending approvals</span><span class="right" id="approvalsNote"></span></h2>
|
|
424
424
|
<ul class="stream" id="approvals"></ul>
|
|
425
425
|
</div>
|
|
426
|
+
<div class="card" id="tenantsCard" hidden style="margin-bottom:14px">
|
|
427
|
+
<h2><span data-i18n="tenants">Workspaces</span><span class="right" id="tenantsNote"></span></h2>
|
|
428
|
+
<ul class="stream" id="tenants"></ul>
|
|
429
|
+
</div>
|
|
426
430
|
<div class="grid kpis stagger" id="kpis"></div>
|
|
427
431
|
<div class="grid two" style="margin-top:14px">
|
|
428
432
|
<div class="card">
|
|
@@ -456,6 +460,10 @@ tbody tr:hover { background: var(--surface-2); }
|
|
|
456
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>
|
|
457
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>
|
|
458
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>
|
|
459
467
|
</section>
|
|
460
468
|
|
|
461
469
|
<!-- Playground -->
|
|
@@ -569,7 +577,7 @@ const I18N = {
|
|
|
569
577
|
remember: 'Remember on this device', saveConnect: 'Save & connect', clear: 'Clear', close: 'Close',
|
|
570
578
|
tabOverview: 'Overview', tabServers: 'Servers', tabPlayground: 'Playground', tabHistory: 'History', tabConnect: 'Connect',
|
|
571
579
|
ovTitle: 'Overview', ovSub: 'Live traffic through the gateway', window: 'Time window',
|
|
572
|
-
reqRate: 'Request rate', requests: 'Requests', errors: 'Errors', latency: 'Latency', topTools: 'Top tools', perKey: 'Usage per key', perServer: 'Calls per server', approvals: 'Pending approvals', 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',
|
|
573
581
|
errRate: 'Error rate', liveStream: 'Live requests', pause: 'Pause', health: 'Server health',
|
|
574
582
|
kRpm: 'Requests / min', kP50: 'p50 latency', kP95: 'p95 latency', kErr: 'Error rate', kSrv: 'Servers online',
|
|
575
583
|
kTotal: '{n} in window', kP99: 'p99 {v}', kErrN: '{n} failed', kTools: '{n} tools',
|
|
@@ -614,7 +622,7 @@ const I18N = {
|
|
|
614
622
|
remember: '在此设备上记住', saveConnect: '保存并连接', clear: '清除', close: '关闭',
|
|
615
623
|
tabOverview: '总览', tabServers: '服务器', tabPlayground: '调试台', tabHistory: '历史', tabConnect: '接入',
|
|
616
624
|
ovTitle: '总览', ovSub: '网关实时流量', window: '时间范围',
|
|
617
|
-
reqRate: '请求速率', requests: '请求', errors: '错误', latency: '延迟', topTools: '热门工具', perKey: '各密钥用量', perServer: '各服务器调用', approvals: '待审批调用', 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: '到期',
|
|
618
626
|
errRate: '错误率', liveStream: '实时请求', pause: '暂停', health: '服务器健康',
|
|
619
627
|
kRpm: '每分钟请求', kP50: 'p50 延迟', kP95: 'p95 延迟', kErr: '错误率', kSrv: '在线服务器',
|
|
620
628
|
kTotal: '窗口内 {n} 次', kP99: 'p99 {v}', kErrN: '{n} 次失败', kTools: '{n} 个工具',
|
|
@@ -967,6 +975,70 @@ document.addEventListener('click', async (e) => {
|
|
|
967
975
|
loadApprovals();
|
|
968
976
|
});
|
|
969
977
|
|
|
978
|
+
// Tenants / workspaces (RBAC): hidden unless the gateway has tenants the caller can see.
|
|
979
|
+
async function loadTenants() {
|
|
980
|
+
const card = $('#tenantsCard');
|
|
981
|
+
let r;
|
|
982
|
+
try { r = await api('/tenants'); } catch { card.hidden = true; return; }
|
|
983
|
+
const list = r.tenants || [];
|
|
984
|
+
card.hidden = list.length === 0;
|
|
985
|
+
$('#tenantsNote').textContent = list.length ? `${r.clientId}` : '';
|
|
986
|
+
const roles = ['owner', 'admin', 'viewer'];
|
|
987
|
+
$('#tenants').innerHTML = list.map((tn) => {
|
|
988
|
+
const manage = tn.role === 'owner' || tn.role === 'operator';
|
|
989
|
+
const members = (tn.members || []).map((m) => manage
|
|
990
|
+
? `<span class="pill mono">${esc(m.client)} <select class="input" style="padding:0 4px;height:22px" data-tenant="${esc(tn.id)}" data-client="${esc(m.client)}" aria-label="${esc(t('role'))}">${roles.map((x) => `<option value="${x}"${x === m.role ? ' selected' : ''}>${x}</option>`).join('')}</select></span>`
|
|
991
|
+
: `<span class="pill mono">${esc(m.client)} · ${esc(m.role)}</span>`).join(' ');
|
|
992
|
+
return `<li><div style="flex:1;min-width:0"><b>${esc(tn.name)}</b> <span class="badge ${tn.role === 'viewer' ? '' : 'ok'}">${esc(tn.role || '')}</span>
|
|
993
|
+
<span class="muted"> · ${esc((tn.serverIds || []).join(', ') || tn.servers.join(', '))}</span>
|
|
994
|
+
${members ? `<div style="margin-top:6px;display:flex;flex-wrap:wrap;gap:6px">${members}</div>` : ''}</div></li>`;
|
|
995
|
+
}).join('');
|
|
996
|
+
}
|
|
997
|
+
document.addEventListener('change', async (e) => {
|
|
998
|
+
const sel = e.target.closest && e.target.closest('select[data-tenant]');
|
|
999
|
+
if (!sel) return;
|
|
1000
|
+
try {
|
|
1001
|
+
await api(`/tenants/${encodeURIComponent(sel.dataset.tenant)}/members`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ client: sel.dataset.client, role: sel.value }) });
|
|
1002
|
+
toast(t('roleSaved'));
|
|
1003
|
+
} catch (err) { toast(err.message || String(err)); }
|
|
1004
|
+
loadTenants();
|
|
1005
|
+
});
|
|
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
|
+
|
|
970
1042
|
async function loadServers() {
|
|
971
1043
|
const btn = $('#srvRefresh'); btn.classList.add('loading');
|
|
972
1044
|
try {
|
|
@@ -1644,6 +1716,9 @@ reloadAll().then(() => {
|
|
|
1644
1716
|
});
|
|
1645
1717
|
// Refresh the slower-moving pieces in the background.
|
|
1646
1718
|
setInterval(() => { if (!document.hidden) loadApprovals(); }, 3000);
|
|
1719
|
+
loadTenants();
|
|
1720
|
+
loadCatalog();
|
|
1721
|
+
setInterval(() => { if (!document.hidden) loadTenants(); }, 30000);
|
|
1647
1722
|
setInterval(() => { if (!document.hidden) { loadServers(); if (state.live === 'live') loadStats().catch(() => {}); } }, 10000);
|
|
1648
1723
|
document.addEventListener('visibilitychange', () => { if (!document.hidden && state.live !== 'live') startLive(); });
|
|
1649
1724
|
})();
|
package/dist/auth/scopes.d.ts
CHANGED
|
@@ -25,6 +25,15 @@ export interface AccessScope {
|
|
|
25
25
|
tools?: string[];
|
|
26
26
|
/** Own rate limit for this client (replaces the global `rateLimit`). */
|
|
27
27
|
rateLimit?: RateLimitConfig;
|
|
28
|
+
/** Tenant confinement: union of the client's tenants' server globs (see auth/tenants). */
|
|
29
|
+
tenantServers?: string[];
|
|
30
|
+
/** Servers of tenants where the client is admin / owner (tool calls allowed). */
|
|
31
|
+
writableServers?: string[];
|
|
32
|
+
/** The client's tenant memberships. */
|
|
33
|
+
tenants?: Array<{
|
|
34
|
+
id: string;
|
|
35
|
+
role: 'owner' | 'admin' | 'viewer';
|
|
36
|
+
}>;
|
|
28
37
|
}
|
|
29
38
|
export declare const JWT_SERVERS_CLAIM = "mcp_servers";
|
|
30
39
|
export declare const JWT_TOOLS_CLAIM = "mcp_tools";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scopes.d.ts","sourceRoot":"","sources":["../../src/auth/scopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAGnE,MAAM,WAAW,WAAW;IAC1B,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,wEAAwE;IACxE,SAAS,CAAC,EAAE,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"scopes.d.ts","sourceRoot":"","sources":["../../src/auth/scopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAGnE,MAAM,WAAW,WAAW;IAC1B,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,wEAAwE;IACxE,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,0FAA0F;IAC1F,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,iFAAiF;IACjF,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,uCAAuC;IACvC,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAA;KAAE,CAAC,CAAC;CACrE;AAED,eAAO,MAAM,iBAAiB,gBAAgB,CAAC;AAC/C,eAAO,MAAM,eAAe,cAAc,CAAC;AAI3C,+DAA+D;AAC/D,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIzF;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAMzG;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,IAAI,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,CAAC,EAC9E,KAAK,EAAE,WAAW,GAAG,SAAS,EAC9B,KAAK,EAAE,SAAS,CAAC,EAAE,GAClB,CAAC,EAAE,CAGL;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAEpE;AAUD,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,CAUtE"}
|
package/dist/auth/scopes.js
CHANGED
|
@@ -23,6 +23,8 @@ export const JWT_TOOLS_CLAIM = 'mcp_tools';
|
|
|
23
23
|
const matches = (value, patterns) => patterns.some((p) => globToRegExp(p).test(value));
|
|
24
24
|
/** Whether the scope lets the client use `serverId` at all. */
|
|
25
25
|
export function isServerInScope(scope, serverId) {
|
|
26
|
+
if (scope?.tenantServers && !matches(serverId, scope.tenantServers))
|
|
27
|
+
return false;
|
|
26
28
|
if (!scope?.servers)
|
|
27
29
|
return true;
|
|
28
30
|
return matches(serverId, scope.servers);
|
|
@@ -39,13 +41,13 @@ export function isToolInScope(scope, serverId, toolName) {
|
|
|
39
41
|
return scope.tools.some((p) => globToRegExp(p).test(p.includes('/') ? qualified : toolName));
|
|
40
42
|
}
|
|
41
43
|
export function filterToolsByScope(scope, tools) {
|
|
42
|
-
if (!
|
|
44
|
+
if (!isRestricted(scope))
|
|
43
45
|
return [...tools];
|
|
44
46
|
return tools.filter((t) => isToolInScope(scope, t.serverId, t.name));
|
|
45
47
|
}
|
|
46
48
|
/** Whether a scope restricts anything (unrestricted clients skip filtering). */
|
|
47
49
|
export function isRestricted(scope) {
|
|
48
|
-
return !!scope && (scope.servers !== undefined || scope.tools !== undefined);
|
|
50
|
+
return !!scope && (scope.servers !== undefined || scope.tools !== undefined || scope.tenantServers !== undefined);
|
|
49
51
|
}
|
|
50
52
|
function claimList(value) {
|
|
51
53
|
if (value === undefined || value === null)
|
package/dist/auth/scopes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../src/auth/scopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../src/auth/scopes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenants (workspaces) with role-based access control.
|
|
3
|
+
*
|
|
4
|
+
* A tenant groups upstream servers (`servers` globs) and members. A member is
|
|
5
|
+
* matched by client id glob (`key:alice`, `jwt:user-*`, `oauth:…`) and has one
|
|
6
|
+
* role per tenant:
|
|
7
|
+
*
|
|
8
|
+
* | role | sees the tenant's servers | calls tools, manages approvals / cache | manages members |
|
|
9
|
+
* |--------|:--:|:--:|:--:|
|
|
10
|
+
* | viewer | ✓ | | |
|
|
11
|
+
* | admin | ✓ | ✓ | |
|
|
12
|
+
* | owner | ✓ | ✓ | ✓ |
|
|
13
|
+
*
|
|
14
|
+
* A client that belongs to at least one tenant is confined to the union of its
|
|
15
|
+
* tenants' servers (on top of its own key / token scope), and may only call
|
|
16
|
+
* tools on servers of tenants where it is `admin` or `owner`. Clients that
|
|
17
|
+
* belong to no tenant keep their 1.x behaviour (operators).
|
|
18
|
+
*
|
|
19
|
+
* @module auth/tenants
|
|
20
|
+
*/
|
|
21
|
+
import type { TenantConfig, TenantRole } from '../utils/types.js';
|
|
22
|
+
import type { AccessScope } from './scopes.js';
|
|
23
|
+
export declare const ROLE_RANK: Record<TenantRole, number>;
|
|
24
|
+
export interface Membership {
|
|
25
|
+
tenant: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
role: TenantRole;
|
|
28
|
+
servers: string[];
|
|
29
|
+
}
|
|
30
|
+
/** The role of a client in a tenant (highest matching member entry). */
|
|
31
|
+
export declare function roleIn(tenant: TenantConfig, clientId: string | undefined): TenantRole | undefined;
|
|
32
|
+
export declare function membershipsOf(tenants: readonly TenantConfig[] | undefined, clientId: string | undefined): Membership[];
|
|
33
|
+
/** Add tenant confinement to a client's scope (unchanged for clients in no tenant). */
|
|
34
|
+
export declare function withTenantScope(tenants: readonly TenantConfig[] | undefined, clientId: string | undefined, scope: AccessScope | undefined): AccessScope | undefined;
|
|
35
|
+
/** Whether the scope may call tools on `serverId` (viewers are read-only). */
|
|
36
|
+
export declare function canCall(scope: AccessScope | undefined, serverId: string): boolean;
|
|
37
|
+
/** Highest role the scope holds in any tenant (undefined = not a tenant member). */
|
|
38
|
+
export declare function highestRole(scope: AccessScope | undefined): TenantRole | undefined;
|
|
39
|
+
/** Validation message for a tenants block (undefined = valid). */
|
|
40
|
+
export declare function invalidTenants(tenants: readonly TenantConfig[] | undefined): string | undefined;
|
|
41
|
+
//# sourceMappingURL=tenants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenants.d.ts","sourceRoot":"","sources":["../../src/auth/tenants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAqC,CAAC;AAEvF,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAID,wEAAwE;AACxE,wBAAgB,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAOjG;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,EAAE,CAOtH;AAED,uFAAuF;AACvF,wBAAgB,eAAe,CAC7B,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,SAAS,EAC5C,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,WAAW,GAAG,SAAS,GAC7B,WAAW,GAAG,SAAS,CASzB;AAED,8EAA8E;AAC9E,wBAAgB,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGjF;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAIlF;AAED,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAO/F"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenants (workspaces) with role-based access control.
|
|
3
|
+
*
|
|
4
|
+
* A tenant groups upstream servers (`servers` globs) and members. A member is
|
|
5
|
+
* matched by client id glob (`key:alice`, `jwt:user-*`, `oauth:…`) and has one
|
|
6
|
+
* role per tenant:
|
|
7
|
+
*
|
|
8
|
+
* | role | sees the tenant's servers | calls tools, manages approvals / cache | manages members |
|
|
9
|
+
* |--------|:--:|:--:|:--:|
|
|
10
|
+
* | viewer | ✓ | | |
|
|
11
|
+
* | admin | ✓ | ✓ | |
|
|
12
|
+
* | owner | ✓ | ✓ | ✓ |
|
|
13
|
+
*
|
|
14
|
+
* A client that belongs to at least one tenant is confined to the union of its
|
|
15
|
+
* tenants' servers (on top of its own key / token scope), and may only call
|
|
16
|
+
* tools on servers of tenants where it is `admin` or `owner`. Clients that
|
|
17
|
+
* belong to no tenant keep their 1.x behaviour (operators).
|
|
18
|
+
*
|
|
19
|
+
* @module auth/tenants
|
|
20
|
+
*/
|
|
21
|
+
import { globToRegExp } from '../utils/tool-filter.js';
|
|
22
|
+
export const ROLE_RANK = { viewer: 1, admin: 2, owner: 3 };
|
|
23
|
+
const matches = (value, patterns) => patterns.some((p) => globToRegExp(p).test(value));
|
|
24
|
+
/** The role of a client in a tenant (highest matching member entry). */
|
|
25
|
+
export function roleIn(tenant, clientId) {
|
|
26
|
+
const id = clientId ?? 'anonymous';
|
|
27
|
+
let best;
|
|
28
|
+
for (const m of tenant.members ?? []) {
|
|
29
|
+
if (globToRegExp(m.client).test(id) && (!best || ROLE_RANK[m.role] > ROLE_RANK[best]))
|
|
30
|
+
best = m.role;
|
|
31
|
+
}
|
|
32
|
+
return best;
|
|
33
|
+
}
|
|
34
|
+
export function membershipsOf(tenants, clientId) {
|
|
35
|
+
const out = [];
|
|
36
|
+
for (const t of tenants ?? []) {
|
|
37
|
+
const role = roleIn(t, clientId);
|
|
38
|
+
if (role)
|
|
39
|
+
out.push({ tenant: t.id, name: t.name, role, servers: t.servers });
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
/** Add tenant confinement to a client's scope (unchanged for clients in no tenant). */
|
|
44
|
+
export function withTenantScope(tenants, clientId, scope) {
|
|
45
|
+
const ms = membershipsOf(tenants, clientId);
|
|
46
|
+
if (ms.length === 0)
|
|
47
|
+
return scope;
|
|
48
|
+
return {
|
|
49
|
+
...(scope ?? {}),
|
|
50
|
+
tenantServers: [...new Set(ms.flatMap((m) => m.servers))],
|
|
51
|
+
writableServers: [...new Set(ms.filter((m) => ROLE_RANK[m.role] >= ROLE_RANK.admin).flatMap((m) => m.servers))],
|
|
52
|
+
tenants: ms.map((m) => ({ id: m.tenant, role: m.role })),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Whether the scope may call tools on `serverId` (viewers are read-only). */
|
|
56
|
+
export function canCall(scope, serverId) {
|
|
57
|
+
if (!scope?.writableServers)
|
|
58
|
+
return true;
|
|
59
|
+
return matches(serverId, scope.writableServers);
|
|
60
|
+
}
|
|
61
|
+
/** Highest role the scope holds in any tenant (undefined = not a tenant member). */
|
|
62
|
+
export function highestRole(scope) {
|
|
63
|
+
let best;
|
|
64
|
+
for (const t of scope?.tenants ?? [])
|
|
65
|
+
if (!best || ROLE_RANK[t.role] > ROLE_RANK[best])
|
|
66
|
+
best = t.role;
|
|
67
|
+
return best;
|
|
68
|
+
}
|
|
69
|
+
/** Validation message for a tenants block (undefined = valid). */
|
|
70
|
+
export function invalidTenants(tenants) {
|
|
71
|
+
const ids = new Set();
|
|
72
|
+
for (const t of tenants ?? []) {
|
|
73
|
+
if (ids.has(t.id))
|
|
74
|
+
return `duplicate tenant id "${t.id}"`;
|
|
75
|
+
ids.add(t.id);
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=tenants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenants.js","sourceRoot":"","sources":["../../src/auth/tenants.ts"],"names":[],"mappings":"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"}
|
|
@@ -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"}
|