@ni-c/mcp-hub 0.9.2 → 0.10.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 +231 -0
- package/README.md +114 -54
- package/dist/admin.js +231 -7
- 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/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/login-page.js +4 -7
- package/dist/auth/login-page.js.map +1 -1
- 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/private-key-jwt.js +213 -0
- package/dist/auth/private-key-jwt.js.map +1 -0
- package/dist/auth/provider.js +102 -45
- package/dist/auth/provider.js.map +1 -1
- package/dist/auth/rate-limit.js +43 -0
- package/dist/auth/rate-limit.js.map +1 -0
- package/dist/auth/redirect-uri.js +58 -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/routes.js +67 -41
- package/dist/auth/routes.js.map +1 -1
- package/dist/auth/signed-token.js +49 -0
- package/dist/auth/signed-token.js.map +1 -0
- package/dist/auth/store.js +357 -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 +130 -4
- package/dist/config.js.map +1 -1
- package/dist/health.js +22 -3
- package/dist/health.js.map +1 -1
- package/dist/hub.js +28 -0
- package/dist/hub.js.map +1 -1
- package/dist/index.js +123 -10
- package/dist/index.js.map +1 -1
- package/dist/proxy.js +25 -3
- package/dist/proxy.js.map +1 -1
- package/dist/stdio.js +27 -2
- package/dist/stdio.js.map +1 -1
- package/dist/supervisor.js +152 -8
- package/dist/supervisor.js.map +1 -1
- package/dist/tool-filter.js +58 -0
- package/dist/tool-filter.js.map +1 -0
- 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 +96 -0
- package/dist/upstream/routes.js.map +1 -0
- package/package.json +5 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-filter.js","sourceRoot":"","sources":["../src/tool-filter.ts"],"names":[],"mappings":"AAmBA,iDAAiD;AACjD,MAAM,UAAU,aAAa,CAAC,MAAwB;IACpD,OAAO,MAAM,CAAC,UAAU,KAAK,SAAS,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC;AAC3E,CAAC;AAED,SAAS,OAAO,CAAC,QAA2B,EAAE,IAAY;IACxD,OAAO,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC;AACtH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,MAAwB,EAAE,IAAY;IAChE,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACvF,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACpF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,WAAW,CAAC,MAAwB,EAAE,KAAa;IACjE,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IACzC,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAwB,EAAE,KAAa;IACvE,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CACvE,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC,CACzD,CAAC;AACJ,CAAC;AAED,2FAA2F;AAC3F,MAAM,kBAAkB,GAAG,wBAAwB,CAAC;AACpD,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;IACnD,OAAO,IAAI,CAAC,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AACvF,CAAC"}
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import { discoverOAuthServerInfo, exchangeAuthorization, refreshAuthorization, startAuthorization } from '@modelcontextprotocol/sdk/client/auth.js';
|
|
2
|
+
import { isPrivateAddress, resolvePublicAddress } from '../auth/address.js';
|
|
3
|
+
import { guardedRequest } from '../auth/pinned-fetch.js';
|
|
4
|
+
import { logSafe } from '../auth/text.js';
|
|
5
|
+
import { UpstreamAuthProvider, callbackUrl, credentialFingerprint, hubClientMetadata } from './provider.js';
|
|
6
|
+
/**
|
|
7
|
+
* Everything the hub needs to authenticate itself to one upstream MCP server.
|
|
8
|
+
*
|
|
9
|
+
* The SDK's `auth()` orchestrator is deliberately not used to drive this. Two
|
|
10
|
+
* reasons, both verified against the SDK:
|
|
11
|
+
*
|
|
12
|
+
* It has no single-flight anywhere, so parallel requests that each see a 401
|
|
13
|
+
* each refresh, and an authorization server with rotating refresh tokens
|
|
14
|
+
* revokes the whole family when the losers replay. Refresh is therefore ours.
|
|
15
|
+
*
|
|
16
|
+
* And the `fetch` given to the transport is the *same object* the SDK hands to
|
|
17
|
+
* `auth()` as `fetchFn` — `createFetchWithInit` returns the base fetch
|
|
18
|
+
* unchanged when there is no `requestInit`. So one function has to serve both
|
|
19
|
+
* the MCP data plane and the authorization server, and it is the classifier
|
|
20
|
+
* below that tells them apart.
|
|
21
|
+
*/
|
|
22
|
+
/** Nothing the hub can do without a human: no credentials, or a refresh token
|
|
23
|
+
* the upstream will not honour any more. */
|
|
24
|
+
export class UpstreamLoginRequiredError extends Error {
|
|
25
|
+
serverName;
|
|
26
|
+
constructor(serverName, reason) {
|
|
27
|
+
super(reason);
|
|
28
|
+
this.serverName = serverName;
|
|
29
|
+
this.name = 'UpstreamLoginRequiredError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Refresh a little before the token actually dies, to absorb clock skew. */
|
|
33
|
+
const EXPIRY_SKEW_S = 60;
|
|
34
|
+
/** Discovery is stable; re-deriving it on every connection is pure latency. */
|
|
35
|
+
const DISCOVERY_TTL_S = 3600;
|
|
36
|
+
const AS_MAX_BYTES = 256 * 1024;
|
|
37
|
+
const AS_TIMEOUT_MS = 10_000;
|
|
38
|
+
export class UpstreamAuth {
|
|
39
|
+
config;
|
|
40
|
+
store;
|
|
41
|
+
identity;
|
|
42
|
+
/** Which configuration a stored credential has to belong to. */
|
|
43
|
+
fingerprint;
|
|
44
|
+
/** Collapses concurrent token work into one request. Without it a burst of
|
|
45
|
+
* tool calls would each refresh with the same rotating token. */
|
|
46
|
+
inFlight;
|
|
47
|
+
/**
|
|
48
|
+
* Whether the authorization server may live on a private address.
|
|
49
|
+
*
|
|
50
|
+
* Derived from the upstream itself rather than fixed: refusing private
|
|
51
|
+
* addresses outright is right when an untrusted party chose the URL, but here
|
|
52
|
+
* the operator did — and the common self-hosted shape is an internal MCP
|
|
53
|
+
* server behind an internal Keycloak on the same Docker network. A *public*
|
|
54
|
+
* upstream pointing discovery at `169.254.169.254` is still an attack, and
|
|
55
|
+
* still refused.
|
|
56
|
+
*/
|
|
57
|
+
allowPrivate;
|
|
58
|
+
constructor(serverName, config, store, externalUrl) {
|
|
59
|
+
this.config = config;
|
|
60
|
+
this.store = store;
|
|
61
|
+
this.identity = { serverName, serverUrl: config.url, oauth: config.oauth, externalUrl };
|
|
62
|
+
this.fingerprint = credentialFingerprint(this.identity);
|
|
63
|
+
}
|
|
64
|
+
provider(options = {}) {
|
|
65
|
+
return new UpstreamAuthProvider(this.identity, this.store, options);
|
|
66
|
+
}
|
|
67
|
+
get record() {
|
|
68
|
+
return this.store.getUpstreamCredentials(this.identity.serverName, this.fingerprint);
|
|
69
|
+
}
|
|
70
|
+
/** The full pair including the refresh token: this class is the only thing
|
|
71
|
+
* allowed to spend it, which is why the provider withholds it. */
|
|
72
|
+
tokens() {
|
|
73
|
+
return this.record?.tokens;
|
|
74
|
+
}
|
|
75
|
+
/** The public key the upstream needs, but only when we sign assertions. */
|
|
76
|
+
async publicJwkIfNeeded() {
|
|
77
|
+
return this.identity.oauth.clientAuth === 'private_key_jwt' ? await this.provider().publicJwk() : undefined;
|
|
78
|
+
}
|
|
79
|
+
/** True when the upstream's own host is private, so its authorization server
|
|
80
|
+
* is allowed to be too. Resolved once. */
|
|
81
|
+
async privateAllowed() {
|
|
82
|
+
if (this.allowPrivate === undefined) {
|
|
83
|
+
const hostname = new URL(this.identity.serverUrl).hostname.replace(/^\[|\]$/g, '');
|
|
84
|
+
try {
|
|
85
|
+
this.allowPrivate = isPrivateAddress(hostname) || (await resolvePublicAddress(hostname).then(() => false, () => true));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
this.allowPrivate = true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return this.allowPrivate;
|
|
92
|
+
}
|
|
93
|
+
/** The guarded fetch used for every authorization-server request: no static
|
|
94
|
+
* headers, no bearer, address-checked, pinned, capped, no redirects. */
|
|
95
|
+
async asFetch(input, init) {
|
|
96
|
+
const url = new URL(input instanceof Request ? input.url : String(input));
|
|
97
|
+
const allowPrivate = await this.privateAllowed();
|
|
98
|
+
const pinned = await resolvePublicAddress(url.hostname, allowPrivate);
|
|
99
|
+
if (!pinned) {
|
|
100
|
+
return fetch(url, { ...init, redirect: 'error', signal: AbortSignal.timeout(AS_TIMEOUT_MS) });
|
|
101
|
+
}
|
|
102
|
+
return guardedRequest(url, {
|
|
103
|
+
pinnedAddress: pinned,
|
|
104
|
+
method: init?.method ?? 'GET',
|
|
105
|
+
headers: toRecord(init?.headers),
|
|
106
|
+
body: typeof init?.body === 'string' ? init.body : init?.body instanceof URLSearchParams ? init.body.toString() : undefined,
|
|
107
|
+
timeoutMs: AS_TIMEOUT_MS,
|
|
108
|
+
maxBytes: AS_MAX_BYTES
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
boundAsFetch = (input, init) => this.asFetch(input, init);
|
|
112
|
+
// --- Discovery and client identity ---------------------------------------
|
|
113
|
+
async discover(force = false) {
|
|
114
|
+
const cached = this.record?.discovery;
|
|
115
|
+
const now = Math.floor(Date.now() / 1000);
|
|
116
|
+
if (!force && cached?.authorizationServerUrl && now - cached.fetchedAt < DISCOVERY_TTL_S)
|
|
117
|
+
return cached;
|
|
118
|
+
const info = await discoverOAuthServerInfo(this.identity.serverUrl, { fetchFn: this.boundAsFetch });
|
|
119
|
+
const discovery = { ...info, fetchedAt: now };
|
|
120
|
+
this.provider().saveDiscoveryState(discovery);
|
|
121
|
+
return discovery;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The client_id this hub uses with this upstream, registering dynamically the
|
|
125
|
+
* first time when that is the configured mode.
|
|
126
|
+
*/
|
|
127
|
+
async clientInformation(discovery) {
|
|
128
|
+
const { oauth } = this.identity;
|
|
129
|
+
if (oauth.mode === 'static') {
|
|
130
|
+
return { client_id: oauth.clientId, ...(oauth.clientSecret ? { client_secret: oauth.clientSecret } : {}) };
|
|
131
|
+
}
|
|
132
|
+
const existing = this.record;
|
|
133
|
+
if (existing?.clientId) {
|
|
134
|
+
return { client_id: existing.clientId, ...(existing.clientSecret ? { client_secret: existing.clientSecret } : {}) };
|
|
135
|
+
}
|
|
136
|
+
if (oauth.mode === 'cimd') {
|
|
137
|
+
if (discovery.authorizationServerMetadata?.client_id_metadata_document_supported !== true) {
|
|
138
|
+
throw new UpstreamLoginRequiredError(this.identity.serverName, 'the upstream does not accept client ID metadata documents; use mode "dcr" or "static"');
|
|
139
|
+
}
|
|
140
|
+
const clientId = this.provider().clientMetadataUrl;
|
|
141
|
+
this.provider().saveClientInformation({ client_id: clientId });
|
|
142
|
+
return { client_id: clientId };
|
|
143
|
+
}
|
|
144
|
+
const registered = await this.registerDynamically(discovery);
|
|
145
|
+
this.provider().saveClientInformation(registered);
|
|
146
|
+
console.log(`mcp-hub: registered with ${logSafe(discovery.authorizationServerUrl)} for upstream "${logSafe(this.identity.serverName)}"`);
|
|
147
|
+
return registered;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* RFC 7591 registration, done here rather than through the SDK's
|
|
151
|
+
* `registerClient`.
|
|
152
|
+
*
|
|
153
|
+
* That helper parses the response with `OAuthClientInformationFullSchema`,
|
|
154
|
+
* which strips unknown fields — including `registration_access_token` and
|
|
155
|
+
* `registration_client_uri`. Those are exactly what `upstream logout` needs
|
|
156
|
+
* to delete the registration again (RFC 7592), so they have to survive.
|
|
157
|
+
*/
|
|
158
|
+
async registerDynamically(discovery) {
|
|
159
|
+
const endpoint = discovery.authorizationServerMetadata?.registration_endpoint;
|
|
160
|
+
if (!endpoint) {
|
|
161
|
+
throw new UpstreamLoginRequiredError(this.identity.serverName, 'the upstream offers no dynamic client registration; use mode "static" with a client_id it issued you');
|
|
162
|
+
}
|
|
163
|
+
const response = await this.asFetch(new URL(endpoint), {
|
|
164
|
+
method: 'POST',
|
|
165
|
+
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
166
|
+
body: JSON.stringify(hubClientMetadata(this.identity, await this.publicJwkIfNeeded()))
|
|
167
|
+
});
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
const detail = await response.text().catch(() => '');
|
|
170
|
+
throw new UpstreamLoginRequiredError(this.identity.serverName, `dynamic registration failed with HTTP ${response.status}${detail ? `: ${errorCode(detail)}` : ''}`);
|
|
171
|
+
}
|
|
172
|
+
const registered = (await response.json());
|
|
173
|
+
if (typeof registered.client_id !== 'string') {
|
|
174
|
+
throw new UpstreamLoginRequiredError(this.identity.serverName, 'the upstream returned a registration without a client_id');
|
|
175
|
+
}
|
|
176
|
+
return registered;
|
|
177
|
+
}
|
|
178
|
+
// --- Tokens ---------------------------------------------------------------
|
|
179
|
+
/** Seconds until the stored access token should be replaced; negative when it
|
|
180
|
+
* already should have been. Infinity when the upstream gave no expiry. */
|
|
181
|
+
secondsLeft() {
|
|
182
|
+
const validUntil = this.record?.accessTokenValidUntil;
|
|
183
|
+
if (validUntil === undefined)
|
|
184
|
+
return Number.POSITIVE_INFINITY;
|
|
185
|
+
return validUntil - EXPIRY_SKEW_S - Math.floor(Date.now() / 1000);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Makes sure a usable access token is on disk, doing nothing when one already
|
|
189
|
+
* is. Every caller funnels through here, so a burst produces one request.
|
|
190
|
+
*
|
|
191
|
+
* `staleAccessToken` is what a 401 handler passes: the token that was just
|
|
192
|
+
* refused. If the stored token is no longer that one, somebody else has
|
|
193
|
+
* already replaced it and there is nothing to do — without that check a
|
|
194
|
+
* straggler arriving just after a refresh completed would spend the rotating
|
|
195
|
+
* refresh token a second time, which is exactly what the upstream treats as
|
|
196
|
+
* replay.
|
|
197
|
+
*/
|
|
198
|
+
async prepare(options = {}) {
|
|
199
|
+
const usable = Boolean(this.tokens()?.access_token) && this.secondsLeft() > 0;
|
|
200
|
+
if (options.staleAccessToken !== undefined) {
|
|
201
|
+
if (this.tokens()?.access_token !== options.staleAccessToken)
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
else if (!options.force && usable) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (this.inFlight)
|
|
208
|
+
return this.inFlight;
|
|
209
|
+
this.inFlight = this.obtain(true).finally(() => {
|
|
210
|
+
this.inFlight = undefined;
|
|
211
|
+
});
|
|
212
|
+
return this.inFlight;
|
|
213
|
+
}
|
|
214
|
+
async obtain(force) {
|
|
215
|
+
// Another process may have refreshed while we queued; adopt rather than
|
|
216
|
+
// spend the refresh token a second time.
|
|
217
|
+
if (!force && this.tokens()?.access_token && this.secondsLeft() > 0)
|
|
218
|
+
return;
|
|
219
|
+
const discovery = await this.discover();
|
|
220
|
+
const clientInformation = await this.clientInformation(discovery);
|
|
221
|
+
const resource = discovery.resourceMetadata?.resource ? new URL(discovery.resourceMetadata.resource) : undefined;
|
|
222
|
+
if (this.identity.oauth.grant === 'client_credentials') {
|
|
223
|
+
const tokens = await this.fetchClientCredentialsTokens(discovery, clientInformation, resource);
|
|
224
|
+
this.provider().saveTokens(tokens);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const refreshToken = this.tokens()?.refresh_token;
|
|
228
|
+
if (!refreshToken) {
|
|
229
|
+
throw new UpstreamLoginRequiredError(this.identity.serverName, 'no upstream tokens are stored');
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
const tokens = await refreshAuthorization(discovery.authorizationServerUrl, {
|
|
233
|
+
metadata: discovery.authorizationServerMetadata,
|
|
234
|
+
clientInformation,
|
|
235
|
+
refreshToken,
|
|
236
|
+
resource,
|
|
237
|
+
addClientAuthentication: this.provider().addClientAuthentication,
|
|
238
|
+
fetchFn: this.boundAsFetch
|
|
239
|
+
});
|
|
240
|
+
this.provider().saveTokens(tokens);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
// A refresh token the upstream will not honour cannot be recovered from
|
|
244
|
+
// without a human, so say so rather than restarting forever.
|
|
245
|
+
throw new UpstreamLoginRequiredError(this.identity.serverName, `refresh failed: ${error.message}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async fetchClientCredentialsTokens(discovery, clientInformation, resource) {
|
|
249
|
+
// The SDK has no client_credentials helper of its own that we can call
|
|
250
|
+
// without going through auth(); the request is three fields.
|
|
251
|
+
const provider = this.provider();
|
|
252
|
+
const params = provider.prepareTokenRequest();
|
|
253
|
+
if (resource)
|
|
254
|
+
params.set('resource', resource.href);
|
|
255
|
+
const tokenUrl = new URL(discovery.authorizationServerMetadata?.token_endpoint ?? new URL('/token', discovery.authorizationServerUrl).href);
|
|
256
|
+
const headers = {
|
|
257
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
258
|
+
accept: 'application/json'
|
|
259
|
+
};
|
|
260
|
+
if (this.identity.oauth.clientAuth === 'private_key_jwt') {
|
|
261
|
+
// Same hook the authorization-code paths use, so there is one place that
|
|
262
|
+
// decides how this hub proves itself.
|
|
263
|
+
await provider.addClientAuthentication(new Headers(), params, tokenUrl);
|
|
264
|
+
}
|
|
265
|
+
else if (clientInformation.client_secret) {
|
|
266
|
+
const basic = Buffer.from(`${clientInformation.client_id}:${clientInformation.client_secret}`).toString('base64');
|
|
267
|
+
headers.authorization = `Basic ${basic}`;
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
params.set('client_id', clientInformation.client_id);
|
|
271
|
+
}
|
|
272
|
+
const response = await this.asFetch(tokenUrl, { method: 'POST', headers, body: params.toString() });
|
|
273
|
+
if (!response.ok) {
|
|
274
|
+
const detail = await response.text().catch(() => '');
|
|
275
|
+
throw new UpstreamLoginRequiredError(this.identity.serverName, `client_credentials token request failed with HTTP ${response.status}${detail ? `: ${errorCode(detail)}` : ''}`);
|
|
276
|
+
}
|
|
277
|
+
return (await response.json());
|
|
278
|
+
}
|
|
279
|
+
// --- The interactive login, driven by the admin CLI -----------------------
|
|
280
|
+
/** Everything the operator has to be sent to, plus what the callback will
|
|
281
|
+
* need to finish. The caller persists the login and prints the URL. */
|
|
282
|
+
async startLogin(signedState) {
|
|
283
|
+
const discovery = await this.discover(true);
|
|
284
|
+
const clientInformation = await this.clientInformation(discovery);
|
|
285
|
+
const scope = this.identity.oauth.scopes.join(' ') || undefined;
|
|
286
|
+
const { authorizationUrl, codeVerifier } = await startAuthorization(discovery.authorizationServerUrl, {
|
|
287
|
+
metadata: discovery.authorizationServerMetadata,
|
|
288
|
+
clientInformation,
|
|
289
|
+
redirectUrl: callbackUrl(this.identity.externalUrl),
|
|
290
|
+
scope,
|
|
291
|
+
state: signedState,
|
|
292
|
+
resource: discovery.resourceMetadata?.resource ? new URL(discovery.resourceMetadata.resource) : undefined
|
|
293
|
+
});
|
|
294
|
+
return {
|
|
295
|
+
authorizationUrl: authorizationUrl.href,
|
|
296
|
+
login: {
|
|
297
|
+
serverName: this.identity.serverName,
|
|
298
|
+
codeVerifier,
|
|
299
|
+
authorizationServerUrl: discovery.authorizationServerUrl,
|
|
300
|
+
...(discovery.resourceMetadata?.resource ? { resourceMetadataUrl: discovery.resourceMetadata.resource } : {}),
|
|
301
|
+
...(scope ? { scope } : {})
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/** Redeems the code the upstream sent back. */
|
|
306
|
+
async finishLogin(login, code) {
|
|
307
|
+
const discovery = await this.discover();
|
|
308
|
+
const clientInformation = await this.clientInformation(discovery);
|
|
309
|
+
const tokens = await exchangeAuthorization(login.authorizationServerUrl, {
|
|
310
|
+
metadata: discovery.authorizationServerMetadata,
|
|
311
|
+
clientInformation,
|
|
312
|
+
authorizationCode: code,
|
|
313
|
+
codeVerifier: login.codeVerifier,
|
|
314
|
+
redirectUri: callbackUrl(this.identity.externalUrl),
|
|
315
|
+
resource: discovery.resourceMetadata?.resource ? new URL(discovery.resourceMetadata.resource) : undefined,
|
|
316
|
+
addClientAuthentication: this.provider().addClientAuthentication,
|
|
317
|
+
fetchFn: this.boundAsFetch
|
|
318
|
+
});
|
|
319
|
+
this.provider().saveTokens(tokens);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Best effort: tell the upstream to forget the token (RFC 7009) and, for a
|
|
323
|
+
* dynamic registration, the registration itself (RFC 7592). Neither is
|
|
324
|
+
* offered by the SDK, and neither may stop the local record from going away.
|
|
325
|
+
*/
|
|
326
|
+
async revokeRemotely() {
|
|
327
|
+
const problems = [];
|
|
328
|
+
const record = this.record;
|
|
329
|
+
if (!record)
|
|
330
|
+
return problems;
|
|
331
|
+
const discovery = record.discovery;
|
|
332
|
+
// Declared by RFC 8414 and carried by the SDK's loose metadata schema, but
|
|
333
|
+
// absent from its TypeScript type.
|
|
334
|
+
const revocationEndpoint = discovery?.authorizationServerMetadata?.revocation_endpoint;
|
|
335
|
+
const tokens = record.tokens;
|
|
336
|
+
if (revocationEndpoint && tokens?.refresh_token) {
|
|
337
|
+
try {
|
|
338
|
+
const body = new URLSearchParams({ token: tokens.refresh_token, token_type_hint: 'refresh_token' });
|
|
339
|
+
if (record.clientId)
|
|
340
|
+
body.set('client_id', record.clientId);
|
|
341
|
+
if (record.clientSecret)
|
|
342
|
+
body.set('client_secret', record.clientSecret);
|
|
343
|
+
await this.asFetch(new URL(revocationEndpoint), {
|
|
344
|
+
method: 'POST',
|
|
345
|
+
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
346
|
+
body: body.toString()
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
problems.push(`token revocation failed: ${error.message}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (record.registrationClientUri && record.registrationAccessToken) {
|
|
354
|
+
try {
|
|
355
|
+
await this.asFetch(new URL(record.registrationClientUri), {
|
|
356
|
+
method: 'DELETE',
|
|
357
|
+
headers: { authorization: `Bearer ${record.registrationAccessToken}` }
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
problems.push(`registration delete failed: ${error.message}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return problems;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* The one fetch the transport gets.
|
|
368
|
+
*
|
|
369
|
+
* Requests to the upstream itself carry the configured headers and the bearer
|
|
370
|
+
* and go out on the ordinary fetch, because the data plane streams. Anything
|
|
371
|
+
* aimed at the authorization server takes the guarded path instead, and is
|
|
372
|
+
* never shown a header or a token belonging to the upstream.
|
|
373
|
+
*/
|
|
374
|
+
createFetch() {
|
|
375
|
+
const upstreamOrigin = new URL(this.identity.serverUrl).origin;
|
|
376
|
+
return async (input, init) => {
|
|
377
|
+
const url = new URL(input instanceof Request ? input.url : String(input));
|
|
378
|
+
const isControlPlane = url.origin !== upstreamOrigin || url.pathname.startsWith('/.well-known/');
|
|
379
|
+
if (isControlPlane)
|
|
380
|
+
return this.asFetch(url, init);
|
|
381
|
+
const send = async () => {
|
|
382
|
+
const headers = new Headers(init?.headers);
|
|
383
|
+
const token = this.tokens()?.access_token;
|
|
384
|
+
if (token)
|
|
385
|
+
headers.set('authorization', `Bearer ${token}`);
|
|
386
|
+
for (const [key, value] of Object.entries(this.config.headers)) {
|
|
387
|
+
if (!headers.has(key))
|
|
388
|
+
headers.set(key, value);
|
|
389
|
+
}
|
|
390
|
+
return { response: await fetch(url, { ...init, headers }), token };
|
|
391
|
+
};
|
|
392
|
+
const first = await send();
|
|
393
|
+
if (first.response.status !== 401)
|
|
394
|
+
return first.response;
|
|
395
|
+
// Handled here rather than by the SDK: its recovery has no single flight,
|
|
396
|
+
// so parallel 401s would each refresh. Passing the token that was refused
|
|
397
|
+
// means a caller whose token was already replaced skips straight to the
|
|
398
|
+
// retry instead of refreshing again.
|
|
399
|
+
try {
|
|
400
|
+
await this.prepare({ staleAccessToken: first.token });
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
// Leave the 401 standing; the supervisor turns it into `unauthorized`.
|
|
404
|
+
return first.response;
|
|
405
|
+
}
|
|
406
|
+
return (await send()).response;
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function toRecord(headers) {
|
|
411
|
+
const result = {};
|
|
412
|
+
if (!headers)
|
|
413
|
+
return result;
|
|
414
|
+
if (headers instanceof Headers)
|
|
415
|
+
for (const [key, value] of headers)
|
|
416
|
+
result[key] = value;
|
|
417
|
+
else if (Array.isArray(headers))
|
|
418
|
+
for (const [key, value] of headers)
|
|
419
|
+
result[key] = value;
|
|
420
|
+
else
|
|
421
|
+
Object.assign(result, headers);
|
|
422
|
+
return result;
|
|
423
|
+
}
|
|
424
|
+
/** An OAuth error body carries a code worth logging and a description that may
|
|
425
|
+
* carry anything at all; only the code is repeated. */
|
|
426
|
+
function errorCode(body) {
|
|
427
|
+
try {
|
|
428
|
+
const parsed = JSON.parse(body);
|
|
429
|
+
return typeof parsed.error === 'string' ? parsed.error : 'unknown_error';
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
return 'unknown_error';
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/upstream/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { signPayload } from '../auth/signed-token.js';
|
|
2
|
+
import { UpstreamAuth } from './auth.js';
|
|
3
|
+
/**
|
|
4
|
+
* The operator-facing half of outbound OAuth, shared by the admin CLI and — for
|
|
5
|
+
* the parts that need it — the callback route.
|
|
6
|
+
*
|
|
7
|
+
* Deliberately free of Express and of `process.exit`, so `admin.ts` stays what
|
|
8
|
+
* it is elsewhere: argument parsing and printing. That also keeps this file
|
|
9
|
+
* inside the coverage gate, which excludes `admin.ts`.
|
|
10
|
+
*/
|
|
11
|
+
/** Long enough for a sign-in with MFA and a consent screen, short enough that an
|
|
12
|
+
* abandoned attempt does not linger. */
|
|
13
|
+
export const LOGIN_TTL_MS = 15 * 60_000;
|
|
14
|
+
export class UpstreamCommandError extends Error {
|
|
15
|
+
}
|
|
16
|
+
/** The remote server with this name, or a clear reason why it cannot be used. */
|
|
17
|
+
export function requireOAuthServer(config, name) {
|
|
18
|
+
const server = config.get(name);
|
|
19
|
+
if (!server)
|
|
20
|
+
throw new UpstreamCommandError(`Unknown server: ${name}`);
|
|
21
|
+
if (server.kind !== 'remote')
|
|
22
|
+
throw new UpstreamCommandError(`Server "${name}" is not a remote server`);
|
|
23
|
+
if (!server.oauth)
|
|
24
|
+
throw new UpstreamCommandError(`Server "${name}" has no "oauth" block in mcp.json`);
|
|
25
|
+
return server;
|
|
26
|
+
}
|
|
27
|
+
export function authFor(store, externalUrl, name, server) {
|
|
28
|
+
return new UpstreamAuth(name, server, store, externalUrl);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Starts an interactive login and returns the URL the operator has to open.
|
|
32
|
+
*
|
|
33
|
+
* The `state` is signed with the same secret the hub's own browser tokens use,
|
|
34
|
+
* so the callback can trust which server a redirect belongs to without taking
|
|
35
|
+
* the server name from the query string.
|
|
36
|
+
*/
|
|
37
|
+
export async function startUpstreamLogin(store, auth) {
|
|
38
|
+
if (auth.identity.oauth.grant !== 'authorization_code') {
|
|
39
|
+
throw new UpstreamCommandError(`Server "${auth.identity.serverName}" uses the ${auth.identity.oauth.grant} grant, which needs no browser. Use "upstream refresh" instead.`);
|
|
40
|
+
}
|
|
41
|
+
const expiresAt = Date.now() + LOGIN_TTL_MS;
|
|
42
|
+
const payload = { n: auth.identity.serverName, exp: expiresAt };
|
|
43
|
+
const state = signPayload(payload, store.cookieSecret);
|
|
44
|
+
const { authorizationUrl, login } = await auth.startLogin(state);
|
|
45
|
+
store.saveUpstreamLogin(state, { ...login, expiresAt: Math.floor(expiresAt / 1000) });
|
|
46
|
+
return { authorizationUrl, expiresAt };
|
|
47
|
+
}
|
|
48
|
+
/** What `upstream list`/`status` prints. Reads only — never starts anything. */
|
|
49
|
+
export function upstreamStatus(store, config, only) {
|
|
50
|
+
const stored = store.listUpstreamCredentials();
|
|
51
|
+
const rows = [];
|
|
52
|
+
const pendingFor = new Set(Object.values(store.listUpstreamLogins()).map(login => login.serverName));
|
|
53
|
+
for (const [name, server] of config) {
|
|
54
|
+
if (server.kind !== 'remote' || !server.oauth)
|
|
55
|
+
continue;
|
|
56
|
+
if (only && name !== only)
|
|
57
|
+
continue;
|
|
58
|
+
const auth = new UpstreamAuth(name, server, store, store.getExternalUrl() ?? 'http://localhost/');
|
|
59
|
+
// Reading through the manager applies the fingerprint check, so a record
|
|
60
|
+
// left over from a previous configuration shows up as `stale` rather than
|
|
61
|
+
// as a working credential.
|
|
62
|
+
const current = store.getUpstreamCredentials(name, auth.fingerprint);
|
|
63
|
+
const record = current ?? stored[name];
|
|
64
|
+
rows.push({
|
|
65
|
+
server: name,
|
|
66
|
+
mode: server.oauth.mode,
|
|
67
|
+
grant: server.oauth.grant,
|
|
68
|
+
scopes: server.oauth.scopes,
|
|
69
|
+
state: describe(current, record),
|
|
70
|
+
clientId: current?.clientId ?? server.oauth.clientId,
|
|
71
|
+
registered: current?.clientId !== undefined,
|
|
72
|
+
hasRefreshToken: typeof current?.tokens?.refresh_token === 'string',
|
|
73
|
+
...(current?.accessTokenValidUntil ? { accessTokenValidUntil: new Date(current.accessTokenValidUntil * 1000).toISOString() } : {}),
|
|
74
|
+
...(current?.obtainedAt ? { obtainedAt: new Date(current.obtainedAt * 1000).toISOString() } : {}),
|
|
75
|
+
loginPending: pendingFor.has(name)
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (only && rows.length === 0)
|
|
79
|
+
throw new UpstreamCommandError(`Unknown server, or it has no "oauth" block: ${only}`);
|
|
80
|
+
return rows;
|
|
81
|
+
}
|
|
82
|
+
/** `stale` means a record exists but belongs to a configuration that has since
|
|
83
|
+
* changed — the operator sees why a login they remember doing does not count. */
|
|
84
|
+
function describe(current, anyRecord) {
|
|
85
|
+
if (!current)
|
|
86
|
+
return anyRecord ? 'stale' : 'login_required';
|
|
87
|
+
if (!current.tokens)
|
|
88
|
+
return 'login_required';
|
|
89
|
+
if (current.accessTokenValidUntil !== undefined && current.accessTokenValidUntil <= Math.floor(Date.now() / 1000)) {
|
|
90
|
+
return 'expired';
|
|
91
|
+
}
|
|
92
|
+
return 'authorized';
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/upstream/login.ts"],"names":[],"mappings":"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"}
|