@ni-c/mcp-hub 0.9.2 → 0.11.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 +619 -0
- package/README.md +189 -64
- package/dist/admin.js +232 -8
- package/dist/admin.js.map +1 -1
- package/dist/auth/address.js +89 -0
- package/dist/auth/address.js.map +1 -0
- package/dist/auth/api-tokens.js +27 -0
- package/dist/auth/api-tokens.js.map +1 -0
- package/dist/auth/cimd.js +367 -0
- package/dist/auth/cimd.js.map +1 -0
- package/dist/auth/consent-page.js +4 -7
- package/dist/auth/consent-page.js.map +1 -1
- package/dist/auth/headers.js +22 -1
- package/dist/auth/headers.js.map +1 -1
- package/dist/auth/login-page.js +4 -7
- package/dist/auth/login-page.js.map +1 -1
- package/dist/auth/oidc/adapter.js +135 -0
- package/dist/auth/oidc/adapter.js.map +1 -0
- package/dist/auth/oidc/interactions.js +187 -0
- package/dist/auth/oidc/interactions.js.map +1 -0
- package/dist/auth/oidc/mount.js +144 -0
- package/dist/auth/oidc/mount.js.map +1 -0
- package/dist/auth/oidc/provider.js +440 -0
- package/dist/auth/oidc/provider.js.map +1 -0
- package/dist/auth/oidc/quirks.js +234 -0
- package/dist/auth/oidc/quirks.js.map +1 -0
- package/dist/auth/oidc/verifier.js +121 -0
- package/dist/auth/oidc/verifier.js.map +1 -0
- package/dist/auth/page.js +22 -0
- package/dist/auth/page.js.map +1 -1
- package/dist/auth/pinned-fetch.js +129 -0
- package/dist/auth/pinned-fetch.js.map +1 -0
- package/dist/auth/protected-resource.js +41 -0
- package/dist/auth/protected-resource.js.map +1 -0
- package/dist/auth/rate-limit.js +156 -0
- package/dist/auth/rate-limit.js.map +1 -0
- package/dist/auth/redirect-uri.js +90 -0
- package/dist/auth/redirect-uri.js.map +1 -0
- package/dist/auth/registration.js +146 -0
- package/dist/auth/registration.js.map +1 -0
- package/dist/auth/session.js +43 -0
- package/dist/auth/session.js.map +1 -0
- package/dist/auth/signed-token.js +49 -0
- package/dist/auth/signed-token.js.map +1 -0
- package/dist/auth/store.js +516 -5
- package/dist/auth/store.js.map +1 -1
- package/dist/auth/text.js +51 -0
- package/dist/auth/text.js.map +1 -0
- package/dist/config.js +184 -5
- package/dist/config.js.map +1 -1
- package/dist/docker-proxy/policy.js +1 -0
- package/dist/docker-proxy/policy.js.map +1 -1
- package/dist/docker-proxy/server.js +1 -0
- package/dist/docker-proxy/server.js.map +1 -1
- package/dist/elicitation.js +0 -0
- package/dist/elicitation.js.map +1 -0
- package/dist/forward.js +0 -0
- package/dist/forward.js.map +1 -0
- package/dist/health.js +22 -3
- package/dist/health.js.map +1 -1
- package/dist/hub.js +337 -29
- package/dist/hub.js.map +1 -1
- package/dist/index.js +218 -23
- package/dist/index.js.map +1 -1
- package/dist/limits.js +13 -1
- package/dist/limits.js.map +1 -1
- package/dist/mcp-limits.js +14 -2
- package/dist/mcp-limits.js.map +1 -1
- package/dist/proxy.js +262 -34
- package/dist/proxy.js.map +1 -1
- package/dist/stdio.js +97 -7
- package/dist/stdio.js.map +1 -1
- package/dist/subscriptions.js +236 -0
- package/dist/subscriptions.js.map +1 -0
- package/dist/supervisor.js +472 -28
- package/dist/supervisor.js.map +1 -1
- package/dist/timings.js +61 -0
- package/dist/timings.js.map +1 -0
- package/dist/tool-filter.js +59 -0
- package/dist/tool-filter.js.map +1 -0
- package/dist/transports/docker.js.map +1 -1
- package/dist/transports/stream.js +33 -20
- package/dist/transports/stream.js.map +1 -1
- package/dist/upstream/auth.js +435 -0
- package/dist/upstream/auth.js.map +1 -0
- package/dist/upstream/login.js +94 -0
- package/dist/upstream/login.js.map +1 -0
- package/dist/upstream/provider.js +286 -0
- package/dist/upstream/provider.js.map +1 -0
- package/dist/upstream/routes.js +97 -0
- package/dist/upstream/routes.js.map +1 -0
- package/package.json +23 -7
- package/dist/auth/provider.js +0 -380
- package/dist/auth/provider.js.map +0 -1
- package/dist/auth/routes.js +0 -223
- package/dist/auth/routes.js.map +0 -1
package/dist/proxy.js
CHANGED
|
@@ -1,25 +1,64 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { CompleteResultSchema, GetPromptResultSchema, ListPromptsResultSchema, ListResourcesResultSchema, ListResourceTemplatesResultSchema, ListToolsResultSchema, ReadResourceResultSchema } from '@modelcontextprotocol/core';
|
|
2
|
+
import { NodeStreamableHTTPServerTransport, toNodeHandler, toWebRequest } from '@modelcontextprotocol/node';
|
|
3
|
+
import { Server, ProtocolError, ProtocolErrorCode, createMcpHandler, isInputRequiredResult, isLegacyRequest } from '@modelcontextprotocol/server';
|
|
4
4
|
import { ABSOLUTE_CALL_OPTIONS, assertForwardedResultSize } from './mcp-limits.js';
|
|
5
|
+
import { filterTools, loggableToolName, toolAllowed } from './tool-filter.js';
|
|
6
|
+
import { forwardToolCall } from './forward.js';
|
|
7
|
+
import { KEEPALIVE_MS, MAX_STREAM_MS, MAX_SUBSCRIPTIONS, MAX_SUBSCRIPTION_URIS, SubscriptionRegistry, isListenRequest, parseListenFilter, subscriptionsAllowed } from './subscriptions.js';
|
|
8
|
+
/**
|
|
9
|
+
* The revision each era puts on the wire.
|
|
10
|
+
*
|
|
11
|
+
* Only ever read to *tell* somebody which one they are on; nothing branches on
|
|
12
|
+
* the string. `Era` stays the type everything else is written against, because
|
|
13
|
+
* a revision date is a fact about the wire and an era is a fact about
|
|
14
|
+
* behaviour, and the code cares about the second one.
|
|
15
|
+
*/
|
|
16
|
+
export const REVISION = { modern: '2026-07-28', legacy: '2025-11-25' };
|
|
5
17
|
/**
|
|
6
18
|
* The child's capabilities minus what this proxy does not actually serve.
|
|
7
19
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
20
|
+
* The rule is one sentence: announce only what we answer. Three claims are
|
|
21
|
+
* decided here, and each used to be decided the other way.
|
|
22
|
+
*
|
|
23
|
+
* `listChanged` and `resources.subscribe` now survive — but only on the era
|
|
24
|
+
* that carries them. A 2026-07-28 client opens a `subscriptions/listen` stream
|
|
25
|
+
* and the hub delivers on it; a 2025 client would need `resources/subscribe`
|
|
26
|
+
* and an unsolicited server-to-client channel, and gets neither, so it is told
|
|
27
|
+
* neither. The 2025 side is the behaviour that shipped before, now stated
|
|
28
|
+
* rather than assumed.
|
|
29
|
+
*
|
|
30
|
+
* `logging` goes on both eras. `logging/setLevel` has no handler and never
|
|
31
|
+
* had one, so a client that believed the advertisement got a -32601 at call
|
|
32
|
+
* time — the exact shape of the bug `resources.subscribe` used to have. On
|
|
33
|
+
* 2026-07-28 the level is per-request `_meta` and there is no RPC to implement;
|
|
34
|
+
* carrying `notifications/message` is a separate piece of work, and until it
|
|
35
|
+
* exists the honest answer is silence.
|
|
11
36
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* comes is no worse off than a client that was never told. It is listed under
|
|
16
|
-
* the known gaps in the documentation instead.
|
|
37
|
+
* A server whose operator set `subscriptions: "off"` is treated exactly like a
|
|
38
|
+
* 2025 connection here. Withdrawing the delivery without withdrawing the claim
|
|
39
|
+
* would just recreate the lie one config key lower down.
|
|
17
40
|
*/
|
|
18
|
-
function advertisedCapabilities(capabilities) {
|
|
19
|
-
const caps = { ...
|
|
41
|
+
function advertisedCapabilities(capabilities, era, allowed) {
|
|
42
|
+
const caps = { ...capabilities };
|
|
43
|
+
delete caps.logging;
|
|
44
|
+
const pushes = era === 'modern' && allowed;
|
|
45
|
+
// `listChanged: false` and an absent key mean the same thing to a client, and
|
|
46
|
+
// the absent one is what the specification shows, so drop rather than negate.
|
|
47
|
+
if (caps.tools) {
|
|
48
|
+
const { listChanged, ...rest } = caps.tools;
|
|
49
|
+
caps.tools = pushes && listChanged ? { ...rest, listChanged } : rest;
|
|
50
|
+
}
|
|
51
|
+
if (caps.prompts) {
|
|
52
|
+
const { listChanged, ...rest } = caps.prompts;
|
|
53
|
+
caps.prompts = pushes && listChanged ? { ...rest, listChanged } : rest;
|
|
54
|
+
}
|
|
20
55
|
if (caps.resources) {
|
|
21
|
-
const { subscribe
|
|
22
|
-
caps.resources =
|
|
56
|
+
const { listChanged, subscribe, ...rest } = caps.resources;
|
|
57
|
+
caps.resources = {
|
|
58
|
+
...rest,
|
|
59
|
+
...(pushes && listChanged ? { listChanged } : {}),
|
|
60
|
+
...(pushes && subscribe ? { subscribe } : {})
|
|
61
|
+
};
|
|
23
62
|
}
|
|
24
63
|
return caps;
|
|
25
64
|
}
|
|
@@ -29,23 +68,35 @@ function advertisedCapabilities(capabilities) {
|
|
|
29
68
|
* transport means no server-side session state at all — claude.ai reconnects
|
|
30
69
|
* every few minutes without ever closing sessions, so anything stateful leaks.
|
|
31
70
|
*/
|
|
32
|
-
function buildProxyServer(managed) {
|
|
33
|
-
const server = new Server({ name: managed.serverInfo?.name ?? managed.name, version: managed.serverInfo?.version ?? '0.0.0' }, { capabilities: advertisedCapabilities(managed.capabilities) });
|
|
34
|
-
|
|
71
|
+
function buildProxyServer(managed, secret, era) {
|
|
72
|
+
const server = new Server({ name: managed.serverInfo?.name ?? managed.name, version: managed.serverInfo?.version ?? '0.0.0' }, { capabilities: advertisedCapabilities(managed.capabilities, era, subscriptionsAllowed(managed.config)) });
|
|
73
|
+
/**
|
|
74
|
+
* Forwards one request and hands back exactly what the schema describes.
|
|
75
|
+
*
|
|
76
|
+
* Generic over the schema rather than typed against a union of all of them:
|
|
77
|
+
* v2's request handlers are typed per method, so a `Promise<unknown>` here
|
|
78
|
+
* fails to assign at all eight call sites below. Inferring the result type
|
|
79
|
+
* from the schema is also the honest description — this returns the child's
|
|
80
|
+
* answer, parsed by the schema the caller chose.
|
|
81
|
+
*/
|
|
82
|
+
const forwardLive = (request, resultSchema, options = {}) => {
|
|
35
83
|
const client = managed.client;
|
|
36
84
|
if (!client)
|
|
37
85
|
throw new Error(`Server "${managed.name}" is not running`);
|
|
38
86
|
return client
|
|
39
|
-
.request(request, resultSchema,
|
|
87
|
+
.request(request, resultSchema, {
|
|
88
|
+
...ABSOLUTE_CALL_OPTIONS,
|
|
89
|
+
...options
|
|
90
|
+
})
|
|
40
91
|
.then(assertForwardedResultSize);
|
|
41
92
|
};
|
|
42
93
|
// Real usage: wakes a sleeping on-demand server (blocking until it is up)
|
|
43
94
|
// and resets its idle window. Everything else is answered without a child.
|
|
44
|
-
const use = async (request, resultSchema) => {
|
|
95
|
+
const use = async (request, resultSchema, options = {}) => {
|
|
45
96
|
if (managed.state !== 'up' || !managed.client)
|
|
46
97
|
await managed.wake();
|
|
47
98
|
managed.markUsed();
|
|
48
|
-
return forwardLive(request, resultSchema);
|
|
99
|
+
return forwardLive(request, resultSchema, options);
|
|
49
100
|
};
|
|
50
101
|
const caps = managed.capabilities ?? {};
|
|
51
102
|
if (caps.tools) {
|
|
@@ -53,27 +104,87 @@ function buildProxyServer(managed) {
|
|
|
53
104
|
// all hub paths configured enumerates them on connect, so answering from
|
|
54
105
|
// the cached snapshot (instead of waking) is what keeps a fleet of
|
|
55
106
|
// sleeping servers asleep. Neither branch counts as usage.
|
|
56
|
-
server.setRequestHandler(
|
|
57
|
-
|
|
107
|
+
server.setRequestHandler('tools/list', async (req) => {
|
|
108
|
+
// managed.tools is filtered on the way in, but the live branch forwards
|
|
109
|
+
// the upstream's own answer and never consults it — so it has to filter
|
|
110
|
+
// too. Missing this is the obvious bug here: the filter would appear to
|
|
111
|
+
// work on a sleeping server and vanish the moment it woke.
|
|
112
|
+
if (managed.state !== 'up' || !managed.client)
|
|
113
|
+
return { tools: managed.tools };
|
|
114
|
+
const live = (await forwardLive(req, ListToolsResultSchema));
|
|
115
|
+
return { ...live, tools: filterTools(managed.config, live.tools) };
|
|
116
|
+
});
|
|
117
|
+
server.setRequestHandler('tools/call', async (req, ctx) => {
|
|
118
|
+
// Hiding is not a boundary on this path: the hub forwards by name, so a
|
|
119
|
+
// client holding a stale schema would still reach it. Refused before the
|
|
120
|
+
// forward, which is what wakes a sleeping server — so a forbidden name
|
|
121
|
+
// cannot start one. The message is the neutral one a server gives for a
|
|
122
|
+
// tool it does not have; announcing what was hidden would be a
|
|
123
|
+
// disclosure in itself.
|
|
124
|
+
if (!toolAllowed(managed.config, req.params.name)) {
|
|
125
|
+
const logged = loggableToolName(req.params.name);
|
|
126
|
+
console.warn(`[${managed.name}] refused tools/call "${logged}": not permitted by allowTools/denyTools`);
|
|
127
|
+
throw new ProtocolError(ProtocolErrorCode.MethodNotFound, `Unknown tool: ${req.params.name}`);
|
|
128
|
+
}
|
|
129
|
+
// The caller's params go on unchanged: a gateway that rewrote them would
|
|
130
|
+
// be inventing a contract the child never agreed to.
|
|
131
|
+
const result = await forwardToolCall({ managed, tool: req.params.name, params: req.params, ctx, secret, via: 'server' });
|
|
132
|
+
// A question for the person is not a tool result and has no schema to be
|
|
133
|
+
// projected against.
|
|
134
|
+
if (isInputRequiredResult(result))
|
|
135
|
+
return result;
|
|
136
|
+
/**
|
|
137
|
+
* The projection `McpServer` does for its own tools, which a hand-written
|
|
138
|
+
* handler has to do for itself.
|
|
139
|
+
*
|
|
140
|
+
* It matters for one case, and only because this is a gateway: a child
|
|
141
|
+
* whose `outputSchema` describes a non-object — an array, a number — is
|
|
142
|
+
* advertised to a 2025-era client with that schema rewritten to
|
|
143
|
+
* `{result: …}`, because that revision has nowhere else to put it. The
|
|
144
|
+
* hub was already doing that half, for free, in `encodeResult`. Without
|
|
145
|
+
* this line the *value* went out unwrapped, so the client validated an
|
|
146
|
+
* array against a schema saying "object with a result key" and rejected
|
|
147
|
+
* data that was never wrong.
|
|
148
|
+
*
|
|
149
|
+
* The advertised schema is the child's own, before the rewrite: the
|
|
150
|
+
* codec decides from its root shape whether the wrap applies at all.
|
|
151
|
+
*/
|
|
152
|
+
const advertised = managed.tools.find(t => t.name === req.params.name)?.outputSchema;
|
|
153
|
+
return server.projectCallToolResult(result, advertised);
|
|
154
|
+
});
|
|
58
155
|
}
|
|
59
156
|
if (caps.resources) {
|
|
60
|
-
server.setRequestHandler(
|
|
61
|
-
server.setRequestHandler(
|
|
62
|
-
server.setRequestHandler(
|
|
157
|
+
server.setRequestHandler('resources/list', req => use(req, ListResourcesResultSchema));
|
|
158
|
+
server.setRequestHandler('resources/templates/list', req => use(req, ListResourceTemplatesResultSchema));
|
|
159
|
+
server.setRequestHandler('resources/read', req => use(req, ReadResourceResultSchema));
|
|
63
160
|
}
|
|
64
161
|
if (caps.prompts) {
|
|
65
|
-
server.setRequestHandler(
|
|
66
|
-
server.setRequestHandler(
|
|
162
|
+
server.setRequestHandler('prompts/list', req => use(req, ListPromptsResultSchema));
|
|
163
|
+
server.setRequestHandler('prompts/get', req => use(req, GetPromptResultSchema));
|
|
67
164
|
}
|
|
68
165
|
if (caps.completions) {
|
|
69
|
-
server.setRequestHandler(
|
|
166
|
+
server.setRequestHandler('completion/complete', req => use(req, CompleteResultSchema));
|
|
70
167
|
}
|
|
71
168
|
return server;
|
|
72
169
|
}
|
|
73
|
-
/**
|
|
74
|
-
|
|
170
|
+
/**
|
|
171
|
+
* The 2025-era path, unchanged: one `Server` and one stateless transport per
|
|
172
|
+
* HTTP request, both closed when the response closes.
|
|
173
|
+
*
|
|
174
|
+
* Kept as its own function rather than folded into the modern handler's
|
|
175
|
+
* `legacy: 'stateless'` fallback, because the two are not the same on the wire.
|
|
176
|
+
* That fallback answers GET and DELETE with `405`; this transport answers a GET
|
|
177
|
+
* with an open `text/event-stream` and a DELETE with `200`. claude.ai opens such
|
|
178
|
+
* a stream on every reconnect — which is why `ClientRequestGate` counts GETs
|
|
179
|
+
* separately in the first place — so switching it to `405` would be a visible
|
|
180
|
+
* change to the one client the stateless design was built around. The stream
|
|
181
|
+
* carries nothing today, but "carries nothing" and "is refused" are different
|
|
182
|
+
* answers, and this is not the change in which to find out how clients tell them
|
|
183
|
+
* apart.
|
|
184
|
+
*/
|
|
185
|
+
async function handleLegacyRequest(buildServer, req, res) {
|
|
75
186
|
const server = buildServer();
|
|
76
|
-
const transport = new
|
|
187
|
+
const transport = new NodeStreamableHTTPServerTransport({
|
|
77
188
|
sessionIdGenerator: undefined,
|
|
78
189
|
enableJsonResponse: true
|
|
79
190
|
});
|
|
@@ -84,8 +195,120 @@ export async function handleMcpRequest(buildServer, req, res) {
|
|
|
84
195
|
await server.connect(transport);
|
|
85
196
|
await transport.handleRequest(req, res, req.body);
|
|
86
197
|
}
|
|
198
|
+
export function createRoute(buildServer, options) {
|
|
199
|
+
const allowSubscriptions = options.allowSubscriptions ?? (() => true);
|
|
200
|
+
const handler = createMcpHandler(() => buildServer('modern'), {
|
|
201
|
+
// Strict: the legacy era is served below, by the transport that has always
|
|
202
|
+
// served it. A second, differently-behaving legacy path underneath this one
|
|
203
|
+
// would be unreachable and misleading.
|
|
204
|
+
legacy: 'reject',
|
|
205
|
+
// Set rather than inherited. These bound what one route may hold open on
|
|
206
|
+
// the hub's behalf, and a limit nobody wrote down is a limit nobody can
|
|
207
|
+
// raise when a deployment needs it.
|
|
208
|
+
maxSubscriptions: MAX_SUBSCRIPTIONS,
|
|
209
|
+
keepAliveMs: KEEPALIVE_MS,
|
|
210
|
+
onerror: error => console.error(`mcp-hub: modern-era request failed on ${options.label}: ${error.message}`)
|
|
211
|
+
});
|
|
212
|
+
const registry = new SubscriptionRegistry(handler.notify, { onDemandChange: options.onDemandChange });
|
|
213
|
+
return {
|
|
214
|
+
handler,
|
|
215
|
+
registry,
|
|
216
|
+
allowSubscriptions,
|
|
217
|
+
buildLegacyServer: () => buildServer('legacy'),
|
|
218
|
+
close: async () => {
|
|
219
|
+
registry.close();
|
|
220
|
+
await handler.close();
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Take a lease for a `subscriptions/listen` stream, or answer it outright.
|
|
226
|
+
*
|
|
227
|
+
* The filter has to be read here rather than left to the SDK: the event bus is
|
|
228
|
+
* publish-only and never reports which URIs a stream asked for, so this is the
|
|
229
|
+
* hub's only chance to learn what to hold upstream. The SDK still parses the
|
|
230
|
+
* request properly a moment later and owns every error answer — the one refusal
|
|
231
|
+
* made here is the URI cap, which is the hub's limit and nobody else's.
|
|
232
|
+
*
|
|
233
|
+
* Returns false when the request has already been answered.
|
|
234
|
+
*/
|
|
235
|
+
function beginListen(route, req, res) {
|
|
236
|
+
const filter = parseListenFilter(req.body);
|
|
237
|
+
if (filter.resourceSubscriptions.length > MAX_SUBSCRIPTION_URIS) {
|
|
238
|
+
res.status(400).json({
|
|
239
|
+
jsonrpc: '2.0',
|
|
240
|
+
error: { code: -32602, message: `A subscription may name at most ${MAX_SUBSCRIPTION_URIS} resource URIs` },
|
|
241
|
+
id: null
|
|
242
|
+
});
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
// Node's server-wide requestTimeout (310s by default) would otherwise cut
|
|
246
|
+
// every listen stream a few minutes in, which looks exactly like a flaky
|
|
247
|
+
// upstream and is not. A listening stream is idle by design; its lifetime is
|
|
248
|
+
// bounded by MAX_STREAM_MS below instead.
|
|
249
|
+
req.setTimeout(0);
|
|
250
|
+
const reaper = MAX_STREAM_MS > 0
|
|
251
|
+
? setTimeout(() => {
|
|
252
|
+
// Ends the stream without the specification's graceful completion
|
|
253
|
+
// result — the SDK owns the stream and offers no hook for one. The
|
|
254
|
+
// client sees the same unexpected disconnect an HTTP timeout would
|
|
255
|
+
// produce, which the specification names as a way a subscription
|
|
256
|
+
// ends, and re-listens.
|
|
257
|
+
res.end();
|
|
258
|
+
}, MAX_STREAM_MS)
|
|
259
|
+
: undefined;
|
|
260
|
+
reaper?.unref();
|
|
261
|
+
// A server whose operator switched subscriptions off advertises nothing and
|
|
262
|
+
// is asked for nothing, so the stream stays open and silent rather than
|
|
263
|
+
// taking a lease that would make the hub subscribe upstream on its behalf.
|
|
264
|
+
const lease = route.allowSubscriptions() ? route.registry.acquire(filter) : undefined;
|
|
265
|
+
let done = false;
|
|
266
|
+
const release = () => {
|
|
267
|
+
if (done)
|
|
268
|
+
return;
|
|
269
|
+
done = true;
|
|
270
|
+
clearTimeout(reaper);
|
|
271
|
+
lease?.release();
|
|
272
|
+
};
|
|
273
|
+
res.once('close', release);
|
|
274
|
+
res.once('finish', release);
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Handle one MCP request in whichever protocol era it arrives in.
|
|
279
|
+
*
|
|
280
|
+
* `isLegacyRequest` decides, and its contract is one-way: everything it calls
|
|
281
|
+
* false — including a malformed envelope or a header/body mismatch — belongs to
|
|
282
|
+
* the modern handler, which owns those error answers. Only what it calls true
|
|
283
|
+
* may reach the legacy path.
|
|
284
|
+
*/
|
|
285
|
+
export async function handleMcpRequest(route, req, res) {
|
|
286
|
+
const probe = await toWebRequest(req, req.body);
|
|
287
|
+
if (await isLegacyRequest(probe)) {
|
|
288
|
+
await handleLegacyRequest(route.buildLegacyServer, req, res);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (isListenRequest(req.body) && !beginListen(route, req, res))
|
|
292
|
+
return;
|
|
293
|
+
await toNodeHandler(route.handler)(req, res, req.body);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* The route behind each server path, created on first use and closed with the
|
|
297
|
+
* server. Held here rather than constructed per request because the handler
|
|
298
|
+
* inside owns open subscription streams.
|
|
299
|
+
*/
|
|
300
|
+
function routeFor(managed, secret) {
|
|
301
|
+
if (!managed.channel) {
|
|
302
|
+
managed.channel = createRoute(era => buildProxyServer(managed, secret, era), {
|
|
303
|
+
label: `/${managed.name}`,
|
|
304
|
+
onDemandChange: () => managed.reconcileSubscriptions(),
|
|
305
|
+
allowSubscriptions: () => subscriptionsAllowed(managed.config)
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return managed.channel;
|
|
309
|
+
}
|
|
87
310
|
/** Express handler for /<name> and /<name>/mcp. */
|
|
88
|
-
export function serverRequestHandler(managed) {
|
|
311
|
+
export function serverRequestHandler(managed, secret) {
|
|
89
312
|
return async (req, res) => {
|
|
90
313
|
if (managed.state !== 'up' || !managed.client) {
|
|
91
314
|
if (!managed.onDemand) {
|
|
@@ -108,7 +331,12 @@ export function serverRequestHandler(managed) {
|
|
|
108
331
|
// With a snapshot the proxy server is built from cached identity and
|
|
109
332
|
// capabilities; its handlers wake the child only for real usage.
|
|
110
333
|
}
|
|
111
|
-
|
|
334
|
+
// Deliberately not `use()`: opening a listen stream is not usage. A client
|
|
335
|
+
// that subscribes to a sleeping child gets an honest acknowledgment built
|
|
336
|
+
// from the cached capabilities — they survive the nap — and delivery
|
|
337
|
+
// resumes when something actually wakes it. Waking on subscribe would undo
|
|
338
|
+
// on-demand for every server anyone ever watched.
|
|
339
|
+
await handleMcpRequest(routeFor(managed, secret), req, res);
|
|
112
340
|
};
|
|
113
341
|
}
|
|
114
342
|
//# sourceMappingURL=proxy.js.map
|
package/dist/proxy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
|
package/dist/stdio.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { randomBytes } from 'node:crypto';
|
|
5
|
+
import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
|
5
6
|
import { loadConfig, parseConfig, ConfigWatcher, warnMutableDockerImages } from './config.js';
|
|
6
|
-
import { Supervisor } from './supervisor.js';
|
|
7
|
+
import { Supervisor, UpstreamAuthRegistry } from './supervisor.js';
|
|
8
|
+
import { AuthStore } from './auth/store.js';
|
|
7
9
|
import { ToolCache } from './tool-cache.js';
|
|
8
10
|
import { buildHubServer } from './hub.js';
|
|
9
11
|
import { isMainModule } from './main-module.js';
|
|
12
|
+
import { SubscriptionRegistry } from './subscriptions.js';
|
|
13
|
+
import { IDLE_TIMEOUT_MS } from './timings.js';
|
|
10
14
|
/**
|
|
11
15
|
* Everything the hub writes with console.log/info would land in the middle of
|
|
12
16
|
* the JSON-RPC stream and desynchronise the client, so both are moved to
|
|
@@ -51,6 +55,29 @@ function loadOrEmpty(configPath) {
|
|
|
51
55
|
* Codex, …). Everything HTTP-only — OAuth, tokens, rate limiting, /health —
|
|
52
56
|
* has no counterpart here; the trust boundary is the local user account.
|
|
53
57
|
*/
|
|
58
|
+
/**
|
|
59
|
+
* Outbound OAuth only works here when a state directory was given: there is
|
|
60
|
+
* nowhere else to read a token from, and no HTTP listener for a browser to
|
|
61
|
+
* come back to. Without one, such a server simply cannot connect, so say why
|
|
62
|
+
* once rather than leaving an unexplained failure in the log.
|
|
63
|
+
*/
|
|
64
|
+
function buildUpstreamAuth(config, dataPath) {
|
|
65
|
+
const oauthServers = [...config].filter(([, server]) => server.kind === 'remote' && server.oauth !== undefined);
|
|
66
|
+
if (oauthServers.length === 0)
|
|
67
|
+
return undefined;
|
|
68
|
+
if (!dataPath) {
|
|
69
|
+
console.warn(`mcp-hub: ${oauthServers.map(([name]) => name).join(', ')} need an upstream OAuth token, but this mode has no state directory. ` +
|
|
70
|
+
'Set DATA_PATH to the hub\'s /data to reuse a token authorized there.');
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const store = new AuthStore(dataPath);
|
|
74
|
+
const externalUrl = store.getExternalUrl();
|
|
75
|
+
if (!externalUrl) {
|
|
76
|
+
console.warn(`mcp-hub: ${dataPath} holds no upstream credentials yet; authorize them against the HTTP hub first`);
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
return new UpstreamAuthRegistry(store, externalUrl);
|
|
80
|
+
}
|
|
54
81
|
export function createStdioHub(options) {
|
|
55
82
|
const config = loadOrEmpty(options.configPath);
|
|
56
83
|
warnMutableDockerImages(config);
|
|
@@ -62,14 +89,17 @@ export function createStdioHub(options) {
|
|
|
62
89
|
// exactly the cost this feature exists to avoid. The snapshot lives next to
|
|
63
90
|
// the config rather than in a DATA_PATH — there is no state directory in
|
|
64
91
|
// this mode, and no other state to put in one.
|
|
92
|
+
// IDLE_TIMEOUT_MS is the sub-minute sibling of the documented knob; see timings.ts.
|
|
93
|
+
const idleTimeoutMs = IDLE_TIMEOUT_MS || idleTimeoutMinutes * 60_000;
|
|
65
94
|
const cache = new ToolCache(options.toolCachePath ?? path.join(path.dirname(options.configPath), '.mcp-hub', 'tool-cache.json'));
|
|
66
|
-
if (
|
|
95
|
+
if (idleTimeoutMs > 0) {
|
|
67
96
|
cache.load();
|
|
68
97
|
if (!cache.probeWritable()) {
|
|
69
98
|
console.warn(`mcp-hub: tool cache ${cache.filePath} is not writable — on-demand servers warm-start at every launch instead of sleeping through it`);
|
|
70
99
|
}
|
|
71
100
|
}
|
|
72
|
-
const
|
|
101
|
+
const upstreamAuth = buildUpstreamAuth(config, options.dataPath);
|
|
102
|
+
const supervisor = new Supervisor(config, { idleTimeoutMinutes, idleTimeoutMs, cache, upstreamAuth });
|
|
73
103
|
supervisor.start(); // children come up (or hydrate into `sleeping`) in the background
|
|
74
104
|
void supervisor
|
|
75
105
|
.reapOrphans()
|
|
@@ -89,16 +119,76 @@ export function createStdioHub(options) {
|
|
|
89
119
|
else {
|
|
90
120
|
console.warn(`mcp-hub: ${path.dirname(options.configPath)} does not exist — config hot reload is off`);
|
|
91
121
|
}
|
|
92
|
-
|
|
122
|
+
// The seal on a half-finished tool call, which in this mode only ever has to
|
|
123
|
+
// survive between two legs of the same connection. There is no /data here and
|
|
124
|
+
// nothing to persist: the process IS the session, so a key drawn once at
|
|
125
|
+
// start is not a shortcut but the right lifetime. When the process ends there
|
|
126
|
+
// is no question left to resume.
|
|
127
|
+
const requestStateSecret = randomBytes(32).toString('base64url');
|
|
128
|
+
// A factory rather than an instance: the entry point below decides the era
|
|
129
|
+
// from the opening exchange and pins one instance to the connection, and it
|
|
130
|
+
// can only do that if it owns the construction.
|
|
131
|
+
return { build: (era) => buildHubServer(supervisor, requestStateSecret, era), supervisor, watcher };
|
|
93
132
|
}
|
|
94
133
|
export async function runStdio(options) {
|
|
95
134
|
redirectStdoutLogging();
|
|
96
|
-
const {
|
|
97
|
-
|
|
135
|
+
const { build, supervisor, watcher } = createStdioHub(options);
|
|
136
|
+
// serveStdio rather than server.connect(new StdioServerTransport()): the
|
|
137
|
+
// protocol era is decided by the opening exchange, and only this entry point
|
|
138
|
+
// owns that decision. With a hand-wired transport the connection is 2025 for
|
|
139
|
+
// its whole life, and a child's `input_required` would be answered by the
|
|
140
|
+
// SDK's legacy shim instead of reaching the person at the keyboard.
|
|
141
|
+
//
|
|
142
|
+
// That matters most here of all the hub's doors. Over HTTP the hub is
|
|
143
|
+
// stateless and shared, so a 2025 client has nowhere to receive a question;
|
|
144
|
+
// a workstation hub is spawned per client session and belongs to exactly one
|
|
145
|
+
// person, which is precisely the case where asking them is worth something.
|
|
146
|
+
//
|
|
147
|
+
// legacy: 'serve' is the default and is documented as serving a 2025 opening
|
|
148
|
+
// "exactly as a hand-wired stdio server serves it today" — so nothing changes
|
|
149
|
+
// for a client that speaks the old era. What is new is only that a modern
|
|
150
|
+
// opening is now accepted instead of being answered in the old one.
|
|
151
|
+
// The aggregate's tool list moves whenever a child's does, and over stdio the
|
|
152
|
+
// hub cannot see the client's listen filter: `serveStdio` owns the request and
|
|
153
|
+
// offers no hook for one. So it holds a standing lease instead — one client,
|
|
154
|
+
// one connection, one thing worth watching.
|
|
155
|
+
//
|
|
156
|
+
// That is not a way of pushing at someone who did not ask.
|
|
157
|
+
// `sendToolListChanged()` reaches only the subscriptions actually open, so a
|
|
158
|
+
// client that never sent `subscriptions/listen` receives nothing; the lease
|
|
159
|
+
// only decides whether the hub bothers to watch the children upstream.
|
|
160
|
+
let registry;
|
|
161
|
+
const handle = serveStdio(ctx => {
|
|
162
|
+
// The era the opening exchange settled on. serveStdio hands it over for
|
|
163
|
+
// exactly this: an instance may differ by the era it will serve.
|
|
164
|
+
const hub = build(ctx.era);
|
|
165
|
+
registry?.close();
|
|
166
|
+
registry = new SubscriptionRegistry({
|
|
167
|
+
toolsChanged: () => hub.sendToolListChanged(),
|
|
168
|
+
// /hub aggregates tools and nothing else, so the other three describe
|
|
169
|
+
// nothing a client of this endpoint could read.
|
|
170
|
+
promptsChanged: () => { },
|
|
171
|
+
resourcesChanged: () => { },
|
|
172
|
+
resourceUpdated: () => { }
|
|
173
|
+
}, { onDemandChange: () => supervisor.reconcileAll() });
|
|
174
|
+
registry.acquire({
|
|
175
|
+
toolsListChanged: true,
|
|
176
|
+
promptsListChanged: false,
|
|
177
|
+
resourcesListChanged: false,
|
|
178
|
+
resourceSubscriptions: []
|
|
179
|
+
});
|
|
180
|
+
supervisor.hubSubscriptions = registry;
|
|
181
|
+
return hub;
|
|
182
|
+
}, {
|
|
183
|
+
legacy: 'serve',
|
|
184
|
+
onerror: error => console.error(`mcp-hub: stdio connection error: ${error.message}`)
|
|
185
|
+
});
|
|
98
186
|
console.error(`mcp-hub: serving the hub aggregate over stdio (config ${options.configPath})`);
|
|
99
187
|
const shutdown = async (signal) => {
|
|
100
188
|
console.error(`mcp-hub: received ${signal}, shutting down`);
|
|
101
189
|
watcher.stop();
|
|
190
|
+
registry?.close();
|
|
191
|
+
await handle.close();
|
|
102
192
|
await supervisor.stop();
|
|
103
193
|
process.exit(0);
|
|
104
194
|
};
|
package/dist/stdio.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":"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"}
|